Skip to main content

Language Concepts


This section covers the basic concepts of CSDL that you will need to understand in order to write effective CSDL code. CSDL code is entirely written inside of user defined classes that inherit from Model, CustomExplicitOperation, and CustomImplicitOperation. The Model class is the main container for all CSDL code, while CustomExplicitOperation, and CustomImplicitOperation offer more advanced control over program execution and the ability to integrate external analysis tools into your design optimization workflow. Later, this tutorial will refer back to and build upon the concepts presented in this section.

Model#

A CSDL model is a mathematical specification of a physical system. The majority of user code is located in the definition of a user defined class that inherits from the Model class. Users can supply a mathematical specification using two styles of model composition corresponding to two different programming language paradigms. Users can use a functional programming style, which resembles mathematical notation to define a model as a composition of functions, or an object oriented programming style to define a model as a hierarchical composition of models. Users are also free to define a model specification using a mix of the functional and object oriented paradigms.

Model specifications are defined within the user defined Model.define method:

from csdl import Model
class Example(Model):    def define(self):        # mathematical specification goes here

The functional programming style can be seen in Getting Started, and use of all functions in the Standard Library is considered part of the functional programming style of model composition.

The object oriented programming style is covered in Object Oriented Programming, and use of all functions in the Standard Library is considered part of the functional programming style of model composition.

important

The Model class and user defined subclasses do not provide an implementation of a simulation of the behavior of a physical system, only a representation of the physical system. To generate an implementation, an object of the Simulator class must be constructed from a Model object. The csdl package supplies the Model class, while a separate package chosen by the user supplies the Simulator class.

Variable#

The concept of a variable in CSDL differs from the concept of a variable in Python. In Python, variables store run time values, e.g. x=3, or a=np.eye(3). In CSDL, a variable stores the history of previous operations and variables used to define that variable. The histories form a structure called a Directed Acyclic Graph (DAG). The nodes of the DAG are variables and operations. This DAG structure forms and intermediate representation (IR) of the program. Storing an IR of the program comes with many benfits. The CSDL IR can store properties of variables and operations that the compiler can exploit, either before generating the final executable, or during code generation. For example, the compiler back end is free to implement derivative computation for each operation, requiring no additional input from the user. The DAG structure also encodes a partial ordering of the operations, which enables computing the execution order of the operations within the final simulation via a topological sort.

important

Because CSDL variables do not store run time values, printing a CSDL variable will not show its run time value. In order to print a run time value for a variable, use the Model.print_var method.

CSDL variables can be used the same way Python variables are used. For example,

x = self.declare_variable('x')m = self.declare_variable('m')b = self.declare_variable('b')y = m*x + bz = csdl.sin(x)
important

CSDL expressions can also mix Python int, float, and numpy.ndarray types with CSDL variables. All Python types that are not CSDL variables are computed at compile time and may not be part of the final executable object, possibly resulting in more efficient code.

The next section, Variable Types, will cover Model.declare_variable and Model.register_output into more detail.

important

Unlike NumPy arrays, CSDL variables do not support broadcasting. In order to perform elementwise multiplication, for example, a scalar variable (array with shape (1,)) by a variable that represents a vector, tensor, or matrix, users must use the csdl.reshape or csdl.expand functions to make the shapes of the operands compatible.

As mentioned in Model, CSDL users can use a functional or object oriented style to define a model specification. Defining variables in terms of other variables, or as functions of variables, is how users define a model specification using the functional programming style. The simplest user defined models will use only the functional programming style, as there is only one Model in the model hierarchy. The functional programming style shows how data flows throughout the simulation, which is consistent with the DAG structure of the IR

Part of the functional style to keep in mind is that CSDL variables are immutable; once a variable is defined, there is no way to express a change (or mutation) in that variable's value at run time. The only case where the definition of a variable's computation can be modified after the variable has been created is with variables created using Model.create_output, which is exclusively used for defining a concatenation of variable values.

important

Python does not enforce 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. See Best Practices.

Operation#

The concept of an operation in CSDL also serves to construct the IR. Users do not interact with operations except when defining Custom Operations, which is covered in the Advanced section of this tutorial, but it is still useful at this stage to understand how the IR is constructed when Python executes Model.define. As mentioned in Variable, the IR has the structure of a DAG, where nodes represent either variables or operations. Each operation node has at least one predecessor node of variable type, and at least one successor node of variable type. Each variable node has at most one predecessor node of operation type, and zero or more successor nodes of operation type. This leads to the DAG having a bipartite structure.

simple DAG

Operations can store properties, such as whether or not they are linear operations, elementwise operations, posynomials, etc. These properties are used for performing implementation-independent optimizations/graph transformations on the IR.

The standard library contains functions that add StandardOperation objects to the IR. The CSDL compiler back end uses the StandardOperation classes to generate the executable code for each StandardOperation object. Operation properties and derivatives are hidden from the user so that code optimization and derivative implementation are automated, ensuring that user code is clear and easily maintainable without sacrificing performance of generated code.

Simulator#

The previous subsections describe (at a high level) how users define a model specification using the CSDL language. The csdl package provides classes that represent all of the concepts presented up to this point. The model specification is contained within a single top level, or "main" model, represented by a user defined subclass of Model.

The Model class, however, contains only a specification, not an implementation. That is, there is no way of simulating the model, given only a Model definition. In order to simulate the model, CSDL requires the Simulator class, which the csdl package does not provide.

When a user defines an objective (and usually constraints) within a Model, the Simulator class makes the objective, constriants, and their derivatives available. These values can be used to solve a nonlinear program (NLP).

note

The user does not need to provide derivatives for any operation except for the subclasses of the CustomOperation class.

The Simulator class provides a Python interface to the generated executable object.

Parameters#

The CSDL Model class has an attribute called parameters, which gives users some power over a Model subclass definition without defining a new Model subclass. Parameters are compile time constants that can be set by the user to change a model definition per instance of a model. For example, if a model requires time integration, the number of time steps and the step size can be set without creating a new model definition. Parameters are also useful for setting the resolution of a mesh grid, or the number of instances of another model to add as children in the model hierarchy (e.g. the number of satellites in a swarm). Parameters make model definitions more generic, and thus reusable, reducing overhead for users of your library.

Parameters can be of any Python type, and a Model definition can restrict parameters to a set of types. Parameters were introduced briefly in Getting Started, and are covered in more detail in Advanced.

note

Parameters in CSDL can best be compared to value/non-type template parameters in C++ and const generics in Rust.