Skip to content
Romain Franceschini edited this page Apr 28, 2020 · 5 revisions

This section describes the different types of models available and describes how to define their behaviour and state.

There are two types of models, atomic and coupled. An atomic model describes the behavior of a system, while a coupled model describes a system as a network of components coupled together. These models are in the modular form, which means their interactions are restricted to happen through identified ports with a preestablished connection.

The basic building blocks are atomic models, for which you can define a state, a behaviour and input/output ports. While Quartz first goal is to provide means to describe discrete event models, discrete time models are also supported. Their base classes are Quartz::AtomicModel and Quartz::DTSS::AtomicModel, respectively. While they have distinct APIs as for their behaviour, all atomic models share the same interface to define their state and their input/output ports.

The remainder of this page will describe how to define a state and ports. To learn about model semantics or hierarchical models, you can jump to the following pages:

Defining the state of an atomic model

State are defined through the use of the state macro when subclassing a base atomic model class:

class MyModel < Quartz::AtomicModel
  state do
    parameter a = 0.234
    parameter b = 3.2
    var x = 0.0
    var y : Float64 { b }
  end
end

It defines a companion class for the model that inherits Quartz::State, and expects a block to be passed. The block content is inserted in the definition of the Statesubclass.

State variables are defined via the State#var macro while parameters are defined using the State#parameter macro. The difference between the two is that the former is supposed to vary over (simulated) time while the latter is set as a constant value during simulation initialisation.

Both var and parameter macros must receive a type declaration which is used to declare an instance variable, a getter, and a setter for state variables.

Default values must be declared using the type declaration or the assign notation.

state do
  parameter c = 42
  var x : Int32 = 0
end

Default values are necessary because Quartz will instantiate a default initial state for each model.

As it is too restrictive, default values can be lazily initialized through a block. This is typically necessary to initialize dependent variables. The block must be passed with a type declaration notation:

state do
  parameter b = 3.2
  var y : Float64 { b }
end

Parametrization

While an initial state is constructed using the declared default values, it is possible to parameterize a model with a given instance.

state = MyModel::State.new(a: 3.2314, b: 0.6)
model = MyModel.new("model_name", state)

The #initial_state= method can also be used:

model.initial_state = MyModel::State.new(a: 0.1, b: 0.3)

A constructor is automatically defined in the State class, which accepts any state variable or parameter declared using named parameters, as shown in the previous examples.

Inheritance

It is possible to define a model hierarchy. Just use the state macro again to extend the state of the inherited model.

class Foo < Quartz::AtomicModel
  state { var x = 0 }
end

class Bar < Foo
  state { var y = 0 }
end

Defining ports

Atomic models can define input and output ports from/to which they receive/send values. This input/output interface is used by hierarchical models to build more complex models.

Interface

The input/output interface is provided by the Coupleable module, which defines a common API for adding/deleting/getting ports.

Below an example that build the input/output interface

class MyModel < Quartz::AtomicModel
  def after_initialize
    add_input_port :in1
    add_input_port :in2
    add_output_port "out"
  end
end

Note the ports can be dynamically added and/or removed, which is a requirement to allow dynamic structure models. However, it is possible to statically declare ports using the input and output macros. Those ports are then added to each instance.

class MyModel < Quartz::AtomicModel
  input in1, in2
  output out
end

Values

Port values are wrapped under the type Quartz::Any. All possible values that can be passed through ports are denoted by the union type Quartz::Any::Type.

To extend the types that can be sent/received, use the Quartz::Transferable marker interface:

class Foo
  include Quartz::Transferable
  # ...
end

You can also re-open existing classes to include the marker:

struct BigInt
  include Transferable
end

Those types now can be sent between ports.

To learn how you can send or read values to/from prots, jump to the next pages about model behaviours.


Next: Discrete event models, or skip to: discrete time models

Clone this wiki locally