Using CSDL Components#

CSDL Background#

CSDL is domain-specific language embedded in Python designed for solving Multidisciplinary Design Analysis and Optimization (MDAO) problems. CSDL enables complete separation between specifying a mathematical model and implementing a numerical simulation of a physical system. Separation between specification and implementation enables engineers to operate at a high level of abstraction, without the need to implement low level algorithms, including derivative computation. It is an open source project created by the Large-Scale Design Optimization (LSDO) Lab in the Department of Mechanical and Aerospace Engineering at the University of California San Diego. To learn more, visit the CSDL Website.

Using CSDL in OpenMDAO#

CSDL models can be used directly in OpenMDAO models using the csdl_om_connect package, also created by the LSDO Lab. This document explains how to do that by way of an example. In this example, we optimize the thickness (height) distribution of a cantilever beam.

The example is based on two existing scripts. The first is a pure OpenMDAO implementation of the beam optimization problem. The second is the same problem solved using only CSDL.

The example below is a hybrid where CSDL is used to define the component, but OpenMDAO defines the problem, the driver, and does the optimization.

The first step is to install the csdl_om_connect package, which will also install the CSDL package. The use of CSDL and csdl_om_connect is optional for OpenMDAO so if not already installed, the user needs to install it by issuing the following command at your operating system command prompt:

pip install git+https://github.com/lsdolab/csdl_om_connect.git@main

Using an CSDL Component in OpenMDAO is very simple. You only need to instantiate a CSDLExplicitComponent class passing it a CSDL Jax Simulator object and also lists of the input and output variables.

The key to using a CSDL Component in OpenMDAO is to make sure the input and output variable names match both when creating the CSDLExplicitComponent object and also when setting the design variables, constraints, and objectives on the OpenMDAO driver.

Here is the full script of the hybrid example.

import openmdao.api as om
import csdl_alpha as csdl
import numpy as np
from csdl_om_connect import CSDLExplicitComponent

############
# CSDL Model
############
E0, L0, b0, vol0, F0 = 1.0, 1.0, 0.1, 0.01, -1.0
n_el = 50
rec = csdl.Recorder()
rec.start()

### add design variables ###
x = csdl.Variable(name="x", shape=(n_el,), value=1.0)

### add objective ###
E, L, b, vol = E0, L0, b0, vol0
L_el = L / n_el
n_nodes = n_el + 1

# Moment of inertia
I = b * x**3 / 12

# Force vector
F = np.zeros((n_nodes * 2,))
F[-2] = F0

# Stiffness matrix
c_el = (
    E
    / L_el**3
    * np.array(
        [
            [12, 6 * L_el, -12, 6 * L_el],
            [6 * L_el, 4 * L_el**2, -6 * L_el, 2 * L_el**2],
            [-12, -6 * L_el, 12, -6 * L_el],
            [6 * L_el, 2 * L_el**2, -6 * L_el, 4 * L_el**2],
        ]
    )
)

K = csdl.Variable(name="K", value=np.zeros((n_nodes * 2, n_nodes * 2)))
for i in range(n_el):
    K = K.set(
        csdl.slice[2 * i : 2 * i + 4, 2 * i : 2 * i + 4],
        K[2 * i : 2 * i + 4, 2 * i : 2 * i + 4] + c_el * I[i],
    )
u = csdl.solve_linear(K[2:, 2:], F[2:])
c = csdl.vdot(F[2:], u)
c.add_name("compliance")

### add constraints ###
v = L_el * b * csdl.sum(x)
v.add_name("volume")

rec.stop()

cantilever_beam_sim = csdl.experimental.JaxSimulator(
    recorder=rec,
    additional_inputs=[x,],
    additional_outputs=[c, v],
)

################
# OpenMDAO Model
################

# Generate a CSDLExplicitComponent object from a CSDL Jax Simulator
# Define input and output names
# Both are defined as a list of strings of CSDL variable names.
# The names must be valid names of the CSDL variables
# exposed as `additional_inputs` and `additional_outputs` in the JaxSimulator.
in_names = ["x",]
out_names = ["compliance", "volume"]
cantilever_beam_comp = CSDLExplicitComponent(
    cantilever_beam_sim, in_names=in_names, out_names=out_names
)

# Define OpenMDAO Problem
prob = om.Problem()
prob.model.add_subsystem("cantilever_beam", cantilever_beam_comp, promotes=["*"])

# Set the design variables, objective, and constraints
# Since all the inputs and outputs were promoted, use promoted names
prob.model.add_design_var("x", lower=1e-2)
prob.model.add_objective("compliance")
prob.model.add_constraint("volume", equals=(vol0,))

prob.setup()

# setup and run the optimization
prob.driver = om.ScipyOptimizeDriver(optimizer="SLSQP", tol=1e-9, maxiter=200)
prob.run_driver()