Variable Types
#
(Declared) VariableAll 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.
#
InputDeclared 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.
#
OutputModels 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.
#
ConcatenationThe 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 UsersIt'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.