Skip to content

Commit

Permalink
Updates to the framework and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkinast committed Feb 9, 2024
1 parent aa72d8e commit 6834aaf
Show file tree
Hide file tree
Showing 13 changed files with 698 additions and 13 deletions.
4 changes: 4 additions & 0 deletions Easy2Sim/Connect/AttributeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Easy2Sim.Connect
/// </summary>
public abstract class AttributeBase : Attribute, IFrameworkBase
{

/// <summary>
/// Unique Guid that can be used to uniquely identify class instances
/// </summary>
Expand Down Expand Up @@ -47,5 +48,8 @@ public string SerializeToJson()
{
return JsonConvert.SerializeObject(this);
}



}
}
8 changes: 6 additions & 2 deletions Easy2Sim/Connect/Attributes/Output.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
namespace Easy2Sim.Connect.Attributes
using System.Reflection;

namespace Easy2Sim.Connect.Attributes
{
/// <summary>
/// Attribute which is used to define that a variable is used as output variable in the simulation
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class Output : AttributeBase;
public class Output : AttributeBase
{
}
}
4 changes: 3 additions & 1 deletion Easy2Sim/Connect/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class Connection : IFrameworkBase
[JsonProperty]
internal object? CurrentValue { get; set; }


#region Source information
/// <summary>
/// String representation of the source type.
Expand Down Expand Up @@ -215,7 +216,8 @@ public bool HasChanged
return false;
if (SourceValue != null && CurrentValue == null)
return true;
return SourceValue != null && SourceValue.Equals(CurrentValue);
bool equals = SourceValue.Equals(CurrentValue);
return SourceValue != null && !equals;
}
}

Expand Down
15 changes: 15 additions & 0 deletions Easy2Sim/Environment/SimulationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace Easy2Sim.Environment
{



/// <summary>
/// Base class for simulation components.
/// </summary>
Expand Down Expand Up @@ -38,6 +41,16 @@ public abstract class SimulationBase : IFrameworkBase
[JsonProperty]
public int Index => _simulationIndex;

/// <summary>
/// Set the simulation index of a component manually.
/// Make sure that all indexes are correct!
/// </summary>
/// <param name="index"></param>
public void SetIndexManually(int index)
{
_simulationIndex = index;
}

[JsonIgnore]
private SimulationEnvironment? _simulationEnvironment;

Expand Down Expand Up @@ -131,6 +144,7 @@ protected SimulationBase(Guid environmentGuid)
}



/// <summary>
/// Initialize is called once before the simulation starts.
/// Typical cpu expensive actions are done here: e.g. file access to initialize a component
Expand All @@ -156,5 +170,6 @@ public virtual void DynamicCalculation() { }
/// Serialize to json uses the default constructor.
/// </summary>
public abstract string SerializeToJson();

}
}
74 changes: 66 additions & 8 deletions Easy2Sim/Solvers/Discrete/DiscreteSolver.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Easy2Sim.Connect;
using System.Runtime.CompilerServices;
using Easy2Sim.Connect;
using Easy2Sim.Environment;
using Newtonsoft.Json;

Expand All @@ -15,6 +16,7 @@ public class DiscreteSolver : SolverBase, IDisposable
/// </summary>
[JsonProperty] private DiscreteSolverModel _discreteSolverModel;

[JsonIgnore] public DiscreteSolverModel DiscreteSolverModel => _discreteSolverModel;
/// <summary>
/// Represents all data that is necessary to run one event based simulation.
/// </summary>
Expand Down Expand Up @@ -59,6 +61,8 @@ public override void CalculateFinish()

try
{
AddInitialEvents();

// Stopping condition:
// A component sets the simulation to finished or
// no events left
Expand Down Expand Up @@ -96,17 +100,23 @@ public override void CalculateTo(long maxTime)

try
{

AddInitialEvents();
// Stopping condition:
// A component sets the simulation to finished or
// no events left or
// the simulation time is larger than the given max time
while (!BaseModel.IsFinished && _discreteSolverModel.EventList.Any() && BaseModel.SimulationTime <= maxTime)
while (!BaseModel.IsFinished && _discreteSolverModel.EventList.Any())
{
DiscreteEvent? discreteEvent = GetNextEvent();
if (discreteEvent == null)
break;

if (discreteEvent.TimeStamp > maxTime)
{
BaseModel.IsFinished = true;
break;
}

if (discreteEvent.TimeStamp > BaseModel.SimulationTime)
BaseModel.SimulationTime = discreteEvent.TimeStamp;

Expand All @@ -129,6 +139,27 @@ public override void CalculateTo(long maxTime)
}
}

/// <summary>
/// Each components discrete event should be added at least once at time step 0
/// </summary>
private void AddInitialEvents()
{
//Add events for each component
if (BaseModel.SimulationTime == 0)
{
List<SimulationBase> components = SimulationEnvironment.Model.SimulationObjects.Values.ToList();

List<DiscreteEvent> initialEvents = _discreteSolverModel.EventList.Where(x => x.TimeStamp == 0).ToList();

List<SimulationBase> componentsWithoutEvents = components.Where(x => !initialEvents.Select(y => y.ComponentName).Contains(x.Name)).ToList();

foreach (SimulationBase simulationBase in componentsWithoutEvents)
{
AddEvent(simulationBase);
}
}
}

/// <summary>
/// Initialize can be called before the simulation starts.
/// Typically computational expensive operations are done in the Initialize method.
Expand Down Expand Up @@ -278,11 +309,6 @@ private void AddEventToTime(SimulationBase simulationBase, long simulationTime)
_discreteSolverModel.ComponentsAtSimulationTime[simulationTime].Add(simulationBase.Name);
_discreteSolverModel.EventList.Add(new DiscreteEvent(simulationTime, simulationBase.Name));
}
//Allow one component multiple times at a time step
else
{
_discreteSolverModel.EventList.Add(new DiscreteEvent(simulationTime, simulationBase.Name));
}
}
}

Expand All @@ -301,5 +327,37 @@ public void Dispose()
{
ComponentRegister.RemoveComponent(Guid);
}

public void AddEventForConnection(string connectionName, SimulationBase simBase, long time = -1)
{
List<Connection> outConnections = new List<Connection>();
List<Connection> inConnections = new List<Connection>();
foreach (Connection connection in SimulationEnvironment.Model.Connections)
{
if (connection.SourceName == connectionName && connection.Source == simBase)
outConnections.Add(connection);

else if (connection.TargetName == connectionName && connection.Target == simBase)
inConnections.Add(connection);
}

if (time == -1)
{
foreach (var outConnection in outConnections)
AddEvent(outConnection.Target);

foreach (var inConnection in inConnections)
AddEvent(inConnection.Source);
}
else
{
foreach (var outConnection in outConnections)
AddEventAtTime(outConnection.Target, time);

foreach (var inConnection in inConnections)
AddEventAtTime(inConnection.Source, time);
}
}

}
}
110 changes: 110 additions & 0 deletions Easy2SimDocs/docs/Basics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Basic concepts in the Framework

To create a new simulation component, the base class SimulationBase should be used.
Once instantiated, the Environment will automatically set a simulation index based on the order of instantiation.
This index defines the execution order of the components in one time step.
If this should be changed the method SimulationBase\SetIndexManually(int index) can be used.
Typically this simulation index starts at 0 and is increased by one per instantiated component.
When setting it manually, even negative values can be used.

=== "Index example - Main "

``` { .csharp .annotate .select }
SimulationEnvironment environment = new SimulationEnvironment();
DiscreteSolver solver = new DiscreteSolver(environment);

Sine sine1 = new(environment, solver); // (1)
Sine sine2 = new(environment, solver); // (2)

sine1.SetIndexManually(3) // (3)
```
{ .annotate }

1. sine1 has the simulation index 0
2. sine2 has the simulation index 1
2. sine1 has the simulation index 3

In a dynamic calculation, each components "DynamicCalculation" method is executed once per time step.
When using a discrete solver, each component is added to time step 0.
Further events have to be added. This are the possible ways to add events:

1. **DiscreteSolver/AddEvent(SimulationBase simulationBase)**

If a component should add a event in the same simulation time the solvers AddEvent can be used.

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

Similar to the first variant, however, a time can be specified

3. **Connection changed**

If two components are connected and the value changes, an event is automatically added for the connected component.
To recognize a change the C# Equals method is used on the objects.


=== "Simple simulation component - Sine"

``` { .csharp .annotate .select }
using Easy2Sim.Connect.Attributes;
using Easy2Sim.Environment;
using Easy2Sim.Solvers;
using Newtonsoft.Json;

namespace StandardLibrary.Mathematical.Source
{
public class Sine : SimulationBase // (1)
{
[Output] // (2)
[JsonProperty] // (3)
public double Output;
[JsonProperty]
public double Amplitude;
[JsonProperty]
public double Frequency;
[JsonProperty]
public double Offset;
[JsonProperty]
public int NumberOfSamples;
public Sine() // (4)
{
Amplitude = 1.0;
Frequency = 10.0;
Offset = 0;
Output = 0.0;
NumberOfSamples = 100;
}
public Sine(SimulationEnvironment environment, SolverBase solverBase) : base(environment, solverBase) // (5)
{
Amplitude = 1.0;
Frequency = 10.0;
Offset = 0;
Output = 0.0;
NumberOfSamples = 100;
}
public override void DynamicCalculation() // (6)
{
if (Solver == null) return;

double timeInSeconds = (double)Solver.BaseModel.SimulationTime / NumberOfSamples;
double angle = 2 * Math.PI * Frequency * timeInSeconds + Offset;
double sineValue = Amplitude * Math.Sin(angle);

Output = sineValue;
}
public override string SerializeToJson() // (7)
{
return JsonConvert.SerializeObject(this);
}
}
}
```
{ .annotate }

1. Each simulation component needs to implement SimulationBase
2. Output defines, that this Property can be connected to a Input of another component
3. To allow serialization and deserialization, each component needs to be serializable
4. An empty constructor is needed for the serialization framework
5. Use this constructor when you create components, it will register the components in the framework
6. DynamicCalculation is called in every simulated time step in the Dynamic solver
7. The SerializeToJson method needs to be overwritten in order to allow the framework to serialize this component

1 change: 1 addition & 0 deletions Easy2SimDocs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ site_url: https://example.com/
nav:
- Home: README.md
- UseCase: UseCase.md
- Basics: Basics.md
theme:
name: material
custom_css:
Expand Down
20 changes: 20 additions & 0 deletions Easy2SimDocs/site/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,26 @@









<li class="md-nav__item">
<a href="/Basics/" class="md-nav__link">


<span class="md-ellipsis">
Basics
</span>


</a>
</li>



</ul>
</nav>
</div>
Expand Down
Loading

0 comments on commit 6834aaf

Please sign in to comment.