Sequence diagrams
In this section some key scenarios will be described on a code perspective to help understanding the
call stacks. The illustrations are provided through UML sequence diagrams.
System initialization
When instantiating a System
through code such as:
The following call stack is induced:
sequenceDiagram
participant main as main: System
participant inwards as inwards: ExtensiblePort
participant outwards as outwards: ExtensiblePort
participant child as child: System
participant in_ as in_: Port
participant out as out: Port
main->>+main: init()
main->>inwards: <<create>> _add_port()
main->>outwards: <<create>> _add_port()
main->>main: _initialize()
main->>+main: setup()
main->>child: <<create>> add_child()
main->>in_: <<create>> add_input()
main->>out: <<create>> add_output()
deactivate main
main->>main: update()
main->>+main: enforce_scope()
main->>inwards: set scope_clearance
main->>in_: set scope_clearance
deactivate main
deactivate main
The __init__
method has the following actions:
Create class attributes needed by CoSApp (i.e. not the user
variables)
Creates the ports for the inward and the outwards through _add_port
.
_initialize
method is called. By default this does nothing - its purpose is to provide an
hook method that allow child class to create inward and outward before the user setup is
called.
User defined setup
is called. By default this does nothing. But in the diagram, the scenario
presents a setup
creating a child System
, an input Port
and and output Port
.
update
method is called. By default this does nothing. Its purpose is to build complex
objects from simple inward; for example reading a table. So this could be called manually when
updating the table filename for example.
The final step defines the scope of a variable depending on the user roles.
System simple execution
To execute once the compute
method of a System
, you will use the method run_once
:
s = MySystem('main')
s.run_once()
The call stack is as follow:
sequenceDiagram
participant main as main: System
participant child as child: System
participant connector as connector: Connector
participant connector_out as connector_out: Connector
main->>+main: run_once()
main->>main: is_master_set()
main->>+main: call_setup_run()
main->>main: setup_run()
main->>child: call_setup_run()
deactivate main
main->>main: _precompute()
main->>main: compute_before()
loop Every child
loop Every connector on child inputs
main->>connector: transfer()
end
main->>child: run_once()
end
loop Every connector pulling value from children
main->>connector_out: transfer()
end
main->>main: compute()
main->>main: _postcompute()
main->>+main: call_clean_run()
main->>main: clean_run()
main->>child: call_clean_run()
deactivate main
deactivate main
The calling system test if it is the master one (i.e. is the one from which the execution call
was emitted).
Through call_setup_run
method, setup_run
method of the system and all its children will
be called recursively. By default setup_run
does nothing. It is a hook to carry out operations
needed only once per run. The method will only be executed by the master system.
Next comes the precompute
method.
Then comes the user hook compute_before
. This is the part of the compute that needs to be
evaluated before the children.
Input ports of the child are initialized by pushing the value through the connectors.
from their connector. If there is no connector, the value stays unchanged.
Method run_once
of all children is called
The output ports of main
are then updated. I.e. output ports connected to children output ports are
transferred to the parent.
User defined compute
is then called.
postcompute
updates clean/dirty status and evaluates the system residues.
call_clean_run
method is ending this scenario. This is undo setup_run
actions following the user defined clean_run
method. The method will only be executed by the master system.
Driver execution
To execute a driver on a System
, you will use the method run_drivers
after adding some
drivers to the system.
s = MySystem('main')
s.add_driver(MyDriver('runner'))
s.run_drivers()
The generic call stack is presented below. To simplify it the call to call_setup_run
and
call_clean_run
have not been detailed.
sequenceDiagram
participant main as main: System
participant runner as topRunner: Driver
main->>+main: run_drivers()
main->>main: is_master_set()
main->>main: open_loops()
main->>+main: call_setup_run()
main->>runner: call_setup_run()
deactivate main
main->>runner: run_once()
main->>+main: call_clean_run()
main->>runner: call_clean_run()
deactivate main
main->>main: close_loops()
deactivate main
There are two major differences with run_once
:
If the system is the master (i.e. the one calling the execution), open_loops
method will
be executed to open connection loops in the system. This will be undone at the end through
close_loops
.
The compute sequence is not called. Instead the run_once
method of the drivers are called.
One call not shown above, is that the call_setup_run
will trigger the setup_run
method of
the drivers to be called (similarly to the system children).
NonLinearSolver
Logic
As the major driver used in CoSApp is the NonLinearSolver
, the sequence diagram in that case is
described below for a system having one child and with only one RunSingleCase
subdriver:
sequenceDiagram
autonumber
participant main as main: System
participant child as child: System
participant solver as solver: NonLinearSolver
participant point as point: RunSingleCase
participant subproblem as subproblem: MathematicalProblem
participant fullproblem as fullproblem: MathematicalProblem
main->>+main: run_drivers()
main->>main: is_master_set()
main->>main: open_loops()
main->>+main: call_setup_run()
main->>+solver: call_setup_run()
solver->>+solver: setup_run()
solver->>+point: call_setup_run()
point->>+point: setup_run()
point->>subproblem: <<create>>
point->>+main: assembled_problem()
main->>+child: assembled_problem()
child->>-main: child problem
main->>-point: full unsolved problem
point->>subproblem: set unknowns
point->>subproblem: set equations
deactivate point
deactivate point
deactivate solver
main->>main: setup_run()
deactivate main
main->>+solver: run_once()
solver->>+solver: _precompute()
solver->>fullproblem: <<create>>
loop RunSingleCase
solver->>+point: get_problem()
point->>-solver: single case problem
solver->>+point: get_init()
point->>-solver: single case initial values
end
deactivate solver
solver->>+solver: compute_before()
loop RunSingleCase
solver->>point: set_iteratives()
end
deactivate solver
loop Children drivers
solver->>+point: run_once()
point->>point: _precompute()
Note right of point: Set boundaries and unknowns
point->>+point: compute()
point->>main: run_children_drivers()
Note right of main: Initializing run
deactivate point
point->>point: _postcompute()
deactivate point
end
solver->>+solver: compute()
solver->>fullproblem: validate()
solver->>+solver: resolution_method()
loop Iterations
solver->>+solver: fresidues()
solver->>point: set_iteratives()
solver->>+point: run_once()
point->>point: _precompute()
point->>+point: compute()
point->>main: run_children_drivers()
deactivate point
point->>point: _postcompute()
deactivate point
deactivate solver
end
deactivate solver
deactivate solver
deactivate solver
main->>+main: call_clean_run()
main->>solver: call_clean_run()
deactivate main
main->>main: close_loops()
deactivate main
The key elements are:
At call_setup_run
execution, each RunSingleCase
will create its own mathematical
problem (through calls 9 to 15). This is done by gathering the intrinsic mathematical problem of
the system obtained with assembled_problem
. Then user defined unknowns and
equations will be added to build the mathematical problem on a single point.
During the precompute
execution of NonLinearSolver
, the full mathematical problem will
be built requesting the problem from each RunSingleCase
(call 20 get_problem
).
With the call 26 run_once
, a simple run for initialization is called. This sets the
boundaries and the initial values.
Everything is now in place to solve the non-linear problem using one of the algorithms
available. This is initiated by call 33 resolution_method
. They all share a common interface
requesting a function returning the vector residues. That method called fresidues
is defined in
AbstractSolver
. It handle the dispatch of the unknowns values to the multiple RunSingleCase
as well as the execution of the multiple points to gather all residues required.
Transient Simulation
Here is the sequence diagram for a transient simulation with explicit time integration.
sequenceDiagram
autonumber
participant main as main: System
participant child as child: System
participant solver as solver: RungeKutta
participant manager as TimeVarManager
participant clock as UniversalClock
main->>+main: run_drivers()
main->>main: is_master_set()
main->>main: open_loops()
main->>+main: call_setup_run()
main->>+solver: call_setup_run()
solver->>+solver: setup_run()
deactivate solver
main->>main: setup_run()
main->>manager: <<create>>
manager->>+manager: update_transients()
manager->>+main: assembled_problem()
main->>+child: assembled_problem()
child-->>-main: Child problem
main-->>-manager: Full problem (including transients and rates)
Note right of manager: Construct TransientUnknownStack
manager->>clock: reset(start_time)
deactivate manager
deactivate main
main->>+solver: run_once()
solver->>solver: _precompute()
Note right of solver: Solve children drivers
solver->>+solver: compute()
solver->>+solver: _initialize()
solver->>+solver: _set_time(start_time)
solver->>clock: time = t
solver->>solver: Update boundary conditions
solver->>solver: _update_children()
Note right of solver: Solve children drivers
solver->>solver: _update_rates()
deactivate solver
solver->>solver: Update initial conditions
deactivate solver
loop on Time
solver->>+solver: _set_time(time)
solver->>clock: time = t
solver->>solver: Update boundary conditions
solver->>solver: _update_children()
Note right of solver: Solve children drivers
solver->>solver: _update_rates()
deactivate solver
solver->>solver: Get new time step
solver->>solver: _update_transients(dt)
Note right of solver: Time integration
end
deactivate solver
deactivate solver
deactivate solver
main->>+main: call_clean_run()
main->>solver: call_clean_run()
deactivate main
main->>main: close_loops()
deactivate main