Quickstart Guide
In this guide we’ll go through a typical simulation with which STREAM may be of use.
Creating Calculations
Consider the physical system you would like to simulate, and look at the Calculations already implemented
- you may find just the components needed. If not, please refer to Calculation to see how one goes about
creating a custom component.
If all you want is a simple component, consider using Calculation_factory.
Composition
Assuming you have all of your calculations instantiated, the next step is combining these interdependent
calculations, usually with the end result being an CalculationGraph. Aptly named, the
Composition subpackage offers functionality for composing standard calculations.
External Input variables
Any variables required by the calculations which are not provided by some other calculation must be provided by the
user in the form of funcs. These inputs may be time-dependent functions or any other
value.
Next, an Aggregator instance may be created:
cg = CalculationGraph(...)
agr = cg.to_aggregator()
Initial Guess / Initial Conditions
In order to run the simulation, either finding a steady state solution or a transient based on given, one must provide
an initial guess / conditions. State is useful in that regard, and especially the following may be of help:
Merge states together. |
|
Create a state, or a partial state for given calculations and given target variables, which are assigned a uniform value. |
|
Given either a vector solution of the system or a Solution object, creates a human-readable state description of the solution. |
|
Given a description of the system state either at one time or at many, returns a vector or a solution that fits this information. |
|
Helper functions for creating steady state initial guesses for small and specific subsystems |
Note that a State is basically a dict[Calculation, dict[Any, Value]]
Introspection of the Created System / State
Hooray! You now have a system of equations in the shape of an Aggregator
and your State.
Some tools may be useful to see what you actually have, mostly in the analysis module.
Print a report with useful information regarding an Aggregator |
|
|
Draw an Aggregator's graph. |
Return the application of the Aggregator's functional on a guess, tagged. |
Steady State Solution
Given the initial conditions, one may find a root of the functional \(\vec{F}(\vec{y}, t)\):
steady_state = agr.solve_steady(initial_conditions)
A graph-aware Jacobian may be used as jac=
ALG_jacobian.
Applying agr.save(steady_state) would yield a State, with added information computed post-mortum
(this can be avoided by setting strict=True).
Adding computed thermo-hydraulic critical phenomena thresholds may be done using threshold_analysis.
Transient Simulation
Running a transient solution is done similarly, with the addition of a time parameter.
Here is an example with some important parameters, assuming (as typicali, and can be controlled using eq_type) that the Aggregator forms a DAE:
time = np.linspace(0, 100)
solution = agr.solve(
initial_state,
time,
rtol=1e-6,
atol=absolute_tolerance_vector,
jacfn=DAE_jacobian(agr),
continuous=True,
max_step_size=1e-1,
constraints_type=contraints_vector,
)
Here the graph-aware Jacobian is DAE_jacobian,
the absolute tolerance may be set for each equation separately (using State and loading is a good way to do that),
while the relative tolerance is global. The constraints vector controls the sign of the variables (See SUNDIALS IDA),
and can limit the search space somewhat. It follows the following mapping:
dict(NONE=0.0, NON_NEG=1.0, POS=2.0, NON_POS=-1.0, NEG=-2.0)
For most use cases, the defaults are really good enough, but know that these options can be controlled.
Uncertainty Quantification
A common use case for a system code like STREAM is when one needs a relatively fast and simple
model that can be used for parametric studies, as well as uncertainty quantification (UQ) problems.
In STREAM, the UQ module is used for UQ problems, and it provides two
similar but distinct classes: The UQModel class and the
DASKUQModel.
Both classes are based on a Jacobian approach to UQ, where numeric partial derivatives of a model
are computed to get a linear approximation to the uncertainty of each model feature to its inputs.
Both models base their uncertainty propagation on the concepts of statistical and systematic
uncertainties, where statistical contributions are added in 2-norm, and systematic contributions
are added in 1-norm.
For interactive and simple use cases, we suggest you use the UQModel class.
The UQModel class is simpler, and its use requires just initialization and a call to its
uq method:
uqmod = UQModel(parameters=parameters, model=model)
systematic, statistical = uqmod.uq(**uncertainty_dictionary)
This method call will return the total statistical and systematic uncertainty as vectors for the model features.
For high throughput cases, one will most likely prefer the DASKUQModel class.
This class is designed around lazy execution, and calls to its methods most often return a DASK
Delayed object, which is a lazy object that takes a little effort to wrap one’s head around.
You will likely prefer this class when you need to run multiple models, or the same model in a
large parametric search.
Like the simple case above, most users will initialize their DASKUQModel and then call its
uq method.
Analyzing the Output
After running the simulation, one is free to manipulate the output, which is just an array (1D for a steady state
solution and 2D for a transient, wrapped with its time as Solution). In that regard, the following may be helpful for extracting data and manipulating it:
Given either a vector solution of the system or a Solution object, creates a human-readable state description of the solution. |
|
Return the Place at which a given variable lies |
|
Given a transient solution and a variable, returns the variable at the calculated times. |
|
A nested dictionary with str keys that connects calculations to a dictionary of the values of their variables. |
|
Transforms the state(s) into a DataFrame. |
When saving (agr.save) a steady state solution, you get State.
When saving a transient solution, you get StateTimeseries mapping from timepoint to State.
If you want to use a pandas.DataFrame, those can be created using to_dataframe, containing the following columns: calculation, variable, indices (i, j), and finally value.
See also
You may find the Examples a more concrete down-to-earth approach to learning STREAM.