-
Notifications
You must be signed in to change notification settings - Fork 13
4. Building DEVS Models
The following UML class diagram contains EVERYTHING you need to develop DEVS models with Cadmium.
Cadmium uses ports to model the generation and propagation of events.
The cadmium::PortInterface
class is an interface that describes the basic behavior of a port, regardless of other implementation details.
You won't have to deal with cadmium::PortInterface
objects.
The only thing that you need to know is that you can check if a port contains one or more messages via the bool empty() const
method.
If you want to know the number of messages in the port, you can use the std::size_t size() const
method.
cadmium::Port<T>
objects are ports that can only holds message of the type T
.
For example, a port of type cadmium::Port<int>
only accepts integer number messages.
You can read the messages in the port with the const std::vector<T>& getBag() const
method.
This method returns a vector with all the messages in the port.
If the port type is cadmium::Port<int>
, the return vector is of type std::vector<int>
(i.e., it only contains integer numbers).
On the other hand, if you want to add a new message to the port, you can use the void addMessage(T msg)
method.
If the port type is cadmium::Port<int>
, you can only add integer numbers.
Otherwise, your program won't compile.
Most of the times, you will only deal with objects of the class cadmium::Port<T>
.
However, if the message type of the port is complex/has a lot of fields/it is computationally expensive to clone, you may consider to use a cadmium::BigPort<T>
.
Big ports store messages using pointers to the message.
Pointer are cheap to copy.
However, they are expensive to create.
There is not a magic rule to choose a cadmium::Port<T>
or a cadmium::BigPort<T>
.
If you want to send simple messages (e.g., a number, a boolean, etc.), use cadmium::Port<T>
.
On the other hand, if you want to send a big structure with vectors with thousands of elements, use cadmium::BigInPort<T>
.
If you are not sure, just try with both and check which port implementation is the fastest.
Cadmium DEVS models NEVER work with cadmium::Port<T>
data structures directly.
Instead, they use std::shared_ptr<cadmium::Port<T>>
pointers.
Shared pointers are very useful for internal reasons of the library.
In the next section, we tell you how to properly create ports.
In Cadmium, atomic and coupled models inherit from the cadmium::Component
class.
You will never deal directly with cadmium::Component
objects.
However, they provide useful methods for both atomic and coupled models.
Components have input ports and output ports.
To do.
To do.
First, let's look at the steps we need to follow to develop an atomic DEVS model.
Our atomic model must inherit from the cadmium::Atomic<S>
class. S
is a template argument to determine which data type is used to represent the state of the atomic model.
If you want it to be an integer number, your model must inherit from cadmium::Atomic<int>
.
However, most of the times you will want to use your custom class.
Let us assume that you want to represent the state in a phase-sigma fashion.
Then, we have to define the PhaseSigmaState
structure:
#include <limits>
struct PhaseSigmaState {
std::string phase; // phase (or state) of the atomic model
double sigma; // time to wait until the next internal transition
PhaseSigmaState(std::string phase, double sigma): phase(phase), sigma(sigma) {}
}
We must overwrite the insertion (<<
) operator for our state data structure.
The insertion operator allows us to tell Cadmium how to represent the state of our model as a string.
In our case, we define <<
as follows:
std::ostream& operator<<(std::ostream &out, const PhaseSigmaState& s) {
out << "(" << s.phase << "," << s.sigma << ")";
return out;
}
Now, let's define the behavior of our atomic model.
We will define a very simple model.
It has two input ports: inNewSigma
and inNewPhase
.
The model also has one output port: outNewPhase
.
This port only outputs strings.
include <cadmium/core/modeling/atomic.hpp>
class MyAtomic: public cadmium::Atomic<PhaseSigmaState> {
public:
std::shared_ptr<cadmium::Port<double>> inNewSigma;
std::shared_ptr<cadmium::Port<std::string>> inNewPhase;
std::shared_ptr<cadmium::Port<std::string>> outNewPhase;
MyAtomic(): cadmium::Atomic<PhaseSigmaState>(PhaseSigmaState("initial", 0.)) {
inNewSigma = addInPort<double>("inNewSigma");
inNewPhase = addInPort<std::string>("inNewPhase");
outNewPhase = addOutPort<std::string>("outNewPhase");
}
// it continues below...
We define the ports of our model as public attributes of our new class.
Note that we NEVER work with cadmium::Port<T>
data structures directly.
We ALWAYS work with std::shared_ptr<cadmium::Port<T>>
structures.
The ports are created in the constructor function.
We use the methods addInPort<T>
and addOutPort<T>
to create the new ports.
T
is a template argument that defines the data type of the messages that the port accepts.
For instance, inNewSigma
port only accepts string messages.
If you try to send an integer number via the inNewSigma
port, your code will not compile.
// ... the atomic model definition continues here
double timeAdvance(const PhaseSigmaState& state) const override {
return state.sigma;
}
void output(PhaseSigmaState& state) const override {
outNewPhase->addMessage(state.phase);
}
void internalTransition(PhaseSigmaState& state) override {
state.sigma = std::numeric_limits<double>::infinity();
}
void externalTransition(PhaseSigmaState& state, double e) override {
state.sigma = std::numeric_limits<double>::infinity();
}
// We won't override the confluentTransition function, as we want to use the default one.
}
When receiving a new message, the model sets its phase to the value of the message. When receiving a new message, the model sets its sigma to the value of the message.
To do.