Case Reader#
The CaseReader
object is provided to read case recordings, regardless of which case recorder was used.
Currently, OpenMDAO only implements SqliteCaseRecorder
, therefore all the examples will make use of this recorder. Other types of case recorders are expected to be supported in the future.
CaseReader Constructor#
The call signature for the CaseReader
constructor is:
- SqliteCaseReader.__init__(filename, pre_load=False, metadata_filename=None)[source]
Initialize.
Determining What Sources and Variables Were Recorded#
The CaseReader
object provides methods to determine which objects in the original problem were sources
for for the recorded cases and what variables they recorded. Sources can include the problem, driver, components and solvers.
The list_sources
method provides a list of the names of objects that are the sources of recorded data
in the file.
- BaseCaseReader.list_sources(out_stream=DEFAULT_OUT_STREAM)[source]
List of all the different recording sources for which there is recorded data.
- Parameters:
- out_streamfile-like object
Where to send human readable output. Default is sys.stdout. Set to None to suppress.
- Returns:
- list
- One or more of: problem, driver, <system hierarchy location>,
<solver hierarchy location>
The complementary list_source_vars
method will provide a list of the input and output variables recorded
for a given source.
- BaseCaseReader.list_source_vars(source, out_stream=DEFAULT_OUT_STREAM)[source]
List of all inputs and outputs recorded by the specified source.
- Parameters:
- source{‘problem’, ‘driver’, <system hierarchy location>, <solver hierarchy location>}
Identifies the source for which to return information.
- out_streamfile-like object
Where to send human readable output. Default is sys.stdout. Set to None to suppress.
- Returns:
- dict
{‘inputs’:[key list], ‘outputs’:[key list], ‘residuals’:[key list]}. No recurse.
Here is an example of their usage:
SellarMDA
class definition
class SellarMDA(om.Group):
"""
Group containing the Sellar MDA.
"""
def setup(self):
cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])
cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z', 'y2'],
promotes_outputs=['y1'])
cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z', 'y1'],
promotes_outputs=['y2'])
cycle.set_input_defaults('x', 1.0)
cycle.set_input_defaults('z', np.array([5.0, 2.0]))
# Nonlinear Block Gauss Seidel is a gradient free solver
cycle.nonlinear_solver = om.NonlinearBlockGS()
self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',
z=np.array([0.0, 0.0]), x=0.0),
promotes=['x', 'z', 'y1', 'y2', 'obj'])
self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])
self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])
import openmdao.api as om
from openmdao.test_suite.components.sellar_feature import SellarMDA
import numpy as np
# define Sellar MDA problem
prob = om.Problem(model=SellarMDA())
model = prob.model
model.add_design_var('z', lower=np.array([-10.0, 0.0]),
upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.driver = om.ScipyOptimizeDriver(optimizer='SLSQP', tol=1e-9, disp=False)
# add recorder to the driver, model and solver
recorder = om.SqliteRecorder('cases.sql')
prob.driver.add_recorder(recorder)
model.add_recorder(recorder)
model.nonlinear_solver.add_recorder(recorder)
# run the problem
prob.setup()
prob.set_solver_print(0)
prob.run_driver()
prob.cleanup()
cr = om.CaseReader(prob.get_outputs_dir() / 'cases.sql')
sources = cr.list_sources()
Sources |
---|
driver |
root.nonlinear_solver |
root |
driver_vars = cr.list_source_vars('driver')
inputs | outputs | residuals |
---|---|---|
z | ||
x | ||
con1 | ||
con2 | ||
obj |
model_vars = cr.list_source_vars('root')
inputs | outputs | residuals |
---|---|---|
con_cmp1.y1 | z | z |
con_cmp2.y2 | x | x |
cycle.d1.x | con1 | con1 |
cycle.d1.y2 | con2 | con2 |
cycle.d1.z | y1 | y1 |
cycle.d2.y1 | y2 | y2 |
cycle.d2.z | obj | obj |
obj_cmp.x | ||
obj_cmp.y1 | ||
obj_cmp.y2 | ||
obj_cmp.z |
solver_vars = cr.list_source_vars('root.nonlinear_solver')
inputs | outputs | residuals |
---|---|---|
con_cmp1.y1 | z | |
con_cmp2.y2 | x | |
cycle.d1.z | con1 | |
cycle.d1.x | con2 | |
cycle.d1.y2 | y1 | |
cycle.d2.z | y2 | |
cycle.d2.y1 | obj | |
obj_cmp.x | ||
obj_cmp.y1 | ||
obj_cmp.y2 | ||
obj_cmp.z |
Case Names#
The CaseReader
provides access to Case
objects, each of which encapsulates a data point recorded by
one of the sources.
Case
objects are uniquely identified in a case recorder file by their case names. A case name is a string.
As an example, here is a case name:
'rank0:ScipyOptimize_SLSQP|1|root._solve_nonlinear|1'
The first part of the case name indicates which rank or process that the case was recorded from. The remainder of the case name shows the hierarchical path to the object that was recorded along with the iteration counts for each object along the path. It follows a pattern of repeated pairs of
- object name ( problem, driver, system, or solver )
- iteration count
These are separated by the |
character.
So in the given example, the case is:
- from rank 0
- the first iteration of the driver, `ScipyOptimize_SLSQP`
- the first execution of the `root` system which is the top-level model
Getting Names of the Cases#
The list_cases
method returns the names of the cases in the order in which
the cases were executed. You can optionally request cases only from a specific source
.
- BaseCaseReader.list_cases(source=None, recurse=True, flat=True, out_stream=DEFAULT_OUT_STREAM)[source]
Iterate over Driver, Solver and System cases in order.
- Parameters:
- source‘problem’, ‘driver’, component pathname, solver pathname, case_name
If not None, only cases originating from the specified source or case are returned.
- recursebool, optional
If True, will enable iterating over all successors in case hierarchy.
- flatbool, optional
If False and there are child cases, then a nested ordered dictionary is returned rather than an iterator.
- out_streamfile-like object
Where to send human readable output. Default is sys.stdout. Set to None to suppress.
- Returns:
- iterator or dict
An iterator or a nested dictionary of identified cases.
There are two optional arguments to the list_cases
method that affect what is returned.
- recurse: causes the returned value to include child cases.
- flat: works in conjunction with the `recurse` argument to determine if the returned
results are in the form of a list or nested dict. If recurse=True, flat=False, and there
are child cases, then the returned value is a nested ordered dict. Otherwise, it is a list.
Accessing Cases#
Getting information from the cases is a two-step process. First, you need to get access to the Case object and then you can call a variety of methods on the Case object to get values from it. The second step is described on the Getting Data from a Case page.
There are two methods used to get a specific Case
:
- get_cases
- get_case
Accessing Cases Using get_cases Method#
The get_cases
method provides a quick and easy way to iterate over all the cases.
- BaseCaseReader.get_cases(source, recurse=True, flat=False)[source]
Iterate over the cases.
- Parameters:
- source‘problem’, ‘driver’, component pathname, solver pathname, case_name
Identifies which cases to return.
- recursebool, optional
If True, will enable iterating over all successors in case hierarchy.
- flatbool, optional
If False and there are child cases, then a nested ordered dictionary is returned rather than an iterator.
- Returns:
- list or dict
The cases identified by source.
This method is similar to the list_cases
method in that it has the two optional arguments
recurse
and flat
to control what is returned as described above.
Here is an example of its usage:
import openmdao.api as om
from openmdao.test_suite.components.sellar_feature import SellarMDA
import numpy as np
prob = om.Problem(model=SellarMDA())
model = prob.model
model.add_design_var('z', lower=np.array([-10.0, 0.0]),
upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
driver = prob.driver = om.ScipyOptimizeDriver(optimizer='SLSQP', tol=1e-5)
driver.add_recorder(om.SqliteRecorder('cases.sql'))
prob.setup()
prob.set_solver_print(0)
prob.run_driver()
prob.cleanup()
Optimization terminated successfully (Exit mode 0)
Current function value: 3.18339553462378
Iterations: 8
Function evaluations: 9
Gradient evaluations: 8
Optimization Complete
-----------------------------------
cr = om.CaseReader(prob.get_outputs_dir() / 'cases.sql')
cases = cr.get_cases()
for case in cases:
print(case.name, sorted(case.outputs))
rank0:ScipyOptimize_SLSQP|0 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|1 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|2 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|3 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|4 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|5 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|6 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|7 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|8 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|9 ['con1', 'con2', 'obj', 'x', 'z']
Accessing Cases Using get_case Method#
The get_case
method returns a Case
object given a case name.
- BaseCaseReader.get_case(case_id, recurse=True)[source]
Get case identified by case_id.
- Parameters:
- case_idstr or int
The unique identifier of the case to return or an index into all cases.
- recursebool, optional
If True, will return all successors to the case as well.
- Returns:
- dict
The case identified by case_id.
You can use the get_case
method to get a specific case from the list of case names
returned by list_cases
as shown here:
case_names = cr.list_cases()
driver |
---|
rank0:ScipyOptimize_SLSQP|0 |
rank0:ScipyOptimize_SLSQP|1 |
rank0:ScipyOptimize_SLSQP|2 |
rank0:ScipyOptimize_SLSQP|3 |
rank0:ScipyOptimize_SLSQP|4 |
rank0:ScipyOptimize_SLSQP|5 |
rank0:ScipyOptimize_SLSQP|6 |
rank0:ScipyOptimize_SLSQP|7 |
rank0:ScipyOptimize_SLSQP|8 |
rank0:ScipyOptimize_SLSQP|9 |
# access a Case by name (e.g. first case)
case = cr.get_case("rank0:ScipyOptimize_SLSQP|0")
print(case.name, sorted(case.outputs))
rank0:ScipyOptimize_SLSQP|0 ['con1', 'con2', 'obj', 'x', 'z']
# access a Case by index (e.g. first case)
case = cr.get_case(0)
print(case.name, sorted(case.outputs))
rank0:ScipyOptimize_SLSQP|0 ['con1', 'con2', 'obj', 'x', 'z']
# access a Case by index (e.g. last case)
case = cr.get_case(-1)
print(case.name, sorted(case.outputs))
rank0:ScipyOptimize_SLSQP|9 ['con1', 'con2', 'obj', 'x', 'z']
# get each case by looping over case names
for name in case_names:
case = cr.get_case(name)
print(case.name, sorted(case.outputs))
rank0:ScipyOptimize_SLSQP|0 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|1 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|2 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|3 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|4 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|5 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|6 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|7 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|8 ['con1', 'con2', 'obj', 'x', 'z']
rank0:ScipyOptimize_SLSQP|9 ['con1', 'con2', 'obj', 'x', 'z']
Processing a Nested Dictionary of Its Child Cases#
The following example demonstrates selecting a case from a case list and processing a nested dictionary of its child cases.
import openmdao.api as om
from openmdao.test_suite.components.sellar_feature import SellarMDA
import numpy as np
# define Sellar MDA problem
prob = om.Problem(model=SellarMDA())
model = prob.model
model.add_design_var('z', lower=np.array([-10.0, 0.0]),
upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.driver = om.ScipyOptimizeDriver(optimizer='SLSQP', tol=1e-5)
# add recorder to the driver, model and solver
recorder = om.SqliteRecorder('cases.sql')
prob.driver.add_recorder(recorder)
model.add_recorder(recorder)
model.nonlinear_solver.add_recorder(recorder)
# run the problem
prob.setup()
prob.set_solver_print(0)
prob.run_driver()
prob.cleanup()
Optimization terminated successfully (Exit mode 0)
Current function value: 3.18339553462378
Iterations: 8
Function evaluations: 9
Gradient evaluations: 8
Optimization Complete
-----------------------------------
cr = om.CaseReader(prob.get_outputs_dir() / 'cases.sql')
# get the last driver case
driver_cases = cr.list_cases('driver')
driver |
---|
rank0:ScipyOptimize_SLSQP|0|root._solve_nonlinear|0|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|0|root._solve_nonlinear|0 |
rank0:ScipyOptimize_SLSQP|0 |
rank0:ScipyOptimize_SLSQP|1|root._solve_nonlinear|1|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|1|root._solve_nonlinear|1 |
rank0:ScipyOptimize_SLSQP|1 |
rank0:ScipyOptimize_SLSQP|2|root._solve_nonlinear|2|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|2|root._solve_nonlinear|2 |
rank0:ScipyOptimize_SLSQP|2 |
rank0:ScipyOptimize_SLSQP|3|root._solve_nonlinear|3|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|3|root._solve_nonlinear|3 |
rank0:ScipyOptimize_SLSQP|3 |
rank0:ScipyOptimize_SLSQP|4|root._solve_nonlinear|4|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|4|root._solve_nonlinear|4 |
rank0:ScipyOptimize_SLSQP|4 |
rank0:ScipyOptimize_SLSQP|5|root._solve_nonlinear|5|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|5|root._solve_nonlinear|5 |
rank0:ScipyOptimize_SLSQP|5 |
rank0:ScipyOptimize_SLSQP|6|root._solve_nonlinear|6|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|6|root._solve_nonlinear|6 |
rank0:ScipyOptimize_SLSQP|6 |
rank0:ScipyOptimize_SLSQP|7|root._solve_nonlinear|7|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|7|root._solve_nonlinear|7 |
rank0:ScipyOptimize_SLSQP|7 |
rank0:ScipyOptimize_SLSQP|8|root._solve_nonlinear|8|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|8|root._solve_nonlinear|8 |
rank0:ScipyOptimize_SLSQP|8 |
rank0:ScipyOptimize_SLSQP|9|root._solve_nonlinear|9|NLRunOnce|0 |
rank0:ScipyOptimize_SLSQP|9|root._solve_nonlinear|9 |
rank0:ScipyOptimize_SLSQP|9 |
# get a recursive dict of child cases of the last driver case
last_driver_case = driver_cases[-1]
cases = cr.get_cases(last_driver_case, recurse=True, flat=False)
# display selected information from nested dict of cases
def print_cases(cases, indent=0):
for case, children in cases.items():
print(indent*' ', case.source, '-', case.name.split('.')[-1], sorted(case.outputs))
if children:
print_cases(children, indent+2)
print_cases(cases)
driver - rank0:ScipyOptimize_SLSQP|9 ['con1', 'con2', 'obj', 'x', 'z']
root - _solve_nonlinear|9 ['con1', 'con2', 'obj', 'x', 'y1', 'y2', 'z']
root.nonlinear_solver - _solve_nonlinear|9|NLRunOnce|0 ['con1', 'con2', 'obj', 'x', 'y1', 'y2', 'z']