Object Oriented Programming
#
The Model Hierarchy- model hierarchy figure
All CSDL models exist within a hierarchy.
A single model is the simplest hierarchy.
The model at the top of the model hierarchy is the "main" model,
representing the entire system (e.g. a vehicle) and the models further
down the hierarchy, or submodels, represent subsystems, or disciplines
(e.g. aerodynamics, structures, propulsion).
The model hierarchy has a tree structure; each model has at most one
parent (the "main" model has zero parents), and any number of children.
Assembling a model hierarchy is done using the
Model.add
method.
class Example(Model): def define(self): self.add(Aerodynamics()) self.add(Structures()) self.add(Propulsion())
Just as each variable in CSDL has a name, each model has a name.
CSDL models names are automatically generated unless the user provides a
name via the name
option in Model.add
.
class Example(Model): def define(self): self.add(Aerodynamics(), name='aero') self.add(Structures(), name='structures') self.add(Propulsion(), name='prop')
note
If users do not assign a name to a variable or model, a name will be
generated automatically and the user will not know what the CSDL name of
a particular variable is, making that variable's run time value
effectively inaccessible from outside the Simulator
object.
It is not recommended to name all variables (i.e. register all outputs),
but care must be taken to name all variables of interest.
Run time values are accessed using the namespaces assigned to the models
that contain the variables.
For example, 'A.B.C.x'
can be used to access data from variable 'x'
in model 'C'
, which is contained in model 'B'
, which is contained in
model 'A'
.
To assign a value to 'A.B.C.x'
in the Simulator
object,
sim = Simulator(UserDefinedModel())sim['A.B.C.x'] = xsim.run()
To read a value to 'A.B.C.x'
in the Simulator
object,
sim = Simulator(UserDefinedModel())print(sim['A.B.C.x'])
note
To access (read or write) a variable's run time value, use
sim['A.x']
for variable'x'
in model'A'
sim['A.B.y']
for variable'y'
in model'B'
in model'A'
Whenever possible, CSDL will promote variables.
Promoting a variable in CSDL shortens its name.
In the example with 'A.B.C.x'
, if 'x'
can be promoted to model
'A'
, it will be promoted, and it may be accessed using the key
'A.x'
.
Automatic promotion rules are described in Promotions.
Sometimes it is necessary to declare a connection between two models at different branches and/or levels in the model hierarchy.
#
Promotionscaution
Automatic promotions are currently handled by the CSDL compiler back
end, and not yet implemented in csdl
.
This caution will be removed when these features are implemented in
csdl
.
All variable names exist within a namespace corresponding to the Model
that contains them.
In most cases, the model names are not necessary to access (read or
write) a variable's value, because CSDL promotes names automatically
whenever possible.
Promotion involves moving a variable to a different namespace.
Automatically promoting all variables when possible makes accessing
variables less verbose.
If a declared Variable
and either an Input
or an Output
are
promoted to the same level, then they form a connection; i.e. the data
from the Input
/Output
will transfer to the declared Variable
.
Otherwise, the connection is left open.
Promotions are possible in the following cases:
- An
Input
can be promoted from aModel
to its parentModel
if both:- no other sibling
Model
promotes anInput
, declaredVariable
, orOutput
with the same name to the parent level. - the parent
Model
does not contain anInput
orOutput
with the same name.
- no other sibling
- An
Output
can be promoted from aModel
to its parentModel
if both:- no other sibling
Model
promotes anInput
orOutput
name to the parent level. - the parent
Model
does not contain anInput
orOutput
with the same name.
- no other sibling
- A declared
Variable
can be promoted from aModel
to its parentModel
if both:- no other sibling
Model
promotes anInput
, declaredVariable
, orOutput
with the same name and different shape to the parent level. - the parent
Model
does not contain anInput
, declaredVariable
, orOutput
with the same name and different shape.
- no other sibling
The promotions performed automatically according to these rules are illustrated below.
The cases where no promotions are allowed are illustrated below:
CSDL users can always suppress promotion by selectively promoting variables. CSDL users may only promote variables that are allowed to be promoted, however. If the user promotes variables, all variables not promoted by the user that could otherwise be promoted are not promoted. In some cases, CSDL users may suppress some promotions so that other promotions that would have otherwise not been possible are now possible.
To promote selectively, use the promotes
option:
class Example(Model): def define(self): # promote two variables self.add(Aerodynamics(), name='aero', promotes=['lift', 'drag']) # promote automatically self.add(Structures(), name='structures') # suppress all promotion self.add(Propulsion(), name='prop', promotes=[])
note
Promotions do not change the model definition or "move" variables from one model to another. They only
- Make accessing run time values less verbose
- Ensure data is transfered between models without requiring explicit connections
note
If two variables with the same name can be promoted, then they are connected; i.e. their values are equal. This occurs when when the output of one model is an input to another model, including when the output of one model is an input to many other models. CSDL automatically avoids promotion when there is a naming conflict.
#
Connectionscaution
The process of verifying that connections are valid presented in this
section are not yet implemented in csdl
and rely on the back end.
The process of verifying that connections do not introduce cycles is not
yet implemented.
For now, please use Simulator.visualize_implementation
to check that
your Model
does not contain unnecessary feedback or cycles.
This caution will be removed when these features are implemented in
csdl
.
The purpose of connections is to transfer data from one branch of the model hierarchy to another, especially between two variables with different names (i.e. when promotion would not automatically form a connection).
Connections between models are issued via the
Model.connect
method.
Connections represent equality between variables declared or defined in
different submodels, particularly submodels that are located in
different branches and/or levels of the model hierarchy.
For example, one discipline could compute the load on a wing due to
aerodynamic forces, and another discipline could use that load as an
input to compute the wing deflection.
These two disciplines may not be represented by submodels that are
childred of the same model, so any connection between them cannot be
defined in the same model where they are added.
Most of the time, connections are unnecessary.
- connections figure
The purpose of connections is to transfer data from a Model
in one
branch of the model hierarchy to another Model
in another branch of
the model heirarchy, especially between two variables with different
names (i.e. when promotion would not automatically form a connection).
An input/output can be connected to a declared variable if
- the shapes match
- a connection does not already exist (including by promotion)
The variable names (sans namespace) do not need to match.
important
You may only issue connections between variables using promoted names, and only at the lowest level possible in the model hierarchy.
Instead of
self.connect('A.B.C.x', 'A.B.y')
use
self.connect('B.C.x', 'B.y')
within the Model
named 'A'
.
This may change with future versions of CSDL, but either way, this is
good practice.
Connections are not allowed between models if the connection would form
a cycle.
This is to maintain the DAG structure of a Model
.
If your model specification includes a cycle, you will need to create a
Model
that computes the difference between the source (the first
argument in Model.connect
) and the sink (the second argument in
Model.connect
) and use that Model
to define an implicit operation.
warning
When defining relationships of the form , this type of
connection will form a cycle and the compiler will emit an error
explaining that the Model
that defines this relationship needs to be
redefined so that it has an output of the form .
If is a fixed point iteration and you are not using a Newton
type solver, take care not to reverse the order so that , or
the residual will not converge.
#
Mixing Functional and Object Oriented StylesDefining models via hierarchical composition is different from defining models via functional composition for a few reasons:
- Functional composition adds
Operation
nodes to the IR - Hierarchical composition adds
Subgraph
nodes to the IR. (Subgraph
nodes are hidden from the user, and are simply containers forModel
instances.) - Functional composition makes use of a function interface, which makes data flow apparent
- Hierarchical composition hides the functional interfaces of models.
This may look like functional composition should be the only choice for the user, but there are very good reasons for incorporating Functional and Object Oriented paradigms in CSDL.
- A hierarchical representation of a system mirrors the hierarchy of a system design.
- The Functional paradigm would require changing function interfaces each time a data transfer between variables in different branches and levels of the model hierarchy needs to be declared.
Sometimes it is necessary to connect a variable to a
parent/child model and use a functional style to define variables.
In the simplified example below, the class Child
defines a variable
'c'
in terms of 'b'
using a functional style.
When Child
is added as a submodel to Parent
, 'b'
and 'c'
are
automatically promoted.
Here we show how 'b'
depends on another variable, 'a'
, and a fourth
variable, 'd'
, depends on 'c'
, using both object oriented and
functional styles.
class Child(Model): def define(self): b = self.declare_variable('b') self.register_output('c', f(b))
class Parent(Model): def define(self): a = self.declare_variable('a')
self.register_output('b', g(a)) self.add(Child()) c = self.declare_variable('c')
self.register_output('d', h(c))
In order to connect the variable 'b'
in Parent
to Child
, 'b'
must be registered as an output (note: in Parent
, 'b'
could have
also been a Concatenation
, registered during Model.create_output
),
but because 'b'
is promoted from Child
, no connection is necessary.
In order to connect the variable 'c'
in Child
to Parent
, 'c'
needs to be declared in Parent
.
Once 'c'
is declared, it can be used in functions to define other
variables.
note
Not all variables from a submodel need to be used in the parent model, and not all variables from a parent model need to be used in the child model. Leaving variables unused leaves connections to variables in other branches and levels of the hierarchy open until the user issues a connection.
warning
The following quoted text has not yet been implemented.
You will need to use Simulator.visualize_implementation
to verify that
there are no unnecessary feedbacks or cycles in your Model
.
The quoted text will be moved to the main text and this warning will be
removed once the feature is implemented.
The order in which submodels are added does not matter, as long as they do not form a cycle. The CSDL compiler will automatically detect dependencies, verify that there are no cycles, and compute the correct execution order for each submodel.
tip
Adding submodels in the order of their execution and using whitespace as shown in the example makes your code easier to read, understand, and maintain.