Skip to main content

Variable Types


(Declared) Variable#

All variables are instances of the Variable base class or one of its subclasses. In order to use a variable, it must first be declared using Model.declare_variable, which returns a DeclaredVariable object.

A declared variable represents an input to the model from either a parent model or a child model. (Constructing model hierarchies is covered in detail in the section on Object Oriented Programming.) A declared variable is so called because it may or may not be defined until the model hierarchy is defined.

note

Any object of Variable class or its subclasses supports establishing a dependency on an element or a slice of the array that will be constructed at run time using similar syntax to NumPy.

a = b[:9]
important

Unlike NumPy, indexing a Variable does not automatically remove an axis with dimension 1. This can lead to problems in user code that may be resolved similar to how this is resolved.

Each variable in CSDL has a name. CSDL variable names are automatically generated unless the user provides a name by calling Model.declare_variable, Model.create_input, Model.create_output, or Model.register_output.

note

Calls to Model.declare_variable need not be at the beginning of the Model.define method. Since Model.declare_variable is used for declaring variables from parent or child submodels, it may be used after adding a submodel.

Input#

Declared variables provide a way for models in different levels and branches of the model hierarchy to specify data transfers between corresponding parts of a simulation. They do not however, provide a way to enforce that data be transfered into the simulation, from outside the simulation. The Model.create_input method provides a way to signal to CSDL that data must be provided as an input to the simulation, and not as a data transfer between different parts of a simulation. This is useful for defining optimization problems, where an optimizer updates the design variables.

note

An input to the main model in CSDL can be thought of as an entry point to the resulting simulation, like an argument to the main function in C.

CSDL inputs do not have to be created at the top level, or "main" model, however. Values for inputs can be set for a Simulator after compile time before each simulation, regardless of where they are created in the hierarchy.

important

A declared Variable is also allowed to be an input to the overall simulation, but an Input is required to be an input to the overall simulation. Remember that an Input object represents an input whose source is outside the simulation, and is thus an input to the entire simulation. In contrast, a declared Variable is a data transfer within the simulation at a point corresponding to the current model, and may be an output of a parent model, child model, or external code.

Output#

Models define variables in terms of other variables. The resulting variable is an output, which is represented by an Output object. To create an output, simply define a new variable using basic mathematical expressions or the standard library.

x = self.declare_variable('x')y = csdl.sin(x)self.register_output('y', y)

The Model.register_output method provides a way to name an output. In order to ensure that an Output generates executable code, it or one of its dependencies must be registered as an output.

Concatenation#

The Model.create_output returns a Concatenation for concatenating values. The Concatenation class inherits from Output An object of class Concatenation differs from an Output in that an Output is defined in terms of other Variable objects, and then registered, but a Concatenation object is registered first, and then defined in terms of other Variable objects. The Concatenation class also supports multidimensional indexed assignment, which behaves the same way that NumPy indexed assignment does, except that broadcasting is not supported.

p = self.create_output('p', shape=(2,))p[0] = xp[1] = y

In Getting Started, we mentioned that CSDL variables are immutable. This is still true of Concatenation because each index can have at most one assignment. There can be no overlap between indices used in assignment in different parts of the code. Some indices may be left to their default values.

important

You don't need to call Model.create_output if your Output is not a concatenation of variables. See Troubleshooting.

important

When you call Model.create_output, you don't need to call Model.register_output later. See Troubleshooting.

For NumPy Users#

It's very common to preallocate NumPy variables with np.zeros, np.eye, etc. and then assign only some values to a NumPy array. The Concatenation variable type serves this specific purpose, but it has a slightly different API to maintain immutability.

You might be used to defining an array in Python to define a coordinate rotation matrix:

R1 = np.zeros((3, 3))R1[0, 0] = 1R1[1, 1] = np.cos(theta)R1[1, 2] = np.sin(theta)R1[2, 1] = -np.sin(theta)R1[2, 2] = np.cos(theta)

In the above example, R1 is assigned values of 0 at run time, and then some of those values are mutated at run time. Python allows users to update the values of any of the elments in R1 freely. CSDL on the other hand, does not. In CSDL, the val argument in Model.create_output sets the default value, which if not overwritten at compile time, remains constant at run time. If the value is overwritten at compile time, then the default value is never set at run time.

Let's take a look at how R1 is defined in CSDL:

r1_val = np.zeros((3, 3))r1_val[0, 0] = 1R1 = self.create_output('R1', val=r1_val)R1[1, 1] = csdl.cos(theta)R1[1, 2] = csdl.sin(theta)R1[2, 1] = -csdl.sin(theta)R1[2, 2] = csdl.cos(theta)

In this example, both R1 and theta are CSDL variables, which means the history of operations, not the run time values will be stored in the objects R1 and theta. The val argument tells CSDL that, unless otherwise specified, the run time value for the array R1 is an array of zeros with shape (3,3) (a 3x3 matrix). We then tell CSDL to store a history of operations for individual indices for R1.

note

Run time values cannot be assigned directly to an index of a Concatenation. Only individual indices of the val argument value can be assigned. This is why r_val is created and then assigned a constant value, so that R1 will have a value of 1 at the index (0, 0).

This history of operations define the rotation matrix. Since indices are not allowed to overlap, CSDL is guaranteed to assign a value to each index of R1 exactly once at run time. The indices (0,1), (0,2), (1,0), and (2,0) are left without a history of operations to define their values at run time, so CSDL will assign them whatever values were given in the val argument in Model.create_output.

note

NumPy array values are still allowed to mutate within Model.define, so if you need to construct a constant at compile time, you still have that flexibility because NumPy arrays are CSDL compile time constants. You can think of compile time constants as hard coded values in the final program.

Sometimes it's desirable to assign values to a Concatenation within a function instead of defining a new Model each time. A Concatenation however, cannot be created without a call to the Model.create_output method, so a Concatenation cannot be instantiated within a standalone function.

To define a Concatenation using a function, you will need to pass a Concatenation variable that has no history of operations assigned to any of its indices. The example above may be rewritten as follows.

def rotate_x(R1, theta):    R1[1, 1] = csdl.cos(theta)    R1[1, 2] = csdl.sin(theta)    R1[2, 1] = -csdl.sin(theta)    R1[2, 2] = csdl.cos(theta)
r1_val = np.zeros((3, 3))r1_val[0, 0] = 1R1 = self.create_output('R1', val=r1_val)rotate_x(R1, theta)

Strictly speaking, rotate_x does mutate the Python object R1, but considering that the definition of a Concatenation must follow its construction, and each index of a Concatenation may be assigned a history of operations exactly once, this still follows the policy that CSDL variables must be immutable. Once rotate_x is called, the definition for R1 is complete, enabling meaningful usage of R1 in later expressions.

note

The default value must still be set during the call to Model.create_output. If some elements of a Contatenation are not set using CSDL, they will retain their default values at run time.