Skip to content

Commit

Permalink
Add CSV output for snapshot table observers (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlange-42 authored May 23, 2024
1 parent 0d61900 commit f94fe90
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 12 deletions.
6 changes: 6 additions & 0 deletions _examples/base/observers.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"File": "out/WorkerCohorts.csv"
}
],
"StepTables": [
{
"Observer": "obs.ForagingStats",
"File": "out/ForagingStats.csv"
}
],
"TimeSeriesPlots": [
{
"Observer": "obs.WorkerCohorts",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22.0
require (
github.com/gopxl/pixel/v2 v2.1.0
github.com/mlange-42/arche v0.13.0
github.com/mlange-42/arche-model v0.8.2-0.20240521193509-06db52d11180
github.com/mlange-42/arche-model v0.8.2-0.20240523143440-11595cc41e11
github.com/mlange-42/arche-pixel v0.9.1-0.20240523102310-958292ff764d
github.com/mlange-42/beecs v0.1.1-0.20240523095106-c543d24d7f9e
github.com/spf13/cobra v1.8.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj
github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
github.com/mlange-42/arche v0.13.0 h1:ef0fu9qC2KIr8wIlVs+CgeQ5CSUJ8A1Hut6nXYdf+xk=
github.com/mlange-42/arche v0.13.0/go.mod h1:bFktKnvGDj2kP01xar79z0hKwGHdnoaEZR8HWmJkIyU=
github.com/mlange-42/arche-model v0.8.2-0.20240521193509-06db52d11180 h1:R1u8Xj5uG6jwCv0r3eIbhd8SHs9uLbA5vJ0sjxjCWZY=
github.com/mlange-42/arche-model v0.8.2-0.20240521193509-06db52d11180/go.mod h1:5Pzd3iAgdGNTiMgFJxHdc6a7qoaQPBK9xmyhrKTMmV8=
github.com/mlange-42/arche-model v0.8.2-0.20240523143440-11595cc41e11 h1:NfP5jwtHUcqLbYYPJd8pSfihHV0fx6YUyhWl9R6c3Yo=
github.com/mlange-42/arche-model v0.8.2-0.20240523143440-11595cc41e11/go.mod h1:5Pzd3iAgdGNTiMgFJxHdc6a7qoaQPBK9xmyhrKTMmV8=
github.com/mlange-42/arche-pixel v0.9.1-0.20240523102310-958292ff764d h1:RWHbjBrDtb/YJVdOc1/jd5hLPUBfLqxf/AXuKqD1gbI=
github.com/mlange-42/arche-pixel v0.9.1-0.20240523102310-958292ff764d/go.mod h1:UWr4ZfpvreTRWr76dqi30VCax19kEbiSDSNrbZTbMDQ=
github.com/mlange-42/beecs v0.1.1-0.20240523095106-c543d24d7f9e h1:IG6UGhcUyptr+R3U+T2UbN0WOqLM1A2CzWddM5pt2JI=
Expand Down
26 changes: 24 additions & 2 deletions internal/run/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ func runModel(

result := util.Tables{
Index: idx,
Headers: make([][]string, len(obs.Tables)+1),
Data: make([][][]float64, len(obs.Tables)+1),
Headers: make([][]string, len(obs.Tables)+len(obs.StepTables)+1),
Data: make([][][]float64, len(obs.Tables)+len(obs.StepTables)+1),
}

now := time.Now().UnixMilli()
Expand Down Expand Up @@ -91,6 +91,28 @@ func runModel(
m.AddSystem(t)
}

offset := len(obs.Tables)
for i, t := range obs.StepTables {
t.HeaderCallback = func(header []string) {
h := make([]string, len(header)+2)
h[0] = "Run"
h[1] = "Ticks"
copy(h[2:], header)
result.Headers[offset+i+1] = h
}
t.Callback = func(step int, table [][]float64) {
for _, row := range table {
data := make([]float64, len(row)+2)
data[0] = float64(idx)
data[1] = float64(step)
copy(data[2:], row)

result.Data[offset+i+1] = append(result.Data[offset+i+1], data)
}
}
m.AddSystem(t)
}

if !noUi {
for _, p := range obs.Windows {
m.AddUISystem(p)
Expand Down
3 changes: 3 additions & 0 deletions internal/run/parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func RunParallel(
for _, t := range observers.Tables {
files = append(files, path.Join(dir, t.File))
}
for _, t := range observers.StepTables {
files = append(files, path.Join(dir, t.File))
}
writer, err := util.NewCsvWriter(files, observers.CsvSeparator)
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions internal/run/sequential.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ func RunSequential(
for _, t := range observers.Tables {
files = append(files, path.Join(dir, t.File))
}
for _, t := range observers.StepTables {
files = append(files, path.Join(dir, t.File))
}
writer, err := util.NewCsvWriter(files, observers.CsvSeparator)
if err != nil {
return err
Expand Down
63 changes: 56 additions & 7 deletions internal/util/observers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ func (e *entry) UnmarshalJSON(jsonData []byte) error {
}

type Observers struct {
Windows []*window.Window
Tables []*reporter.Callback
Windows []*window.Window
Tables []*reporter.RowCallback
StepTables []*reporter.TableCallback
}

type TimeSeriesPlotDef struct {
Expand Down Expand Up @@ -60,6 +61,14 @@ type TableDef struct {
Final bool
}

type StepTableDef struct {
File string
Observer string
Params entry
UpdateInterval int
Final bool
}

type ViewDef struct {
Drawer string
Params entry
Expand All @@ -76,6 +85,7 @@ type ObserversDef struct {
LinePlots []LinePlotDef // Live line plots.
Views []ViewDef // Live views.
Tables []TableDef // CSV output with one row per update.
StepTables []StepTableDef // CSV output with a full table per update.
}

func (obs *ObserversDef) CreateObservers(withUI bool) (Observers, error) {
Expand Down Expand Up @@ -105,9 +115,15 @@ func (obs *ObserversDef) CreateObservers(withUI bool) (Observers, error) {
return Observers{}, err
}

stepTables, err := createStepTables(obs.StepTables)
if err != nil {
return Observers{}, err
}

return Observers{
Windows: windows,
Tables: tables,
Windows: windows,
Tables: tables,
StepTables: stepTables,
}, nil
}

Expand Down Expand Up @@ -228,8 +244,8 @@ func createViews(views []ViewDef) ([]*window.Window, error) {
return windows, nil
}

func createTables(tabs []TableDef) ([]*reporter.Callback, error) {
tables := []*reporter.Callback{}
func createTables(tabs []TableDef) ([]*reporter.RowCallback, error) {
tables := []*reporter.RowCallback{}
for _, t := range tabs {
tp, ok := registry.GetObserver(t.Observer)
if !ok {
Expand All @@ -248,7 +264,7 @@ func createTables(tabs []TableDef) ([]*reporter.Callback, error) {
if !ok {
return nil, fmt.Errorf("type '%s' is not a Row observer", tp.String())
}
rep := &reporter.Callback{
rep := &reporter.RowCallback{
Observer: obsCast,
UpdateInterval: t.UpdateInterval,
HeaderCallback: func(header []string) {},
Expand All @@ -260,3 +276,36 @@ func createTables(tabs []TableDef) ([]*reporter.Callback, error) {

return tables, nil
}

func createStepTables(tabs []StepTableDef) ([]*reporter.TableCallback, error) {
tables := []*reporter.TableCallback{}
for _, t := range tabs {
tp, ok := registry.GetObserver(t.Observer)
if !ok {
return nil, fmt.Errorf("observer type '%s' is not registered", t.Observer)
}
observerVal := reflect.New(tp).Interface()
if len(t.Params.Bytes) == 0 {
t.Params.Bytes = []byte("{}")
}
decoder := json.NewDecoder(bytes.NewReader(t.Params.Bytes))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&observerVal); err != nil {
return nil, err
}
obsCast, ok := observerVal.(observer.Table)
if !ok {
return nil, fmt.Errorf("type '%s' is not a Table observer", tp.String())
}
rep := &reporter.TableCallback{
Observer: obsCast,
UpdateInterval: t.UpdateInterval,
HeaderCallback: func(header []string) {},
Callback: func(step int, row [][]float64) {},
Final: t.Final,
}
tables = append(tables, rep)
}

return tables, nil
}

0 comments on commit f94fe90

Please sign in to comment.