Skip to content

Solvers

The solver controls when components are calculated. Easy2Sim provides two main solvers:

  • DynamicSolver (namespace Easy2Sim.Solvers.Dynamic): every component's DynamicCalculation() is executed once per simulation time step. Use it for dynamic (continuous) simulations.
  • DiscreteSolver (namespace Easy2Sim.Solvers.Discrete): a component's DiscreteCalculation() is only executed when an event for it is processed. Use it for discrete-event simulations.

Both solvers share the same lifecycle:

solver.Initialize();              // calls Initialize() on every component, once
solver.CalculateTo(100);          // run until simulation time 100
// or
solver.CalculateFinish();         // run until the simulation is finished

Dynamic solver

In a dynamic calculation, each component's DynamicCalculation() method is executed once per time step, in the order of the components' simulation indexes.

The following complete program is the getting started example — it is compiled and run as part of the documentation example project:

Program.cs
using Easy2Sim.Environment;
using Easy2Sim.Solvers.Dynamic;
using Easy2SimExamples;

// 1. Create the simulation environment and a solver
SimulationEnvironment environment = new SimulationEnvironment();
DynamicSolver solver = new DynamicSolver(environment);

// 2. Create simulation components - they register themselves in the environment
Sine sine = new Sine(environment, solver);

// Print the output value every 10 time steps
sine.Output.PropertyChanged += (_, _) =>
{
    if (solver.SimulationTime % 10 == 0)
        Console.WriteLine($"t={solver.SimulationTime,3}  output={sine.Output.Value,8:F4}");
};

// 3. Initialize the simulation (calls Initialize() on every component)
solver.Initialize();

// 4. Run the simulation until time step 100
solver.CalculateTo(100);

Output:

t=  0  output=  0.0000
t= 10  output=  0.5878
t= 20  output=  0.9511
t= 30  output=  0.9511
t= 40  output=  0.5878
t= 50  output=  0.0000
t= 60  output= -0.5878
t= 70  output= -0.9511
t= 80  output= -0.9511
t= 90  output= -0.5878
t=100  output= -0.0000

Discrete solver

With the discrete solver, a component is only calculated when an event for it exists in the event list. Each event points to a simulation time; the solver always processes the event with the lowest simulation time next. CalculateFinish() stops when no events are left or a component finishes the simulation; CalculateTo(maxTime) additionally stops when the next event lies beyond maxTime.

The following complete example defines a Clock component that schedules a tick every 10 time units and a Printer component that is triggered automatically through a connection:

DiscreteExample.cs
using Easy2Sim.Connect;
using Easy2Sim.Environment;
using Easy2Sim.Solvers;
using Easy2Sim.Solvers.Discrete;
using Newtonsoft.Json;

namespace Easy2SimExamples;

/// <summary>
/// A simulation component that produces a tick every 10 simulation time units.
/// </summary>
public class Clock : SimulationBase
{
    [JsonProperty]
    public SimulationValue<long> Tick;

    public Clock()
    {
        Tick = new SimulationValue<long>(0, nameof(Tick), this, SimulationValueAttributes.Output);
    }

    public Clock(SimulationEnvironment environment, SolverBase solver) : base(environment, solver)
    {
        Tick = new SimulationValue<long>(0, nameof(Tick), this, SimulationValueAttributes.Output);
    }

    // DiscreteCalculation is called by the discrete solver whenever an event
    // for this component is processed
    public override void DiscreteCalculation()
    {
        if (Solver == null) return;

        // Publish the current simulation time to all connected components
        Tick.SetValue(Solver.SimulationTime, SimulationEventType.DiscreteCalculation);

        // Schedule the next tick 10 time units later
        Solver.AsDiscreteSolver?.AddEventAtTime(this, Solver.SimulationTime + 10);
    }
}

/// <summary>
/// A simulation component that prints every value it receives on its input.
/// </summary>
public class Printer : SimulationBase
{
    [JsonProperty]
    public SimulationValue<long> TickIn;

    public Printer()
    {
        TickIn = new SimulationValue<long>(0, nameof(TickIn), this, SimulationValueAttributes.Input);
    }

    public Printer(SimulationEnvironment environment, SolverBase solver) : base(environment, solver)
    {
        TickIn = new SimulationValue<long>(0, nameof(TickIn), this, SimulationValueAttributes.Input);
    }

    public override void DiscreteCalculation()
    {
        Console.WriteLine($"t={Solver?.SimulationTime,3}  printer received tick {TickIn.Value}");
    }
}

public static class DiscreteExample
{
    public static void Run()
    {
        SimulationEnvironment environment = new SimulationEnvironment();
        DiscreteSolver solver = new DiscreteSolver(environment);

        Clock clock = new Clock(environment, solver);
        Printer printer = new Printer(environment, solver);

        // Connect the clock output to the printer input.
        // Whenever Tick changes, the new value is copied to TickIn and an event
        // for the printer is added automatically at the current simulation time.
        environment.AddConnection(clock.Tick, printer.TickIn);

        solver.Initialize();

        // Schedule the first event at the current simulation time (0).
        // All further events are scheduled by the clock itself.
        solver.AddEvent(clock);

        solver.CalculateTo(50);
    }
}

Call it from your program entry point:

Easy2SimExamples.DiscreteExample.Run();

Output:

t=  0  printer received tick 0
t= 10  printer received tick 10
t= 20  printer received tick 20
t= 30  printer received tick 30
t= 40  printer received tick 40
t= 50  printer received tick 50

Ways to add events

  1. DiscreteSolver.AddEvent(SimulationBase simulationBase)

    Adds an event for the component at the current simulation time.

  2. DiscreteSolver.AddEventAtTime(SimulationBase simulationBase, long simulationTime)

    Adds an event for the component at a specific simulation time.

  3. Connection changed

    If two components are connected and the source value changes, an event for the connected target component is automatically added at the current simulation time. This is how the Printer in the example above is triggered — no events are scheduled for it manually.

  4. DiscreteSolver.AddEventForAllComponents() / AddEventForAllComponentsAtTime(long time)

    Adds an event for every component in the environment.