MDF-based MDO on the Sobieski SSBJ test case

from __future__ import annotations

from gemseo import configure_logger
from gemseo import create_discipline
from gemseo import create_scenario
from gemseo import generate_n2_plot
from gemseo.problems.sobieski.core.problem import SobieskiProblem

configure_logger()
<RootLogger root (INFO)>

Instantiate the disciplines

First, we instantiate the four disciplines of the use case: SobieskiPropulsion, SobieskiAerodynamics, SobieskiMission and SobieskiStructure.

disciplines = create_discipline(
    [
        "SobieskiPropulsion",
        "SobieskiAerodynamics",
        "SobieskiMission",
        "SobieskiStructure",
    ]
)

We can quickly access the most relevant information of any discipline (name, inputs, and outputs) with Python’s print() function. Moreover, we can get the default input values of a discipline with the attribute MDODiscipline.default_inputs

for discipline in disciplines:
    print(discipline)
    print(f"Default inputs: {discipline.default_inputs}")
SobieskiPropulsion
Default inputs: {'y_23': array([12562.01206488]), 'x_shared': array([5.0e-02, 4.5e+04, 1.6e+00, 5.5e+00, 5.5e+01, 1.0e+03]), 'c_3': array([4360.]), 'x_3': array([0.5])}
SobieskiAerodynamics
Default inputs: {'x_2': array([1.]), 'c_4': array([0.01375]), 'y_32': array([0.50279625]), 'x_shared': array([5.0e-02, 4.5e+04, 1.6e+00, 5.5e+00, 5.5e+01, 1.0e+03]), 'y_12': array([5.06069742e+04, 9.50000000e-01])}
SobieskiMission
Default inputs: {'y_34': array([1.10754577]), 'x_shared': array([5.0e-02, 4.5e+04, 1.6e+00, 5.5e+00, 5.5e+01, 1.0e+03]), 'y_14': array([50606.9741711 ,  7306.20262124]), 'y_24': array([4.15006276])}
SobieskiStructure
Default inputs: {'c_0': array([2000.]), 'c_1': array([25000.]), 'y_31': array([6354.32430691]), 'x_shared': array([5.0e-02, 4.5e+04, 1.6e+00, 5.5e+00, 5.5e+01, 1.0e+03]), 'c_2': array([6.]), 'y_21': array([50606.9741711]), 'x_1': array([0.25, 1.  ])}

You may also be interested in plotting the couplings of your disciplines. A quick way of getting this information is the high-level function generate_n2_plot(). A much more detailed explanation of coupling visualization is available here.

generate_n2_plot(disciplines, save=False, show=True)
plot sobieski mdf example

Build, execute and post-process the scenario

Then, we build the scenario which links the disciplines with the formulation and the optimization algorithm. Here, we use the MDF formulation. We tell the scenario to minimize -y_4 instead of minimizing y_4 (range), which is the default option.

Instantiate the scenario

During the instantiation of the scenario, we provide some options for the MDF formulations:

formulation_options = {
    "tolerance": 1e-10,
    "max_mda_iter": 50,
    "warm_start": True,
    "use_lu_fact": True,
    "linear_solver_tolerance": 1e-15,
}
  • 'warm_start: warm starts MDA,

  • 'warm_start: optimize the adjoints resolution by storing the Jacobian matrix LU factorization for the multiple RHS (objective + constraints). This saves CPU time if you can pay for the memory and have the full Jacobians available, not just matrix vector products.

  • 'linear_solver_tolerance': set the linear solver tolerance, idem we need full convergence

design_space = SobieskiProblem().design_space
print(design_space)
scenario = create_scenario(
    disciplines,
    "MDF",
    objective_name="y_4",
    design_space=design_space,
    maximize_objective=True,
    **formulation_options,
)
Design space:
+-------------+-------------+--------------------+-------------+-------+
| name        | lower_bound |       value        | upper_bound | type  |
+-------------+-------------+--------------------+-------------+-------+
| x_shared[0] |     0.01    |        0.05        |     0.09    | float |
| x_shared[1] |    30000    |       45000        |    60000    | float |
| x_shared[2] |     1.4     |        1.6         |     1.8     | float |
| x_shared[3] |     2.5     |        5.5         |     8.5     | float |
| x_shared[4] |      40     |         55         |      70     | float |
| x_shared[5] |     500     |        1000        |     1500    | float |
| x_1[0]      |     0.1     |        0.25        |     0.4     | float |
| x_1[1]      |     0.75    |         1          |     1.25    | float |
| x_2         |     0.75    |         1          |     1.25    | float |
| x_3         |     0.1     |        0.5         |      1      | float |
| y_14[0]     |    24850    |   50606.9741711    |    77100    | float |
| y_14[1]     |    -7700    |   7306.20262124    |    45000    | float |
| y_32        |    0.235    | 0.5027962499999999 |    0.795    | float |
| y_31        |     2960    |   6354.32430691    |    10185    | float |
| y_24        |     0.44    |     4.15006276     |    11.13    | float |
| y_34        |     0.44    |     1.10754577     |     1.98    | float |
| y_23        |     3365    |   12194.2671934    |    26400    | float |
| y_21        |    24850    |   50606.9741711    |    77250    | float |
| y_12[0]     |    24850    |     50606.9742     |    77250    | float |
| y_12[1]     |     0.45    |        0.95        |     1.5     | float |
+-------------+-------------+--------------------+-------------+-------+

Set the design constraints

for c_name in ["g_1", "g_2", "g_3"]:
    scenario.add_constraint(c_name, "ineq")

XDSMIZE the scenario

Generate the XDSM file on the fly:

  • log_workflow_status=True will log the status of the workflow in the console,

  • save_html (default True) will generate a self-contained HTML file, that can be automatically opened using show_html=True.

scenario.xdsmize(save_html=False)


Define the algorithm inputs

We set the maximum number of iterations, the optimizer and the optimizer options. Algorithm specific options are passed there. Use the high-level function get_algorithm_options_schema() for more information or read the documentation.

Here ftol_rel option is a stop criteria based on the relative difference in the objective between two iterates ineq_tolerance the tolerance determination of the optimum; this is specific to the GEMSEO wrapping and not in the solver.

algo_options = {
    "ftol_rel": 1e-10,
    "ineq_tolerance": 2e-3,
    "normalize_design_space": True,
}
scn_inputs = {"max_iter": 10, "algo": "SLSQP", "algo_options": algo_options}

See also

We can also generate a backup file for the optimization, as well as plots on the fly of the optimization history if option generate_opt_plot is True. This slows down a lot the process, here since SSBJ is very light

scenario.set_optimization_history_backup(file_path="mdf_backup.h5",
                                         each_new_iter=True,
                                         each_store=False, erase=True,
                                         pre_load=False,
                                         generate_opt_plot=True)

Execute the scenario

scenario.execute(scn_inputs)
    INFO - 16:26:42:
    INFO - 16:26:42: *** Start MDOScenario execution ***
    INFO - 16:26:42: MDOScenario
    INFO - 16:26:42:    Disciplines: SobieskiAerodynamics SobieskiMission SobieskiPropulsion SobieskiStructure
    INFO - 16:26:42:    MDO formulation: MDF
    INFO - 16:26:42: Optimization problem:
    INFO - 16:26:42:    minimize -y_4(x_shared, x_1, x_2, x_3)
    INFO - 16:26:42:    with respect to x_1, x_2, x_3, x_shared
    INFO - 16:26:42:    subject to constraints:
    INFO - 16:26:42:       g_1(x_shared, x_1, x_2, x_3) <= 0.0
    INFO - 16:26:42:       g_2(x_shared, x_1, x_2, x_3) <= 0.0
    INFO - 16:26:42:       g_3(x_shared, x_1, x_2, x_3) <= 0.0
    INFO - 16:26:42:    over the design space:
    INFO - 16:26:42:    +-------------+-------------+-------+-------------+-------+
    INFO - 16:26:42:    | name        | lower_bound | value | upper_bound | type  |
    INFO - 16:26:42:    +-------------+-------------+-------+-------------+-------+
    INFO - 16:26:42:    | x_shared[0] |     0.01    |  0.05 |     0.09    | float |
    INFO - 16:26:42:    | x_shared[1] |    30000    | 45000 |    60000    | float |
    INFO - 16:26:42:    | x_shared[2] |     1.4     |  1.6  |     1.8     | float |
    INFO - 16:26:42:    | x_shared[3] |     2.5     |  5.5  |     8.5     | float |
    INFO - 16:26:42:    | x_shared[4] |      40     |   55  |      70     | float |
    INFO - 16:26:42:    | x_shared[5] |     500     |  1000 |     1500    | float |
    INFO - 16:26:42:    | x_1[0]      |     0.1     |  0.25 |     0.4     | float |
    INFO - 16:26:42:    | x_1[1]      |     0.75    |   1   |     1.25    | float |
    INFO - 16:26:42:    | x_2         |     0.75    |   1   |     1.25    | float |
    INFO - 16:26:42:    | x_3         |     0.1     |  0.5  |      1      | float |
    INFO - 16:26:42:    +-------------+-------------+-------+-------------+-------+
    INFO - 16:26:42: Solving optimization problem with algorithm SLSQP:
    INFO - 16:26:42: ...   0%|          | 0/10 [00:00<?, ?it]
    INFO - 16:26:42: ...  10%|█         | 1/10 [00:00<00:00,  9.57 it/sec, obj=-536]
    INFO - 16:26:42: ...  20%|██        | 2/10 [00:00<00:00,  9.12 it/sec, obj=-2.12e+3]
    INFO - 16:26:42: ...  30%|███       | 3/10 [00:00<00:00,  7.61 it/sec, obj=-3.64e+3]
    INFO - 16:26:42: ...  40%|████      | 4/10 [00:00<00:00,  7.90 it/sec, obj=-4.01e+3]
    INFO - 16:26:43: ...  50%|█████     | 5/10 [00:00<00:00,  7.56 it/sec, obj=-4.51e+3]
 WARNING - 16:26:43: Optimization found no feasible point !  The least infeasible point is selected.
    INFO - 16:26:43: Optimization result:
    INFO - 16:26:43:    Optimizer info:
    INFO - 16:26:43:       Status: 8
    INFO - 16:26:43:       Message: Positive directional derivative for linesearch
    INFO - 16:26:43:       Number of calls to the objective function by the optimizer: 6
    INFO - 16:26:43:    Solution:
 WARNING - 16:26:43:       The solution is not feasible.
    INFO - 16:26:43:       Objective: -3643.2646614710907
    INFO - 16:26:43:       Standardized constraints:
    INFO - 16:26:43:          g_1 = [-0.02648406 -0.03933265 -0.04887821 -0.05560436 -0.06050463 -0.13630937
    INFO - 16:26:43:  -0.10369063]
    INFO - 16:26:43:          g_2 = 0.002396186936539646
    INFO - 16:26:43:          g_3 = [-0.50236422 -0.49763578 -0.23179683 -0.18266046]
    INFO - 16:26:43:       Design space:
    INFO - 16:26:43:       +-------------+-------------+---------------------+-------------+-------+
    INFO - 16:26:43:       | name        | lower_bound |        value        | upper_bound | type  |
    INFO - 16:26:43:       +-------------+-------------+---------------------+-------------+-------+
    INFO - 16:26:43:       | x_shared[0] |     0.01    | 0.06059904673413494 |     0.09    | float |
    INFO - 16:26:43:       | x_shared[1] |    30000    |        60000        |    60000    | float |
    INFO - 16:26:43:       | x_shared[2] |     1.4     |   1.40455692827199  |     1.8     | float |
    INFO - 16:26:43:       | x_shared[3] |     2.5     |         2.5         |     8.5     | float |
    INFO - 16:26:43:       | x_shared[4] |      40     |          70         |      70     | float |
    INFO - 16:26:43:       | x_shared[5] |     500     |         1500        |     1500    | float |
    INFO - 16:26:43:       | x_1[0]      |     0.1     |  0.3947569275200153 |     0.4     | float |
    INFO - 16:26:43:       | x_1[1]      |     0.75    |         0.75        |     1.25    | float |
    INFO - 16:26:43:       | x_2         |     0.75    |         0.75        |     1.25    | float |
    INFO - 16:26:43:       | x_3         |     0.1     |  0.1205118700740507 |      1      | float |
    INFO - 16:26:43:       +-------------+-------------+---------------------+-------------+-------+
    INFO - 16:26:43: *** End MDOScenario execution (time: 0:00:00.718478) ***

{'max_iter': 10, 'algo_options': {'ftol_rel': 1e-10, 'ineq_tolerance': 0.002, 'normalize_design_space': True}, 'algo': 'SLSQP'}

Save the optimization history

We can save the whole optimization problem and its history for further post processing:

scenario.save_optimization_history("mdf_history.h5", file_format="hdf5")
INFO - 16:26:43: Export optimization problem to file: mdf_history.h5

We can also save only calls to functions and design variables history:

scenario.save_optimization_history("mdf_history.xml", file_format="ggobi")
INFO - 16:26:43: Export to ggobi for functions: ['-y_4', 'g_1', 'g_2', 'g_3']
INFO - 16:26:43: Export to ggobi file: mdf_history.xml

Post-process the results

Plot the optimization history view

scenario.post_process("OptHistoryView", save=False, show=True)
  • Evolution of the optimization variables
  • Evolution of the objective value
  • Distance to the optimum
  • Hessian diagonal approximation
  • Evolution of the inequality constraints
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '
 WARNING - 16:26:43: Optimization found no feasible point !  The least infeasible point is selected.

<gemseo.post.opt_history_view.OptHistoryView object at 0x7fea436a9c10>

Plot the basic history view

scenario.post_process(
    "BasicHistory", variable_names=["x_shared"], save=False, show=True
)
History plot
<gemseo.post.basic_history.BasicHistory object at 0x7fea40f7c910>

Plot the constraints and objective history

scenario.post_process("ObjConstrHist", save=False, show=True)
Evolution of the objective and maximum constraint
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '

<gemseo.post.obj_constr_hist.ObjConstrHist object at 0x7fea40ddceb0>

Plot the constraints history

scenario.post_process(
    "ConstraintsHistory",
    constraint_names=["g_1", "g_2", "g_3"],
    save=False,
    show=True,
)
Evolution of the constraints w.r.t. iterations, g_1[0] (inequality), g_1[1] (inequality), g_1[2] (inequality), g_1[3] (inequality), g_1[4] (inequality), g_1[5] (inequality), g_1[6] (inequality), g_2 (inequality), g_3[0] (inequality), g_3[1] (inequality), g_3[2] (inequality), g_3[3] (inequality)
<gemseo.post.constraints_history.ConstraintsHistory object at 0x7fea4107d2b0>

Plot the constraints history using a radar chart

scenario.post_process(
    "RadarChart",
    constraint_names=["g_1", "g_2", "g_3"],
    save=False,
    show=True,
)
Constraints at iteration 2 (optimum)
<gemseo.post.radar_chart.RadarChart object at 0x7fea40a62be0>

Plot the quadratic approximation of the objective

scenario.post_process("QuadApprox", function="-y_4", save=False, show=True)
  • Hessian matrix SR1 approximation of -y_4
  • plot sobieski mdf example
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '

<gemseo.post.quad_approx.QuadApprox object at 0x7fea4052de50>

Plot the functions using a SOM

scenario.post_process("SOM", save=False, show=True)
Self Organizing Maps of the design space, -y_4, g_1_0, g_1_1, g_1_2, g_1_3, g_1_4, g_1_5, g_1_6, g_2, g_3_0, g_3_1, g_3_2, g_3_3
    INFO - 16:26:47: Building Self Organizing Map from optimization history:
    INFO - 16:26:47:     Number of neurons in x direction = 4
    INFO - 16:26:47:     Number of neurons in y direction = 4

<gemseo.post.som.SOM object at 0x7fea404ca100>

Plot the scatter matrix of variables of interest

scenario.post_process(
    "ScatterPlotMatrix",
    variable_names=["-y_4", "g_1"],
    save=False,
    show=True,
    fig_size=(14, 14),
)
plot sobieski mdf example
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '

<gemseo.post.scatter_mat.ScatterPlotMatrix object at 0x7fea3f840c40>

Plot the variables using the parallel coordinates

scenario.post_process("ParallelCoordinates", save=False, show=True)
  • Design variables history colored by '-y_4' value
  • Objective function and constraints history colored by '-y_4' value.
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '

<gemseo.post.para_coord.ParallelCoordinates object at 0x7fea3f840e80>

Plot the robustness of the solution

scenario.post_process("Robustness", save=True, show=True)
<gemseo.post.robustness.Robustness object at 0x7fea3e4d89a0>

Plot the influence of the design variables

scenario.post_process("VariableInfluence", fig_size=(14, 14), save=False, show=True)
Partial variation of the functions wrt design variables, 9 variables required to explain 99% of -y_4 variations, 5 variables required to explain 99% of g_1[0] variations, 5 variables required to explain 99% of g_1[1] variations, 5 variables required to explain 99% of g_1[2] variations, 5 variables required to explain 99% of g_1[3] variations, 5 variables required to explain 99% of g_1[4] variations, 4 variables required to explain 99% of g_1[5] variations, 4 variables required to explain 99% of g_1[6] variations, 1 variables required to explain 99% of g_2 variations, 7 variables required to explain 99% of g_3[0] variations, 7 variables required to explain 99% of g_3[1] variations, 3 variables required to explain 99% of g_3[2] variations, 3 variables required to explain 99% of g_3[3] variations
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/genson/schema/strategies/base.py:42: UserWarning: Schema incompatible. Keyword 'description' has conflicting values ('The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.' vs. 'The width and height of the figure in inches, e.g. `(w, h)`.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'). Using 'The width and height of the figure in inches, e.g. ``(w, h)``.\nIf ``None``, use the :attr:`.OptPostProcessor.DEFAULT_FIG_SIZE`\nof the post-processor.'
  warn(('Schema incompatible. Keyword {0!r} has conflicting '
 WARNING - 16:26:52: Optimization found no feasible point !  The least infeasible point is selected.
    INFO - 16:26:52: Output name; most influential variables to explain 0.99% of the output variation
    INFO - 16:26:52:    -y_4; x_1[1], x_2, x_3, x_shared[0], x_shared[1], x_shared[2], x_shared[3], x_shared[4], x_shared[5]
/home/docs/checkouts/readthedocs.org/user_builds/gemseo/envs/5.0.0/lib/python3.9/site-packages/gemseo/post/variable_influence.py:238: UserWarning: FixedFormatter should only be used together with FixedLocator
  axe.set_xticklabels(x_labels, fontsize=font_size, rotation=rotation)
    INFO - 16:26:52:    g_1[0]; x_1[0], x_1[1], x_shared[0], x_shared[3], x_shared[5]
    INFO - 16:26:52:    g_1[1]; x_1[0], x_1[1], x_shared[0], x_shared[3], x_shared[5]
    INFO - 16:26:52:    g_1[2]; x_1[0], x_1[1], x_shared[0], x_shared[3], x_shared[5]
    INFO - 16:26:52:    g_1[3]; x_1[0], x_1[1], x_shared[0], x_shared[3], x_shared[5]
    INFO - 16:26:52:    g_1[4]; x_1[0], x_1[1], x_shared[0], x_shared[3], x_shared[5]
    INFO - 16:26:52:    g_1[5]; x_1[0], x_1[1], x_shared[3], x_shared[5]
    INFO - 16:26:53:    g_1[6]; x_1[0], x_1[1], x_shared[3], x_shared[5]
    INFO - 16:26:53:    g_2; x_shared[0]
    INFO - 16:26:53:    g_3[0]; x_2, x_3, x_shared[0], x_shared[1], x_shared[2], x_shared[4], x_shared[5]
    INFO - 16:26:53:    g_3[1]; x_2, x_3, x_shared[0], x_shared[1], x_shared[2], x_shared[4], x_shared[5]
    INFO - 16:26:53:    g_3[2]; x_3, x_shared[1], x_shared[2]
    INFO - 16:26:53:    g_3[3]; x_3, x_shared[1], x_shared[2]

<gemseo.post.variable_influence.VariableInfluence object at 0x7fea3f8aedc0>

Total running time of the script: ( 0 minutes 11.785 seconds)

Gallery generated by Sphinx-Gallery