Docs: GoDoc
Looper implements a fully generic looping control system, with a Stack
of Loop
elements that iterate over different time scales of processing, where the processing performed is provided by function closures on the Loop elements.
Critically, the loop logic supports reentrant stepping, such that you can iteratively Step
through the loop processing and accomplish exactly the same outcomes as if you did a complete Run
from the start.
Each loop implements the following logic, where it is key to understand that the time scale associated with the loop runs the full iterations over that time scale. For example, a Trial
loop iterates over Trials -- it is not a single trial, but the whole sequence (loop) of trials.
for {
Events[Counter == AtCounter] // run events at counter
OnStart()
Run Sub-Loop to completion
OnEnd()
Counter += Inc
if Counter >= Max || IsDone() {
break
}
}
The Loop
object has the above function lists (OnStart
, OnEnd
, and IsDone
), where function closures can be added to perform any relevant functionality. Events
have the trigger AtCounter
and a list of functions to call. If the Loop is the last one in the Stack, then,
Each level of loop holds a corresponding Counter
value, which increments at each iteration, and its Max
value determines when the loop iteration terminates.
Each Stack
of loops has an associated Mode
enum, e.g., Train
or Test
, and each Loop
has an associated Time
level, e.g., Run
, Epoch
, Trial
.
The collection of Stacks
has a high-level API for configuring and controlling the set of Stack
elements, and has the logic for running everything, in the form of Run
, Step
, Stop
methods, etc.
The following examples use the etime Modes
and Times
enums. It is recommended that you define your own Modes
enums if not using the basic Train
and Test
cases, to provide a better egui representation of the loop stack.
From step_test.go
ExampleStacks
:
stacks := NewStacks()
stacks.AddStack(etime.Train).
AddTime(etime.Epoch, 3).
AddTime(etime.Trial, 2)
// add function closures:
stacks.Loop(etime.Train, etime.Epoch).OnStart.Add("Epoch Start", func() { fmt.Println("Epoch Start") })
stacks.Loop(etime.Train, etime.Epoch).OnEnd.Add("Epoch End", func() { fmt.Println("Epoch End") })
stacks.Loop(etime.Train, etime.Trial).OnStart.Add("Trial Run", func() { fmt.Println(" Trial Run") })
// add events:
stacks.Loop(etime.Train, etime.Epoch).AddEvent("EpochTwoEvent", 2, func() { fmt.Println("Epoch==2") })
stacks.Loop(etime.Train, etime.Trial).AddEvent("TrialOneEvent", 1, func() { fmt.Println(" Trial==1") })
The DocString
for this stack is:
Stack Train:
Epoch[0 : 3]:
Events:
EpochTwoEvent: [at 2] Events: EpochTwoEvent
Start: Epoch Start
Trial[0 : 2]:
Events:
TrialOneEvent: [at 1] Events: TrialOneEvent
Start: Trial Run
End: Epoch End
and the output when run is:
Epoch Start
Trial Run
Trial==1
Trial Run
Epoch End
Epoch Start
Trial Run
Trial==1
Trial Run
Epoch End
Epoch==2
Epoch Start
Trial Run
Trial==1
Trial Run
Epoch End
Run a full stack:
stacks.Run(etime.Train)
Reset first and Run, ensures that the full sequence is run even if it might have been stopped or stepped previously:
stacks.ResetAndRun(etime.Train)
Step by 1 Trial:
stacks.Step(etime.Train, 1, etime.Trial)