OpenMDAO Example#
This example demonstrates how to use UQPCE with OpenMDAO for robust design optimization under uncertainty. It is based on the OpenMDAO paraboloid tutorial, extended with uncertain variables.
Problem Description#
We consider a paraboloid function with two uncertain parameters (a, b) and two design variables (x, y):
The uncertain parameters are described by normal distributions:
uncerta: \(\mathcal{N}(\mu=-3,\; \sigma=0.5)\)uncertb: \(\mathcal{N}(\mu=-4,\; \sigma=1)\)
The goal is to find values of the design variables desx and desy that minimize the upper confidence interval of f, accounting for uncertainty in a and b.
Defining the Paraboloid Component#
First, we define an OpenMDAO ExplicitComponent for the paraboloid. The uncertain inputs uncerta and uncertb are vectorized (length n) to evaluate the function at all UQPCE sample points simultaneously. The design variables desx and desy remain scalar.
import openmdao.api as om
import numpy as np
class Paraboloid(om.ExplicitComponent):
"""
Evaluates the equation f(a,b,x,y) = (a*x-3)**2 + a*b*x*y + (b*y+4)**2 - 3
"""
def initialize(self):
self.options.declare('vec_size', types=int)
def setup(self):
n = self.options['vec_size']
# Uncertain variables (vectorized for UQPCE sample points)
self.add_input('uncerta', shape=(n,))
self.add_input('uncertb', shape=(n,))
# Design variables (scalar)
self.add_input('desx')
self.add_input('desy')
# Output (vectorized)
self.add_output('f_abxy', shape=(n,))
def setup_partials(self):
n = self.options['vec_size']
incr = np.linspace(0, n - 1, n)
self.declare_partials(of='f_abxy', wrt=['desx', 'desy'])
self.declare_partials(
of='f_abxy', wrt=['uncerta', 'uncertb'], rows=incr, cols=incr
)
def compute_partials(self, inputs, partials, discrete_inputs=None):
a = inputs['uncerta']
b = inputs['uncertb']
x = inputs['desx']
y = inputs['desy']
partials['f_abxy', 'desx'] = 2 * a**2 * x + a * (-6 + b * y)
partials['f_abxy', 'desy'] = 8 * b + a * b * x + 2 * b**2 * y
partials['f_abxy', 'uncerta'] = -6 * x + 2 * a * x**2 + b * x * y
partials['f_abxy', 'uncertb'] = 8 * y + a * x * y + 2 * b * y**2
def compute(self, inputs, outputs):
a = inputs['uncerta']
b = inputs['uncertb']
x = inputs['desx']
y = inputs['desy']
outputs['f_abxy'] = (a * x - 3)**2 + a * b * x * y + (b * y + 4)**2 - 3
Initializing UQPCE#
Next, we set up UQPCE using the programmatic PCE API. We define the uncertain variables, generate sample points, and build the polynomial chaos basis matrices that UQPCEGroup requires.
%%capture --no-stdout
from uqpce import PCE, UQPCEGroup, interface
np.random.seed(0)
samp_count = 100
aleat_cnt = 500000
epist_cnt = 1
pce = PCE(
order=2, verbose=True, outputs=False, plot=False,
aleat_samp_size=aleat_cnt, epist_samp_size=epist_cnt
)
# Define the uncertain variables with their distributions
pce.add_variable(distribution='normal', mean=-3, stdev=0.5, name='uncerta')
pce.add_variable(distribution='normal', mean=-4, stdev=1, name='uncertb')
# Generate sample points for the uncertain variables
Xt = pce.sample(count=samp_count)
# Build the PCE basis matrices
pce.set_samples(Xt)
pce.build_basis(order=2)
pce.resample_surrogate()
[runnervm8k3b6:04033] mca_base_component_repository_open: unable to open mca_btl_openib: librdmacm.so.1: cannot open shared object file: No such file or directory (ignored)
Adding variable number 1
Adding variable number 2
Constructing surrogate model
Building norm-squared matrix
Assembling psi matrix
17% Complete
33% Complete
50% Complete
67% Complete
83% Complete
100% Complete
Psi matrix assembled
Evaluating psi matrix
Generating aleatory resampling values
Building the OpenMDAO Problem#
We construct the OpenMDAO problem with two subsystems:
Paraboloid — evaluates \(f(a, b, x, y)\) at all sample points, producing a vector of responses.
UQPCEGroup — takes the response vector and computes uncertainty statistics: mean, variance, and confidence intervals.
The optimizer minimizes ci_upper (the upper confidence interval of \(f\)) by varying the design variables desx and desy.
prob = om.Problem()
# Add the Paraboloid component
prob.model.add_subsystem(
'parab',
Paraboloid(vec_size=samp_count),
promotes_inputs=['uncerta', 'uncertb', 'desx', 'desy'],
promotes_outputs=['f_abxy']
)
# Add the UQPCEGroup for uncertainty quantification
prob.model.add_subsystem(
'uq',
UQPCEGroup(
significance=pce.significance, var_basis=pce.var_basis,
norm_sq=pce.norm_sq, resampled_var_basis=pce.resampled_var_basis,
tail='upper', epistemic_cnt=epist_cnt, aleatory_cnt=aleat_cnt
),
promotes_inputs=[('responses', 'f_abxy')],
promotes_outputs=['ci_upper', 'mean', 'variance']
)
/usr/share/miniconda/envs/test/lib/python3.11/site-packages/openmdao/core/constants.py:16: OMDeprecationWarning:The INF_BOUND sentinel in OpenMDAO is deprecated. Infinite bounds should now be specified using None or +/-np.inf.
'uq' <class UQPCEGroup>
Setting Up the Optimizer#
We use the ScipyOptimizeDriver with the SLSQP algorithm. The design variables desx and desy are each bounded between -10 and 10, and the objective is to minimize the upper confidence interval of \(f\).
# Configure the optimizer
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'
prob.driver.options['tol'] = 1e-9
prob.driver.options['disp'] = True
# Add design variables with bounds
prob.model.add_design_var('desx', lower=-10, upper=10)
prob.model.add_design_var('desy', lower=-10, upper=10)
# Minimize the upper confidence interval
prob.model.add_objective('ci_upper')
prob.setup()
<openmdao.core.problem.Problem at 0x7faac7be0390>
Setting Initial Values#
The uncertain variable values are set using interface.set_vals(), which populates each uncertain variable with the corresponding column from the sample matrix. The design variables are set to initial guesses.
# Set the uncertain variable values from the sample matrix
interface.set_vals(prob, pce.variables, Xt)
# Set initial values for the design variables
prob.set_val('desx', 7)
prob.set_val('desy', -9)
Running the Optimization#
With the problem fully configured, we run the optimizer. The SLSQP driver will find the values of desx and desy that minimize the upper confidence interval of \(f(a, b, x, y)\).
prob.run_driver()
Optimization terminated successfully (Exit mode 0)
Current function value: -11.059430691664577
Iterations: 37
Function evaluations: 105
Gradient evaluations: 37
Optimization Complete
-----------------------------------
Problem: problem
Driver: ScipyOptimizeDriver
success : True
iterations : 106
runtime : 2.4433E+01 s
model_evals : 107
model_time : 1.7376E+01 s
deriv_evals : 37
deriv_time : 6.9787E+00 s
exit_status : SUCCESS
Examining Results#
After optimization, we can inspect the optimal design variable values and the resulting uncertainty statistics.
print('Design Variable desx is ', prob.get_val('desx'))
print('Design Variable desy is ', prob.get_val('desy'))
print('Mean of f_abxy is ', prob.get_val('mean'))
print('Variance of f_abxy is ', prob.get_val('variance'))
print('Upper Confidence Interval of f_abxy is ', prob.get_val('ci_upper'))
Design Variable desx is [-1.64640661]
Design Variable desy is [1.49576904]
Mean of f_abxy is [-21.9435751]
Variance of f_abxy is [17.56599871]
Upper Confidence Interval of f_abxy is [-11.05943069]
Note
This example mirrors uqpce/examples/paraboloid/paraboloid_example.py in the UQPCE repository. That script uses the file-based workflow with interface.initialize() to read a YAML input file and run matrix, while this notebook uses the programmatic PCE API to define variables and generate samples inline. Both approaches produce equivalent results.
File-Based Alternative#
The paraboloid_example.py script in the repository demonstrates the file-based workflow, which is useful for larger projects where the input configuration is managed separately:
Create a YAML input file (
input_paraboloid.yaml) describing the uncertain variable distributions and UQPCE settings.Generate the run matrix at the command line:
python -m uqpce -i input_paraboloid.yaml --generate-samples
Call
interface.initialize(input_file, matrix_file)to load the configuration and return all arrays needed byUQPCEGroup.Call
interface.set_vals(prob, variables, run_matrix)to populate the uncertain variables on the OpenMDAO problem.
The YAML input file for this example looks like:
Variable 0:
name: uncerta
distribution: normal
mean: -3
stdev: 0.5
type: aleatory
Variable 1:
name: uncertb
distribution: normal
mean: -4
stdev: 1
type: aleatory
Settings:
order: 2
backend: Agg
track_convergence_off: False
aleat_samp_size: 100000
See the Inputs page for full reference on the YAML input format.