Skip to content

4. Building DEVS Models

Román Cárdenas edited this page Jul 18, 2022 · 15 revisions

The following UML class diagram contains EVERYTHING you need to develop DEVS models with Cadmium.

UML Class Diagram of Cadmium 2 Models.

First, let's look at the steps we need to follow to develop an atomic DEVS model.

Building Atomic DEVS Models

Defining the state type S

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;
}

Defining the behavior of the atomic model

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.

Clone this wiki locally