Note
Go to the end to download the full example code
Store observables¶
Introduction¶
In this example,
we will learn how to store the history of state variables using the
add_observable()
method.
This is useful in situations where we wish to access, post-process,
or save the values of discipline outputs that are not design variables,
constraints or objective functions.
The Sellar problem¶
We will consider in this example the Sellar problem:
where the coupling variables are
and
and where the general constraints are
Imports¶
All the imports needed for the tutorials are performed here.
from __future__ import annotations
from numpy import array
from numpy import ones
from gemseo import configure_logger
from gemseo import create_discipline
from gemseo import create_scenario
from gemseo.algos.design_space import DesignSpace
configure_logger()
<RootLogger root (INFO)>
Create the problem disciplines¶
In this section,
we use the available classes Sellar1
, Sellar2
and SellarSystem
to define the disciplines of the problem.
The create_discipline()
API function allows us to
carry out this task easily, as well as store the instances in a list
to be used later on.
disciplines = create_discipline(["Sellar1", "Sellar2", "SellarSystem"])
Create and execute the scenario¶
Create the design space¶
In this section, we define the design space which will be used for the creation of the MDOScenario.
design_space = DesignSpace()
design_space.add_variable("x_local", l_b=0.0, u_b=10.0, value=ones(1))
design_space.add_variable(
"x_shared", 2, l_b=(-10, 0.0), u_b=(10.0, 10.0), value=array([4.0, 3.0])
)
Create the scenario¶
In this section, we build the MDO scenario which links the disciplines with the formulation, the design space and the objective function.
scenario = create_scenario(disciplines, "MDF", "obj", design_space)
Add the constraints¶
Then, we have to set the design constraints
scenario.add_constraint("c_1", constraint_type="ineq")
scenario.add_constraint("c_2", constraint_type="ineq")
Add the observables¶
Only the design variables, objective function and constraints are stored by
default. In order to be able to recover the data from the state variables,
y1 and y2, we have to add them as observables. All we have to do is enter
the variable name as a string to the
add_observable()
method.
If more than one output name is provided (as a list of strings),
the observable function returns a concatenated array of the output values.
scenario.add_observable("y_1")
It is also possible to add the observable with a custom name, using the option observable_name. Let us store the variable y_2 as y2.
scenario.add_observable("y_2", observable_name="y2")
Execute the scenario¶
Then, we execute the MDO scenario with the inputs of the MDO scenario as a dictionary. In this example, the gradient-based SLSQP optimizer is selected, with 10 iterations at maximum:
scenario.execute({"max_iter": 10, "algo": "SLSQP"})
INFO - 08:59:14:
INFO - 08:59:14: *** Start MDOScenario execution ***
INFO - 08:59:14: MDOScenario
INFO - 08:59:14: Disciplines: Sellar1 Sellar2 SellarSystem
INFO - 08:59:14: MDO formulation: MDF
INFO - 08:59:14: Optimization problem:
INFO - 08:59:14: minimize obj(x_local, x_shared)
INFO - 08:59:14: with respect to x_local, x_shared
INFO - 08:59:14: subject to constraints:
INFO - 08:59:14: c_1(x_local, x_shared) <= 0.0
INFO - 08:59:14: c_2(x_local, x_shared) <= 0.0
INFO - 08:59:14: over the design space:
INFO - 08:59:14: +-------------+-------------+-------+-------------+-------+
INFO - 08:59:14: | Name | Lower bound | Value | Upper bound | Type |
INFO - 08:59:14: +-------------+-------------+-------+-------------+-------+
INFO - 08:59:14: | x_local | 0 | 1 | 10 | float |
INFO - 08:59:14: | x_shared[0] | -10 | 4 | 10 | float |
INFO - 08:59:14: | x_shared[1] | 0 | 3 | 10 | float |
INFO - 08:59:14: +-------------+-------------+-------+-------------+-------+
INFO - 08:59:14: Solving optimization problem with algorithm SLSQP:
INFO - 08:59:14: 10%|█ | 1/10 [00:00<00:00, 37.68 it/sec, obj=21.8+0j]
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.3.1/lib/python3.9/site-packages/scipy/optimize/_slsqp_py.py:431: ComplexWarning: Casting complex values to real discards the imaginary part
slsqp(m, meq, x, xl, xu, fx, c, g, a, acc, majiter, mode, w, jw,
INFO - 08:59:14: 20%|██ | 2/10 [00:00<00:00, 25.90 it/sec, obj=5.39+0j]
INFO - 08:59:14: 30%|███ | 3/10 [00:00<00:00, 24.27 it/sec, obj=3.41+0j]
INFO - 08:59:14: 40%|████ | 4/10 [00:00<00:00, 23.35 it/sec, obj=3.19+0j]
INFO - 08:59:14: 50%|█████ | 5/10 [00:00<00:00, 22.97 it/sec, obj=3.18+0j]
INFO - 08:59:14: 60%|██████ | 6/10 [00:00<00:00, 22.54 it/sec, obj=3.18+0j]
INFO - 08:59:14: 70%|███████ | 7/10 [00:00<00:00, 22.37 it/sec, obj=3.18+0j]
INFO - 08:59:14: 80%|████████ | 8/10 [00:00<00:00, 24.77 it/sec, obj=3.18+0j]
INFO - 08:59:14: Optimization result:
INFO - 08:59:14: Optimizer info:
INFO - 08:59:14: Status: None
INFO - 08:59:14: Message: Successive iterates of the objective function are closer than ftol_rel or ftol_abs. GEMSEO Stopped the driver
INFO - 08:59:14: Number of calls to the objective function by the optimizer: 9
INFO - 08:59:14: Solution:
INFO - 08:59:14: The solution is feasible.
INFO - 08:59:14: Objective: (3.183393951638041+0j)
INFO - 08:59:14: Standardized constraints:
INFO - 08:59:14: c_1 = (3.419042826635632e-12+0j)
INFO - 08:59:14: c_2 = (-20.24472223307516+0j)
INFO - 08:59:14: Design space:
INFO - 08:59:14: +-------------+-------------+-----------------------+-------------+-------+
INFO - 08:59:14: | Name | Lower bound | Value | Upper bound | Type |
INFO - 08:59:14: +-------------+-------------+-----------------------+-------------+-------+
INFO - 08:59:14: | x_local | 0 | 0 | 10 | float |
INFO - 08:59:14: | x_shared[0] | -10 | 1.977638883461871 | 10 | float |
INFO - 08:59:14: | x_shared[1] | 0 | 8.135637827350935e-13 | 10 | float |
INFO - 08:59:14: +-------------+-------------+-----------------------+-------------+-------+
INFO - 08:59:14: *** End MDOScenario execution (time: 0:00:00.340609) ***
{'max_iter': 10, 'algo': 'SLSQP'}
Access the observable variables¶
Retrieve observables from a dataset¶
In order to create a dataset, we use the
corresponding OptimizationProblem
:
opt_problem = scenario.formulation.opt_problem
We can easily build an OptimizationDataset
from this OptimizationProblem
:
either by separating the design parameters from the functions
(default option):
dataset = opt_problem.to_dataset("sellar_problem")
dataset
or by considering all features as default parameters:
dataset = opt_problem.to_dataset("sellar_problem", categorize=False)
dataset
or by using an input-output naming rather than an optimization naming:
dataset = opt_problem.to_dataset("sellar_problem", opt_naming=False)
dataset
Access observables by name¶
We can get the observable data by variable names:
dataset.get_view(variable_names=["y_1", "y2"])
Use the observables in a post-processing method¶
Finally, we can generate plots with the observable variables. Have a look at the Basic History plot and the Scatter Plot Matrix:
scenario.post_process(
"BasicHistory",
variable_names=["obj", "y_1", "y2"],
save=False,
show=True,
)
scenario.post_process(
"ScatterPlotMatrix",
variable_names=["obj", "c_1", "c_2", "y2", "y_1"],
save=False,
show=True,
)
<gemseo.post.scatter_mat.ScatterPlotMatrix object at 0x7f8aeb77bd60>
Total running time of the script: (0 minutes 1.842 seconds)