Note
Go to the end to download the full example code.
Introduction to Condor¶
We wanted to have an API that looks as much like a mathematical description as possible with as little distraction from programming cruft as possible. For example, an arbitrary system of equations like from Sellar [sellar],
should be writable as
y1 == x[0] ** 2 + x[1] + x[2] - 0.2 * y2
y2 == y1**0.5 + x[0] + x[1]
Of course, in both the mathematical and programmatic description, the source of each
symbol must be defined. In an engineering memo, we might say “where
import condor
class Coupling(condor.AlgebraicSystem):
x = parameter(shape=3)
y1 = variable(initializer=1.)
y2 = variable(initializer=1.)
residual(y1 == x[0] ** 2 + x[1] + x[2] - 0.2 * y2)
residual(y2 == y1**0.5 + x[0] + x[1])
which can be evaluated by instantiating the model with numerical values for the parameters:
coupling = Coupling([5., 2., 1])
Once the model is finished running, the model binds the numerical results from the iterative solver to the named element and field attributes on the instance. That is, elements of fields accessible directly:
print(coupling.y1, coupling.y2)
[25.58830237] [12.05848815]
Fields are bound as dataclasses
print(coupling.variable)
CouplingVariable(y1=array([25.58830237]), y2=array([12.05848815]))
Models can be used recursively, building up more sophisticated models by embedding models within another. However, system encapsulation is enforced so only elements from input and output fields are accessible after the model has been defined. For example, we may wish to optimize Sellar’s algebraic system of equations. Mathematically, we can define the optimization as
where
from condor.backend import operators as ops
class Sellar(condor.OptimizationProblem):
x = variable(shape=3, lower_bound=0, upper_bound=10)
coupling = Coupling(x)
y1, y2 = coupling
objective = x[2]**2 + x[1] + y1 + ops.exp(-y2)
constraint(y1 >= 3.16)
constraint(24. >= y2)
As with the system of algebraic equations, we can numerically solve this optimization problem by providing an initial value for the variables and instantiating the model.
Sellar.set_initial(x=[5,2,1])
sellar = Sellar()
This is Ipopt version 3.14.11, running with linear solver MUMPS 5.4.1.
Number of nonzeros in equality constraint Jacobian...: 0
Number of nonzeros in inequality constraint Jacobian.: 6
Number of nonzeros in Lagrangian Hessian.............: 6
Total number of variables............................: 3
variables with only lower bounds: 0
variables with lower and upper bounds: 3
variables with only upper bounds: 0
Total number of equality constraints.................: 0
Total number of inequality constraints...............: 2
inequality constraints with only lower bounds: 1
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 2.8588308e+01 0.00e+00 9.61e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 2.5032242e+01 0.00e+00 9.60e+00 -1.0 1.90e+03 - 4.74e-03 1.12e-03f 1
2 1.6557725e+01 0.00e+00 4.45e+00 -1.0 3.81e+01 - 1.00e+00 5.42e-01f 1
3 1.5031940e+01 0.00e+00 3.92e+00 -1.0 6.87e+00 - 1.00e+00 1.15e-01f 1
4 4.5491414e+00 0.00e+00 6.34e-01 -1.0 6.04e+00 - 1.00e+00 1.00e+00f 1
5 3.4782092e+00 0.00e+00 5.17e-02 -1.7 9.99e-01 - 1.00e+00 8.87e-01f 1
6 3.2369916e+00 0.00e+00 2.15e-03 -1.7 2.29e-01 - 1.00e+00 1.00e+00f 1
7 3.1867988e+00 0.00e+00 3.56e-05 -3.8 5.09e-02 - 1.00e+00 1.00e+00h 1
8 3.1845411e+00 0.00e+00 8.40e-07 -3.8 2.55e-02 - 1.00e+00 1.00e+00h 1
9 3.1836276e+00 0.00e+00 3.86e-07 -5.7 1.34e-02 - 1.00e+00 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
10 3.1834622e+00 0.00e+00 8.19e-08 -5.7 6.64e-03 - 1.00e+00 1.00e+00h 1
11 3.1834161e+00 0.00e+00 1.91e-08 -5.7 3.20e-03 - 1.00e+00 1.00e+00h 1
12 3.1834029e+00 0.00e+00 3.60e-09 -5.7 1.39e-03 - 1.00e+00 1.00e+00h 1
13 3.1833947e+00 0.00e+00 7.48e-10 -8.6 6.21e-04 - 1.00e+00 1.00e+00h 1
14 3.1833939e+00 0.00e+00 2.48e-11 -8.6 1.15e-04 - 1.00e+00 1.00e+00h 1
15 3.1833939e+00 0.00e+00 3.38e-14 -8.6 4.25e-06 - 1.00e+00 1.00e+00h 1
Number of Iterations....: 15
(scaled) (unscaled)
Objective...............: 3.1833939181332549e+00 3.1833939181332549e+00
Dual infeasibility......: 3.3805821765621465e-14 3.3805821765621465e-14
Constraint violation....: 0.0000000000000000e+00 0.0000000000000000e+00
Variable bound violation: 7.4470968355807926e-09 7.4470968355807926e-09
Complementarity.........: 2.5421361853922688e-09 2.5421361853922688e-09
Overall NLP error.......: 2.5421361853922688e-09 2.5421361853922688e-09
Number of objective function evaluations = 16
Number of objective gradient evaluations = 16
Number of equality constraint evaluations = 0
Number of inequality constraint evaluations = 16
Number of equality constraint Jacobian evaluations = 0
Number of inequality constraint Jacobian evaluations = 16
Number of Lagrangian Hessian evaluations = 15
Total seconds in IPOPT = 0.062
EXIT: Optimal Solution Found.
The resulting object will have a dot-able data structure with the bound results,
including the embedded Coupling
model:
print("objective value:", sellar.objective) # scalar value
print(sellar.constraint) # field
print(sellar.coupling.y1) # embedded-model element
objective value: 3.183393918133255
SellarConstraint(Sellar_constraint_0=array([3.16]), Sellar_constraint_1=array([3.75527764]))
[3.15999998]
References
Sellar, R., Batill, S., and Renaud, J., “Response Surface Based, Concurrent Subspace Optimization for Multidisciplinary System Design,” 1996. https://doi.org/10.2514/6.1996-714
Total running time of the script: (0 minutes 0.154 seconds)