├── .gitignore ├── LICENSE ├── README.md ├── cmd └── dynamo │ └── main.go ├── dia ├── shapes │ ├── aux.png │ ├── aux.shape │ ├── constant.png │ ├── constant.shape │ ├── constant_b.png │ ├── constant_b.shape │ ├── exogenous.png │ ├── exogenous.shape │ ├── level.png │ ├── level.shape │ ├── rate.png │ ├── rate.shape │ ├── rate2.png │ ├── rate2.shape │ ├── rate2b.png │ ├── rate2b.shape │ ├── rate_d.png │ ├── rate_d.shape │ ├── rate_l.png │ ├── rate_l.shape │ ├── rate_r.png │ ├── rate_r.shape │ ├── source-sink.png │ ├── source-sink.shape │ ├── table.png │ └── table.shape └── sheets │ └── SystemDynamics.sheet ├── eqnlist.go ├── equation.go ├── functions.go ├── functions_test.go ├── go.mod ├── model.go ├── model_test.go ├── output.go ├── parse.go ├── plotter.go ├── printer.go ├── result.go ├── rt ├── .gitignore ├── README.md ├── book │ ├── README.md │ ├── flu.dynamo │ ├── inventory.dynamo │ ├── plots │ │ ├── flu-model-1.svg │ │ ├── flu-model-2.svg │ │ ├── flu_(1).svg │ │ ├── flu_(2).svg │ │ ├── inventory_(1).svg │ │ ├── inventory_(2).svg │ │ ├── inventory_(3).svg │ │ ├── inventory_(4).svg │ │ ├── inventory_(5).svg │ │ ├── inventory_(6).svg │ │ ├── inventory_(7).svg │ │ ├── prison-model.svg │ │ ├── prison_(1).svg │ │ ├── prison_(2).svg │ │ └── project_(1).svg │ ├── prison.dynamo │ └── project.dynamo ├── checo │ ├── README.md │ ├── checo-orig.dynamo │ ├── checo-plot.png │ ├── checo.dynamo │ ├── checo.png │ └── checo_(1).svg ├── misc │ ├── README.md │ ├── dove_hawk_law-abider.dynamo │ └── plots │ │ └── dove_hawk_law-abider_(1).svg └── world │ ├── README.md │ ├── plots │ ├── world2_(1).svg │ ├── world2_(2).svg │ ├── world3_(1).svg │ ├── world3_(10).svg │ ├── world3_(11).svg │ ├── world3_(12).svg │ ├── world3_(13).svg │ ├── world3_(14).svg │ ├── world3_(15).svg │ ├── world3_(16).svg │ ├── world3_(17).svg │ ├── world3_(18).svg │ ├── world3_(19).svg │ ├── world3_(2).svg │ ├── world3_(20).svg │ ├── world3_(21).svg │ ├── world3_(22).svg │ ├── world3_(23).svg │ ├── world3_(24).svg │ ├── world3_(25).svg │ ├── world3_(26).svg │ ├── world3_(27).svg │ ├── world3_(28).svg │ ├── world3_(29).svg │ ├── world3_(3).svg │ ├── world3_(30).svg │ ├── world3_(31).svg │ ├── world3_(32).svg │ ├── world3_(33).svg │ ├── world3_(4).svg │ ├── world3_(5).svg │ ├── world3_(6).svg │ ├── world3_(7).svg │ ├── world3_(8).svg │ └── world3_(9).svg │ ├── world2-orig.dynamo │ ├── world2.dynamo │ ├── world3-orig.dynamo │ └── world3.dynamo └── variable.go /.gitignore: -------------------------------------------------------------------------------- 1 | /work/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | dynamo: Interpreter for the DYNAMO programming language 3 | ======================================================= 4 | 5 | (c) 2020,2021 Bernd Fix >Y< 6 | 7 | Dynamo is free software: you can redistribute it and/or modify it 8 | under the terms of the GNU Affero General Public License as published 9 | by the Free Software Foundation, either version 3 of the License, 10 | or (at your option) any later version. 11 | 12 | Dynamo is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Affero General Public License for more details. 16 | 17 | You should have received a copy of the GNU Affero General Public License 18 | along with this program. If not, see . 19 | 20 | SPDX-License-Identifier: AGPL3.0-or-later 21 | 22 | ## About DYNAMO 23 | 24 | DYNAMO (see https://en.wikipedia.org/wiki/DYNAMO_(programming_language) for 25 | details) is a rather ancient system dynamics modelling language that was used 26 | some 50 years ago in various areas; among others: 27 | 28 | * Project CYBERSYN (https://en.wikipedia.org/wiki/Project_Cybersyn) used 29 | DYNAMO for the CHECO part of CYBERSTRIDE to simulate the CHilean ECOnomy during 30 | the Allende presidency (the project died with Allende on September 31 | 11th, 1973. You can find a snapshot of the CHECO implementation from 32 | September 1972 in the `rt/checo` folder, along with charts and graphs. 33 | 34 | * "THE LIMITS TO GROWTH" (https://en.wikipedia.org/wiki/The_Limits_to_Growth) 35 | used DYNAMO to simulate global developments leading to the famous "Club of 36 | Rome" (https://en.wikipedia.org/wiki/Club_of_Rome) publication in 1972 that 37 | triggered environmental awareness globally. You can find the WORLD3 38 | implementation (as well as its predecessor WORLD2) in the `rt/world` folder. 39 | 40 | ## The DYNAMO interpreter 41 | 42 | This is work-in-progress at a rather early stage. It is capable of running 43 | simple models (see the `rt/` folder for examples), but can fail (or need 44 | adjustment) on anything more complex. If you have some old DYNAMO models and 45 | they don't work with the current version, please let me know (email to 46 | brf@hoi-polloi.org) and I am happy to work on the interpreter to make them 47 | run again. 48 | 49 | This interpreter was build as a "clean-room implementation"; it is not based on 50 | any other DYNAMO compiler code but was derived from publically available 51 | documentation (like the _DYNAMO User's Manual_ by Alexander L. Pugh, Alexander 52 | L. Pugh III from 1983) or from looking at the few DYNAMO models that can be 53 | found on the internet. 54 | 55 | DYNAMO itself was developed over a course of some 30 years from 1960 to 1990; 56 | so some features were not available in all versions or some language rules were 57 | less strict and only enforced in later versions. This DYNAMO interpreter tries 58 | to follow the most advanced language version as possible, but there are some 59 | limitations that need to be noticed: 60 | 61 | * The interpreter does not support macros (MACRO/MEND blocks) yet. The delay 62 | functions normally implemented as macros are hard-coded in the interpreter. 63 | 64 | * The DELAYP function is not available. A workaround is to use the DELAY3 65 | function and two additional equations: 66 | 67 | ``` 68 | R OUT.KL=DELAY3(IN.JK,DEL) 69 | L INTRN.K=INTRN.J+(DT)(IN.JK-OUT.JK) 70 | N INTRN=IN*DEL 71 | ``` 72 | 73 | * No interactive edit mode: The interpreter provides a "EDIT" directive to 74 | allow the editing (replacing and adding equations) of a model in the source 75 | code. The examples in `rt/book/` folder make use of this feature; have a look 76 | at the models to understand the use of the edit functionality. 77 | 78 | * A print symbol ***** or **#** in the PLOT statement will trigger "point" mode 79 | (instead of "line" mode) in the GNUplot graph. 80 | 81 | * `PLTPER` and `PRTPER` can't be variable in the current version of the DYNAMO 82 | interpreter. 83 | 84 | ### Build the interpreter 85 | 86 | At the moment no pre-built binaries of the DYNAMO interpreter are provided; to 87 | build the application, you need a working installation of Go 88 | (https://golang.org/) on your computer; make sure your Go installation is 89 | up-to-date (at least Go1.11; Go1.17 recommended). 90 | 91 | In the base directory of this repository issue the following commands: 92 | 93 | ```bash 94 | # install dependencies 95 | go mod tidy 96 | # build library 97 | go build 98 | # install interpreter 99 | go install github.com/bfix/dynamo/cmd/dynamo 100 | ``` 101 | 102 | The executable is available as `${GOPATH}/bin/dynamo`. Make sure that 103 | `${GOPATH}/bin` is included in `${PATH}` if you want to use it directly. 104 | 105 | ### Running a DYNAMO model 106 | 107 | Change into the `rt/` (runtime) folder; below that folder you can find sample 108 | DYNAMO models to play around with. For example you can run the epidemic model 109 | with the following command: 110 | 111 | ```bash 112 | dynamo -p ~/flu.prt book/flu.dynamo 113 | ``` 114 | 115 | This will run the `flu.dynamo` model and generate a print output in the file 116 | `~/flu.prt`. 117 | 118 | The following command line options are available: 119 | 120 | * `-v`: verbose output; show more status messages during processing. 121 | * `-d `: write debug output to specified file. Use `-` to log to 122 | console. 123 | * `-p `: write printer output to file: the extension used in the 124 | filename specifies which print format to use: 125 | * `.prt`: Generate classic DYNAMO print output (line printer) 126 | * `.csv`: Generate CSV-compatible files (e.g. for import into other apps) 127 | * `-g `: write plot output to file: the extension used in the 128 | filename specifies whicht plot format to use: 129 | * `.plt`: Generate classic DYNAMO plot output (line printer) 130 | * `.gnuplot`: Generate GNUplot script (SVG generator) 131 | 132 | See the README in the `rt/` folder (and subfolders) for more details on the 133 | example models provided. 134 | 135 | ## DYNAMO flowchart shapes 136 | 137 | To create DYNAMO flowcharts in "Dia" (a GNU/Linux diagrammer), you can install 138 | custom shapes and a corresponding sheet from the `dia/` folder of this 139 | repository. All DYNAMO flowcharts in the `rt/` folder have been done that way. 140 | -------------------------------------------------------------------------------- /cmd/dynamo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2020-2021 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "flag" 25 | "os" 26 | 27 | "github.com/bfix/dynamo" 28 | ) 29 | 30 | // main entry point: call DYNAMO interpreter with given arguments 31 | func main() { 32 | dynamo.Msg("---------------------------------------") 33 | dynamo.Msg("DYNAMO interpreter v0.6 (2021-12-14)") 34 | dynamo.Msg("Copyright (C) 2020,2021 Bernd Fix >Y<") 35 | dynamo.Msg("---------------------------------------") 36 | 37 | var ( 38 | debugFile string 39 | printFile string 40 | plotFile string 41 | verbose bool 42 | ) 43 | flag.StringVar(&debugFile, "d", "", "Debug file name (default: none)") 44 | flag.StringVar(&printFile, "p", "", "Printer file name (default: none)") 45 | flag.StringVar(&plotFile, "g", "", "Plotter file name (default: none)") 46 | flag.BoolVar(&verbose, "v", false, "More log messages (default: false)") 47 | flag.Parse() 48 | if flag.NArg() != 1 { 49 | dynamo.Fatal("No DYNAMO source file provided.") 50 | } 51 | 52 | fname := flag.Arg(0) 53 | dynamo.Msgf("Reading source file '%s'...\n", fname) 54 | src, err := os.Open(fname) 55 | if err != nil { 56 | dynamo.Fatal(err.Error()) 57 | } 58 | defer src.Close() 59 | 60 | dynamo.Msg("Processing system model...") 61 | dynamo.SetDebugger(debugFile) 62 | mdl := dynamo.NewModel(printFile, plotFile) 63 | mdl.Verbose = verbose 64 | if res := mdl.Parse(src); !res.Ok { 65 | dynamo.Fatalf("Line %d: %s\n", res.Line, res.Err.Error()) 66 | } 67 | dynamo.Msg(" Model processing completed.") 68 | mdl.Quit() 69 | dynamo.Msg("Done.") 70 | } 71 | -------------------------------------------------------------------------------- /dia/shapes/aux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/aux.png -------------------------------------------------------------------------------- /dia/shapes/aux.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - aux 4 | aux.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dia/shapes/constant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/constant.png -------------------------------------------------------------------------------- /dia/shapes/constant.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - constant 4 | constant.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dia/shapes/constant_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/constant_b.png -------------------------------------------------------------------------------- /dia/shapes/constant_b.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - constant_b 4 | constant_b.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dia/shapes/exogenous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/exogenous.png -------------------------------------------------------------------------------- /dia/shapes/exogenous.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - exogenous 4 | exogenous.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dia/shapes/level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/level.png -------------------------------------------------------------------------------- /dia/shapes/level.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - level 4 | level.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dia/shapes/rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate.png -------------------------------------------------------------------------------- /dia/shapes/rate.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate 4 | rate.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dia/shapes/rate2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate2.png -------------------------------------------------------------------------------- /dia/shapes/rate2.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate2 4 | rate2.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dia/shapes/rate2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate2b.png -------------------------------------------------------------------------------- /dia/shapes/rate2b.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate2b 4 | rate2b.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dia/shapes/rate_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate_d.png -------------------------------------------------------------------------------- /dia/shapes/rate_d.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate_d 4 | rate_d.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dia/shapes/rate_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate_l.png -------------------------------------------------------------------------------- /dia/shapes/rate_l.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate_l 4 | rate_l.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dia/shapes/rate_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/rate_r.png -------------------------------------------------------------------------------- /dia/shapes/rate_r.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - rate_r 4 | rate_r.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dia/shapes/source-sink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/source-sink.png -------------------------------------------------------------------------------- /dia/shapes/source-sink.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - source-sink 4 | source-sink.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dia/shapes/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/dia/shapes/table.png -------------------------------------------------------------------------------- /dia/shapes/table.shape: -------------------------------------------------------------------------------- 1 | 2 | 3 | shapes - table 4 | table.png 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dia/sheets/SystemDynamics.sheet: -------------------------------------------------------------------------------- 1 | 2 | 3 | System Dynamics 4 | System Dynamics glyphs 5 | 6 | 7 | Constant (top) 8 | 9 | 10 | Constant (bottom) 11 | 12 | 13 | Level 14 | 15 | 16 | Auxiliary 17 | 18 | 19 | Rate (named, up) 20 | 21 | 22 | Rate (named, down) 23 | 24 | 25 | Rate (named, right) 26 | 27 | 28 | Rate (named, left) 29 | 30 | 31 | Rate (horiz) 32 | 33 | 34 | Rate (vert) 35 | 36 | 37 | Exogenous 38 | 39 | 40 | Table 41 | 42 | 43 | Source/Sink 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /equation.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2020-2021 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "go/ast" 25 | "go/parser" 26 | "go/token" 27 | "reflect" 28 | "strconv" 29 | "strings" 30 | ) 31 | 32 | // Dependency handling modes 33 | const ( 34 | DEP_NORMAL = iota // normal dependencies 35 | DEP_ENFORCE // enforce dependencies 36 | DEP_SKIP // skip dependencies 37 | ) 38 | 39 | //---------------------------------------------------------------------- 40 | // EQUATION -- An equation is a formula that describes the (new) value 41 | // of a variable with given name as a computation between old variables 42 | // (and probably constants). An equation is only computable in the 43 | // context of a model state. 44 | //---------------------------------------------------------------------- 45 | 46 | // Equation represents a formula; the result is assigned to a variable 47 | type Equation struct { 48 | Target *Name // Name of (indexed) variable (left side of equation) 49 | Dependencies []*Name // List of (indexed) dependencies from right side. 50 | References []*Name // List of references on the right side (non-dependent) 51 | Mode string // Mode of equation as given in the source 52 | Formula ast.Expr // formula in Go AST 53 | stmt string // complete equation in DYNAMO notation 54 | } 55 | 56 | // NewEquation converts a statement into one or more equation instances 57 | func NewEquation(stmt *Line) (eqns *EqnList, res *Result) { 58 | eqns = NewEqnList() 59 | Dbg.Msgf("NewEquation(%s)\n", stmt.String()) 60 | 61 | // check for spaces in equation 62 | if strings.Contains(stmt.Stmt, " ") { 63 | res = Failure(ErrParseInvalidSpace) 64 | return 65 | } 66 | // Const statements can have multiple assignments in one line. 67 | if stmt.Mode == "C" && strings.Count(stmt.Stmt, "=") > 1 { 68 | // add new extracted equation 69 | addEqn := func(line string) (res *Result) { 70 | var list *EqnList 71 | if list, res = NewEquation(&Line{ 72 | Stmt: line, 73 | Mode: "C", 74 | }); res.Ok { 75 | eqns.AddList(list) 76 | } 77 | return 78 | } 79 | // parse from end of statement 80 | line := stmt.Stmt 81 | for { 82 | pos := strings.LastIndex(line, "=") 83 | delim := strings.LastIndex(line[:pos], ",") 84 | if delim == -1 { 85 | if delim = strings.LastIndex(line[:pos], "/"); delim == -1 { 86 | res = addEqn(line) 87 | break 88 | } 89 | } 90 | Dbg.Msgf("Delim: %d\n", delim) 91 | if res = addEqn(line[delim+1:]); !res.Ok { 92 | break 93 | } 94 | line = line[:delim] 95 | } 96 | return 97 | } 98 | // expand multiplication shortcut 99 | line := strings.ReplaceAll(stmt.Stmt, ")(", ")*(") 100 | // assignment work-around (HACK!) 101 | line = strings.ReplaceAll(line, "=", "==") 102 | // use Go to parse expression 103 | expr, err := parser.ParseExpr(line) 104 | if err != nil { 105 | res = Failure(err) 106 | return 107 | } 108 | switch x := expr.(type) { 109 | case *ast.BinaryExpr: 110 | // prepare equation instance 111 | eqn := &Equation{ 112 | stmt: stmt.Stmt, 113 | Mode: stmt.Mode, 114 | Dependencies: make([]*Name, 0), 115 | References: make([]*Name, 0), 116 | } 117 | eqn.Formula = x.Y 118 | 119 | // Handle LEFT side of equation 120 | if eqn.Target, res = NewName(x.X); !res.Ok { 121 | return 122 | } 123 | switch stmt.Mode { 124 | case "N": 125 | if eqn.Target.Kind != NAME_KIND_CONST { 126 | res = Failure(ErrModelEqnBadTargetKind) 127 | return 128 | } 129 | eqn.Target.Kind = NAME_KIND_INIT 130 | case "A": 131 | if eqn.Target.Kind != NAME_KIND_LEVEL && eqn.Target.Kind != NAME_KIND_RATE { 132 | res = Failure(ErrModelEqnBadTargetKind) 133 | return 134 | } 135 | eqn.Target.Kind = NAME_KIND_AUX 136 | if eqn.Target.Stage != NAME_STAGE_NEW { 137 | res = Failure(ErrModelEqnBadTargetStage) 138 | return 139 | } 140 | case "S": 141 | if eqn.Target.Kind != NAME_KIND_LEVEL { 142 | res = Failure(ErrModelEqnBadTargetKind) 143 | return 144 | } 145 | eqn.Target.Kind = NAME_KIND_SUPPL 146 | if eqn.Target.Stage != NAME_STAGE_NEW { 147 | res = Failure(ErrModelEqnBadTargetStage) 148 | return 149 | } 150 | } 151 | 152 | // Handle RIGHT side of equation recursively 153 | var check func(ast.Expr, int) *Result 154 | check = func(f ast.Expr, mode int) (res *Result) { 155 | res = Success() 156 | switch x := f.(type) { 157 | case *ast.Ident, *ast.SelectorExpr: 158 | var name *Name 159 | if name, res = NewName(x); res.Ok { 160 | if stmt.Mode == "N" { 161 | name.Stage = NAME_STAGE_NONE 162 | } 163 | // add variable as dependency or reference 164 | if (mode == DEP_NORMAL && name.Stage != NAME_STAGE_OLD) || mode == DEP_ENFORCE { 165 | eqn.Dependencies = append(eqn.Dependencies, name) 166 | } else { 167 | eqn.References = append(eqn.References, name) 168 | } 169 | } 170 | case *ast.BinaryExpr: 171 | if res = check(x.X, mode); res.Ok { 172 | res = check(x.Y, mode) 173 | } 174 | case *ast.ParenExpr: 175 | res = check(x.X, mode) 176 | case *ast.BasicLit: 177 | // skipped intentionally 178 | case *ast.UnaryExpr: 179 | res = check(x.X, mode) 180 | case *ast.CallExpr: 181 | // get function name 182 | var name *Name 183 | if name, res = NewName(x.Fun); !res.Ok { 184 | break 185 | } 186 | // check for function availibility 187 | Dbg.Msgf("Calling '%s'\n", name.Name) 188 | var ( 189 | intern []ast.Expr 190 | modes []int 191 | ) 192 | if modes, intern, res = HasFunction(name.Name, x.Args); !res.Ok { 193 | break 194 | } 195 | // check function arguments 196 | for i, arg := range x.Args { 197 | if res = check(arg, modes[i]); !res.Ok { 198 | break 199 | } 200 | } 201 | // add internal variable 202 | x.Args = append(x.Args, intern...) 203 | 204 | default: 205 | res = Failure(ErrParseSyntax+": %v\n", reflect.TypeOf(x)) 206 | } 207 | return 208 | } 209 | 210 | res = check(x.Y, DEP_NORMAL) 211 | if res.Ok { 212 | eqns.Add(eqn) 213 | } 214 | return 215 | 216 | default: 217 | res = Failure(ErrParseSyntax+": %v\n", reflect.TypeOf(x)) 218 | } 219 | return 220 | } 221 | 222 | // String returns a human-readable equation formula. 223 | func (eqn *Equation) String() string { 224 | return "'" + eqn.Mode + ":" + eqn.stmt + "'" 225 | } 226 | 227 | // DependsOn returns true if a variable is referenced in the formula. 228 | func (eqn *Equation) DependsOn(v *Name) bool { 229 | for _, d := range eqn.Dependencies { 230 | if d.Compare(v)&NAME_SAMEVAR != 0 { 231 | return true 232 | } 233 | } 234 | return false 235 | } 236 | 237 | // Eval an equation and get the resulting numerical value and a status 238 | // result. The computation is performed on the state variables (level, rate) 239 | // of a DYNAMO model. 240 | // If the 'ini' flag is set, the initial value is computed by treating all 241 | // quantity references in "initial value" form. 242 | func (eqn *Equation) Eval(mdl *Model) (val Variable, res *Result) { 243 | Dbg.Msgf("----------------------------\n") 244 | Dbg.Msgf("Evaluating: %s\n", eqn.String()) 245 | missing := make(map[string]*Name) 246 | if val, res = eval(eqn.Formula, mdl, missing); res.Ok { 247 | res = mdl.Set(eqn.Target, val) 248 | 249 | // if we have missing variables, check the terminal equations 250 | // that use this equation 251 | if len(missing) > 0 { 252 | targets := make(map[string]*Equation) 253 | targets[eqn.Target.Name] = eqn 254 | maxDepth := mdl.Eqns.Len() 255 | for depth := 0; depth < maxDepth && len(targets) > 0; depth++ { 256 | nextTargets := make(map[string]*Equation) 257 | for name := range targets { 258 | list := mdl.Eqns.Dependent(name) 259 | for _, e := range list.eqns { 260 | if e.Mode != "S" { 261 | nextTargets[e.Target.Name] = e 262 | } 263 | } 264 | } 265 | targets = nextTargets 266 | } 267 | // if not all terminal equations are supplementary, 268 | // give warnings for missing variables. 269 | if len(targets) > 0 { 270 | for _, name := range missing { 271 | Msgf("WARN: Missing variable %s", name) 272 | } 273 | } 274 | } 275 | } 276 | return 277 | } 278 | 279 | // recursively evaluate the equation for a given model state 280 | func eval(expr ast.Expr, mdl *Model, missing map[string]*Name) (val Variable, res *Result) { 281 | res = Success() 282 | 283 | switch x := expr.(type) { 284 | case *ast.BinaryExpr: 285 | var left, right Variable 286 | if left, res = eval(x.X, mdl, missing); !res.Ok { 287 | break 288 | } 289 | if right, res = eval(x.Y, mdl, missing); !res.Ok { 290 | break 291 | } 292 | switch x.Op { 293 | case token.ADD: 294 | val = left + right 295 | case token.SUB: 296 | val = left - right 297 | case token.MUL: 298 | val = left * right 299 | case token.QUO: 300 | val = left / right 301 | default: 302 | res = Failure(ErrParseInvalidOp+": %d", x.Op) 303 | } 304 | return 305 | 306 | case *ast.ParenExpr: 307 | val, res = eval(x.X, mdl, missing) 308 | 309 | case *ast.BasicLit: 310 | v, err := strconv.ParseFloat(x.Value, 64) 311 | if err != nil { 312 | res = Failure(err) 313 | } 314 | val = Variable(v) 315 | 316 | case *ast.Ident, *ast.SelectorExpr: 317 | var name *Name 318 | if name, res = NewName(x); !res.Ok { 319 | break 320 | } 321 | if val, res = mdl.Get(name); !res.Ok { 322 | if val, res = mdl.Initial(name.Name); !res.Ok { 323 | missing[name.Name] = name 324 | val = 0 325 | res = Success() 326 | } 327 | } 328 | 329 | case *ast.CallExpr: 330 | // get name of function 331 | var name *Name 332 | if name, res = NewName(x.Fun); !res.Ok { 333 | break 334 | } 335 | // convert arguments to strings 336 | args := make([]string, len(x.Args)) 337 | for i, arg := range x.Args { 338 | switch x := arg.(type) { 339 | case *ast.Ident: 340 | args[i] = x.Name 341 | case *ast.SelectorExpr: 342 | var n *Name 343 | if n, res = NewName(x); !res.Ok { 344 | return 345 | } 346 | name := n.Name 347 | if idx := n.GetIndex(); len(idx) > 0 { 348 | name += idx 349 | } 350 | args[i] = name 351 | case *ast.BasicLit: 352 | args[i] = x.Value 353 | case *ast.BinaryExpr: 354 | if val, res = eval(x, mdl, missing); !res.Ok { 355 | return 356 | } 357 | args[i] = val.String() 358 | case *ast.ParenExpr: 359 | if val, res = eval(x, mdl, missing); !res.Ok { 360 | return 361 | } 362 | args[i] = val.String() 363 | case *ast.UnaryExpr: 364 | if val, res = eval(x.X, mdl, missing); !res.Ok { 365 | break 366 | } 367 | switch x.Op { 368 | case token.SUB: 369 | val = -val 370 | default: 371 | res = Failure(ErrParseInvalidOp+": %d", x.Op) 372 | return 373 | } 374 | args[i] = val.String() 375 | default: 376 | res = Failure(ErrModelFunctionArg+": %s", reflect.TypeOf(x)) 377 | return 378 | } 379 | } 380 | val, res = CallFunction(name.Name, args, mdl) 381 | 382 | case *ast.UnaryExpr: 383 | if val, res = eval(x.X, mdl, missing); !res.Ok { 384 | break 385 | } 386 | switch x.Op { 387 | case token.SUB: 388 | val = -val 389 | default: 390 | res = Failure(ErrParseInvalidOp+": %d", x.Op) 391 | } 392 | 393 | default: 394 | res = Failure(ErrParseSyntax+": %v\n", reflect.TypeOf(x)) 395 | } 396 | return 397 | } 398 | -------------------------------------------------------------------------------- /functions_test.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFcnTableList(t *testing.T) { 9 | 10 | mdl := NewModel("", "") 11 | pnts := []float64{0, 2.8, 5.5, 8, 9.5, 10} 12 | 13 | tbl := "TEST=" 14 | for i, v := range pnts { 15 | if i > 0 { 16 | tbl += "/" 17 | } 18 | tbl += fmt.Sprintf("%f", v) 19 | } 20 | stmt := &Line{ 21 | Mode: "T", 22 | Stmt: tbl, 23 | } 24 | res := mdl.AddStatement(stmt) 25 | if !res.Ok { 26 | t.Fatal(res.Err) 27 | } 28 | t.Logf("X;Y;TABLE") 29 | for x := -20; x <= 70; x++ { 30 | xx := float64(x) / 50 31 | xs := fmt.Sprintf("%f", xx) 32 | val, res := CallFunction("TABLE", []string{"TEST", xs, "0", "1", "0.2"}, mdl) 33 | if !res.Ok { 34 | t.Fatal(res.Err) 35 | } 36 | t.Logf("%f;%f\n", xx, val) 37 | } 38 | t.Logf("X;Y;TABHL") 39 | for x := -20; x <= 70; x++ { 40 | xx := float64(x) / 50 41 | xs := fmt.Sprintf("%f", xx) 42 | val, res := CallFunction("TABHL", []string{"TEST", xs, "0", "1", "0.2"}, mdl) 43 | if !res.Ok { 44 | t.Fatal(res.Err) 45 | } 46 | t.Logf("%f;%f\n", xx, val) 47 | } 48 | t.Logf("X;Y;TABXT") 49 | for x := -20; x <= 70; x++ { 50 | xx := float64(x) / 50 51 | xs := fmt.Sprintf("%f", xx) 52 | val, res := CallFunction("TABXT", []string{"TEST", xs, "0", "1", "0.2"}, mdl) 53 | if !res.Ok { 54 | t.Fatal(res.Err) 55 | } 56 | t.Logf("%f;%f\n", xx, val) 57 | } 58 | t.Logf("X;Y;TABPL") 59 | for x := -20; x <= 70; x++ { 60 | xx := float64(x) / 50 61 | xs := fmt.Sprintf("%f", xx) 62 | val, res := CallFunction("TABPL", []string{"TEST", xs, "0", "1", "0.2"}, mdl) 63 | if !res.Ok { 64 | t.Fatal(res.Err) 65 | } 66 | t.Logf("%f;%f\n", xx, val) 67 | } 68 | } 69 | 70 | func TestFcnTable(t *testing.T) { 71 | 72 | mdl := NewModel("", "") 73 | pnts := []float64{0, 2.8, 5.5, 8, 9.5, 10} 74 | 75 | tbl := "TEST=" 76 | for i, v := range pnts { 77 | if i > 0 { 78 | tbl += "/" 79 | } 80 | tbl += fmt.Sprintf("%f", v) 81 | } 82 | stmt := &Line{ 83 | Mode: "T", 84 | Stmt: tbl, 85 | } 86 | res := mdl.AddStatement(stmt) 87 | if !res.Ok { 88 | t.Fatal(res.Err) 89 | } 90 | for x := 0; x <= 5; x++ { 91 | xx := float64(x) / 5 92 | xs := fmt.Sprintf("%f", xx) 93 | val, res := CallFunction("TABLE", []string{"TEST", xs, "0", "1", "0.2"}, mdl) 94 | if !res.Ok { 95 | t.Fatal(res.Err) 96 | } 97 | if compare(float64(val), pnts[x]) != 0 { 98 | t.Fatalf("Value mismatch: %f != %f", val, pnts[x]) 99 | } 100 | } 101 | } 102 | 103 | func TestFcnTabpl(t *testing.T) { 104 | pnts := []string{"0", "2.8", "5.5", "8", "9.5", "10"} 105 | tbl, res := NewTable(pnts) 106 | if !res.Ok { 107 | t.Fatal(res.Err) 108 | } 109 | for x := 0; x <= 5; x++ { 110 | y := tbl.Newton(Variable(x) / 5.) 111 | if y.Compare(Variable(tbl.Data[x])) != 0 { 112 | t.Fatalf("Value mismatch: %f != %f", y, tbl.Data[x]) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bfix/dynamo 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /model_test.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "bytes" 25 | "testing" 26 | ) 27 | 28 | // testData is a data structure for a test case 29 | type testData struct { 30 | name string // name of test case 31 | src []string // DYNAMO source code 32 | lineno int // lines processed 33 | err string // parse error 34 | run bool // run model? 35 | } 36 | 37 | // testSet is a collection of all test cases 38 | var testSet = []*testData{ 39 | //------------------------------------------------------------------ 40 | // valid source code 41 | { 42 | name: "mdl: ok", 43 | src: []string{ 44 | "R CHNGE.KL=CONST*(ROOM-COFFEE.K)", 45 | "L COFFEE.K=COFFEE.J+(DT)(CHNG.JK)", 46 | "C DIFF=ROOM-COFFEE", 47 | "C CONST=0.2", 48 | "C ROOM=20", 49 | "C COFFEE=90", 50 | }, 51 | lineno: 6, 52 | err: "", 53 | }, 54 | //------------------------------------------------------------------ 55 | // Dependency loop detection 56 | { 57 | name: "mdl: dependency loop", 58 | src: []string{ 59 | "L INV.K=INV.J+DT*CHNG.JK+TEST.K", 60 | "L TEST.K=CONST*INV.K", 61 | "R CHNG.KL=0", 62 | "C CONST=1", 63 | }, 64 | lineno: 4, 65 | err: ErrModelDependencyLoop, 66 | }, 67 | //------------------------------------------------------------------ 68 | // Missing mode 69 | { 70 | name: "eqn: missing mode", 71 | src: []string{ 72 | "INV.K=INV.J+DT*CHNG.JK", 73 | "R CHNG.KL=0", 74 | }, 75 | lineno: 2, 76 | err: ErrParseInvalidMode, 77 | }, 78 | //------------------------------------------------------------------ 79 | // Bad mode 80 | { 81 | name: "eqn: bad mode", 82 | src: []string{ 83 | "Y INV.K=INV.J+DT*CHNG.JK", 84 | "R CHNG.KL=0", 85 | }, 86 | lineno: 2, 87 | err: ErrParseInvalidMode, 88 | }, 89 | //------------------------------------------------------------------ 90 | // Syntax error in equation 91 | { 92 | name: "eqn: syntax", 93 | src: []string{ 94 | "L INV.K=INV.J+DT**CHNG.JK", 95 | "R CHNG.KL=0", 96 | }, 97 | lineno: 2, 98 | err: ErrParseSyntax, 99 | }, 100 | //------------------------------------------------------------------ 101 | // Invalid target state (not NEW) 102 | { 103 | name: "eqn: bad target stage", 104 | src: []string{ 105 | "L INV.J=INV.K+DT*CHNG.JK", 106 | }, 107 | lineno: 1, 108 | err: ErrModelEqnBadTargetStage, 109 | }, 110 | //------------------------------------------------------------------ 111 | // Duplicate equation 112 | { 113 | name: "eqn: overwrite", 114 | src: []string{ 115 | "L INV.K=INV.J+DT*CHNG.JK", 116 | "L INV.K=CONST*INV.J", 117 | }, 118 | lineno: 2, 119 | err: ErrModelEqnOverwrite, 120 | }, 121 | //------------------------------------------------------------------ 122 | // Line too long 123 | { 124 | name: "parse: line too long", 125 | src: []string{ 126 | "L INV.K=INV.J+DT*CHNG.JK-COFFEE.J+(DT)(CHNG.JK)+CONST*(ROOM-COFFEE.K)+OFFSET", 127 | }, 128 | lineno: 1, 129 | err: ErrParseLineLength, 130 | }, 131 | //------------------------------------------------------------------ 132 | // Invalid variable name (too long) 133 | { 134 | name: "eqn: var name too long", 135 | src: []string{ 136 | "L INVENTARLISTE.K=INVENTARLISTE.J+DT*CHNG.JK", 137 | }, 138 | lineno: 1, 139 | err: ErrParseNameLength, 140 | }, 141 | //------------------------------------------------------------------ 142 | // Invalid variable index 143 | { 144 | name: "eqn: bad var index", 145 | src: []string{ 146 | "L INV.L=INV.J+DT*CHNG.JK", 147 | }, 148 | lineno: 1, 149 | err: ErrParseInvalidIndex, 150 | }, 151 | //------------------------------------------------------------------ 152 | // Wrong target kind for equation 153 | { 154 | name: "eqn: bad var kind", 155 | src: []string{ 156 | "R INV.K=INV.J+DT*CHNG.JK", 157 | }, 158 | lineno: 1, 159 | err: ErrModelEqnBadTargetKind, 160 | }, 161 | } 162 | 163 | func TestModel(t *testing.T) { 164 | failed := 0 165 | for _, td := range testSet { 166 | mdl := NewModel("", "") 167 | buf := new(bytes.Buffer) 168 | for _, line := range td.src { 169 | buf.WriteString(line + "\n") 170 | } 171 | res := mdl.Parse(bytes.NewReader(buf.Bytes())) 172 | if !res.Ok && !res.IsA(td.err) { 173 | t.Logf("[%s] Error mismtach: %s != %s\n", td.name, res.Err.Error(), td.err) 174 | failed++ 175 | } 176 | if res.Line != td.lineno { 177 | t.Logf("[%s] Line mismtach: %d != %d\n", td.name, res.Line, td.lineno) 178 | failed++ 179 | } 180 | if res.Ok && td.run { 181 | if res = mdl.Run(); !res.IsA(td.err) { 182 | t.Logf("[%s] Status mismtach: %s != %s\n", td.name, res.Err.Error(), td.err) 183 | failed++ 184 | } 185 | } 186 | } 187 | if failed > 0 { 188 | t.Fatalf("%d test cases failed", failed) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /output.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "fmt" 25 | "log" 26 | "os" 27 | ) 28 | 29 | //====================================================================== 30 | // Normal program messages 31 | //====================================================================== 32 | 33 | // Msg (plain message) 34 | func Msg(msg string) { 35 | log.Println(msg) 36 | } 37 | 38 | // Msgf (formatted message) 39 | func Msgf(format string, args ...interface{}) { 40 | log.Printf(format, args...) 41 | } 42 | 43 | // Fatal terminates the application with plain message 44 | func Fatal(msg string) { 45 | log.Fatal(msg) 46 | } 47 | 48 | // Fatalf terminates the application with formatted message 49 | func Fatalf(format string, args ...interface{}) { 50 | log.Fatalf(format, args...) 51 | } 52 | 53 | //====================================================================== 54 | // DEBUG messages 55 | //====================================================================== 56 | 57 | // Shared debugger instance 58 | var Dbg *Debugger 59 | 60 | // Debugger writes debug messages to a file (if defined) 61 | type Debugger struct { 62 | file *os.File // reference to debug file (or nil if not defined) 63 | console bool 64 | } 65 | 66 | // SetDebugger instantiates a new Debugger 67 | func SetDebugger(file string) { 68 | Dbg = new(Debugger) 69 | if len(file) == 0 { 70 | Dbg.file = nil 71 | } else { 72 | if file == "-" { 73 | Dbg.console = true 74 | Dbg.file = os.Stdout 75 | } else { 76 | var err error 77 | if Dbg.file, err = os.Create(file); err != nil { 78 | Fatal(err.Error()) 79 | } 80 | } 81 | } 82 | } 83 | 84 | // Close debugger file 85 | func (dbg *Debugger) Close() *Result { 86 | if dbg != nil && dbg.file != nil && !dbg.console { 87 | dbg.file.Close() 88 | } 89 | return Success() 90 | } 91 | 92 | // Msg to write a plain message into the debugger file 93 | func (dbg *Debugger) Msg(msg string) { 94 | if dbg != nil && dbg.file != nil { 95 | dbg.file.WriteString(msg + "\n") 96 | } 97 | } 98 | 99 | // Msgf to write a formatted message into the debugger file 100 | func (dbg *Debugger) Msgf(format string, args ...interface{}) { 101 | if dbg != nil && dbg.file != nil { 102 | msg := fmt.Sprintf(format, args...) 103 | dbg.file.WriteString(msg) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "bufio" 25 | "fmt" 26 | "io" 27 | "strings" 28 | ) 29 | 30 | //---------------------------------------------------------------------- 31 | // Parser for DYNAMO-formatted source files 32 | //---------------------------------------------------------------------- 33 | 34 | // Parser-related constants 35 | const ( 36 | MAX_LINE_LENGTH = 72 // max. length of line in 'strict' mode 37 | ) 38 | 39 | // Line represents a line in a DYNAMO source code stream. It consists of a 40 | // mode, a statement and an optional comment 41 | type Line struct { 42 | Mode string 43 | Stmt string 44 | Comment string 45 | } 46 | 47 | // String returns a human-readable representatun of a line 48 | func (l *Line) String() string { 49 | return fmt.Sprintf("[%s] %s {%s}", l.Mode, l.Stmt, l.Comment) 50 | } 51 | 52 | // Parse a DYNAMO source file and return a model instance for it. 53 | func (mdl *Model) Parse(rdr io.Reader) (res *Result) { 54 | // compact string (trim and remove double spaces) 55 | compact := func(s string) string { 56 | s = strings.TrimSpace(s) 57 | for strings.Contains(s, " ") { 58 | s = strings.Replace(s, " ", " ", -1) 59 | } 60 | return s 61 | } 62 | 63 | // parse a single (complete) line of model code 64 | var ( 65 | input string 66 | lineNo int 67 | stmtNo int 68 | ) 69 | parseInput := func() (res *Result) { 70 | res = Success() 71 | // skip empty input line 72 | if len(input) == 0 { 73 | return 74 | } 75 | // create new statement 76 | stmt := new(Line) 77 | // dissect inout 78 | if pos := strings.Index(input, " "); pos != -1 { 79 | stmt.Mode = input[:pos] 80 | input = strings.TrimSpace(input[pos:]) 81 | stmt.Stmt = input 82 | stmt.Comment = "" 83 | if strings.Contains("CNARLST", stmt.Mode) { 84 | if pos := strings.Index(input, " "); pos != -1 { 85 | stmt.Stmt = input[:pos] 86 | stmt.Comment = compact(input[pos:]) 87 | } 88 | } 89 | res = mdl.AddStatement(stmt).SetLine(stmtNo) 90 | } 91 | input = "" 92 | return 93 | } 94 | 95 | // parse source stream 96 | brdr := bufio.NewReader(rdr) 97 | lineNo = 0 98 | for { 99 | // read next line and check length limit 100 | data, _, err := brdr.ReadLine() 101 | lineNo++ 102 | if strict && len(data) > MAX_LINE_LENGTH { 103 | res = Failure(ErrParseLineLength).SetLine(lineNo) 104 | return 105 | } 106 | // handle read error 107 | if err != nil { 108 | if err == io.EOF { 109 | // add last pending statement 110 | res = parseInput() 111 | } else { 112 | res = Failure(err).SetLine(lineNo) 113 | } 114 | return 115 | } 116 | // process line 117 | line := strings.ToUpper(string(data)) 118 | if len(line) == 0 { 119 | // skip empty lines 120 | continue 121 | } 122 | // check for continuation line 123 | if line[0] == 'X' { 124 | input += strings.TrimSpace(line[1:]) 125 | continue 126 | } 127 | // process pending input 128 | if res = parseInput(); !res.Ok { 129 | break 130 | } 131 | input = line 132 | stmtNo = lineNo 133 | } 134 | res.SetLine(lineNo) 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /printer.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "fmt" 25 | "math" 26 | "os" 27 | "strconv" 28 | "strings" 29 | ) 30 | 31 | //====================================================================== 32 | // PRINTER for DYNAMO results 33 | //====================================================================== 34 | 35 | //---------------------------------------------------------------------- 36 | // PrintVar 37 | //---------------------------------------------------------------------- 38 | 39 | // PrintVar represents a printed variable 40 | type PrintVar struct { 41 | TSVar 42 | Scale float64 43 | } 44 | 45 | // NewPrintVar creates a new named variable for print output. 46 | func NewPrintVar(name string) *PrintVar { 47 | return &PrintVar{ 48 | TSVar: TSVar{ 49 | Name: name, 50 | Values: make([]float64, 0), 51 | }, 52 | Scale: 1.0, 53 | } 54 | } 55 | 56 | // Calculate optimal scale of data series 57 | func (pv *PrintVar) calcScale() { 58 | max := math.Max(math.Abs(pv.Max), math.Abs(pv.Min)) 59 | if max > 0 { 60 | x := int(math.Round(math.Log10(max))) - 2 61 | if x > 0 { 62 | pv.Scale = math.Pow10(x) 63 | } 64 | } 65 | } 66 | 67 | //---------------------------------------------------------------------- 68 | // PrtCol 69 | //---------------------------------------------------------------------- 70 | 71 | // PrtCol has an ordered list of variables to appear in a column 72 | type PrtCol struct { 73 | Vars []string 74 | Scale float64 75 | } 76 | 77 | // NewPrtCol instantiates a new column (multi-label) 78 | func NewPrtCol() *PrtCol { 79 | return &PrtCol{ 80 | Vars: make([]string, 0), 81 | Scale: -1.0, 82 | } 83 | } 84 | 85 | // Add a name to the colum 86 | func (pc *PrtCol) Add(name string) *PrtCol { 87 | pc.Vars = append(pc.Vars, name) 88 | return pc 89 | } 90 | 91 | // Merge scales of sub-columns 92 | func (pc *PrtCol) mergeScale(scale float64) { 93 | if scale > pc.Scale { 94 | pc.Scale = scale 95 | } 96 | } 97 | 98 | //---------------------------------------------------------------------- 99 | // Print jobs 100 | //---------------------------------------------------------------------- 101 | 102 | // PrintJob is printing a single table of selected variables 103 | type PrintJob struct { 104 | stmt string // PRINT statement 105 | prt *Printer // printer instance 106 | cols map[int]*PrtCol // print columns 107 | } 108 | 109 | // NewPrintJob creates a new print job for the printer based on 110 | // the PRINT statement. 111 | func NewPrintJob(stmt string, prt *Printer) *PrintJob { 112 | pj := &PrintJob{ 113 | stmt: stmt, 114 | prt: prt, 115 | cols: make(map[int]*PrtCol, 1), 116 | } 117 | // Add TIME as first column 118 | prt.vars["TIME"] = NewPrintVar("TIME") 119 | pj.cols[0] = NewPrtCol().Add("TIME") 120 | return pj 121 | } 122 | 123 | //---------------------------------------------------------------------- 124 | // Printer 125 | //---------------------------------------------------------------------- 126 | 127 | // Printing modes 128 | const ( 129 | PRT_DYNAMO = iota // Old-style DYNAMO printing 130 | PRT_CSV // CSV-formatted print 131 | ) 132 | 133 | // Printer writes print output to a file (if defined) 134 | type Printer struct { 135 | file *os.File // reference to print file (or nil if not defined) 136 | mode int // printing mode (PRT_????) 137 | mdl *Model // back-ref to model instance 138 | steps int // number of DT steps between printed points 139 | vars map[string]*PrintVar // variables to use in print 140 | xnum int // number of x-values 141 | jobs []*PrintJob // list of print jobs to perform 142 | add bool // printer is adding jobs 143 | } 144 | 145 | // NewPrinter instantiates a new printer output. 146 | func NewPrinter(file string, mdl *Model) *Printer { 147 | // determine printing mode from file name 148 | mode := PRT_DYNAMO 149 | if pos := strings.LastIndex(file, "."); pos != -1 { 150 | switch strings.ToUpper(file[pos:]) { 151 | case ".PRT": 152 | mode = PRT_DYNAMO 153 | case ".CSV": 154 | mode = PRT_CSV 155 | } 156 | } 157 | // create new printer instance 158 | prt := &Printer{ 159 | mdl: mdl, 160 | mode: mode, 161 | vars: make(map[string]*PrintVar), 162 | jobs: make([]*PrintJob, 0), 163 | add: true, 164 | } 165 | // open file for output 166 | if len(file) == 0 { 167 | prt.file = nil 168 | } else { 169 | var err error 170 | if prt.file, err = os.Create(file); err != nil { 171 | Fatal(err.Error()) 172 | } 173 | } 174 | return prt 175 | } 176 | 177 | // Reset a printer 178 | func (prt *Printer) Reset() { 179 | // clear time-series on PrintVar 180 | for _, v := range prt.vars { 181 | v.Reset() 182 | } 183 | prt.add = false 184 | prt.xnum = 0 185 | } 186 | 187 | // Generate print output. 188 | func (prt *Printer) Generate() *Result { 189 | if prt.file != nil { 190 | // do the actual printing 191 | return prt.print() 192 | } 193 | return Success() 194 | } 195 | 196 | // Close a printer if job is complete 197 | func (prt *Printer) Close() (res *Result) { 198 | res = Success() 199 | if prt.file != nil { 200 | if err := prt.file.Close(); err != nil { 201 | res = Failure(err) 202 | } 203 | } 204 | return 205 | } 206 | 207 | // Prepare the printer for output ased on the PRINT statement 208 | func (prt *Printer) Prepare(stmt string) (res *Result) { 209 | res = Success() 210 | 211 | // if we do not add jobs, clear exisiting jobs and vars 212 | if !prt.add { 213 | prt.vars = make(map[string]*PrintVar) 214 | prt.jobs = make([]*PrintJob, 0) 215 | prt.add = true 216 | } 217 | 218 | // create a new print job 219 | pj := NewPrintJob(stmt, prt) 220 | prt.jobs = append(prt.jobs, pj) 221 | 222 | // split into column groups 223 | var err error 224 | grps := strings.Split(stmt, "/") 225 | if len(grps) == 1 { 226 | // we only have one column group: flat list of columns 227 | for pos, label := range strings.Split(grps[0], ",") { 228 | pv := &PrintVar{ 229 | TSVar: TSVar{ 230 | Name: label, 231 | Values: make([]float64, 0), 232 | }, 233 | Scale: 1.0, 234 | } 235 | prt.vars[label] = pv 236 | pj.cols[pos+1] = NewPrtCol().Add(label) 237 | } 238 | } else { 239 | // parse column groups 240 | for pos, grp := range grps { 241 | // parse optional column index. 242 | col := pos + 1 243 | if delim := strings.Index(grp, ")"); delim != -1 { 244 | if col, err = strconv.Atoi(grp[:delim]); err != nil { 245 | return Failure(err) 246 | } 247 | grp = grp[delim+1:] 248 | } 249 | // add labels to column 250 | column := NewPrtCol() 251 | pj.cols[col] = column 252 | for _, label := range strings.Split(grp, ",") { 253 | // add variable 254 | pv := &PrintVar{ 255 | TSVar: TSVar{ 256 | Name: label, 257 | Values: make([]float64, 0), 258 | }, 259 | Scale: 1.0, 260 | } 261 | prt.vars[label] = pv 262 | // add to column 263 | column.Add(label) 264 | } 265 | } 266 | } 267 | return 268 | } 269 | 270 | // Start is called when the model starts executing 271 | func (prt *Printer) Start() (res *Result) { 272 | res = Success() 273 | if prt.file != nil { 274 | // get print stepping 275 | pp, ok := prt.mdl.Current["PRTPER"] 276 | if !ok { 277 | return Failure(ErrModelMissingDef + ": PRTPER") 278 | } 279 | dt, ok := prt.mdl.Current["DT"] 280 | if !ok { 281 | return Failure(ErrModelMissingDef + ": DT") 282 | } 283 | prt.steps = int(pp / dt) 284 | if compare(float64(pp), float64(prt.steps)*float64(dt)) != 0 { 285 | Msgf("WARNING: PRTPER != n * DT") 286 | } 287 | } 288 | return 289 | } 290 | 291 | // Add a new line for results in this epoch 292 | func (prt *Printer) Add(epoch int) (res *Result) { 293 | res = Success() 294 | if prt.file != nil { 295 | // check for output epoch 296 | if prt.steps == 0 || (prt.steps > 1 && epoch%prt.steps != 1) { 297 | return 298 | } 299 | // get values for printed variables 300 | for name, pv := range prt.vars { 301 | val, ok := prt.mdl.Current[name] 302 | if !ok { 303 | return Failure(ErrModelNoVariable+": %s [Printer]", name) 304 | } 305 | pv.Add(float64(val)) 306 | } 307 | prt.xnum++ 308 | } 309 | return 310 | } 311 | 312 | //---------------------------------------------------------------------- 313 | // Print routines 314 | //---------------------------------------------------------------------- 315 | 316 | // Print collected data 317 | func (prt *Printer) print() *Result { 318 | Msgf(" Generating print(s)...") 319 | // handle all print jobs 320 | if prt.steps > 0 { 321 | for _, pj := range prt.jobs { 322 | switch prt.mode { 323 | case PRT_DYNAMO: 324 | return prt.print_dyn(pj) 325 | case PRT_CSV: 326 | return prt.print_csv(pj) 327 | default: 328 | return Failure(ErrPrintMode) 329 | } 330 | } 331 | } 332 | return Success() 333 | } 334 | 335 | // Print data in classic DYNAMO style 336 | func (prt *Printer) print_dyn(pj *PrintJob) (res *Result) { 337 | res = Success() 338 | 339 | // print intro 340 | fmt.Fprintf(prt.file, "\n\n") 341 | fmt.Fprintf(prt.file, " PRINT %s\n", pj.stmt) 342 | fmt.Fprintln(prt.file) 343 | if len(prt.mdl.Title) > 0 { 344 | fmt.Fprintf(prt.file, "***** %s *****\n", prt.mdl.Title) 345 | fmt.Fprintln(prt.file) 346 | } 347 | if len(prt.mdl.RunID) > 0 { 348 | fmt.Fprintf(prt.file, "Print results for run '%s'\n", prt.mdl.RunID) 349 | fmt.Fprintln(prt.file) 350 | } 351 | // compute optimal scale for printed variables 352 | for _, pv := range prt.vars { 353 | pv.calcScale() 354 | } 355 | // assemble array of columns with sub-columns (in print order) 356 | list := make([][]string, 20) 357 | maxcol := 0 358 | maxsub := 0 359 | for col := 0; col < 20; col++ { 360 | list[col] = nil 361 | if pc, ok := pj.cols[col]; ok { 362 | list[col] = pc.Vars 363 | maxcol = col + 1 364 | for _, name := range pc.Vars { 365 | pc.mergeScale(prt.vars[name].Scale) 366 | } 367 | if len(pc.Vars) > maxsub { 368 | maxsub = len(pc.Vars) 369 | } 370 | } 371 | } 372 | // print header 373 | for sub := 0; sub < maxsub; sub++ { 374 | for col := 0; col < maxcol; col++ { 375 | vl := list[col] 376 | if vl == nil || sub >= len(vl) { 377 | fmt.Fprintf(prt.file, " ") 378 | } else { 379 | fmt.Fprintf(prt.file, " %7s", vl[sub]) 380 | } 381 | } 382 | fmt.Fprintln(prt.file) 383 | } 384 | // print scales 385 | for col := 0; col < maxcol; col++ { 386 | vl := list[col] 387 | if vl == nil { 388 | fmt.Fprintf(prt.file, " ") 389 | } else { 390 | scale := fmt.Sprintf("%E", pj.cols[col].Scale) 391 | pos := strings.LastIndex(scale, "E") 392 | fmt.Fprintf(prt.file, " %7s", scale[pos:]) 393 | } 394 | } 395 | fmt.Fprintln(prt.file) 396 | // print data 397 | for x := 0; x < prt.xnum; x++ { 398 | for sub := 0; sub < maxsub; sub++ { 399 | for col := 0; col < maxcol; col++ { 400 | vl := list[col] 401 | if vl == nil || sub >= len(vl) { 402 | fmt.Fprintf(prt.file, " ") 403 | } else { 404 | val := prt.vars[vl[sub]].Values[x] / pj.cols[col].Scale 405 | fmt.Fprintf(prt.file, " %7.3f", val) 406 | } 407 | } 408 | fmt.Fprintln(prt.file) 409 | } 410 | } 411 | return 412 | } 413 | 414 | // Print data into a CSV file 415 | func (prt *Printer) print_csv(pj *PrintJob) (res *Result) { 416 | res = Success() 417 | 418 | // get (flat) list of labels 419 | var list []string 420 | for col := 0; col < 20; col++ { 421 | if pc, ok := pj.cols[col]; ok { 422 | list = append(list, pc.Vars...) 423 | } 424 | } 425 | // emit header 426 | for i, name := range list { 427 | if i > 0 { 428 | prt.file.WriteString(";") 429 | } 430 | prt.file.WriteString(name) 431 | } 432 | fmt.Fprintln(prt.file) 433 | // emit data 434 | for x := 0; x < prt.xnum; x++ { 435 | for i, name := range list { 436 | if i > 0 { 437 | prt.file.WriteString(";") 438 | } 439 | pv, ok := prt.vars[name] 440 | if !ok { 441 | return Failure(ErrPrintNoVar) 442 | } 443 | fmt.Fprintf(prt.file, "%f", pv.Values[x]) 444 | } 445 | fmt.Fprintln(prt.file) 446 | } 447 | 448 | return 449 | } 450 | -------------------------------------------------------------------------------- /result.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "fmt" 25 | "strings" 26 | ) 27 | 28 | // DYNAMO error messages 29 | const ( 30 | ErrModelDependencyLoop = "Equations have cyclic dependencies" 31 | ErrModelEqnBadTargetKind = "Wrong kind of equation target" 32 | ErrModelEqnBadTargetStage = "Wrong stage for equation target" 33 | ErrModelEqnBadDependClass = "Wrong class (kind/stage) of equation dependency" 34 | ErrModelEqnBadMode = "Wrong mode for equation" 35 | ErrModelEqnOverwrite = "Equation overwrite" 36 | ErrModelEqnAmbigious = "Ambigious equation" 37 | ErrModelUnknownEqn = "No defining equation for variable found" 38 | ErrModelUnknownFunction = "Unknown function call" 39 | ErrModelFunctionArg = "Invalid function argument" 40 | ErrModelNoVariable = "No variable found" 41 | ErrModelVariabeExists = "Variable already known" 42 | ErrModelNoSuchTable = "No such table" 43 | ErrModelWrongTableSize = "Tabe size mismatch" 44 | ErrModelNoTime = "No TIME defined" 45 | ErrModelMaxRetry = "Retry limit reached" 46 | ErrModelMissingDef = "Missing definition of value" 47 | ErrModelNoData = "No data available" 48 | ErrModelFunction = "Error in function" 49 | ErrModelNotAvailable = "Model equations not available" 50 | ErrModelNoInitial = "No initial value" 51 | 52 | ErrParseLineLength = "Line too long" 53 | ErrParseInvalidSpace = "Space in equation" 54 | ErrParseInvalidMode = "Line does not start with a valid mode" 55 | ErrParseInvalidName = "Invalid variable name" 56 | ErrParseInvalidIndex = "Invalid variable index" 57 | ErrParseNameLength = "Variable name too long" 58 | ErrParseSyntax = "Syntax error" 59 | ErrParseInvalidOp = "Unknown operand" 60 | ErrParseTableTooSmall = "Not enough table elements" 61 | ErrParseUnknownFunction = "Unknown function" 62 | ErrParseInvalidNumArgs = "Invalid number of arguments" 63 | ErrParseMacroDepth = "Invalid nesting for macro function" 64 | ErrParseNotANumber = "Not a number" 65 | 66 | ErrPlotRange = "Range failure" 67 | ErrPlotNoVar = "Not a plot variable" 68 | ErrPlotMode = "No such plotter mode" 69 | 70 | ErrPrintNoVar = "Not a print variable" 71 | ErrPrintMode = "No such plotter mode" 72 | ) 73 | 74 | // Result represents the response of a method call in the Dynamo framework. 75 | // It allows to track failures with more information than 'error' alone 76 | // provides. 77 | type Result struct { 78 | Ok bool // call returned without problems 79 | Err error // error (if !Ok) 80 | Line int // line number in input stream (0 if not parsing) 81 | Ctx interface{} // Optional failure context 82 | } 83 | 84 | // Success is used if the call finishes without problems 85 | func Success() *Result { 86 | return &Result{ 87 | Ok: true, 88 | Err: nil, 89 | Line: 0, 90 | Ctx: nil, 91 | } 92 | } 93 | 94 | // Failure returns a result for a failed operation. The parameter can be 95 | // of type 'string' or 'error'. 96 | func Failure(err interface{}, args ...interface{}) *Result { 97 | var e error = nil 98 | switch x := err.(type) { 99 | case error: 100 | e = x 101 | case string: 102 | if len(args) > 0 { 103 | e = fmt.Errorf(x, args...) 104 | } else { 105 | e = fmt.Errorf(x) 106 | } 107 | } 108 | return &Result{ 109 | Ok: false, 110 | Err: e, 111 | Line: 0, 112 | Ctx: nil, 113 | } 114 | } 115 | 116 | // SetLine should be used by methods that are involved with parsing 117 | // DYNAMO source code to report the problematic line in the input stream. 118 | func (r *Result) SetLine(line int) *Result { 119 | r.Line = line 120 | return r 121 | } 122 | 123 | // IsA returns true if the given (failure) result is of given error 124 | func (r *Result) IsA(err string) bool { 125 | if r.Err == nil { 126 | return false 127 | } 128 | return strings.HasPrefix(r.Err.Error(), err) 129 | } 130 | -------------------------------------------------------------------------------- /rt/.gitignore: -------------------------------------------------------------------------------- 1 | /*.dbg 2 | /*.prt 3 | /*.plt 4 | -------------------------------------------------------------------------------- /rt/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Example models in the DYNAMO programming language 3 | 4 | ## Real-world models 5 | 6 | ### CHECO / Project CYBERSYN 7 | 8 | [Project CYBERSYN](https://en.wikipedia.org/wiki/Project_Cybersyn) (short for 9 | CYBERnetic SYNergy) was the attempt of the British cybernetician 10 | [Stafford Beer](https://en.wikipedia.org/wiki/Stafford_Beer) to help manage 11 | the Chilean economy (**CHECO**) with a single IBM mainframe and 99 telex machines 12 | in 1972/73. The project was never finished; it ended with the coup d'etat 13 | against the Allende presidency on Sepember 11th, 1973. 14 | 15 | The CHECO simulator was written in DYNAMO; the very first version (1.0, 16 | Sepember 10th, 1972) can be found in the `checo/` folder. 17 | Never versions of the simulator are possibly lost; the first version was 18 | recovered from documents found in the _Stafford Beer Collection_ at the 19 | Liverpool John Moores University. 20 | 21 | ### World Dynamics 22 | 23 | In his book "**World Dynamics**" (published in 1971) 24 | [J.W. Forrester](https://en.wikipedia.org/wiki/Jay_Wright_Forrester) 25 | designed a model to simulate the system dynamics of the world's population, 26 | food supply, natural resources, pollution and many other key features. This 27 | model - called **WORLD2** - was written in DYNAMO and published in the book 28 | on pages 136-138. 29 | 30 | A [newer DYNAMO model](https://en.wikipedia.org/wiki/World3) - called 31 | **WORLD3** - was used by Dennis Meadows et al. as a basis for the report 32 | "_The Limits to Growth_" published by the _Club of Rome_. The source code for 33 | **WORLD3** can be found on pages 549-557 of the book "**Dynamics of growth in 34 | a finite world**". 35 | 36 | Both world models can be found in the folder `world/`. 37 | 38 | ## Models from the book "Introduction to System Dynamics Modeling with DYNAMO" 39 | 40 | The example models in the folder `book/` are taken from the book 41 | **Introduction to System Dynamics Modeling with DYNAMO** by George P. 42 | Richardson and Alexander L. Pugh III, The MIT Press, 1981 (ISBN 43 | 0-262-18102-9). 44 | 45 | All models can be found below the `book/` folder. 46 | 47 | ## Miscellaneous models 48 | 49 | A collection of DYNAMO models found in the wild is in the `rt/misc/` folder. 50 | 51 | If you have old DYNAMO models and want to share them, please send them to me 52 | with a description what they are about. Intersting models will be included in 53 | this collection. 54 | -------------------------------------------------------------------------------- /rt/book/README.md: -------------------------------------------------------------------------------- 1 | 2 | Introduction to System Dynamics Modeling with DYNAMO 3 | ==================================================== 4 | 5 | The models in this section are taken from the book 6 | **Introduction to System Dynamics Modeling with DYNAMO** by George P. 7 | Richardson and Alexander L. Pugh III, The MIT Press, 1981 (ISBN 8 | 0-262-18102-9). 9 | 10 | ## flu 11 | 12 | **Epidemic model for flu** 13 | 14 | 15 | 16 | 20 | 24 | 25 | 26 | 31 | 36 | 37 |
17 | flu.dynamo graph (simple) 18 |

Simple epidemic model (Page 96, Figure 3.7)

19 |
21 | flu.dynamo graph (delay) 22 |

Improved epidemic model with incubation (Page 104, Figure 3.4)

23 |
27 |
28 | flu model (simple) 29 |

Model for simple epidemic model (Page 95, Figure 3.6)

30 |
32 |
33 | flu model (delay) 34 |

Model for epidemic model with incubation (Page 105, Figure 3.14)

35 |
38 | 39 | ## inventory 40 | 41 | A simple inventory control model (Page 124, Figure 3.25) 42 | 43 | This is a base model to show the effects of different test functions (STEP, 44 | RAMP, NOISE,...) in a model run. See the following specific models which 45 | were executed for 100 epochs (101 iterations): 46 | 47 | 48 | 49 | 59 | 69 | 70 | 71 | 81 | 91 | 92 | 93 | 103 | 113 | 114 | 115 | 128 | 130 |
50 |
51 | inv-1.dynamo 52 | 53 | Run with `TEST1=1` (Page 125, Figure 3.26) to enable the `STEP` function. 54 | 55 |

56 | inv-1.dynamo graph 57 |

58 |
60 |
61 | inv-2.dynamo 62 | 63 | Run with `TEST2=1` (Page 126, Figure 3.27) to enable the `RAMP` function. 64 | 65 |

66 | inv-2.dynamo graph 67 |

68 |
72 |
73 | inv-3.dynamo 74 | 75 | Run with `TEST3=1` and `INTVL=200` (Page 128, Figure 3.28) to enable the `PULSE` function. 76 | 77 |

78 | inv-3.dynamo graph 79 |

80 |
82 |
83 | inv-4.dynamo 84 | 85 | Run with `TEST3=1` and `INTVL=5` (Page 129, Figure 3.29) to enable the `PULSE` function. 86 | 87 |

88 | inv-4.dynamo graph 89 |

90 |
94 |
95 | inv-5.dynamo 96 | 97 | Run with `TEST4=1` and `PER=5` (Page 130, Figure 3.30) to enable the `SIN` function. 98 | 99 |

100 | inv-5.dynamo graph 101 |

102 |
104 |
105 | inv-6.dynamo 106 | 107 | Run with `TEST4=1` and `PER=10` (Page 131, Figure 3.31) to enable the `SIN` function. 108 | 109 |

110 | inv-6.dynamo graph 111 |

112 |
116 |
117 | inv-7.dynamo 118 | 119 | Run with `TEST5=1` (Page 132, Figure 3.32) to enable the `NOISE` function. 120 | 121 | N.B.: The graph is slightly different from the book,as `NOISE` involves 122 | randomness; re-running the model would also generate a different graph. 123 | 124 |

125 | inv-7.dynamo graph 126 |

127 |
129 |
131 | 132 | ## prison 133 | 134 | This is a **simple model for prison population** as described on pages 135 | 181-185; the model listing can be found on pages 211-213: 136 | 137 | 138 | 139 | 143 | 147 | 148 | 149 | 152 | 154 | 155 |
140 | prison.dynamo graph (below capacity) 141 |

Prison population below capacity (Page 183, Figure 4.22)

142 |
144 | prison.dynamo graph (above capacity) 145 |

Prison population above capacity (Page 184, Figure 4.23)

146 |
150 | prison flowchart 151 | 153 |
156 | 157 | ## project 158 | 159 | Designing and implementing a **simple project model** is described in the book 160 | on pages 190 to 213; the model code can be found on pages 211 to 213: 161 | 162 | 163 | 164 | 168 | 170 | 171 |
165 | project.dynamo graph 166 |

Base run of the project model (Page 210, Figure 4.32)

167 |
169 |
172 | -------------------------------------------------------------------------------- /rt/book/flu.dynamo: -------------------------------------------------------------------------------- 1 | * SIMPLE EPIDEMIC MODEL 2 | NOTE 3 | L SUSC.K=SUSC.J+DT*(-INF.JK) 4 | N SUSC=988 5 | NOTE SUSPECTIBLE POPULATION (PEOPLE) 6 | R INF.KL=SICK.K*CNTCTS.K*FRSICK 7 | NOTE INFECTION RATE (PEOPLE PER DAY) 8 | C FRSICK=0.05 9 | NOTE FRACTION OF CONTACTS BECOMING SICK 10 | NOTE (DIMENSIONLESS) 11 | L SICK.K=SICK.J+DT*(INF.JK-CURE.JK) 12 | N SICK=2 13 | NOTE SICK POPULATION (PEOPLE) 14 | A CNTCTS.K=TABLE(TABCON,SUSC.K/TOTAL,0,1,0.2) 15 | NOTE SUSPECTIBLE CONTACTED PER INFECTED PERSON 16 | NOTE PER DAY (PEOPLE PER PERSON PER DAY) 17 | T TABCON=0/2.8/5.5/8/9.5/10 18 | NOTE TABLE FOR CONTACTS 19 | N TOTAL=SUSC+SICK+RECOV 20 | NOTE TOTAL POPULATION (PEOPLE) 21 | R CURE.KL=SICK.K/DUR 22 | NOTE CURE RATE (PEOPLE PER DAY) 23 | C DUR=10 24 | NOTE DURATION OF DISEASE (DAYS) 25 | L RECOV.K=RECOV.J+DT*CURE.JK 26 | N RECOV=10 27 | NOTE RECOVERED POPULATION (PEOPLE) 28 | NOTE 29 | SPEC DT=0.25,LENGTH=50,PRTPER=5,PLTPER=0.5 30 | PRINT SUSC,SICK,RECOV,INF,CURE 31 | PLOT SUSC=W,SICK=S,RECOV=R(0,1000)/INF=I,CURE=C(0,200) 32 | RUN SIMPLE 33 | NOTE 34 | NOTE **** MODIFIED MODEL WITH DELAY (INCUBATION) 35 | NOTE 36 | EDIT SIMPLE 37 | NOTE INCUBATION DELAY (DAYS) 38 | C TSS=3 39 | NOTE FRACTION OF CONTACTS SHOWING SYMPTOMS 40 | NOTE (DIMENSIONLESS) 41 | R SYMP.KL=DELAY1(INF.JK,TSS) 42 | L SICK.K=SICK.J+DT*(SYMP.JK-CURE.JK) 43 | RUN DELAY 44 | -------------------------------------------------------------------------------- /rt/book/inventory.dynamo: -------------------------------------------------------------------------------- 1 | * SIMPLE INVENTORY MODEL 2 | NOTE 3 | NOTE SHIPMENTS 4 | NOTE 5 | R SHIP.KL=NSHIP+TEST.K SHIPMENT RATE (UNITS/WK) 6 | C NSHIP=100 7 | A TEST.K=TEST1*STEP(STH,STRT)+ 8 | X TEST2*RAMP(SLP,STRT)+ 9 | X TEST3*PULSE(HGHT,STRT,INTVL)+ 10 | X TEST4*AMP*SIN(6.238*TIME.K/PER)+ 11 | X TEST5*RANGE*NOISE() 12 | C TEST1=0/TEST2=0/TEST3=0/TEST4=0/TEST5=0 13 | C STH=10,STRT=2,SLP=20,HGHT=10,INTVL=200,AMP=10, 14 | X PER=5,RANGE=20 15 | L INV.K=INV.J+DT*(ORDRCV.JK-SHIP.JK) 16 | N INV=DSINV INVENTORY (UNITS) 17 | R ORDRCV.KL=DELAY3(ORDRS.JK,DEL) ORDERS RECEIVED (UNITS/WK) 18 | C DEL=3 (WKS) DELAY IN RECEIVING ORDERS 19 | NOTE 20 | NOTE ORDERS 21 | NOTE 22 | R ORDRS.KL=AVSHIP.K+INVADJ.K ORDERS PLACED (UNITS/WK) 23 | A AVSHIP.K=SMOOTH(SHIP.JK,TAS) AVERAGE SHIPMENT RATE (UNITS/WK) 24 | C TAS=2 (WKS) TIME TO AVERAGE SHIPMENTS 25 | A INVADJ.K=(DSINV-INV.K)/IAT INVENTORY ADJUSTMENT (UNITS/WK) 26 | C IAT=2 (WKS) INVENTORY ADJUSTMENT TIME 27 | N DSINV=DIC*NSHIP DESIRED INVENTORY (UNITS) 28 | C DIC=3 (WKS) DESIRED INVENTORY COVERAGE 29 | NOTE 30 | NOTE CONTROL STATEMENTS 31 | NOTE 32 | SPEC DT=.25/LENGTH=25/PRTPER=.5/PLTPER=.25 33 | PRINT SHIP,TEST,INV,ORDRCV,ORDRS,AVSHIP,INVADJ 34 | RUN BASE 35 | EDIT BASE 36 | C TEST1=1 37 | PLOT SHIP=S(97,113)/INV=I(240,320) 38 | RUN STEP 39 | EDIT BASE 40 | C TEST2=1 41 | PLOT SHIP=S(0,800)/INV=I(0,400) 42 | RUN RAMP 43 | EDIT BASE 44 | C TEST3=1 45 | PLOT SHIP=S(97,113)/INV=I(295,304) 46 | RUN PULSE-1 47 | EDIT PULSE-1 48 | C INTVL=5 49 | RUN PULSE-2 50 | EDIT BASE 51 | C TEST4=1 52 | PLOT SHIP=S(80,120)/INV=I(275,315) 53 | RUN PERIODIC-1 54 | EDIT PERIODIC-1 55 | C PER=10 56 | PLOT SHIP=S(80,120)/INV=I(175,375) 57 | RUN PERIODIC-2 58 | EDIT BASE 59 | C TEST5=1 60 | PLOT SHIP=*(80,120)/INV=I(250,330) 61 | RUN NOISE 62 | -------------------------------------------------------------------------------- /rt/book/plots/flu-model-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | SUSC 9 | Susceptibles 10 | population 11 | 12 | 13 | 14 | 15 | 16 | 17 | INF 18 | Infection 19 | rate 20 | 21 | 22 | 23 | 24 | 25 | SICK 26 | Sick 27 | population 28 | 29 | 30 | 31 | 32 | 33 | 34 | CURE 35 | Cure 36 | rate 37 | 38 | 39 | 40 | 41 | 42 | RECOV 43 | Recovered 44 | population 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | CNTCTS 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | TOTAL 79 | Total 80 | population 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | FRSICK 93 | Fraction of contacts 94 | becoming sick 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | DUR 117 | Duration 118 | of disease 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /rt/book/plots/flu-model-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | SUSC 9 | Susceptibles 10 | population 11 | 12 | 13 | 14 | 15 | 16 | 17 | INF 18 | Infection 19 | rate 20 | 21 | 22 | 23 | 24 | 25 | SICK 26 | Sick 27 | population 28 | 29 | 30 | 31 | 32 | 33 | RECOV 34 | Recovered 35 | population 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | CNTCTS 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | TOTAL 70 | Total 71 | population 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | FRSICK 84 | Fraction of contacts 85 | becoming sick 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | DUR 98 | Duration 99 | of disease 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | SYMP 111 | 112 | 113 | 114 | 115 | 116 | 117 | INC 118 | Incubation 119 | population 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | CURE 131 | Cure rate 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(1).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 97. 47 | 48 | 49 | 240. 50 | 51 | 52 | 53 | 54 | 101. 55 | 56 | 57 | 260. 58 | 59 | 60 | 61 | 62 | 105. 63 | 64 | 65 | 280. 66 | 67 | 68 | 69 | 70 | 109. 71 | 72 | 73 | 300. 74 | 75 | 76 | 77 | 78 | 113. 79 | 80 | 81 | 320. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | STEP (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(2).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0. 47 | 48 | 49 | 0. 50 | 51 | 52 | 53 | 54 | 200. 55 | 56 | 57 | 100. 58 | 59 | 60 | 61 | 62 | 400. 63 | 64 | 65 | 200. 66 | 67 | 68 | 69 | 70 | 600. 71 | 72 | 73 | 300. 74 | 75 | 76 | 77 | 78 | 800. 79 | 80 | 81 | 400. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | RAMP (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(3).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 97. 47 | 48 | 49 | 295. 50 | 51 | 52 | 53 | 54 | 101. 55 | 56 | 57 | 297. 58 | 59 | 60 | 61 | 62 | 105. 63 | 64 | 65 | 299. 66 | 67 | 68 | 69 | 70 | 109. 71 | 72 | 73 | 301. 74 | 75 | 76 | 77 | 78 | 113. 79 | 80 | 81 | 304. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | PULSE-1 (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(4).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 97. 47 | 48 | 49 | 295. 50 | 51 | 52 | 53 | 54 | 101. 55 | 56 | 57 | 297. 58 | 59 | 60 | 61 | 62 | 105. 63 | 64 | 65 | 299. 66 | 67 | 68 | 69 | 70 | 109. 71 | 72 | 73 | 301. 74 | 75 | 76 | 77 | 78 | 113. 79 | 80 | 81 | 304. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | PULSE-2 (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(5).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 80. 47 | 48 | 49 | 275. 50 | 51 | 52 | 53 | 54 | 90. 55 | 56 | 57 | 285. 58 | 59 | 60 | 61 | 62 | 100. 63 | 64 | 65 | 295. 66 | 67 | 68 | 69 | 70 | 110. 71 | 72 | 73 | 305. 74 | 75 | 76 | 77 | 78 | 120. 79 | 80 | 81 | 315. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | PERIODIC-1 (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/plots/inventory_(6).svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Gnuplot 10 | Produced by GNUPLOT 5.2 patchlevel 6 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 80. 47 | 48 | 49 | 175. 50 | 51 | 52 | 53 | 54 | 90. 55 | 56 | 57 | 225. 58 | 59 | 60 | 61 | 62 | 100. 63 | 64 | 65 | 275. 66 | 67 | 68 | 69 | 70 | 110. 71 | 72 | 73 | 325. 74 | 75 | 76 | 77 | 78 | 120. 79 | 80 | 81 | 375. 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 5 92 | 93 | 94 | 95 | 96 | 10 97 | 98 | 99 | 100 | 101 | 15 102 | 103 | 104 | 105 | 106 | 20 107 | 108 | 109 | 110 | 111 | 25 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | PERIODIC-2 (SIMPLE INVENTORY MODEL) 121 | 122 | 123 | SHIP 124 | 125 | 126 | 127 | 128 | SHIP 129 | 130 | 131 | 132 | 145 | 146 | INV 147 | 148 | 149 | INV 150 | 151 | 152 | 153 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /rt/book/prison.dynamo: -------------------------------------------------------------------------------- 1 | * SIMPLE PRISON POPULATION MODEL 2 | L PP.K=PP.J+DT*(IR.JK-RR.JK) 3 | N PP=IPP 4 | C IPP=1000 5 | NOTE PRISON POPULATION (PEOPLE) 6 | R IR.KL=NIR+STEP(SH,ST) 7 | NOTE INCARCERATION RATE (PEOPLE/YEAR) 8 | N NIR=PP/ATS 9 | NOTE NORMAL INCARCERATION RATE (PEOPLE/YEAR) 10 | C SH=40 11 | NOTE STEP HEIGHT FOR IR 12 | C ST=2.5 13 | NOTE STEP TIME FOR IR 14 | R RR.KL=PP.K/ATS.K 15 | NOTE RELEASE RATE (PEOPLE/YEAR) 16 | A ATS.K=ASL*ECTS.K 17 | NOTE AVERAGE TIME SERVED (YEARS) 18 | C ASL=5 19 | NOTE AVERAGE SENTENCE LENGTH (YEARS) 20 | A ECTS.K=TABLE(TECTS,CR.K,0,2,.2) 21 | NOTE EFFECT OF CAPACITY ON TIME SERVED (DIMENSIONLESS) 22 | T TECTS=1/1/1/1/1/.94/.7/.5/.3/.2/.15 23 | NOTE TABLE FOR EFFECT ON CAPACITY ON TIME SERVED 24 | A CR.K=PP.K/PC 25 | NOTE CAPACITY RATIO (DIMENSIONLESS) 26 | C PC=2000 27 | NOTE PRISION CAPACITY (PEOPLE) 28 | NOTE 29 | NOTE CONTROL STATEMENTS 30 | NOTE 31 | SPEC DT=.5/LENGTH=25/PLTPER=.5/PRTPER=.5 32 | PLOT PP=P(1000,1400)/IR=I,RR=R(160,240)/CR=C(.5,1.5)/ATS=S(1,5) 33 | RUN FIG 4.22 34 | EDIT FIG 4.22 35 | C PC=700 36 | PLOT PP=P(1000,1400)/IR=I,RR=R(385,465)/CR=C(.5,1.5)/ATS=S(1,5) 37 | RUN FIG 4.23 38 | 39 | -------------------------------------------------------------------------------- /rt/book/project.dynamo: -------------------------------------------------------------------------------- 1 | * SIMPLE PROJECT MODEL, 6/17/80 2 | NOTE 3 | NOTE REAL PROGRESS 4 | NOTE 5 | A APPRG.K=WF.K*GPROD APPARENT PROGRESS RATE (TASKS/MONTH) 6 | C GPROD=1 GROSS PRODUCTIVITY (TASKS/PERSON/MONTH) 7 | R RPRG.KL=APPRG.K*FSAT REAL PROGRESS RATE (TASKS/MONTH) 8 | C FSAT=0.7 FRACTION SATISFACTORY (DIMENSIONLESS) 9 | L CRPRG.K=CRPRG.J+DT*RPRG.JK 10 | N CRPRG=0 CUMULATIVE REAL PROGRESS (TASKS) 11 | NOTE 12 | NOTE UNDISCOVERED WORK 13 | NOTE 14 | R GURW.KL=APPRG.K*(1-FSAT) GENERATION OF UNDISCOVERED REWORK 15 | X (TASKS/MONTH) 16 | L URW.K=URW.J+DT*(GURW.JK-DURW.JK) 17 | N URW=0 UNDISCOVERED REWORK (TASKS) 18 | R DURW.KL=URW.K/TDRW.K DETECTION OF UNDISCOVERED REWORK 19 | X (TASKS/MONTH) 20 | A TDRW.K=TABLE(TTDRW,FPCOMP.K,0,1,0.2) TIME TO DETECT REWORK 21 | X (MONTHS) 22 | T TTDRW=12/12/12/10/5/0.5 TABLE FOR TIME TO DETECT REWORK 23 | NOTE 24 | NOTE PERCEIVED PROGRESS 25 | NOTE 26 | A CPPRG.K=CRPRG.K+URW.K CUMULATIVE PERCEIVED PROGRESS (TASKS) 27 | A FPCOMP.K=CPPRG.K/IPD FRACTION PERCEIVED COMPLETED (DIMENSIONLESS) 28 | C IPD=1200 INITIAL PROJECT DEFINITION (TASKS) 29 | NOTE 30 | NOTE EFFORT PERCEIVED REMAINING 31 | NOTE 32 | A EPREM.K=(IPD-CPPRG.K)/PPROD.K EFFORT PERCEIVED REMAINING 33 | X (PERSON*MONTH) 34 | A PPROD.K=SMOOTH(IPROD.K,TPPROD) PERCEIVED PRODUCTIVITY 35 | X (TASKS/PERSON/MONTH) 36 | C TPPROD=6 TIME TO PERCEIVE PRODUCTIVITY (MONTH) 37 | A IPROD.K=WTRP.K*RPROD.K+(1-WTRP.K)*GPROD INDICATED PRODUCTIVITY 38 | X (TASKS/PERSON/MONTH) 39 | A RPROD.K=GPROD*FSAT REAL PRODUCTIVITY (TASKS/PERSON/MONTH) 40 | A WTRP.K=TABLE(TWTRP,FPCOMP.K,0,1,0.2) WEIGHT GIVEN REAL 41 | X PRODUCTIVITY 42 | T TWTRP=0/.1/.25/.5/.9/1 TABLE FOR WTRP 43 | NOTE 44 | NOTE HIRING 45 | NOTE 46 | L WF.K=WF.J+DT*HR.JK WORKFORCE (PERSONS) 47 | N WF=WFN 48 | C WFN=2 INITIAL VALUE FOR WORKFORCE (PERSONS) 49 | R HR.KL=(WFS.K-WF.K)/WFAT NET HIRING RATE (PERSON/MONTH) 50 | C WFAT=3 WORKFORCE ADJUSTMENT TIME (MONTH) 51 | A WFS.K=WCWF.K*IWF.K+(1-WCWF.K)*WF.K WORKFORCE SOUGHT (PERSONS) 52 | A IWF.K=EPREM.K/TREM.K INDICATED WORKFORCE (PERSONS) 53 | A WCWF.K=TABHL(TWCWF,TREM.K,0,21,3) WILLINGNESS TO CHANGE WORKFORCE 54 | X (DIMENSIONLESS) 55 | T TWCWF=0/0/0/.1/.3/.7/.9/1 TABLE FOR WCWF 56 | NOTE 57 | NOTE SCHEDULING 58 | NOTE 59 | A TREM.K=SCD.K-TIME.K TIME REMAINING (MONTHS) 60 | L SCD.K=SCD.J+DT*NAS.JK SCHEDULED COMPLETION DATE (MONTH) 61 | N SCD=SCDN 62 | C SCDN=40 INITIAL VALUE OF SCD 63 | R NAS.KL=(ICD.K-SCD.K)/SAT NET ADDITIONS TO SCHEDULE (MONTHS/MONTH) 64 | C SAT=6 SCHEDULE ADJUSTMENT TIME (MONTHS) 65 | A ICD.K=TIME.K+TPREQ.K INDICATED COMPLETION DATE (MONTH) 66 | A TPREQ.K=EPREM.K/WFS.K TIME PERCEIVED REQUIRED (MONTHS) 67 | NOTE 68 | NOTE CONTROL STATEMENTS 69 | NOTE 70 | SPEC DT=.25/MAXLEN=100/PLTPER=1/PRTPER=1 71 | A LENGTH.K=CLIP(MAXLEN,0,1,FCOMP.K) 72 | A FCOMP.K=CRPRG.K/IPD 73 | PRINT WF,SCD,CPPRG,CRPRG,URW,PPROD 74 | PLOT WF=W(0,80)/SCD=S(30,70)/CPPRG=P,CRPRG=R,URW=U(0,1200) 75 | X /PPROD=+(.6,1) 76 | RUN BASE 77 | -------------------------------------------------------------------------------- /rt/checo/README.md: -------------------------------------------------------------------------------- 1 | 2 | # CHECO / Project CYBERSYN 3 | 4 | When in 1972 [Stafford Beer](https://en.wikipedia.org/wiki/Stafford_Beer) 5 | decided to use the DYNAMO programming language for the economic simulator in 6 | the project [Cyberstride](https://en.wikipedia.org/wiki/Project_Cybersyn), he 7 | asked Ron Anderton, the leading expert for DYNAMO in the UK, to start working 8 | on an implementation of a software that later became known as CHECO (CHilean 9 | ECOnomy). Together with K.A.Gilligan they designed an initial version; most of 10 | the work on coding the CHECO simulator took place at Queens Mary College in 11 | London. The software design was later continued by Chilean members of the 12 | CHECO team with the support and coaching from Anderton. 13 | 14 | "By September 1972, the economic model described by the CHECO team, which by 15 | its own admission was 'relatively simple and incomplete', included an inflation 16 | model that took into account the levels of goods and services, productive 17 | captial, investment funds, prices and the total currency in the economy." (see 18 | page 106 of the book "Cybernetic Revolutionaries" by Eden Medina, MIT Press) 19 | 20 | This version of the CHECO simulator is presented here; the design sketch and 21 | the source code for version 1.0 (September 10th, 1972) are from the document 22 | "Futuro at September 1972", contained in Box 58 of the Stafford Beer Collection 23 | at Liverpool John Moores University. 24 | 25 | 26 | 27 | 45 | 103 | 104 |
28 | 29 |

CHECO design sketch (September 10th, 1972)

30 | 31 | CHECO design sketch 32 | 33 |

CHECO original output

34 | 35 | CHECO plot 36 | 37 | The original plot looks a bit different from the one generated by the DYNAMO 38 | interpreter; this is due to combined Y-axis settings. 39 | 40 | The sinodial form of the stock curve seems to be a model error; the note on 41 | the design sketch annotates: "This causes instability - stock cycle is an 42 | ARTEFACT". 43 | 44 |
46 | 47 |

CHECO run with the DYNAMO interpreter

48 | 49 | To run the model with the DYNMAO interpreter, the source code required one 50 | small adjustment; line 8 of the [original code](checo-orig.dynamo) read: 51 | 52 | ``` 53 | R R10.KL=DELAY1(CPRR.K,DEM1) 54 | ``` 55 | 56 | According to the DYNAMO interpreter language rules the first argument to DELAY1 57 | must be a rate (with index ".JK"); so the original line is replaced with: 58 | 59 | ``` 60 | R R10X.KL=CPRR.K 61 | R R10.KL=DELAY1(R10X.JK,DEM1) 62 | ``` 63 | 64 | Now we can run the model: 65 | 66 | ``` 67 | ----------------------------------- 68 | DYNAMO interpreter v0.5 (20200830) 69 | Copyright (C) 2020, Bernd Fix >Y< 70 | ----------------------------------- 71 | Reading source file 'checo/checo.dynamo'... 72 | Processing system model... 73 | Runtime specification: 74 | DT = 0.010000 75 | LENGTH = 10.000000 76 | PRTPER = 0.100000 77 | PLTPER = 0.100000 78 | Running system model 'PRIMERA'... 79 | Initializing state... 80 | INFO: Setting 'TIME' to 0.000000 81 | Checking state... 82 | YNR not used 83 | Iterating epochs... 84 | 1001 epochs computed. 85 | Generating print(s)... 86 | Generating plot(s)... 87 | Stacking system model 'PRIMERA'... 88 | Done. 89 | Model processing completed. 90 | Done. 91 | ``` 92 | 93 | checo.dynamo graph 94 | 95 | * **STB** = Stocks 96 | * **CP** = Productive capital 97 | * **CPD** = Available capital 98 | * **FIN** = Funds 99 | * **NVR** = Price level (right scale) 100 | * **CIR** = Cash 101 | 102 |
105 | -------------------------------------------------------------------------------- /rt/checo/checo-orig.dynamo: -------------------------------------------------------------------------------- 1 | * PRIMER GRAN MODELO PRODUCIDO EN CHILE 2 | L STB.K=STB.J+DT*(R21.JK-R22.JK) 3 | A DFS.K=STD-STB.K 4 | A CPR.K=DFS.K*RCP 5 | C STD=20000. 6 | C RCP=2.0 7 | A CPRR.K=MIN(CPO.K,CPR.K) 8 | R R10.KL=DELAY1(CPRR.K,DEM1) 9 | C DEM1=0.1 10 | L CP.K=CP.J+DT*R10.JK 11 | A CPO.K=(CPD.K/NVP.K)-AUX3.K 12 | A AUX3.K=MAX(CP.K,0.0) 13 | R R21.KL=AUX3.K*1.14 14 | A DF3.K=MAX(CPR.K,0.0)-CPO.K 15 | A DFI.K=MAX(DF3.K,0.0)-FIN.K 16 | L CPD.K=CPD.J+DT*(TCD.JK-R12.JK) 17 | R TCD.KL=DELAY3(R32.JK,DEM2) 18 | C DEM2=1.0 19 | R R12.KL=CPD.K*0.05 20 | L FIN.K=FIN.J+DT*(R31.JK-R32.JK) 21 | A AUX1.K=DF3.K*NVP.K 22 | R R32.KL=MAX(AUX1.K,0.0) 23 | R R31.KL=MAX(DFI.K,0.0)+AHI.K 24 | L NVP.K=NVP.J+DT*R51.JK 25 | A TAP.K=AUX3.K*1.14+1.0 26 | A AUX4.K=DF3.K/(TAP.K*RCP) 27 | A AUX5.K=MAX(AUX4.K,0.0) 28 | R R51.KL=AUX5.K/DEM5 29 | C DEM5=0.25 30 | R R22.KL=GST.K/NVP.K 31 | A GST.K=YNN.K*(1-P2.K) 32 | A P2.K=0.16 33 | A AHI.K=YNN.K*P2.K 34 | A YNR.K=YNN.K/NVP.K 35 | A YNN.K=CIR.K*CTY 36 | C CTY=5.4 37 | L CIR.K=CIR.J+DT*R41.JK 38 | R R41.KL=MAX(DFI.K,0.0) 39 | C CSTB=20000. 40 | N STB=CSTB 41 | C CNVP=1.0 42 | N NVP=CNVP 43 | C CCIR=17000. 44 | N CIR=CCIR 45 | C CFIN=6000. 46 | N FIN=CFIN 47 | C CCPD=91000. 48 | N CPD=CCPD 49 | C CCP=80500. 50 | N CP=CCP 51 | PRINT STB,CP,CPD,FIN,NVP,CIR 52 | PLOT STB=S/CP=C/CPD=P/FIN=F/NVP=N/CIR=* 53 | SPEC DT=0.01/LENGTH=10/PRTPER=0.1/PLTPER=0.1 54 | RUN PRIMERA 55 | -------------------------------------------------------------------------------- /rt/checo/checo-plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/rt/checo/checo-plot.png -------------------------------------------------------------------------------- /rt/checo/checo.dynamo: -------------------------------------------------------------------------------- 1 | * PRIMER GRAN MODELO PRODUCIDO EN CHILE 2 | L STB.K=STB.J+DT*(R21.JK-R22.JK) 3 | A DFS.K=STD-STB.K 4 | A CPR.K=DFS.K*RCP 5 | C STD=20000. 6 | C RCP=2.0 7 | A CPRR.K=MIN(CPO.K,CPR.K) 8 | R R10X.KL=CPRR.K 9 | R R10.KL=DELAY1(R10X.JK,DEM1) 10 | C DEM1=0.1 11 | L CP.K=CP.J+DT*R10.JK 12 | A CPO.K=(CPD.K/NVP.K)-AUX3.K 13 | A AUX3.K=MAX(CP.K,0.0) 14 | R R21.KL=AUX3.K*1.14 15 | A DF3.K=MAX(CPR.K,0.0)-CPO.K 16 | A DFI.K=MAX(DF3.K,0.0)-FIN.K 17 | L CPD.K=CPD.J+DT*(TCD.JK-R12.JK) 18 | R TCD.KL=DELAY3(R32.JK,DEM2) 19 | C DEM2=1.0 20 | R R12.KL=CPD.K*0.05 21 | L FIN.K=FIN.J+DT*(R31.JK-R32.JK) 22 | A AUX1.K=DF3.K*NVP.K 23 | R R32.KL=MAX(AUX1.K,0.0) 24 | R R31.KL=MAX(DFI.K,0.0)+AHI.K 25 | L NVP.K=NVP.J+DT*R51.JK 26 | A TAP.K=AUX3.K*1.14+1.0 27 | A AUX4.K=DF3.K/(TAP.K*RCP) 28 | A AUX5.K=MAX(AUX4.K,0.0) 29 | R R51.KL=AUX5.K/DEM5 30 | C DEM5=0.25 31 | R R22.KL=GST.K/NVP.K 32 | A GST.K=YNN.K*(1-P2.K) 33 | A P2.K=0.16 34 | A AHI.K=YNN.K*P2.K 35 | A YNR.K=YNN.K/NVP.K 36 | A YNN.K=CIR.K*CTY 37 | C CTY=5.4 38 | L CIR.K=CIR.J+DT*R41.JK 39 | R R41.KL=MAX(DFI.K,0.0) 40 | C CSTB=20000. 41 | N STB=CSTB 42 | C CNVP=1.0 43 | N NVP=CNVP 44 | C CCIR=17000. 45 | N CIR=CCIR 46 | C CFIN=6000. 47 | N FIN=CFIN 48 | C CCPD=91000. 49 | N CPD=CCPD 50 | C CCP=80500. 51 | N CP=CCP 52 | PRINT STB,CP,CPD,FIN,NVP,CIR 53 | PLOT STB=S/CP=C/CPD=P/FIN=F/NVP=N/CIR=I 54 | SPEC DT=0.01/LENGTH=10/PRTPER=0.1/PLTPER=0.1 55 | RUN PRIMERA 56 | -------------------------------------------------------------------------------- /rt/checo/checo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bfix/dynamo/06fb783a7b94e5733dc36036cf6100b33d3bb781/rt/checo/checo.png -------------------------------------------------------------------------------- /rt/misc/README.md: -------------------------------------------------------------------------------- 1 | 2 | Miscellaneous models 3 | ==================== 4 | 5 | ## Dove - Hawk - Law-Abider 6 | 7 | This model from the field of social sciences "was described by Martinez Coll 8 | [1986] who tried 'to develop a formal model of the Hobbesian state of nature 9 | from the perspective of bioeconomics.' He defines Hobbes' state of nature as 10 | a society whose members are continually competing with each other to obtain a 11 | resource. All resources belong to someone, thus conflicts arise between 12 | resource ownersand those who want an additional resource. Martinez Coll 13 | follows Maynard Smith [1982] in that he endows the members of his model 14 | society with one of three strategies: the Hawk, the Dove and the Law-Abiding 15 | strategies." (ftp://ftp.uni-koblenz.de/outgoing/SozInf/kgt/SICSS/Chapter3.pdf) 16 | 17 | The hawk-dove-law-abider macro model is on pages 32-43 in the book 18 | "Simulation for the Social Scientist" by Nigel Gilbert and Klaus Troitzsch. 19 | The lines 32ff were added to generate print and plot output. Line 30 (LENGTH) 20 | was adjusted to "7" (140 steps with DT=0.05). 21 | 22 | dove-hawk-law abider 23 | 24 | Looks like that "law-abiding people" win in the end... 25 | -------------------------------------------------------------------------------- /rt/misc/dove_hawk_law-abider.dynamo: -------------------------------------------------------------------------------- 1 | * HOBBESIAN STATE OF NATURE FROM THE PERSPECTIVE OF BIOECONOMICS 2 | L DOVE.K=DOVE.J+DT*DDOVE.JK 3 | L HAWK.K=HAWK.J+DT*(-DDOVE.JK-DLAWA.JK) 4 | L LAWA.K=LAWA.J+DT*DLAWA.JK 5 | R DDOVE.KL=YIELDD.K-DOVE.K*YIELDS.K 6 | R DLAWA.KL=YIELDL.K-LAWA.K*YIELDS.K 7 | A YIELDD.K=(DOVE.K*RDD+HAWK.K*RDH+LAWA.K*RDL)*DOVE.K 8 | A YIELDH.K=(DOVE.K*RHD+HAWK.K*RHH+LAWA.K*RHL)*HAWK.K 9 | A YIELDL.K=(DOVE.K*RLD+HAWK.K*RLH+LAWA.K*RLL)*LAWA.K 10 | A YIELDS.K=YIELDD.K+YIELDH.K+YIELDL.K 11 | N RHH=(POSS-COHA)/2 12 | N RHD=POSS 13 | N RHL=(RHH+POSS)/2 14 | C RDH=0 15 | N RDD=POSS/2-CODO 16 | N RDL=RDD/2 17 | N RLH=RHH/2 18 | N RLD=(RDD+POSS)/2 19 | N RLL=POSS/2 20 | N DOVE=IDOVE 21 | N HAWK=IHAWK 22 | N LAWA=ILAWA 23 | C POSS=10 24 | C CODO=3 25 | C COHA=20 26 | C IDOVE=0.05 27 | C IHAWK=0.9 28 | C ILAWA=0.05 29 | N TIME=0 30 | C LENGTH=7 31 | C DT=0.05 32 | C PLTPER=0.1 33 | C PRTPER=0.1 34 | PRINT DOVE,HAWK,LAWA 35 | PLOT DOVE=D,HAWK=H,LAWA=L(0,1) 36 | RUN COLL 37 | -------------------------------------------------------------------------------- /rt/world/README.md: -------------------------------------------------------------------------------- 1 | # World Dynamics 2 | 3 | The world dynamics models have been manually re-typed from copies and scans of 4 | books; it is possible that some typos have not yet been detected, leading to 5 | corrupted models with wrong calculations and graphs. If you find a typo or any 6 | other error, please contact me so I can fix the issue. 7 | 8 | ## WORLD2 9 | 10 | ### Changes to the original model 11 | 12 | The [original code](world2-orig.dynamo) from the 1971 book **World Dynamics** 13 | by _J.W. Forrester_ (Cambridge, Mass., ISBN 0262560186) was slightly adjusted in 14 | the control cards; the original lines: 15 | 16 | ``` 17 | C DT=.2 18 | C LENGTH=2100 19 | N TIME=1900 20 | A PRTPER.K=CLIP(PRTP1,PRTP2,PRSWT,TIME.K) 21 | C PRTP1=0 22 | C PRTP2=0 23 | C PRSWT=0 24 | A PLTPER.K=CLIP(PLTP1,PLTP2,PLSWT,TIME.K) 25 | C PLTP1=4 26 | C PLTP2=4 27 | C PLSWT=0 28 | ``` 29 | 30 | have been replaced by: 31 | 32 | ``` 33 | SPEC DT=.2/LENGTH=2100/PRTPER=4/PLTPER=4 34 | C TIME=1900 35 | ``` 36 | 37 | N.B.: `PLTPER` and `PRTPER` can't be variable in the current version of the 38 | DYNAMO interpreter. 39 | 40 | ### DYNAMO interpreter output 41 | 42 | The following PLOTs have been generated with the GNUplot option: 43 | 44 | 45 | 46 | 51 | 56 | 57 |
47 |

48 | world2.dynamo graph #1 49 |

50 |
52 |

53 | world2.dynamo graph #2 54 |

55 |
58 | 59 | ## WORLD3 60 | 61 | The [original code](world3-orig.dynamo) from the 1974 book **Dynamics of growth 62 | in a finite world** by _Dennis Meadows_ et al. (Cambridge, Mass., ISBN 0960029443) 63 | was adjusted to work with the DYNAMO interpreter: 64 | 65 | ### Changes to the original model 66 | 67 | #### Control cards of the main deck 68 | 69 | * The lines: 70 | 71 | ``` 72 | A PLTPER.K=STEP(PLP,PLIT) 73 | C PLP=5 74 | C PLIT=1900 75 | C PRP=0 76 | A PRTPER.K=STEP(PRP,PRIT)+STEP(-PRP,PRTT) 77 | C PRIT=1900 78 | C PRTT=2100 79 | ``` 80 | 81 | were replaced with: 82 | 83 | ``` 84 | C PLTPER=2 85 | C PRTPER=2 86 | ``` 87 | 88 | Like with the **WORLD2** model above, no variable `PLTPER` or `PRTPER` are 89 | available in the current version of the interpreter. 90 | 91 | * The PLOT commands have been changed to drop CBR (Crude Birth Rate) and CDR 92 | (Crude Death Rate) from the plots. The reason for this is that all figures in 93 | the book basically "ignore" these variables; dropping them from the plots makes 94 | comparing the result of the original code and that of the DYNAMO interpreter 95 | much easier. 96 | 97 | #### Edit statements 98 | 99 | Blocks of code starting with: 100 | 101 | ``` 102 | NOTE ** THE FOLLOWING CHANGES MUST BE MADE IN EDIT MODE: 103 | NOTE ** : 104 | ``` 105 | 106 | are instructions on how to change the model in interactive-mode. Since the 107 | DYNAMO interpreter does not have such a mode, the changes are done in 108 | EDIT mode of the interpreter (replacing/adding equations in a named model). 109 | 110 | 111 | ### DYNAMO interpreter output 112 | 113 | The following PLOTs have been generated with the GNUplot option: 114 | 115 | 116 | 117 | 122 | 127 | 128 | 129 | 134 | 139 | 140 | 141 | 146 | 151 | 152 | 153 | 158 | 163 | 164 | 165 | 170 | 175 | 176 | 177 | 182 | 187 | 188 | 189 | 194 | 199 | 200 | 201 | 206 | 211 | 212 | 213 | 218 | 223 | 224 | 225 | 230 | 235 | 236 | 237 | 254 | 259 | 260 | 261 | 266 | 271 | 272 | 273 | 288 | 296 | 297 | 298 | 303 | 308 | 309 | 310 | 315 | 320 | 321 | 322 | 327 | 332 | 333 | 334 | 342 | 344 | 345 |
118 |

119 | world3.dynamo graph #1 120 |

121 |
123 |

124 | world3.dynamo graph #2 125 |

126 |
130 |

131 | world3.dynamo graph #3 132 |

133 |
135 |

136 | world3.dynamo graph #4 137 |

138 |
142 |

143 | world3.dynamo graph #5 144 |

145 |
147 |

148 | world3.dynamo graph #6 149 |

150 |
154 |

155 | world3.dynamo graph #7 156 |

157 |
159 |

160 | world3.dynamo graph #8 161 |

162 |
166 |

167 | world3.dynamo graph #9 168 |

169 |
171 |

172 | world3.dynamo graph #10 173 |

174 |
178 |

179 | world3.dynamo graph #11 180 |

181 |
183 |

184 | world3.dynamo graph #12 185 |

186 |
190 |

191 | world3.dynamo graph #13 192 |

193 |
195 |

196 | world3.dynamo graph #14 197 |

198 |
202 |

203 | world3.dynamo graph #15 204 |

205 |
207 |

208 | world3.dynamo graph #16 209 |

210 |
214 |

215 | world3.dynamo graph #17 216 |

217 |
219 |

220 | world3.dynamo graph #18 221 |

222 |
226 |

227 | world3.dynamo graph #19 228 |

229 |
231 |

232 | world3.dynamo graph #20 233 |

234 |
238 |

239 | world3.dynamo graph #21 240 |

241 | 242 | The above graph is not matching the figure in the book; this is certainly due 243 | to a problem with the original source code: Translating the existing EDIT 244 | instructions results in a model with two missing level equations: 245 | 246 | ``` 247 | L LLYM1.K=... 248 | L LLYM2.K=... 249 | ``` 250 | 251 | The assumption to set them to be constant at value 1.0 is obviously wrong. 252 | 253 |
255 |

256 | world3.dynamo graph #22 257 |

258 |
262 |

263 | world3.dynamo graph #23 264 |

265 |
267 |

268 | world3.dynamo graph #24 269 |

270 |
274 |

275 | world3.dynamo graph #25 276 |

277 | 278 | The above graph is not matching the figure in the book; this is certainly due 279 | to a problem with the original source code: Translating the existing EDIT 280 | instructions results in a model with a missing constant equations: 281 | 282 | ``` 283 | C GGPF1=... 284 | ``` 285 | 286 | The assumption to set this constant to value 1.0 is obviously wrong. 287 |
289 |

290 | world3.dynamo graph #26 291 |

292 | The model used to generate this graph is based on "Figure 7-30", a model with 293 | known problems. The graph is therefore not matching the corresponding figure 294 | in the book. 295 |
299 |

300 | world3.dynamo graph #27 301 |

302 |
304 |

305 | world3.dynamo graph #28 306 |

307 |
311 |

312 | world3.dynamo graph #29 313 |

314 |
316 |

317 | world3.dynamo graph #30 318 |

319 |
323 |

324 | world3.dynamo graph #31 325 |

326 |
328 |

329 | world3.dynamo graph #32 330 |

331 |
335 |

336 | world3.dynamo graph #33 337 |

338 | The model used to generate this graph is based on "Figure 7-30", a model with 339 | known problems. The graph is therefore not matching the corresponding figure 340 | in the book. 341 |
343 |
346 | -------------------------------------------------------------------------------- /rt/world/world2-orig.dynamo: -------------------------------------------------------------------------------- 1 | * WORLD DYNAMICS W5 2 | L P.K=P.J+(DT)(BR.JK-DR.JK) 3 | N P=PI 4 | C PI=1.65E9 5 | R BR.KL=(P.K)(CLIP(BRN,BRN1,SWT1,TIME.K))(BRFM.K)(BRMM.K)(BRCM.K)(BR 6 | X PM.K) 7 | C BRN=.04 8 | C BRN1=.04 9 | C SWT1=1970 10 | A BRMM.K=TABHL(BRMMT,MSL.K,0,5,1) 11 | T BRMMT=1.2/1/.85/.75/.7/.7 12 | A MSL.K=ECIR.K/(ECIRN) 13 | C ECIRN=1 14 | A ECIR.K=(CIR.K)(1-CIAF.K)(NREM.K)/(1-CIAFN) 15 | A NREM.K=TABLE(NREMT,NRFR.K,0,1,.25) 16 | T NREMT=0/.15/.5/.85/1 17 | A NRFR.K=NR.K/NRI 18 | L NR.K=NR.J+(DT)(-NRUR.JK) 19 | N NR=NRI 20 | C NRI=900E9 21 | R NRUR.KL=(P.K)(CLIP(NRUN,NRUN1,SWT2,TIME.K))(NRMM.K) 22 | C NRUN=1 23 | C NRUN1=1 24 | C SWT2=1970 25 | NOTE EQUATION 42 CONNECTS HERE FROM EQ. 4 TO EQ. 9 26 | R DR.KL=(P.K)(CLIP(DRN,DRN1,SWT3,TIME.K))(DRMM.K)(DRPM.K)(DRFM.K)(DR 27 | X CM.K) 28 | C DRN=.028 29 | C DRN1=.028 30 | C SWT3=1970 31 | A DRMM.K=TABHL(DRMMT,MSL.K,0,5,.5) 32 | T DRMMT=3/1.8/1/.8/.7/.6/.53/.5/.5/.5/.5 33 | A DRPM.K=TABLE(DRPMT,POLR.K,0,60,10) 34 | T DRPMT=.92/1.3/2/3.2/4.8/6.8/9.2 35 | A DRFM.K=TABHL(DRFMT,FR.K,0,2,.25) 36 | T DRFMT=30/3/2/1.4/1/.7/.6/.5/.5 37 | A DRCM.K=TABLE(DRCMT,CR.K,0,5,1) 38 | T DRCMT=.9/1/1.2/1.5/1.9/3 39 | A CR.K=(P.K)/(LA*PDN) 40 | C LA=135E6 41 | C PDN=26.5 42 | A BRCM.K=TABLE(BRCMT,CR.K,0,5,1) 43 | T BRCMT=1.05/1/.9/.7/.6/.55 44 | A BRFM.K=TABHL(BRFMT,FR.K,0,4,1) 45 | T BRFMT=0/1/1.6/1.9/2 46 | A BRPM.K=TABLE(BRPMT,POLR.K,0,60,10) 47 | T BRPMT=1.02/.9/.7/.4/.25/.15/.1 48 | A FR.K=(FPCI.K)(FCM.K)(FPM.K)(CLIP(FC,FC1,SWT7,TIME.K))/FN 49 | C FC=1 50 | C FC1=1 51 | C FN=1 52 | C SWT7=1970 53 | A FCM.K=TABLE(FCMT,CR.K,0,5,1) 54 | T FCMT=2.4/1/.6/.4/.3/.2 55 | A FPCI.K=TABHL(FPCIT,CIRA.K,0,6,1) 56 | T FPCIT=.5/1/1.4/1.7/1.9/2.05/2.2 57 | A CIRA.K=(CIR.K)(CIAF.K)/CIAFN 58 | C CIAFN=.3 59 | A CIR.K=CI.K/P.K 60 | L CI.K=CI.J+(DT)(CIG.JK-CID.JK) 61 | N CI=CII 62 | C CII=.4E9 63 | R CIG.KL=(P.K)(CIM.K)(CLIP(CIGN,CIGN1,SWT4,TIME.K)) 64 | C CIGN=.05 65 | C CIGN1=.05 66 | C SWT4=1970 67 | A CIM.K=TABHL(CIMT,MSL.K,0,5,1) 68 | T CIMT=.1/1/1.8/2.4/2.8/3 69 | R CID.KL=(CI.K)(CLIP(CIDN,CIDN1,SWT5,TIME.K)) 70 | C CIDN=.025 71 | C CIDN1=.025 72 | C SWT5=1970 73 | A FPM.K=TABLE(FPMT,POLR.K,0,60,10) 74 | T FPMT=1.02/.9/.65/.35/.2/.1/.05 75 | A POLR.K=POL.K/POLS 76 | C POLS=3.6E9 77 | L POL.K=POL.J+(DT)(POLG.JK-POLA.JK) 78 | N POL=POLI 79 | C POLI=.2E9 80 | R POLG.KL=(P.K)(CLIP(POLN,POLN1,SWT6,TIME.K))(POLCM.K) 81 | C POLN=1 82 | C POLN1=1 83 | C SWT6=1970 84 | A POLCM.K=TABHL(POLCMT,CIR.K,0,5,1) 85 | T POLCMT=.05/1/3/5.4/7.4/8 86 | R POLA.KL=POL.K/POLAT.K 87 | A POLAT.K=TABLE(POLATT,POLR.K,0,60,10) 88 | T POLATT=.6/2.5/5/8/11.5/15.5/20 89 | L CIAF.K=CIAF.J+(DT/CIAFT)(CFIFR.J*CIQR.J-CIAF.J) 90 | N CIAF=CIAFI 91 | C CIAFI=.2 92 | C CIAFT=15 93 | A CFIFR.K=TABHL(CFIFRT,FR.K,0,2,.5) 94 | T CFIFRT=1/.6/.3/.15/.1 95 | A QL.K=(QLS)(QLM.K)(QLC.K)(QLF.K)(QLP.K) 96 | C QLS=1 97 | A QLM.K=TABHL(QLMT,MSL.K,0,5,1) 98 | T QLMT=.2/1/1.7/2.3/2.7/2.9 99 | A QLC.K=TABLE(QLCT,CR.K,0,5,.5) 100 | T QLCT=2/1.3/1/.75/.55/.45/.38/.3/.25/.22/.2 101 | A QLF.K=TABHL(QLFT,FR.K,0,4,1) 102 | T QLFT=0/1/1.8/2.4/2.7 103 | A QLP.K=TABLE(QLPT,POLR.K,0,60,10) 104 | T QLPT=1.04/.85/.6/.3/.15/.05/.02 105 | NOTE EQUATION 42 LOCATED BETWEEN EQ. 4 AND 9. 106 | A NRMM.K=TABHL(NRMMT,MSL.K,0,10,1) 107 | T NRMMT=0/1/1.8/2.4/2.9/3.3/3.6/3.8/3.9/3.95/4 108 | NOTE INPUT FROM EQN. 38 AND 40 TO EQN. 35 109 | A CIQR.K=TABHL(CIQRT,QLM.K/QLF.K,0,2,0.5) 110 | T CIQRT=.7/.8/1/1.5/2 111 | NOTE 112 | NOTE CONTROL CARDS 113 | NOTE 114 | C DT=.2 115 | C LENGTH=2100 116 | N TIME=1900 117 | A PRTPER.K=CLIP(PRTP1,PRTP2,PRSWT,TIME.K) 118 | C PRTP1=0 119 | C PRTP2=0 120 | C PRSWT=0 121 | A PLTPER.K=CLIP(PLTP1,PLTP2,PLSWT,TIME.K) 122 | C PLTP1=4 123 | C PLTP2=4 124 | C PLSWT=0 125 | PLOT P=P(0,8E9)/POLR=2(0,40)/CI=C(0,20E9)/QL=Q(0,2)/NR=N(0,1000E9) 126 | PLOT FR=F,MSL=M,QLC=4,QLP=5(0,2)/CIAF=A(.2,.6) 127 | RUN ORIG 128 | -------------------------------------------------------------------------------- /rt/world/world2.dynamo: -------------------------------------------------------------------------------- 1 | * WORLD DYNAMICS W5 2 | L P.K=P.J+(DT)(BR.JK-DR.JK) 3 | N P=PI 4 | C PI=1.65E9 5 | R BR.KL=(P.K)(CLIP(BRN,BRN1,SWT1,TIME.K))(BRFM.K)(BRMM.K)(BRCM.K)(BR 6 | X PM.K) 7 | C BRN=.04 8 | C BRN1=.04 9 | C SWT1=1970 10 | A BRMM.K=TABHL(BRMMT,MSL.K,0,5,1) 11 | T BRMMT=1.2/1/.85/.75/.7/.7 12 | A MSL.K=ECIR.K/(ECIRN) 13 | C ECIRN=1 14 | A ECIR.K=(CIR.K)(1-CIAF.K)(NREM.K)/(1-CIAFN) 15 | A NREM.K=TABLE(NREMT,NRFR.K,0,1,.25) 16 | T NREMT=0/.15/.5/.85/1 17 | A NRFR.K=NR.K/NRI 18 | L NR.K=NR.J+(DT)(-NRUR.JK) 19 | N NR=NRI 20 | C NRI=900E9 21 | R NRUR.KL=(P.K)(CLIP(NRUN,NRUN1,SWT2,TIME.K))(NRMM.K) 22 | C NRUN=1 23 | C NRUN1=1 24 | C SWT2=1970 25 | NOTE EQUATION 42 CONNECTS HERE FROM EQ. 4 TO EQ. 9 26 | R DR.KL=(P.K)(CLIP(DRN,DRN1,SWT3,TIME.K))(DRMM.K)(DRPM.K)(DRFM.K)(DR 27 | X CM.K) 28 | C DRN=.028 29 | C DRN1=.028 30 | C SWT3=1970 31 | A DRMM.K=TABHL(DRMMT,MSL.K,0,5,.5) 32 | T DRMMT=3/1.8/1/.8/.7/.6/.53/.5/.5/.5/.5 33 | A DRPM.K=TABLE(DRPMT,POLR.K,0,60,10) 34 | T DRPMT=.92/1.3/2/3.2/4.8/6.8/9.2 35 | A DRFM.K=TABHL(DRFMT,FR.K,0,2,.25) 36 | T DRFMT=30/3/2/1.4/1/.7/.6/.5/.5 37 | A DRCM.K=TABLE(DRCMT,CR.K,0,5,1) 38 | T DRCMT=.9/1/1.2/1.5/1.9/3 39 | A CR.K=(P.K)/(LA*PDN) 40 | C LA=135E6 41 | C PDN=26.5 42 | A BRCM.K=TABLE(BRCMT,CR.K,0,5,1) 43 | T BRCMT=1.05/1/.9/.7/.6/.55 44 | A BRFM.K=TABHL(BRFMT,FR.K,0,4,1) 45 | T BRFMT=0/1/1.6/1.9/2 46 | A BRPM.K=TABLE(BRPMT,POLR.K,0,60,10) 47 | T BRPMT=1.02/.9/.7/.4/.25/.15/.1 48 | A FR.K=(FPCI.K)(FCM.K)(FPM.K)(CLIP(FC,FC1,SWT7,TIME.K))/FN 49 | C FC=1 50 | C FC1=1 51 | C FN=1 52 | C SWT7=1970 53 | A FCM.K=TABLE(FCMT,CR.K,0,5,1) 54 | T FCMT=2.4/1/.6/.4/.3/.2 55 | A FPCI.K=TABHL(FPCIT,CIRA.K,0,6,1) 56 | T FPCIT=.5/1/1.4/1.7/1.9/2.05/2.2 57 | A CIRA.K=(CIR.K)(CIAF.K)/CIAFN 58 | C CIAFN=.3 59 | A CIR.K=CI.K/P.K 60 | L CI.K=CI.J+(DT)(CIG.JK-CID.JK) 61 | N CI=CII 62 | C CII=.4E9 63 | R CIG.KL=(P.K)(CIM.K)(CLIP(CIGN,CIGN1,SWT4,TIME.K)) 64 | C CIGN=.05 65 | C CIGN1=.05 66 | C SWT4=1970 67 | A CIM.K=TABHL(CIMT,MSL.K,0,5,1) 68 | T CIMT=.1/1/1.8/2.4/2.8/3 69 | R CID.KL=(CI.K)(CLIP(CIDN,CIDN1,SWT5,TIME.K)) 70 | C CIDN=.025 71 | C CIDN1=.025 72 | C SWT5=1970 73 | A FPM.K=TABLE(FPMT,POLR.K,0,60,10) 74 | T FPMT=1.02/.9/.65/.35/.2/.1/.05 75 | A POLR.K=POL.K/POLS 76 | C POLS=3.6E9 77 | L POL.K=POL.J+(DT)(POLG.JK-POLA.JK) 78 | N POL=POLI 79 | C POLI=.2E9 80 | R POLG.KL=(P.K)(CLIP(POLN,POLN1,SWT6,TIME.K))(POLCM.K) 81 | C POLN=1 82 | C POLN1=1 83 | C SWT6=1970 84 | A POLCM.K=TABHL(POLCMT,CIR.K,0,5,1) 85 | T POLCMT=.05/1/3/5.4/7.4/8 86 | R POLA.KL=POL.K/POLAT.K 87 | A POLAT.K=TABLE(POLATT,POLR.K,0,60,10) 88 | T POLATT=.6/2.5/5/8/11.5/15.5/20 89 | L CIAF.K=CIAF.J+(DT/CIAFT)(CFIFR.J*CIQR.J-CIAF.J) 90 | N CIAF=CIAFI 91 | C CIAFI=.2 92 | C CIAFT=15 93 | A CFIFR.K=TABHL(CFIFRT,FR.K,0,2,.5) 94 | T CFIFRT=1/.6/.3/.15/.1 95 | A QL.K=(QLS)(QLM.K)(QLC.K)(QLF.K)(QLP.K) 96 | C QLS=1 97 | A QLM.K=TABHL(QLMT,MSL.K,0,5,1) 98 | T QLMT=.2/1/1.7/2.3/2.7/2.9 99 | A QLC.K=TABLE(QLCT,CR.K,0,5,.5) 100 | T QLCT=2/1.3/1/.75/.55/.45/.38/.3/.25/.22/.2 101 | A QLF.K=TABHL(QLFT,FR.K,0,4,1) 102 | T QLFT=0/1/1.8/2.4/2.7 103 | A QLP.K=TABLE(QLPT,POLR.K,0,60,10) 104 | T QLPT=1.04/.85/.6/.3/.15/.05/.02 105 | NOTE EQUATION 42 LOCATED BETWEEN EQ. 4 AND 9. 106 | A NRMM.K=TABHL(NRMMT,MSL.K,0,10,1) 107 | T NRMMT=0/1/1.8/2.4/2.9/3.3/3.6/3.8/3.9/3.95/4 108 | NOTE INPUT FROM EQN. 38 AND 40 TO EQN. 35 109 | A CIQR.K=TABHL(CIQRT,QLM.K/QLF.K,0,2,0.5) 110 | T CIQRT=.7/.8/1/1.5/2 111 | NOTE 112 | NOTE CONTROL CARDS 113 | NOTE 114 | SPEC DT=.2/LENGTH=2100/PRTPER=4/PLTPER=4 115 | C TIME=1900 116 | PLOT P=P(0,8E9)/POLR=2(0,40)/CI=C(0,20E9)/QL=Q(0,2)/NR=N(0,1000E9) 117 | PLOT FR=F,MSL=M,QLC=4,QLP=5(0,2)/CIAF=A(.2,.6) 118 | RUN ORIG 119 | -------------------------------------------------------------------------------- /variable.go: -------------------------------------------------------------------------------- 1 | package dynamo 2 | 3 | //---------------------------------------------------------------------- 4 | // This file is part of Dynamo. 5 | // Copyright (C) 2011-2020 Bernd Fix 6 | // 7 | // Dynamo is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, 10 | // or (at your option) any later version. 11 | // 12 | // Dynamo is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | // Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | // 20 | // SPDX-License-Identifier: AGPL3.0-or-later 21 | //---------------------------------------------------------------------- 22 | 23 | import ( 24 | "fmt" 25 | "go/ast" 26 | "math" 27 | "reflect" 28 | "strings" 29 | "unicode" 30 | ) 31 | 32 | //====================================================================== 33 | // A VARIABLE represents a named feature (or a constant) in a system 34 | // model. The name is string beginning with a letter and an optional 35 | // index. In 'strict' mode the name is limited to max. six characters. 36 | //====================================================================== 37 | 38 | //---------------------------------------------------------------------- 39 | // NAME -- A variable name consists of two parts: a simple name part and 40 | // an (optional) index, spearated by a dot ("."). The name must be 41 | // uppercase and start with a letter; the length of a name is limited to 42 | // MAX_NAME_LENGTH. The index part is limited to values as defined in 43 | // INDEX_LIST. The 'stage' attribute classifies the temporality of the 44 | // index (whether it refers to 'now' or 'past' states). 45 | // 46 | // Example names: COFFEE, SHPMTS.JK, INV.K 47 | //---------------------------------------------------------------------- 48 | 49 | // Name-related constants 50 | const ( 51 | MAX_NAME_LENGTH = 6 // max. length of name in 'strict' mode 52 | 53 | // Kind of variable 54 | NAME_KIND_CONST = 0 55 | NAME_KIND_INIT = 1 56 | NAME_KIND_LEVEL = 2 57 | NAME_KIND_RATE = 3 58 | NAME_KIND_AUX = 4 59 | NAME_KIND_SUPPL = 5 60 | 61 | // Stage of variable 62 | NAME_STAGE_NONE = 0 // only constants can have this stage 63 | NAME_STAGE_OLD = 1 64 | NAME_STAGE_NEW = 2 65 | 66 | // Results for Name.Compare: 67 | NAME_MISMATCH = 0 // names don't match 68 | NAME_SAMEVAR = 1 // variables have same name 69 | NAME_SAMEKIND = 2 // variable are of same kind 70 | NAME_SAMESTAGE = 4 // variables have same stage 71 | NAME_MATCH = 7 // names match fully 72 | ) 73 | 74 | var ( 75 | autoId = 0 // last automatic variable identifier 76 | ) 77 | 78 | // NewAutoVar generates a new automatic variable name 79 | func NewAutoVar() string { 80 | autoId++ 81 | return fmt.Sprintf("_%d", autoId) 82 | } 83 | 84 | // Class is a classification for variables 85 | type Class struct { 86 | Kind int // NAME_KIND_? 87 | Stage int // NAME_STAGE_? 88 | } 89 | 90 | // Name of a state variable 91 | type Name struct { 92 | Class 93 | Name string // Name of the variable 94 | } 95 | 96 | // NewName returns a name instance for a given identifier. 97 | func NewName(v ast.Expr) (name *Name, res *Result) { 98 | res = Success() 99 | 100 | switch x := v.(type) { 101 | case *ast.Ident: 102 | name = new(Name) 103 | name.Kind = NAME_KIND_CONST 104 | name.Stage = NAME_STAGE_NONE 105 | name.Name = x.Name 106 | if len(name.Name) > MAX_NAME_LENGTH { 107 | if strict { 108 | res = Failure(ErrParseNameLength+": %s", name.Name) 109 | } else { 110 | Msgf("WARN: "+ErrParseNameLength+": %s", name.Name) 111 | } 112 | } 113 | start := []rune(name.Name)[0] 114 | if !unicode.IsLetter(start) && start != '_' { 115 | if strict { 116 | res = Failure(ErrParseInvalidName+": %s", name.Name) 117 | } else { 118 | Msgf("WARN: "+ErrParseInvalidName+": %s", name.Name) 119 | } 120 | } 121 | return 122 | case *ast.SelectorExpr: 123 | if name, res = NewName(x.X); !res.Ok { 124 | return 125 | } 126 | res = name.setIndex(x.Sel.Name) 127 | default: 128 | res = Failure(ErrParseInvalidName+": %s", reflect.TypeOf(v)) 129 | } 130 | return 131 | } 132 | 133 | // NewNameFromString returns a name instance for a given identifier. 134 | func NewNameFromString(n string) (name *Name, res *Result) { 135 | res = Success() 136 | parts := strings.Split(n, ".") 137 | name = new(Name) 138 | name.Kind = NAME_KIND_CONST 139 | name.Stage = NAME_STAGE_NONE 140 | name.Name = parts[0] 141 | if len(name.Name) > MAX_NAME_LENGTH { 142 | if strict { 143 | res = Failure(ErrParseNameLength+": %d", len(name.Name)) 144 | } else { 145 | Msgf("WARN: "+ErrParseNameLength+": %s", name.Name) 146 | } 147 | } 148 | if len(parts) > 1 { 149 | res = name.setIndex(parts[1]) 150 | } 151 | return 152 | } 153 | 154 | // SetIndex sets name flags for a given index string 155 | func (n *Name) setIndex(idx string) (res *Result) { 156 | res = Success() 157 | switch idx { 158 | case "J": 159 | n.Kind = NAME_KIND_LEVEL 160 | n.Stage = NAME_STAGE_OLD 161 | case "JK": 162 | n.Kind = NAME_KIND_RATE 163 | n.Stage = NAME_STAGE_OLD 164 | case "K": 165 | n.Kind = NAME_KIND_LEVEL 166 | n.Stage = NAME_STAGE_NEW 167 | case "KL": 168 | n.Kind = NAME_KIND_RATE 169 | n.Stage = NAME_STAGE_NEW 170 | default: 171 | res = Failure(ErrParseInvalidIndex+": %s", idx) 172 | } 173 | return 174 | } 175 | 176 | // GetIndex returns the variable index 177 | func (n *Name) GetIndex() string { 178 | if n.Stage == NAME_STAGE_OLD { 179 | if n.Kind == NAME_KIND_LEVEL { 180 | return ".J" 181 | } 182 | if n.Kind == NAME_KIND_RATE { 183 | return ".JK" 184 | } 185 | } else if n.Stage == NAME_STAGE_NEW { 186 | if n.Kind == NAME_KIND_LEVEL { 187 | return ".K" 188 | } 189 | if n.Kind == NAME_KIND_RATE { 190 | return ".KL" 191 | } 192 | } 193 | return "" 194 | } 195 | 196 | // String returns a name in human-readable format 197 | func (n *Name) String() (name string) { 198 | name = n.Name 199 | switch n.Kind { 200 | case NAME_KIND_CONST: 201 | name += "/C" 202 | case NAME_KIND_INIT: 203 | name += "/I" 204 | case NAME_KIND_AUX: 205 | name += "/A" 206 | case NAME_KIND_SUPPL: 207 | name += "/S" 208 | case NAME_KIND_LEVEL: 209 | name += "/L" 210 | case NAME_KIND_RATE: 211 | name += "/R" 212 | } 213 | return 214 | } 215 | 216 | // Compare checks if two names (partially) match. 217 | func (n *Name) Compare(m *Name) int { 218 | match := NAME_MISMATCH 219 | if n.Name == m.Name { 220 | match |= NAME_SAMEVAR 221 | } 222 | if n.Kind == m.Kind { 223 | match |= NAME_SAMEKIND 224 | } 225 | if n.Stage == m.Stage { 226 | match |= NAME_SAMESTAGE 227 | } 228 | return match 229 | } 230 | 231 | //---------------------------------------------------------------------- 232 | // VARIABLE -- represents variables in equations. 233 | //---------------------------------------------------------------------- 234 | 235 | // Variable has a floating point value 236 | type Variable float64 237 | 238 | // String returns the human-readable representation of a variable 239 | func (v Variable) String() string { 240 | return fmt.Sprintf("%f", v) 241 | } 242 | 243 | //---------------------------------------------------------------------- 244 | // MATH methods on variables 245 | //---------------------------------------------------------------------- 246 | 247 | // Sqrt: return the sqare root 248 | func (v Variable) Sqrt() Variable { 249 | return Variable(math.Sqrt(float64(v))) 250 | } 251 | 252 | func (v Variable) Sin() Variable { 253 | return Variable(math.Sin(float64(v))) 254 | } 255 | 256 | func (v Variable) Cos() Variable { 257 | return Variable(math.Cos(float64(v))) 258 | } 259 | 260 | func (v Variable) Exp() Variable { 261 | return Variable(math.Exp(float64(v))) 262 | } 263 | 264 | func (v Variable) Log() Variable { 265 | return Variable(math.Log(float64(v))) 266 | } 267 | 268 | func (v Variable) Floor() Variable { 269 | return Variable(math.Floor(float64(v))) 270 | } 271 | 272 | func (v Variable) Compare(x Variable) int { 273 | return compare(float64(v), float64(x)) 274 | } 275 | 276 | //---------------------------------------------------------------------- 277 | // TSVar -- Time-series variable 278 | //---------------------------------------------------------------------- 279 | 280 | // TSVar is a named variable with a list of values (time series) 281 | type TSVar struct { 282 | Name string // variable name 283 | Min, Max float64 // plot range 284 | Values []float64 // time-series of values 285 | } 286 | 287 | // Add a TSVar value 288 | func (ts *TSVar) Add(y float64) { 289 | if len(ts.Values) == 0 { 290 | ts.Min = y 291 | ts.Max = y 292 | } else if y < ts.Min { 293 | ts.Min = y 294 | } else if y > ts.Max { 295 | ts.Max = y 296 | } 297 | ts.Values = append(ts.Values, y) 298 | } 299 | 300 | // Reset time-series 301 | func (ts *TSVar) Reset() { 302 | ts.Values = make([]float64, 0) 303 | } 304 | --------------------------------------------------------------------------------