Skip to content

Commit

Permalink
docs: add Markdown version
Browse files Browse the repository at this point in the history
  • Loading branch information
mtiller-jh committed Sep 4, 2024
1 parent 0d41984 commit 64a2721
Showing 1 changed file with 383 additions and 0 deletions.
383 changes: 383 additions & 0 deletions cheatsheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,383 @@
# JSML Cheatsheet

## Defining Types

Generally speaking, you'll want to define types for physical quantities. This
will include the units associated with that physical type along with things like
limits. Here are a few basic types for electrical systems (although the goal
will be to eventually create a comprehensive library of SI types).

```
type Voltage = Real(units="V")
type Current = Real(units="A")
type Resistance = Real(units="Ω", min=0)
type Capacitance = Real(units="F", min=0)
type Inductance = Real(units="H", min=0)
type VoltageRate = Real(units="V/s")
```

There is no MTK equivalent of `type`, but we'll see the effect of these types in
the next section.

## Creating a Connector

In JSML, a connector is defined with matching pairs of `potential` and `flow`
variables and then any number of `stream` and `singleton` fields. The latter
two are not discussed here, but a basic electrical pin connector could be
defined as:

```
connector Pin
potential v::Voltage
flow i::Current
end
```

...and the generated MTK code would be:

```julia
@connector Pin begin
v(t), [unit = u"V"]
i(t), [unit = u"A", connect = Flow]
end
export Pin
Pin
```

Note how the variables have units? Those units are based on the `type` used in
the JSML source.

## Creating a Model

In JSML, creating an acausal component model looks like this:

```
component Resistor
parameter R::Resistance
p = Pin()
n = Pin()
variable v::Voltage
variable i::Current
relations
v = p.v - n.v
i = p.i
p.i + n.i = 0
v = i * R
end
```

The generated Julia code will then look like this:

```julia
@component function Resistor(; name, R=nothing)
systems = @named begin
p = Pin()
n = Pin()
end
vars = @variables begin
v(t), [unit = u"V", guess = 0]
i(t), [unit = u"A", guess = 0]
end
params = @parameters begin
(R = R), [unit = u"Ω"]
end
eqs = [
v ~ p.v - n.v
i ~ p.i
p.i + n.i ~ 0
v ~ i * R
]
return ODESystem(eqs, t, vars, params; systems, name)
end
export Resistor
```

JSML **does not have comments**. But it does have descriptive strings that
are part of the JSML grammar and, therefore, bind the descriptions to
specific entities. So, for example, we could add these descriptive strings to
our JSML model:

```
# A basic linear [resistor](https://en.wikipedia.org/wiki/Resistor)
component Resistor
# The resistance of the resistor
parameter R::Resistance
p = Pin()
n = Pin()
variable v::Voltage
variable i::Current
relations
v = p.v - n.v
i = p.i
p.i + n.i = 0
# Ohm's law
v = i * R
end
```

...and we'd get the following updated Julia code:

```julia
"""
A basic linear [resistor](https://en.wikipedia.org/wiki/Resistor)
"""
@component function Resistor(; name, R=nothing)
systems = @named begin
p = Pin()
n = Pin()
end
vars = @variables begin
v(t), [unit = u"V", guess = 0]
i(t), [unit = u"A", guess = 0]
end
params = @parameters begin
(R = R), [description = "The resistance of the resistor", unit = u"Ω"]
end
eqs = [
v ~ p.v - n.v
i ~ p.i
p.i + n.i ~ 0
# Ohm's Law
v ~ i * R
]
return ODESystem(eqs, t, vars, params; systems, name)
end
export Resistor
```

## System Models

A system level model in JSML would look something like:

```
component RLCModel
resistor = Resistor(R=100)
capacitor = Capacitor(C=1m)
inductor = Inductor(L=1)
source::TwoPin = ConstantVoltage(V=30)
ground = Ground()
relations
initial inductor.i = 0
connect(source.p, inductor.n)
connect(inductor.p, resistor.p, capacitor.p)
connect(resistor.n, ground.g, capacitor.n, source.n)
end
```

## Graphics

In order to represent the layout the components and connections in a system
level model, we add metadata to the model. So our previous `RLCModel` with
graphical layout information would look like this:

```
component RLCModel
resistor = Resistor(R=100) [
{ "JuliaSim": { "placement": { "icon": { "x1": 700, "y1": 400, "x2": 900, "y2": 600, "rot": 90 } } } }
]
capacitor = Capacitor(C=1m) [
{ "JuliaSim": { "placement": { "icon": { "x1": 400, "y1": 400, "x2": 600, "y2": 600, "rot": 90 } } } }
]
inductor = Inductor(L=1) [
{ "JuliaSim": { "placement": { "icon": { "x1": 200, "y1": 100, "x2": 400, "y2": 300, "rot": 180 } } } }
]
source::TwoPin = ConstantVoltage(V=30) [
{ "JuliaSim": { "placement": { "icon": { "x1": 0, "y1": 400, "x2": 200, "y2": 600, "rot": 90 } } } }
]
ground = Ground() [
{ "JuliaSim": { "placement": { "icon": { "x1": 400, "y1": 900, "x2": 600, "y2": 1100 } } } }
]
relations
initial inductor.i = 0
connect(source.p, inductor.n) [{
"JuliaSim": {
"route": [[{"x": 100, "y":200}]]
}
}]
connect(inductor.p, resistor.p, capacitor.p) [{
"JuliaSim": {
"route": [
[{"x": 500, "y": 200}, {"x": 800, "y":200}],
[{"x": 800, "y": 200}, {"x": 500, "y": 200}]
]
}
}]
connect(resistor.n, ground.g, capacitor.n, source.n) [{
"JuliaSim": {
"route": [
[{"x": 800, "y": 800}, {"x": 500, "y": 800}],
[{"x": 500, "y": 800}],
[{"x": 500, "y": 800}, {"x": 100, "y": 800}]
]
}
}]
end
```

...will have the same generated Julia code. But it is also possible to generate
an SVG of the system:

![RLC Model Diagram](./rlc.svg)

## Test Cases

A component definition can also include experiments and test cases. For
example, the same `RLCModel` could be augmented with metadata as follows:

```
component RLCModel
resistor = Resistor(R=100)
capacitor = Capacitor(C=1m)
inductor = Inductor(L=1)
source::TwoPin = ConstantVoltage(V=30)
ground = Ground()
relations
initial inductor.i = 0
connect(source.p, inductor.n)
connect(inductor.p, resistor.p, capacitor.p)
connect(resistor.n, ground.g, capacitor.n, source.n)
metadata {
"JuliaSim": {
"experiments": {
"simple": { "start": 0, "stop": 10.0, "initial": { "capacitor.v": 10, "inductor.i": 0 } }
},
"tests": {
"case1": {
"stop": 10,
"initial": { "capacitor.v": 10, "inductor.i": 0 },
"expect": {
"initial": {
"t": 0,
"capacitor.v": 10.0
},
"final": {
"t": 10.0
}
}
}
}
}
}
end
```

...and the generated Julia code would be augmented with additional functions,
`@test`s and `@testset`s as a result:

```julia
"""Run model RLCModel from 0 to 10"""
function simple()
@mtkbuild model = RLCModel()
u0 = [model.capacitor.v => 10, model.inductor.i => 0]
prob = ODEProblem(model, u0, (0, 10))
sol = solve(prob)
end
export simple

@test try
simple()
true
catch
false
end

@testset "Running test case1 for RLCModel" begin
@mtkbuild model = RLCModel()
u0 = [model.capacitor.v => 10, model.inductor.i => 0]
prob = ODEProblem(model, u0, (0, 10))
sol = solve(prob)
@test sol.t[1] 0
@test sol[model.capacitor.v][1] 10
@test sol.t[end] 10
end
```

## Expressions

```
expression
| simple_expression
| ternary_expression
simple_expression
| logical_expressions (':' logical_expression (`:` logical_expression)?)?
ternary_expression
| 'if' expression 'then' expression ('elseif' expressions 'then' expression)* 'else' expression
logical_expression
| logical_term ('or' logical_term)*
logical_term
| logical_factor ('and' logical_factor)*
logical_factor
| (`not`)? relation_expr
relation_expr
| arithmetic_expression (rel_op arithmetic_expression)?
rel_op
| '<='
| '<'
| '>='
| '>'
| '=='
| '<>'
arithmetic_expression
| (`+' | '-') term (add_op term)*
| term (add_op term)*
add_op
| '+'
| '-'
| '.+'
| '.-'
term
| factor (mul_op factor)*
mul_op
| '%'
| '*'
| '/'
| '.%'
| '.*'
| './'
factor
| primary (('^' | '.^') primary)*
primary
| UNSIGNED_NUMBER
| STRING_LITERAL
| BOOLEAN_LITERAL
| component_reference ('(' argument_list ')')?
| parenthetical_expression
| array_expression
component_reference
| deref ('.' deref)+
deref
| IDENTIFIER ('[' expression (',' expression)+ ']')
argument_list
| (expression | keyword_pair) (',' (expression | keyword_pair))+
keyword_pair
| IDENTIFIER '=' expression
parenthetical_expression
| `(` expression `)`
array_expression
| '[' ']'
| '[' expression (',' expression)* ']'
```

0 comments on commit 64a2721

Please sign in to comment.