stepper

package
v1.4.31 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2023 License: BSD-3-Clause Imports: 4 Imported by: 3

README

Visit the GoDoc link for a detailed description of the Stepper API.

Stepper

The stepper package provides a facility for pausing a simulation run at various time scales without having to worry about saving and restoring simulation state.

How to use the Stepper

  1. Import the stepper package:

    import "github.com/emer/emergent/stepper"
    
    
  2. Define an enumerated type for whatever different types/granularities of steps you'd like. Note that the stepper does not interpret these values, and only checks equality to see decide whether or not to stop at any given StepPoint.

    type StepGrain int
    
    const (
          Cycle StepGrain = iota
          Quarter
          SettleMinus
          SettlePlus
          AlphaCycle
          SGTrial // Trial
          TrialGroup
          RunBlock
          StepGrainN
    )
    //go:generate stringer -linecomment -type=StepGrain
    
    var KiT_StepGrain = kit.Enums.AddEnum(StepGrainN, kit.NotBitFlag, nil)
    
    
  3. Add a few fields to your Sim struct:

    struct Sim {
       Stepper                      *stepper.Stepper  `view:"-"`        
       StepsToRun                   int               `view:"-" desc:"number of steps to execute before stopping"`
       OrigSteps                    int               `view:"-" desc:"saved number of steps to execute before stopping"`
       StepGrain                    StepGrain         `view:"-" desc:"granularity for the Step command"`
       StopStepCondition            StopStepCond      `desc:"granularity for conditional stop"`
       StopConditionTrialNameString string            `desc:"if StopStepCond is TrialName or NotTrialName, this string is used for matching the current AlphaTrialName"`
       StopStepCounter              env.Ctr           `inactive:"+" view:"-" desc:"number of times we've hit whatever StopStepGrain is set to'"`
    }
    
    
  4. Define a stepper.PauseNotifyFn callback:

    // NotifyPause is called from within the Stepper, with the Stepper's lock held.
    // This function's code must not call any Stepper methods (e.g. Enter) that try to take the Stepper mutex lock.
    func (ss *Sim) NotifyPause() {
       if int(ss.StepGrain) != ss.Stepper.Grain() {
          ss.Stepper.StepGrain = int(ss.StepGrain)
       }
       if ss.StepsToRun != ss.OrigSteps { // User has changed the step count while running
         ss.Stepper.StepsPer = ss.StepsToRun
         ss.OrigSteps = ss.StepsToRun
       }
       ss.IsRunning = false
       ss.ToolBar.UpdateActions()
       ss.UpdateView()
       ss.Win.Viewport.SetNeedsFullRender()
    }
    
    
  5. (OPTIONAL) Create a stepper.StopCheckFn callback:

    // CheckStopCondition is called from within the Stepper, with the Stepper's lock held.
    // This function's code must not call any Stepper methods (e.g. Enter) that try to take the Stepper mutex lock.
    func (ss *Sim) CheckStopCondition(_ int) bool {
       ev := &ss.Env
       ret := false
       switch ss.StopStepCondition {
          case SSNone:
             return false
          case SSError:
             ret = ss.SumSSE > 0.0
          case SSCorrect:
             ret = ss.SumSSE == 0.0
          case SSTrialNameMatch:
             ret = strings.Contains(ev.AlphaTrialName, ss.StopConditionTrialNameString)
          case SSTrialNameNonmatch:
             ret = !strings.Contains(ev.AlphaTrialName, ss.StopConditionTrialNameString)
          default:
             ret = false
       }
       return ret
    }
    
    
  6. Somewhere in your initialization code, create the actual Stepper and register your stepper.PauseNotifyFn and (optionally) stepper.StopCheckFn functions:

    ss.Stepper = stepper.New()
    ss.Stepper..StopCheckFn = CheckStopCondition
    ss.Stepper.PauseNotifyFn = NotifyPause
    
    
  7. At appropriate points in your simulation code, insert stepper.StepPoint calls, e.g.:

    func (ev *PVLVEnv) RunOneTrial(ss *Sim, curTrial *data.TrialInstance) {
       trialDone := false
       for !trialDone {
          ev.SetupOneAlphaTrial(curTrial, 0)
          ev.RunOneAlphaCycle(ss, curTrial)
          trialDone = ev.AlphaCycle.Incr()
          if ss.Stepper.StepPoint(int(AlphaCycle)) {
             return
       }
     }
    
    
  8. Add code to the user interface to start, pause, and stop the simulation:

    func (ss *Sim) ConfigGui() *gi.Window {
       ...
       tbar.AddAction(gi.ActOpts{Label: "Stop", Icon: "stop",
          Tooltip: "Stop the current program at its next natural stopping point (i.e., cleanly stopping when appropriate chunks of computation have completed).",
          UpdateFunc: func(act *gi.Action) {
             act.SetActiveStateUpdt(ss.IsRunning)
       }}, win.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
          fmt.Println("STOP!")
          ss.Stepper.Pause() // NOTE: call Pause here. Stop should only be called when starting over for a new run
          ss.IsRunning = false
          ss.ToolBar.UpdateActions()
          ss.Win.Viewport.SetNeedsFullRender()
       })
       tbar.AddAction(gi.ActOpts{Label: "Cycle", Icon: "run", Tooltip: "Step to the end of a Cycle.",
          UpdateFunc: func(act *gi.Action) {
             act.SetActiveStateUpdt(!ss.IsRunning)
          }}, win.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
          ss.RunSteps(Cycle, tbar)
       })
       ...
    }
    
    func (ss *Sim) RunSteps(grain StepGrain, tbar *gi.ToolBar) {
       if !ss.IsRunning {
          ss.IsRunning = true
          tbar.UpdateActions()
          if ss.Stepper.RunState == stepper.Stopped {
             ss.SimHasRun = true
             ss.OrigSteps = ss.StepsToRun
             ss.Stepper.Start(int(grain), ss.StepsToRun)
             ss.ToolBar.UpdateActions()
             go ss.Train()
       } else if ss.Stepper.RunState == stepper.Paused {
          ss.Stepper.StepGrain = int(grain)
          ss.Stepper.StepsPer = ss.StepsToRun
          ss.Stepper.Enter(stepper.Stepping)
          ss.ToolBar.UpdateActions()
       }
    }
    
    
  9. Add buttons for selecting a StepGrain value for variable-sized steps. See the PVLV model for more detail.

  10. That's it!

Documentation

Overview

Package `stepper` allows you to set StepPoints in simulation code that will pause if some condition is satisfied. While paused, the simulation waits for the top-level process (the user interface) to tell it to continue. Once a continue notification is received, the simulation continues on its way, with all internal state exactly as it was when the StepPoint was hit, without having to explicitly save anything.

There are two "running" states, Stepping and Running. The difference is that in the Running state, unless there is a Stop request, the application will forego the possibly-complex checking for a pause. The actual StepPoint function is written to make checking as quick as possible. Although the program will not stop at StepPoints without interaction, it will pause if RunState is Paused. The main difference between Paused and Stopped is that in the Paused state, the application waits for a state change, whereas in the Stopped state, the Stepper exits, and no application state is preserved. After entering Stopped, the controlling program (i.e., the user interface) should make sure that everything is properly reinitialized before running again.

Usage Basics

The Stepper struct includes an integer field, "StepGrain", which controls whether it will actually pause. The StepPoint function checks that its argument is equal to the current StepGrain, and if so, calls the PauseNotifyFn callback with whatever state information was set up when the PauseNotifyFn was registered. It then enters a loop, waiting for a change in the RunState field of the Stepper. If it sees that RunState has become Stopped, it return true, and the caller (i.e., the simulation) should exit the current run. If it sees that RunState has changed to either Running or Stepping, it returns false, and the caller should continue.

Before running, the caller should call Stepper.New() to get a fresh, initialized Stepper, and must also call RegisterPauseNotifyFn to designate a callback to be invoked when StepPoint is called with a value of grain that matches the value of StepGrain set in the struct. Internally, StepGrain is just an integer, but the intention is that callers will define a StepGrain enumerated type to designate meaningful points at which to pause. Note that the current implementation makes no use of the actual values of StepGrain, i.e., the fact that one value is greater than another has no effect. This might change in a future version.

In addition to the required PauseNotifyFn callback, there is an optional StopCheckFn callback. This callback can check any state information that it likes, and if it returns true, the PauseNotifyFn will be invoked.

Whether or not a StopCheckFn has been set, if RunState is Stepping and the grain argument matches StepGrain, StepPoint decrements the value of StepsPer. If stepsLeft goes to zero, PauseNotifyFn is called, and the Stepper goes into a wait loop, waiting for RunState to be something other than Paused. If RunState becomes Stopped, StepPoint exits with a value of true, indicating that the caller should end the current run. If the new state is either Running or Stepping, StepPoint returns false, indicating that the caller should continue.

Index

Constants

This section is empty.

Variables

View Source
var KiT_RunState = kit.Enums.AddEnum(RunStateN, kit.NotBitFlag, nil)

Functions

This section is empty.

Types

type PauseNotifyFn

type PauseNotifyFn func()

A PauseNotifyFn is a callback that will be invoked if the program enters the Paused state. NOTE! The PauseNotifyFn is called with the Stepper's lock held, so it must not call any Stepper methods that try to take the lock on entry, or a deadlock will result.

type RunState

type RunState int
const (
	Stopped  RunState = iota // execution is stopped. The Stepper is NOT waiting, so running again is basically a restart. The only way to go from Running or Stepping to Stopped is to explicitly call Stop(). Program state will not be preserved once the Stopped state is entered.
	Paused                   // execution is paused. The sim is waiting for further instructions, and can continue, or stop.
	Stepping                 // the application is running, but will pause if it hits a StepPoint that matches the current StepGrain.
	Running                  // the application is running, and will NOT pause at StepPoints. It will pause if a stop has been requested.
	RunStateN
)

func (RunState) String

func (i RunState) String() string

type Stepper

type Stepper struct {

	// current run state
	RunState RunState `desc:"current run state"`

	// granularity of one step. No enum type here so clients can define their own
	StepGrain int `desc:"granularity of one step. No enum type here so clients can define their own"`

	// number of steps to execute before returning
	StepsPer int `desc:"number of steps to execute before returning"`

	// [view: -] function to deal with any changes on client side when paused after stepping
	PauseNotifyFn PauseNotifyFn `view:"-" desc:"function to deal with any changes on client side when paused after stepping"`

	// [view: -] function to test for special stopping conditions
	StopCheckFn StopCheckFn `view:"-" desc:"function to test for special stopping conditions"`
	// contains filtered or unexported fields
}

The Stepper struct contains all of the state info for stepping a program, enabling step points. where the running application can be suspended with no loss of state.

func New

func New() *Stepper

New makes a new Stepper. Always call this to create a Stepper, so that initialization will be run correctly.

func (*Stepper) Active

func (st *Stepper) Active() bool

Active checks that the application is either Running or Stepping (neither Paused nor Stopped). This needs to use the State mutex because it checks two different fields.

func (*Stepper) Enter

func (st *Stepper) Enter(state RunState)

Enter unconditionally enters the specified RunState. It broadcasts a stateChange, which should be picked up by a paused application.

func (*Stepper) Init

func (st *Stepper) Init() *Stepper

Init puts everything into a good state before starting a run Init is called automatically by New, and should be called before running again after calling Stop (not Pause). Init should not be called explicitly when creating a new Stepper--the preferred way to initialize is to call New.

func (*Stepper) Pause

func (st *Stepper) Pause()

Pause sets RunState to Paused. The running program will actually pause at the next StepPoint call.

func (*Stepper) ResetParams

func (st *Stepper) ResetParams(nSteps int, grain int)

Reset StepsPer and StepGrain parameters

func (*Stepper) Start

func (st *Stepper) Start(grain int, nSteps int)

Start enters the Stepping run state. This should be called at the start of a run only.

func (*Stepper) StepPoint

func (st *Stepper) StepPoint(grain int) (stop bool)

StepPoint checks for possible pause or stop. If the application is: Running: keep going with no further examination of state. Stopped: return true, and the application should return (i.e., stop completely). Stepping: check to see if we should pause (if StepGrain matches, decrement stepsLeft, stop if <= 0). Paused: wait for state change.

func (*Stepper) Stop

func (st *Stepper) Stop()

Stop sets RunState to Stopped. The running program will exit at the next StepPoint it hits.

type StopCheckFn

type StopCheckFn func(grain int) (matched bool)

A StopCheckFn is a callback to check whether an arbitrary condition has been matched. If a StopCheckFn returns true, the program is suspended with a RunState of Paused, and will remain so until the RunState changes to Stepping, Running, or Stopped. As noted below for the PauseNotifyFn, the StopCheckFn is called with the Stepper's lock held.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL