Best Practices
This page provides some best practices to help you avoid making common errors when using CSDL. This section assumes you have read the preceding sections in this tutorial. These best practices are not required, but highly recommended. If you run into any issues because of something you did wrong, the Troubleshooting will be more appropriate.
#
Read the error messagescsdl
has early error messages meant to prevent users from providing
ill-formed or physically meaningless model specifications.
The error messages should be helpful when debugging model
specifications.
Ideally, all errors should occur within Model
, and if Model
does
not emit an error, then the back end will not emit an error and the
compilation will be successful.
#
Use the same (compile time) name for the Python Variable object as the (run time) name for the CSDL variablelift = self.declare_variable('lift')
not
l = self.declare_variable('lift')
This is not required, but it avoids confusion.
#
Prefer Promotion Over ConnectionCSDL promotes variable names from child models by default.
In some cases, promotions are not allowed, and connections are required.
Promoting more than one variable from more than one child model is
allowed if both variables are constructed using
Model.declare_variable
.
If two variables are outputs, either constructed by
Model.create_output
, or registered using Model.register_output
, and
they have the same name, then they cannot be promoted to the same level
in the hierarchy.
Connections are meant for connecting two variables with different names, or at different levels within the model hierarchy.
self.connect('child_1.a', 'child_2.b')self.connect('child_1.c', 'child_2.grandchild.b')
#
Don't redefine variablesPython does not enforce variable immutability, so if a variable is redefined (or in Python parlance, if a variable name is bound to a new reference), then the object in memory storing the variable data is not in use in later parts of the code.
This means that your Model
will lose track of the original variable.
Either the original variable will be ignored during dead code removal,
or the original variable will not be used in other parts of the code
where it should have been, leading to unexpected/incorrect behavior in
the final simulation.
#
When creating multiple variables in a compile time loop to aggregate later, store them in a listIn Python, you might be used to mutating
for i, obj in enumerate(iterable): v[i] += obj
use
l = []for i, obj in enumerate(iterable): l.append(obj)s = csdl.sum(*l)
or better yet,
x = self.create_ouput('x', shape=(n,))# concatenate some variables in x...s = csdl.sum(*filter(lambda x: True, [x[i] for i in range(n)]))
#
Issue connections at the lowest possible level in the model hierarchyInstead of
self.connect('A.B.C.x', 'A.B.y')
use
self.connect('B.C.x', 'B.y')
within the Model
named 'A'
.
#
Always assign types to parametersInstead of
self.parameters.declare('cubesats')self.parameters.declare('groundstations')self.parameters.declare('timesteps')self.parameters.declare('stepsize')
use
self.parameters.declare('cubesats', types=dict)self.parameters.declare('groundstations', types=dict)self.parameters.declare('timesteps', types=int)self.parameters.declare('stepsize', types=float)
#
Register outputs immediately after they are definedPrefer
a = f(x)self.register('a', a)
over
a = f(x)
# ...
self.register('a', a)
This will make your code easier to read (and easier to debug). Remember, you can always use a variable after it is registered.
a = f(x)self.register('a', a)
# ...
b = g(a)
# ...
#
Do not append type information to class/object namesFor example,
class AirfoilModel(Model): # ...
The AirfoilModel
is a Model
subclass.
This information is already captured in the definition of
AirfoilModel
.
Insetead, use Airfoil
:
class Airfoil(Model): # ...
There can't be two Python objects named Airfoil
in the same scope, so
there's no risk of confusing what Airfoil
means in a given context.
#
Use Python ints, floats, and NumPy arays wherever possible, until you need derivativesPython objects used to define a model specification are compile time
constants; they are always hardcoded values in the final program (if
they are present at all).
This provides a performance boost over using CSDL variables because the
history of operations for computing compile time constants (e.g.
2*np.pi
) is not part of the intermediate representation, so no
additional code is generated in the final program.
Defining a model specification using entirely Python ints, floats, and NumPy arrays however, not only results in a simulation without access to derivatives, but this results in no program being generated from CSDL code at all.
If you are using CSDL for optimization, or would like to leave that option available for code that you initially develop only for running analyses, you will need to use CSDL variables, but wherever you don't need derivatives, compile time constants will give your final code a performance boost.