|
| 1 | +# JSML Cheatsheet |
| 2 | + |
| 3 | +## Defining Types |
| 4 | + |
| 5 | +Generally speaking, you'll want to define types for physical quantities. This |
| 6 | +will include the units associated with that physical type along with things like |
| 7 | +limits. Here are a few basic types for electrical systems (although the goal |
| 8 | +will be to eventually create a comprehensive library of SI types). |
| 9 | + |
| 10 | +``` |
| 11 | +type Voltage = Real(units="V") |
| 12 | +type Current = Real(units="A") |
| 13 | +type Resistance = Real(units="Ω", min=0) |
| 14 | +type Capacitance = Real(units="F", min=0) |
| 15 | +type Inductance = Real(units="H", min=0) |
| 16 | +type VoltageRate = Real(units="V/s") |
| 17 | +``` |
| 18 | + |
| 19 | +There is no MTK equivalent of `type`, but we'll see the effect of these types in |
| 20 | +the next section. |
| 21 | + |
| 22 | +## Creating a Connector |
| 23 | + |
| 24 | +In JSML, a connector is defined with matching pairs of `potential` and `flow` |
| 25 | +variables and then any number of `stream` and `singleton` fields. The latter |
| 26 | +two are not discussed here, but a basic electrical pin connector could be |
| 27 | +defined as: |
| 28 | + |
| 29 | +``` |
| 30 | +
|
| 31 | +connector Pin |
| 32 | +potential v::Voltage |
| 33 | +flow i::Current |
| 34 | +end |
| 35 | +
|
| 36 | +``` |
| 37 | + |
| 38 | +...and the generated MTK code would be: |
| 39 | + |
| 40 | +```julia |
| 41 | +@connector Pin begin |
| 42 | + v(t), [unit = u"V"] |
| 43 | + i(t), [unit = u"A", connect = Flow] |
| 44 | +end |
| 45 | +export Pin |
| 46 | +Pin |
| 47 | +``` |
| 48 | + |
| 49 | +Note how the variables have units? Those units are based on the `type` used in |
| 50 | +the JSML source. |
| 51 | + |
| 52 | +## Creating a Model |
| 53 | + |
| 54 | +In JSML, creating an acausal component model looks like this: |
| 55 | + |
| 56 | +``` |
| 57 | +component Resistor |
| 58 | + parameter R::Resistance |
| 59 | + p = Pin() |
| 60 | + n = Pin() |
| 61 | + variable v::Voltage |
| 62 | + variable i::Current |
| 63 | +relations |
| 64 | + v = p.v - n.v |
| 65 | + i = p.i |
| 66 | + p.i + n.i = 0 |
| 67 | + v = i * R |
| 68 | +end |
| 69 | +``` |
| 70 | + |
| 71 | +The generated Julia code will then look like this: |
| 72 | + |
| 73 | +```julia |
| 74 | +@component function Resistor(; name, R=nothing) |
| 75 | + systems = @named begin |
| 76 | + p = Pin() |
| 77 | + n = Pin() |
| 78 | + end |
| 79 | + vars = @variables begin |
| 80 | + v(t), [unit = u"V", guess = 0] |
| 81 | + i(t), [unit = u"A", guess = 0] |
| 82 | + end |
| 83 | + params = @parameters begin |
| 84 | + (R = R), [unit = u"Ω"] |
| 85 | + end |
| 86 | + eqs = [ |
| 87 | + v ~ p.v - n.v |
| 88 | + i ~ p.i |
| 89 | + p.i + n.i ~ 0 |
| 90 | + v ~ i * R |
| 91 | + ] |
| 92 | + return ODESystem(eqs, t, vars, params; systems, name) |
| 93 | +end |
| 94 | +export Resistor |
| 95 | +``` |
| 96 | + |
| 97 | +JSML **does not have comments**. But it does have descriptive strings that |
| 98 | +are part of the JSML grammar and, therefore, bind the descriptions to |
| 99 | +specific entities. So, for example, we could add these descriptive strings to |
| 100 | +our JSML model: |
| 101 | + |
| 102 | +``` |
| 103 | +# A basic linear [resistor](https://en.wikipedia.org/wiki/Resistor) |
| 104 | +component Resistor |
| 105 | + # The resistance of the resistor |
| 106 | + parameter R::Resistance |
| 107 | + p = Pin() |
| 108 | + n = Pin() |
| 109 | + variable v::Voltage |
| 110 | + variable i::Current |
| 111 | +relations |
| 112 | + v = p.v - n.v |
| 113 | + i = p.i |
| 114 | + p.i + n.i = 0 |
| 115 | + # Ohm's law |
| 116 | + v = i * R |
| 117 | +end |
| 118 | +
|
| 119 | +``` |
| 120 | + |
| 121 | +...and we'd get the following updated Julia code: |
| 122 | + |
| 123 | +```julia |
| 124 | +""" |
| 125 | +A basic linear [resistor](https://en.wikipedia.org/wiki/Resistor) |
| 126 | +""" |
| 127 | +@component function Resistor(; name, R=nothing) |
| 128 | + systems = @named begin |
| 129 | + p = Pin() |
| 130 | + n = Pin() |
| 131 | + end |
| 132 | + vars = @variables begin |
| 133 | + v(t), [unit = u"V", guess = 0] |
| 134 | + i(t), [unit = u"A", guess = 0] |
| 135 | + end |
| 136 | + params = @parameters begin |
| 137 | + (R = R), [description = "The resistance of the resistor", unit = u"Ω"] |
| 138 | + end |
| 139 | + eqs = [ |
| 140 | + v ~ p.v - n.v |
| 141 | + i ~ p.i |
| 142 | + p.i + n.i ~ 0 |
| 143 | + # Ohm's Law |
| 144 | + v ~ i * R |
| 145 | + ] |
| 146 | + return ODESystem(eqs, t, vars, params; systems, name) |
| 147 | +end |
| 148 | +export Resistor |
| 149 | +``` |
| 150 | + |
| 151 | +## System Models |
| 152 | + |
| 153 | +A system level model in JSML would look something like: |
| 154 | + |
| 155 | +``` |
| 156 | +component RLCModel |
| 157 | + resistor = Resistor(R=100) |
| 158 | + capacitor = Capacitor(C=1m) |
| 159 | + inductor = Inductor(L=1) |
| 160 | + source::TwoPin = ConstantVoltage(V=30) |
| 161 | + ground = Ground() |
| 162 | +relations |
| 163 | + initial inductor.i = 0 |
| 164 | + connect(source.p, inductor.n) |
| 165 | + connect(inductor.p, resistor.p, capacitor.p) |
| 166 | + connect(resistor.n, ground.g, capacitor.n, source.n) |
| 167 | +end |
| 168 | +
|
| 169 | +``` |
| 170 | + |
| 171 | +## Graphics |
| 172 | + |
| 173 | +In order to represent the layout the components and connections in a system |
| 174 | +level model, we add metadata to the model. So our previous `RLCModel` with |
| 175 | +graphical layout information would look like this: |
| 176 | + |
| 177 | +``` |
| 178 | +component RLCModel |
| 179 | + resistor = Resistor(R=100) [ |
| 180 | + { "JuliaSim": { "placement": { "icon": { "x1": 700, "y1": 400, "x2": 900, "y2": 600, "rot": 90 } } } } |
| 181 | + ] |
| 182 | + capacitor = Capacitor(C=1m) [ |
| 183 | + { "JuliaSim": { "placement": { "icon": { "x1": 400, "y1": 400, "x2": 600, "y2": 600, "rot": 90 } } } } |
| 184 | + ] |
| 185 | + inductor = Inductor(L=1) [ |
| 186 | + { "JuliaSim": { "placement": { "icon": { "x1": 200, "y1": 100, "x2": 400, "y2": 300, "rot": 180 } } } } |
| 187 | + ] |
| 188 | + source::TwoPin = ConstantVoltage(V=30) [ |
| 189 | + { "JuliaSim": { "placement": { "icon": { "x1": 0, "y1": 400, "x2": 200, "y2": 600, "rot": 90 } } } } |
| 190 | + ] |
| 191 | + ground = Ground() [ |
| 192 | + { "JuliaSim": { "placement": { "icon": { "x1": 400, "y1": 900, "x2": 600, "y2": 1100 } } } } |
| 193 | + ] |
| 194 | +relations |
| 195 | + initial inductor.i = 0 |
| 196 | + connect(source.p, inductor.n) [{ |
| 197 | + "JuliaSim": { |
| 198 | + "route": [[{"x": 100, "y":200}]] |
| 199 | + } |
| 200 | + }] |
| 201 | + connect(inductor.p, resistor.p, capacitor.p) [{ |
| 202 | + "JuliaSim": { |
| 203 | + "route": [ |
| 204 | + [{"x": 500, "y": 200}, {"x": 800, "y":200}], |
| 205 | + [{"x": 800, "y": 200}, {"x": 500, "y": 200}] |
| 206 | + ] |
| 207 | + } |
| 208 | + }] |
| 209 | + connect(resistor.n, ground.g, capacitor.n, source.n) [{ |
| 210 | + "JuliaSim": { |
| 211 | + "route": [ |
| 212 | + [{"x": 800, "y": 800}, {"x": 500, "y": 800}], |
| 213 | + [{"x": 500, "y": 800}], |
| 214 | + [{"x": 500, "y": 800}, {"x": 100, "y": 800}] |
| 215 | + ] |
| 216 | + } |
| 217 | + }] |
| 218 | +end |
| 219 | +``` |
| 220 | + |
| 221 | +...will have the same generated Julia code. But it is also possible to generate |
| 222 | +an SVG of the system: |
| 223 | + |
| 224 | + |
| 225 | + |
| 226 | +## Test Cases |
| 227 | + |
| 228 | +A component definition can also include experiments and test cases. For |
| 229 | +example, the same `RLCModel` could be augmented with metadata as follows: |
| 230 | + |
| 231 | +``` |
| 232 | +component RLCModel |
| 233 | + resistor = Resistor(R=100) |
| 234 | + capacitor = Capacitor(C=1m) |
| 235 | + inductor = Inductor(L=1) |
| 236 | + source::TwoPin = ConstantVoltage(V=30) |
| 237 | + ground = Ground() |
| 238 | +relations |
| 239 | + initial inductor.i = 0 |
| 240 | + connect(source.p, inductor.n) |
| 241 | + connect(inductor.p, resistor.p, capacitor.p) |
| 242 | + connect(resistor.n, ground.g, capacitor.n, source.n) |
| 243 | +metadata { |
| 244 | + "JuliaSim": { |
| 245 | + "experiments": { |
| 246 | + "simple": { "start": 0, "stop": 10.0, "initial": { "capacitor.v": 10, "inductor.i": 0 } } |
| 247 | + }, |
| 248 | + "tests": { |
| 249 | + "case1": { |
| 250 | + "stop": 10, |
| 251 | + "initial": { "capacitor.v": 10, "inductor.i": 0 }, |
| 252 | + "expect": { |
| 253 | + "initial": { |
| 254 | + "t": 0, |
| 255 | + "capacitor.v": 10.0 |
| 256 | + }, |
| 257 | + "final": { |
| 258 | + "t": 10.0 |
| 259 | + } |
| 260 | + } |
| 261 | + } |
| 262 | + } |
| 263 | + } |
| 264 | +} |
| 265 | +end |
| 266 | +``` |
| 267 | + |
| 268 | +...and the generated Julia code would be augmented with additional functions, |
| 269 | +`@test`s and `@testset`s as a result: |
| 270 | + |
| 271 | +```julia |
| 272 | +"""Run model RLCModel from 0 to 10""" |
| 273 | +function simple() |
| 274 | + @mtkbuild model = RLCModel() |
| 275 | + u0 = [model.capacitor.v => 10, model.inductor.i => 0] |
| 276 | + prob = ODEProblem(model, u0, (0, 10)) |
| 277 | + sol = solve(prob) |
| 278 | +end |
| 279 | +export simple |
| 280 | + |
| 281 | +@test try |
| 282 | + simple() |
| 283 | + true |
| 284 | +catch |
| 285 | + false |
| 286 | +end |
| 287 | + |
| 288 | +@testset "Running test case1 for RLCModel" begin |
| 289 | + @mtkbuild model = RLCModel() |
| 290 | + u0 = [model.capacitor.v => 10, model.inductor.i => 0] |
| 291 | + prob = ODEProblem(model, u0, (0, 10)) |
| 292 | + sol = solve(prob) |
| 293 | + @test sol.t[1] ≈ 0 |
| 294 | + @test sol[model.capacitor.v][1] ≈ 10 |
| 295 | + @test sol.t[end] ≈ 10 |
| 296 | +end |
| 297 | +``` |
| 298 | + |
| 299 | +## Expressions |
| 300 | + |
| 301 | +``` |
| 302 | +expression |
| 303 | + | simple_expression |
| 304 | + | ternary_expression |
| 305 | +
|
| 306 | +simple_expression |
| 307 | + | logical_expressions (':' logical_expression (`:` logical_expression)?)? |
| 308 | +
|
| 309 | +ternary_expression |
| 310 | + | 'if' expression 'then' expression ('elseif' expressions 'then' expression)* 'else' expression |
| 311 | +
|
| 312 | +logical_expression |
| 313 | + | logical_term ('or' logical_term)* |
| 314 | +
|
| 315 | +logical_term |
| 316 | + | logical_factor ('and' logical_factor)* |
| 317 | +
|
| 318 | +logical_factor |
| 319 | + | (`not`)? relation_expr |
| 320 | +
|
| 321 | +relation_expr |
| 322 | + | arithmetic_expression (rel_op arithmetic_expression)? |
| 323 | +
|
| 324 | +rel_op |
| 325 | + | '<=' |
| 326 | + | '<' |
| 327 | + | '>=' |
| 328 | + | '>' |
| 329 | + | '==' |
| 330 | + | '<>' |
| 331 | +
|
| 332 | +arithmetic_expression |
| 333 | + | (`+' | '-') term (add_op term)* |
| 334 | + | term (add_op term)* |
| 335 | +
|
| 336 | +add_op |
| 337 | + | '+' |
| 338 | + | '-' |
| 339 | + | '.+' |
| 340 | + | '.-' |
| 341 | +
|
| 342 | +term |
| 343 | + | factor (mul_op factor)* |
| 344 | +
|
| 345 | +mul_op |
| 346 | + | '%' |
| 347 | + | '*' |
| 348 | + | '/' |
| 349 | + | '.%' |
| 350 | + | '.*' |
| 351 | + | './' |
| 352 | +
|
| 353 | +factor |
| 354 | + | primary (('^' | '.^') primary)* |
| 355 | +
|
| 356 | +primary |
| 357 | + | UNSIGNED_NUMBER |
| 358 | + | STRING_LITERAL |
| 359 | + | BOOLEAN_LITERAL |
| 360 | + | component_reference ('(' argument_list ')')? |
| 361 | + | parenthetical_expression |
| 362 | + | array_expression |
| 363 | +
|
| 364 | +component_reference |
| 365 | + | deref ('.' deref)+ |
| 366 | +
|
| 367 | +deref |
| 368 | + | IDENTIFIER ('[' expression (',' expression)+ ']') |
| 369 | +
|
| 370 | +argument_list |
| 371 | + | (expression | keyword_pair) (',' (expression | keyword_pair))+ |
| 372 | +
|
| 373 | +keyword_pair |
| 374 | + | IDENTIFIER '=' expression |
| 375 | +
|
| 376 | +parenthetical_expression |
| 377 | + | `(` expression `)` |
| 378 | +
|
| 379 | +array_expression |
| 380 | + | '[' ']' |
| 381 | + | '[' expression (',' expression)* ']' |
| 382 | +``` |
| 383 | + |
0 commit comments