Syntax
This section provides an overview of Dyad's syntax and language constructs. It only briefly touches on how to use them, and should be read as reference material for what you can do.
Dyad files (.dyad
) typically include component
definitions and analysis
definitions. They may also includes using
statements when referencing external Julia packages.
In this syntax guide, we have placed all optional keywords in square brackets, with |
separating the options. For example, [structural | final] parameter
means that the following syntax is supported:
parameter
structural parameter
final parameter
Components
Components are the fundamental building blocks in Dyad. The are meant to capture mathematical behavior (causal, acausal or discrete) as a reusable component. They may contain variable
s, parameter
s, connector
s, subcomponents, and relation
s. Furthermore, they may extend
from other components.
The component declaration may be prefixed with:
partial
to indicate that it is a partial component, which can be used as a base for other components,example
to indicate that it is an example component to show what you can do with other components,test
to indicate that it is a component used only for testing, and should not be exported.
Here is an example component
definition:
# Sample component
component MyComponent
# A component can have one or more extends clauses, like this one...
extends BaseComponent
# A normal parameter only results in a parametric change, not a structural change,
parameter y::Real
# A structural parameter is a parameter changes the number of equations or variables.
structural parameter N::Integer = 10
# Variables and parameters can have a number of different attributes
variable x::Real(min = 0, max = 10, guess = 5, units = "m")
# Subcomponents are components nested inside other components forming hierarchical models
subcomponent = SomeComponent()
# Connectors represent points of interaction between the components
p = Pin()
relations
# One type of relation is an equation, like this one
der(x) = y
# Another type of relation is a connection
connect(subcomponent.p, p)
end
Component metadata
Components can also have metadata, which is used by the UI, documentation tools, and codegen for some specific keys. For example, you can specify the icon for a component in the metadata.
component MyComponent
# ...
metadata {
"Dyad": {"icon": "dyad://YourComponentLibrary/assets/icon.svg"}
}
end
The Dyad
namespace in metadata is reserved. But all other namespaces can be used. This allows application/user/customer specific metadata to be included in models. An example use of metadata might be to include the corresponding physical part number in the component metadata.
Analyses
Analyses describe workflows that can be performed. One way to think about this is the a component
describes a problem while an analysis
is something that ultimately leads to a "result" (some kind of computation typically performed on a component
or perhaps even another analysis
).
Examples of analyses might include:
simulate a model over time with a TransientAnalysis,
run a sweep over some parameters with a ParameterSweepAnalysis,
run a Monte Carlo analysis with a MonteCarloAnalysis,
or even define and run a custom analysis using your own Julia code (see the advanced users guide).
To write an analysis in Dyad you must extend from some existing analysis; ultimately all analyses are implemented in Julia. See the custom analysis tutorial for more details.
analysis MyAnalysis
extends TransientAnalysis(alg="Rodas5P", abstol=0.001)
model = CircuitModel()
relations
# Relations between model and data
end
Importing libraries
The using
statement imports components, types, or other definitions from other packages. Crucially, these can be functions or variables from Julia as well as Dyad component libraries.
To import a Julia function, you must specify the input and return types. Suppose we have a simple Julia function myfunc(x, y) = x + y
. We can import it like this:
using MyLibrary: myfunc(::Real, ::Real)::Real
Note the type annotations. These can be any Dyad type; the most permissive is Native
, which translates to Julia's Any
.
To import a component from another component library, it's sufficient to fully specify the component library and the name, like so:
subcomponent = BlockComponents.SomeComponent()
All the external libraries you pull in must be in your Project.toml
file. You can add to this file in the Julia REPL, by calling using Pkg; Pkg.add("MyLibrary")
, or going into "Pkg-mode" by typing ]
, and then running add MyLibrary
. See Julia's package manager documentation for more details.
Variables
Variables represent quantities that can change during simulation. For variables of type Real
you can define what units are associated with that variable, in which case Dyad will automatically check that the units are correct in every relation
it is involved in.
component MyComponent
variable position::Real
variable velocity::Real(units="m/s")
relations
# ...
end
Parameters
Parameters are values that remain fixed during a simulation, but can be changed between simulations.
component MyComponent
parameter mass::Mass = 1.0
parameter length::Length = 0.5
# Cannot be modified at all, even when constructing a new component
final parameter id::Integer = 12345
# See below for more on structural parameters.
structural parameter N::Integer = 3
# ...
end
Structural parameters
Structural parameters are parameters whose changes might imply structural changes in the model. They are fixed at component creation time, and cannot be changed after the component is created.
The size of an array in a component can be defined by a literal integer or a structural parameter.
component MyComponent
structural parameter N::Integer = 3 # Number of elements
variable x::Real[N] # Array size depends on N
relations
# ...
end
Relations
The relations
block contains equations and other statements that define a component's behavior.
You can use initial
in front of an equation to set the initial value of a variable (at the start time of the simulation).
Loops via for
are supported, and can contain other relations within them. Note that they will be unrolled in Julia, so you shouldn't have extremely long loops here (O(10_000) and above) for performance reasons (But they will still work!).
You can also use connect
to connect connector
s. A connect
call can have any number of arguments, and will connect all the listed connector
s to each other.
relations
# Equations
initial x = 0.0 # Initial condition
der(x) = v # Differential equation (dx/dt = v)
F = m * der(v) # Newton's law
# Loops
for i in 1:5
initial array_of_components[i].x = 0.0
end
for i in 1:4
connect(array_of_components[i].y, array_of_components[i+1].x)
end
# Connections
connect(source.p, resistor.p)
connect(resistor.n, ground.g)
end
Expressions
Expressions are used to compute values in equations and other contexts. They can be used in most any context where a value is expected (except for array sizes, which require literals, or parameter guesses).
Most expressions are pretty similar to Julia syntax - you can use +
for addition, *
for multiplication, ^
for exponentiation, etc. as you would in most programming or modeling languages.
Operators
Dyad supports a variety of operators for arithmetic and logical operations. The operator precedence here is the same as in Julia.
a + b # Addition
a - b # Subtraction
a * b # Multiplication
a / b # Division
a ^ b # Power
a % b # Modulo
a > b # Greater than
a < b # Less than
a >= b # Greater than or equal
a <= b # Less than or equal
a == b # Equal
a != b # Not equal
a and b # Logical AND
a or b # Logical OR
!a # Logical NOT
a = b # Assignment
Function calls
Dyad supports calling functions with arguments. You can also provide default values for arguments.
You can call any Julia function that is available in your component library. That means anything from Julia Base, or any packages you load within that module (using using
), or any functions you define within that module.
You must import a function as shown in the importing libraries section.
sin(angle)
atan2(y, x)
custom_func(data, tolerance=0.01)
Conditional expressions
If statements are available in Dyad, but you may also want to see enumerations for more complex cases.
y = if x > 0 then x else 0 # Inline if expression
Literals
x = 10 # Integer
y = 3.14 # Real number
z = 1.5e-3 # Scientific notation
R = 10k # With metric prefix (10 kilo => 10 * 10e3 => 10,000)
text = "Hello" # String
flag = true # Boolean
Time
Dyad supports a time
variable, which is a special variable that represents the current time of the simulation.
Here is a step function that changes at t=10
:
v = if time < 10 then 0 else 1
You can use time
anywhere you could use a variable. For example, here's an expression that switches from sine to cosine at t=10
:
v = if time < 10 then sin(time) else cos(time)
Arrays
Arrays can be used for variables, parameters, and other data structures.
parameter vector::Real[3] = [1, 2, 3]
variable matrix::Real[2, 2]
variable pos::Position[3] # 3D position vector
Array comprehensions can initialize arrays of components:
resistors = [Resistor(R=i*10) for i in 1:5] # Array of 5 resistors
The size of an array can be defined by a literal integer or a structural parameter
, but cannot vary at runtime and any change to a structural parameter will require that the model undergo symbolic processing again.
Control flow
Enums and switch-case
Enums define a type with a finite set of named values.
enum InitOptions =
| FixedPosition(s0::Position)
| Equilibrium
| None
Switch statements allow different equations based on enum values:
parameter init_option::InitOptions
...
relations
switch init_option
case FixedPosition
initial s = init_option.s0
initial v = 0
end
case Equilibrium
initial der(s) = 0
initial der(v) = 0
end
default
initial s = 0
initial v = 0
end
end
If statements
If statements provide conditional logic in relations:
if temperature < 0 then
heat_output = 1000.0
else if temperature > 25 then
heat_output = 0.0
else
heat_output = 500.0
end
Not that the number of equations must match for each contingency unless the expression depends only on constants or structural parameters.
Metadata
Metadata attaches additional information to model elements for documentation, UI hints, or tool-specific data. Metadata is organized into namespaces. The Dyad
namespace is reserved, but other namespaces can be used to manage any kind of structured (JSON) data, e.g.,
component MyComponent
# ...
metadata {
"Dyad": {
"icons": {
"default": "dyad://MyLibrary/my_component.svg"
}
},
"ACME Enterprises": {
"Author": "Wile E. Coyote",
"Part Number": "XRocketA/30/F"
}
}
end
For associating metadata with a definition, or...
p = Pin() [{ "Dyad": { "iconName": "pos" } }]
For associating metadata with individual components, connectors, variables, etc.