Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 9 additions & 92 deletions Sources/PoieticFlows/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,9 @@

import PoieticCore

/// Error thrown by the compiler during compilation.
/// Systems required to be run for creating a simulation plan.
///
/// The only relevant case is ``hasIssues``, any other case means a programming error.
///
/// After catching the ``hasIssues`` error, the caller might get the issues from
/// the compiler and propagate them to the user.
///
public enum CompilerError: Error {
case issues([ObjectID:[Issue]])

/// Error caused by some internal functioning. This error typically means something was not
/// correctly validated either within the library or by an application. The internal error
/// is not caused by the user.
case internalError(InternalCompilerError)

}

/// Error caused by some compiler internals, not by the user.
///
/// This error should not be displayed to the user fully, only as a debug information or as an
/// information provided to the developers by the user.
///
public enum InternalCompilerError: Error, Equatable {
/// Error thrown during compilation that should be captured by the compiler.
///
/// Used to indicate that the compilation might continue to collect more errors, but must
/// result in an error at the end.
///
/// This error should never escape the compiler.
///
case objectIssue

/// Attribute is missing or attribute type is mismatched. This error means
/// that the frame is not valid according to the ``FlowsMetamodel``.
case attributeExpectationFailure(ObjectID, String)

/// Formula compilation failed in an unexpected way.
case formulaCompilationFailure(ObjectID)

// Invalid Frame Error - validation on the caller side failed
case structureTypeMismatch(ObjectID)
case objectNotFound(ObjectID)
}

nonisolated(unsafe) public let ModelInspectionSystemGroup: [System.Type] = [
nonisolated(unsafe) public let SimulationPlanningSystems: [System.Type] = [
ExpressionParserSystem.self,
ParameterResolutionSystem.self,
ComputationOrderSystem.self,
Expand All @@ -62,56 +20,15 @@ nonisolated(unsafe) public let ModelInspectionSystemGroup: [System.Type] = [
SimulationPlanningSystem.self,
]

nonisolated(unsafe) public let SimulationPlanningSystemGroup: [System.Type] =
ModelInspectionSystemGroup + [
SimulationPlanningSystem.self,
nonisolated(unsafe) public let SimulationRunningSystems: [System.Type] = [
StockFlowSimulationSystem.self,
]

nonisolated(unsafe) public let SimulationPresentationSystemGroup: [System.Type] =
SimulationPlanningSystemGroup + [
ChartResolutionSystem.self,
]

/// Legacy wrapper to provide same API. DO NOT USE!
///
/// An object that compiles the model into an internal representation called Compiled Model.
///
/// The design represents an idea or a creation of a user in a form that
/// is closest to the user. To perform a simulation we need a different form
/// that can be interpreted by a machine.
///
/// The purpose of the compiler is to validate the design and
/// translate it into an internal representation.
/// Systems used to present the simulation results.
///
/// - SeeAlso: ``compile()``, ``SimulationPlan``
/// The systems in this collection are expected to be run after ``SimulationPlanningSystems``.
///
@available(*, deprecated, message: "Moving towards Systems")
public class Compiler {
public let frame: AugmentedFrame

@available(*, deprecated, message: "Moving towards Systems")
public init(frame: DesignFrame) {
self.frame = AugmentedFrame(frame)
}

@available(*, deprecated, message: "Moving towards Systems")
public func compile() throws (CompilerError) -> SimulationPlan {
let systems = SystemGroup(SimulationPlanningSystemGroup)

do {
try systems.update(frame)
}
catch {
fatalError("Execution failed: \(error)")
}

if frame.hasIssues {
throw .issues(frame.issues)
}
guard let plan: SimulationPlan = frame.component(for: .Frame) else {
fatalError("Plan was not created")
}
return plan
}
}
nonisolated(unsafe) public let SimulationPresentationSystems: [System.Type] = [
ChartResolutionSystem.self,
]

17 changes: 17 additions & 0 deletions Sources/PoieticFlows/Components/Chart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ public struct ChartComponent: Component {
public let series: [ObjectSnapshot]
}

extension ChartComponent: InspectableComponent {
public static let attributeKeys: [String] = ["name"]
public func attribute(forKey key: String) -> Variant? {
switch key {
case "name": name.map { Variant($0) }
default: nil
}
}

public static let toManyDesignReferenceKeys: [String] = ["series"]
public func designReferences(forKey key: String) -> [DesignEntityID] {
switch key {
case "series": series.map {$0.objectID}
default: []
}
}
}
8 changes: 4 additions & 4 deletions Sources/PoieticFlows/Components/SimulationComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import PoieticCore
///
/// The computational dependency is determined by flow and parameters edges in the design graph.
///
/// - **Produced by:** ``SimulationOrderDependencySystem``
/// - **Produced by:** ``ComputationOrderSystem``
///
public struct SimulationOrderComponent: Component {
internal init(objects: [ObjectSnapshot] = [], stocks: [ObjectID] = [], flows: [ObjectID] = []) {
Expand Down Expand Up @@ -44,7 +44,7 @@ public struct SimulationOrderComponent: Component {
/// can incorporate variety of object types into the simulation. However, computational perspective
/// we recognise only three roles of nodes: stocks, flows and auxiliaries.
///
/// - **Produced by:** ``SimulationOrderDependencySystem``
/// - **Produced by:** ``ComputationOrderSystem``
///
public struct SimulationRoleComponent: Component {
public var role: SimulationObject.Role
Expand All @@ -69,10 +69,10 @@ public struct SimulationObjectNameComponent: Component {
/// - SeeAlso: ``StockDependencySystem``, ``FlowCollectorSystem``.
///
public struct StockComponent: Component {
/// List of ``ObjectType/FlowRate`` nodes that fill the stock.
/// List of ``/PoieticCore/ObjectType/FlowRate`` nodes that fill the stock.
public let inflowRates: [ObjectID]

/// List of ``ObjectType/FlowRate`` nodes that drain the stock.
/// List of ``/PoieticCore/ObjectType/FlowRate`` nodes that drain the stock.
public let outflowRates: [ObjectID]

/// List of stocks that are drained.
Expand Down
123 changes: 36 additions & 87 deletions Sources/PoieticFlows/Components/SimulationPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,25 @@

import PoieticCore

/// Core structure describing the simulation.
/// Structure according to which a simulation is performed.
///
/// Simulation plan describes how the simulation is computed, how does the simulation state look
/// like, what is the order in which the objects are being computed.
/// The design describes the model from user's perspective. The content and data structures needed
/// for the modelling process – for the editing – are different than the data used by the machine to
/// perform the simulation. Simulation plan is contains validated and derived information from the
/// design.
///
/// The main content of the simulation plan is a list of computed objects in order of computational
/// dependency ``simulationObjects`` and a list of simulation state variables ``stateVariables``.
/// The simulation plan is created by the ``SimulationPlanningSystems`` and typically used by the
/// ``StockFlowSimulationSystem``. It can be also used to explain the simulation process (loosely
/// analogous to a SQL explain plan).
///
/// ## Uses by Applications
/// The primary content of the simulation plan is:
///
/// Applications running simulations can use the simulation plan to fetch various
/// information that is to be presented to the user or that can be expected
/// from the user as an input or as a configuration. For example:
/// - List of simulation objects ``SimulationObject`` in order of their computational dependency: ``simulationObjects``.
/// - Structure of the simulation state, list of state variables ``StateVariable``: ``stateVariables``.
/// - List of stocks with inflows and outflows resolved (``BoundStock``).
/// - List of flows with resolved stocks that the flow drains and fills (``BoundFlow``).
///
/// - ``charts`` to get a list of charts that are specified in the design
/// that the designer considers relevant to be displayed to the user.
/// - ``valueBindings`` to get a list of controls and their targets to generate
/// user interface for changing initial values of model-specific objects.
/// - ``stateVariables`` and their stored property ``StateVariable/name`` to
/// get a list of variables that can be observed.
/// - ``variable(named:)`` to fetch detailed information about a specific
/// variable.
/// - ``builtins`` to get indices of builtin variables like time.
/// - ``simulationDefaults`` for simulation run configuration.
///
/// - Note: The simulation plan is loosely analogous to a SQL execution plan.
///
/// - SeeAlso: ``Compiler/compile()``, ``StockFlowSimulation``,
/// - SeeAlso: ``StockFlowSimulationSystem``, ``SimulationPlanningSystems``.
///
public struct SimulationPlan {
internal init(simulationObjects: [SimulationObject] = [],
Expand All @@ -44,15 +35,15 @@ public struct SimulationPlan {
flows: [BoundFlow] = [],
// charts: [Chart] = [],
valueBindings: [CompiledControlBinding] = [],
simulationParameters: SimulationParameters? = nil) {
simulationParameters: SimulationSettings? = nil) {
self.simulationObjects = simulationObjects
self.stateVariables = stateVariables
self.builtins = builtins
self.stocks = stocks
self.flows = flows
// self.charts = charts
self.valueBindings = valueBindings
self.simulationParameters = simulationParameters
self.simulationSettings = simulationParameters
}

/// List of objects that are considered in the computation computed, ordered by computational
Expand All @@ -66,28 +57,24 @@ public struct SimulationPlan {
/// Computing objects in this order assures that we have all the parameters computed when
/// they are needed.
///
/// - SeeAlso: ``variableIndex(of:)``
/// The order is computed by the ``ComputationOrderSystem`` and then filled with details in the
/// ``SimulationPlanningSystem``.
///
/// - SeeAlso: ``variableIndex(_:)``
///
public let simulationObjects: [SimulationObject]

/// List of simulation state variables.
///
/// The list of state variables contain values of builtins, values of
/// nodes and values of internal states.
/// The list of state variables contain values of simulation objects (usually nodes) their
/// internal states (for example previous values for delay) and built-ins.
///
/// Each node is typically assigned one state variable which represents
/// the node's value at given state. Some nodes might contain internal
/// state that might be present in multiple state variables.
/// Simulation object's state might be contained in multiple state variables. For example, delay
/// uses two state variables: list of double values for the queue and an initial value.
///
/// The internal state is typically not user-presentable and is a state
/// associated with stateful functions or other computation objects.
/// The internal state is typically not to be presented to the user.
///
/// A state of a variable is computed in the simulator with
/// ``StockFlowSimulation/update(_:)``.
///
/// - SeeAlso: ``StockFlowSimulation/initialize(_:)``,
/// ``StockFlowSimulation/update(objectAt:in:)``,
/// ``Compiler/stateVariables``
/// - SeeAlso: ``SimulationPlanningSystem``.
///
public let stateVariables: [StateVariable]

Expand All @@ -96,45 +83,30 @@ public struct SimulationPlan {
/// The compiled builtin variable references a state variable that holds
/// the value for the builtin variable and a kind of the builtin variable.
///
/// - SeeAlso: ``stateVariables``, ``CompiledBuiltin``, ``/PoieticCore/Variable``,
/// ``FlowsMetamodel``
///
public let builtins: BoundBuiltins


/// Stocks ordered by the computation (parameter) dependency.
///
/// This list contains all stocks used in the simulation and adds
/// derived information to each stock such as its inflows and outflows.
/// Stocks with resolved inflows and outflows, ordered by the computation dependency.
///
/// This property is used in computation.
///
/// See ``SimulatedStock`` for more information.
///
/// - SeeAlso: ``StockFlowSimulation/computeStockDelta(_:in:)``,
/// ``StockFlowSimulation/stockDifference(state:time:)``
/// - SeeAlso: ``BoundStock``, ``StockFlowSimulationSystem``.
///
public let stocks: [BoundStock]

public let flows: [BoundFlow]

/// List of charts.
/// Flows with resolved stocks the flow drains and fills.
///
/// This property is not used during computation, it is provided for
/// consumers of the simulation state or simulation result.
/// - SeeAlso: ``BoundFlow``, ``StockFlowSimulationSystem``.
///
// public let charts: [Chart]

public let flows: [BoundFlow]

/// Compiled bindings of controls to their value objects.
///
public let valueBindings: [CompiledControlBinding]

/// Collection of default values for running a simulation.
/// Time range, time delta and other settings to control the simulation.
///
/// See ``SimulationParameters`` for more information.
/// See ``SimulationSettings`` for more information.
///
public var simulationParameters: SimulationParameters?
public let simulationSettings: SimulationSettings?

/// Get index into a list of computed variables for an object with given ID.
///
Expand All @@ -144,7 +116,7 @@ public struct SimulationPlan {
/// - Complexity: O(n)
/// - SeeAlso: ``stateVariables``, ``simulationObject(_:)``
///
public func variableIndex(of id: ObjectID) -> SimulationState.Index? {
public func variableIndex(_ id: ObjectID) -> SimulationState.Index? {
// Since this is just for debug purposes, O(n) should be fine, no need
// for added complexity of the code.
guard let first = simulationObjects.first(where: {$0.objectID == id}) else {
Expand All @@ -158,10 +130,8 @@ public struct SimulationPlan {
/// This function is not used during computation, it is provided for
/// consumers of the simulation state or simulation result.
///
/// The objects are computed with ``StockFlowSimulation/update(objectAt:in:)``.
///
/// - Complexity: O(n)
/// - SeeAlso: ``simulationObjects``, ``variableIndex(of:)``
/// - SeeAlso: ``simulationObjects``, ``variableIndex(_:)``
///
public func simulationObject(_ id: ObjectID) -> SimulationObject? {
return simulationObjects.first { $0.objectID == id }
Expand Down Expand Up @@ -191,26 +161,5 @@ public struct SimulationPlan {

return object
}

/// Index of a stock in a list of stocks or in a stock difference vector.
///
/// This function is not used during computation. It is provided for
/// potential inspection, testing and debugging.
///
/// - Precondition: The plan must contain a stock with given ID.
///
func stockIndex(_ id: ObjectID) -> NumericVector.Index {
guard let index = stocks.firstIndex(where: { $0.objectID == id }) else {
preconditionFailure("The plan does not contain stock with ID \(id)")
}
return index
}
func flowIndex(_ id: ObjectID) -> NumericVector.Index {
guard let index = flows.firstIndex(where: { $0.objectID == id }) else {
preconditionFailure("The plan does not contain flow with ID \(id)")
}
return index
}

}

Loading
Loading