Skip to content

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 variables, parameters, connectors, subcomponents, and relations. 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:

dyad
# 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.

dyad
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.

dyad
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:

dyad
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:

dyad
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.

dyad
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.

dyad
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.

dyad
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 connectors. A connect call can have any number of arguments, and will connect all the listed connectors to each other.

dyad
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.

dyad
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.

dyad
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.

dyad
y = if x > 0 then x else 0  # Inline if expression

Literals

dyad
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:

dyad
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:

dyad
v = if time < 10 then sin(time) else cos(time)

Arrays

Arrays can be used for variables, parameters, and other data structures.

dyad
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:

dyad
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.

dyad
enum InitOptions =
  | FixedPosition(s0::Position)
  | Equilibrium
  | None

Switch statements allow different equations based on enum values:

dyad
  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:

dyad
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.,

dyad
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...

dyad
p = Pin() [{ "Dyad": { "iconName": "pos" } }]

For associating metadata with individual components, connectors, variables, etc.