Skip to main content

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 messages#

csdl 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 variable#

lift = self.declare_variable('lift')

not

l = self.declare_variable('lift')

This is not required, but it avoids confusion.

Prefer Promotion Over Connection#

CSDL 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 variables#

Python 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 list#

In 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 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'.

Always assign types to parameters#

Instead 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 defined#

Prefer

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 names#

For 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 derivatives#

Python 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.