{ "cells": [ { "cell_type": "markdown", "id": "f17a116a", "metadata": {}, "source": [ "# Design Space Exploration\n", "\n", "In this demonstration we show how we can leverage the `OptProblem` class from the `standard_evaluator` library to perform optimization studies that can be version controlled. \n", "\n", "\n", "\n", "The idea is that we will want to run multiple optimization problems that are driven by JSOn files that we can version control. In a future step we will also manage the assembly information itself in a JSON file that can be version controlled, but for this demonstration we went down an easier path.\n", "\n", "We first load all required libraries and methods." ] }, { "cell_type": "code", "execution_count": 1, "id": "acef51ce-e90f-43a3-9716-ecb16167b371", "metadata": { "metadata": {} }, "outputs": [], "source": [ "import typing\n", "import openmdao.api as om\n", "import openmdao.core.system as oms\n", "import pandas as pd\n", "\n", "from standard_evaluator import get_interface, show_structure, create_problem, get_state, set_state, load_state, save_state\n", "from standard_evaluator import load_assembly, save_assembly, convert_om_var, get_opt_problem, set_opt_problem\n", "from standard_evaluator import Variable, FloatVariable, IntVariable, ArrayVariable, OptProblem" ] }, { "cell_type": "markdown", "id": "763bdf4f-e824-456c-898f-1513dcff5e7c", "metadata": {}, "source": [ "## Creating the OpenMDAO assembly\n", "\n", "In this example we use the Sellar Problem from the [OpenMDAO documentation](https://openmdao.org/newdocs/versions/latest/basic_user_guide/multidisciplinary_optimization/sellar_opt.html). \n", "\n", "We first setup the assembly, set the optimization variables, constraints, and the objective. We can then run the optimization." ] }, { "cell_type": "code", "execution_count": 2, "id": "d9bdb3c0", "metadata": { "metadata": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.183393951729169\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "minimum found at\n", "0.0\n", "[1.97763888e+00 8.83056605e-15]\n", "minumum objective\n", "3.183393951729169\n" ] } ], "source": [ "import numpy as np\n", "import openmdao.api as om\n", "\n", "from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2\n", "\n", "\n", "class SellarMDA(om.Group):\n", " \"\"\"\n", " Group containing the Sellar MDA.\n", " \"\"\"\n", "\n", " def setup(self):\n", " cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])\n", " cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z', 'y2'],\n", " promotes_outputs=['y1'])\n", " cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z', 'y1'],\n", " promotes_outputs=['y2'])\n", "\n", " cycle.set_input_defaults('x', 1.0)\n", " cycle.set_input_defaults('z', np.array([5.0, 2.0]))\n", "\n", " # Nonlinear Block Gauss Seidel is a gradient free solver\n", " cycle.nonlinear_solver = om.NonlinearBlockGS()\n", "\n", " self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',\n", " z=np.array([0.0, 0.0]), x=0.0),\n", " promotes=['x', 'z', 'y1', 'y2', 'obj'])\n", "\n", " self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])\n", " self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])\n", "\n", "class SellarMDA(om.Group):\n", " \"\"\"\n", " Group containing the Sellar MDA.\n", " \"\"\"\n", "\n", " def setup(self):\n", " cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])\n", " cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z', 'y2'],\n", " promotes_outputs=['y1'])\n", " cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z', 'y1'],\n", " promotes_outputs=['y2'])\n", "\n", " cycle.set_input_defaults('x', 1.0)\n", " cycle.set_input_defaults('z', np.array([5.0, 2.0]))\n", "\n", " # Nonlinear Block Gauss Seidel is a gradient free solver\n", " cycle.nonlinear_solver = om.NonlinearBlockGS()\n", "\n", " self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',\n", " z=np.array([0.0, 0.0]), x=0.0),\n", " promotes=['x', 'z', 'y1', 'y2', 'obj'])\n", "\n", " self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])\n", " self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])\n", "\n", "import openmdao.api as om\n", "from openmdao.test_suite.components.sellar_feature import SellarMDA\n", "\n", "prob = om.Problem()\n", "prob.model = SellarMDA()\n", "\n", "prob.driver = om.ScipyOptimizeDriver()\n", "prob.driver.options['optimizer'] = 'SLSQP'\n", "# prob.driver.options['maxiter'] = 100\n", "prob.driver.options['tol'] = 1e-8\n", "\n", "prob.model.add_design_var('x', lower=0, upper=10)\n", "prob.model.add_design_var('z', lower=0, upper=10)\n", "prob.model.add_objective('obj')\n", "prob.model.add_constraint('con1', upper=0)\n", "prob.model.add_constraint('con2', upper=0)\n", "\n", "# Ask OpenMDAO to finite-difference across the model to compute the gradients for the optimizer\n", "prob.model.approx_totals()\n", "\n", "prob.setup()\n", "prob.set_solver_print(level=0)\n", "\n", "prob.run_driver()\n", "\n", "print('minimum found at')\n", "print(prob.get_val('x')[0])\n", "print(prob.get_val('z'))\n", "\n", "print('minumum objective')\n", "print(prob.get_val('obj')[0])" ] }, { "cell_type": "markdown", "id": "44266398", "metadata": {}, "source": [ "In the next step we get the description of the optimization problem and store that in an instance of the neutral `OptProblem`. We also capture the structure of the assembly in the neutral format.\n", "\n", "We show the content of the `original_opt` variable," ] }, { "cell_type": "code", "execution_count": 3, "id": "c4baddaf", "metadata": { "metadata": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Warning: More work is needed to support array variables\n" ] }, { "data": { "text/plain": [ "OptProblem(name='opt_problem', class_type='OptProblem', variables=[FloatVariable(name='x', default=None, bounds=(0.0, 10.0), shift=0.0, scale=1.0, units=None, description='', options={'parallel_deriv_color': None}, class_type='float'), ArrayVariable(name='z', default=None, bounds=(array([0., 0.]), array([10., 10.])), shift=array([0., 0.]), scale=array([1., 1.]), units=None, description='', options={'parallel_deriv_color': None}, class_type='floatarray', shape=(2,))], responses=[FloatVariable(name='con1', default=None, bounds=(-1e+30, 0.0), shift=0.0, scale=1.0, units=None, description='', options={'parallel_deriv_color': None}, class_type='float'), FloatVariable(name='con2', default=None, bounds=(-1e+30, 0.0), shift=0.0, scale=1.0, units=None, description='', options={'parallel_deriv_color': None}, class_type='float'), FloatVariable(name='obj', default=None, bounds=(-inf, inf), shift=0.0, scale=1.0, units=None, description='', options={'parallel_deriv_color': None}, class_type='float')], objectives=['obj'], constraints=['con1', 'con2'], description=None, cite=None, options={})" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "original_opt = get_opt_problem(prob)\n", "info = get_interface(prob.model)\n", "original_opt" ] }, { "cell_type": "markdown", "id": "6376615a", "metadata": {}, "source": [ "## Setting up a series of optimization studies\n", "\n", "In the next step we take the original optimization problem and create a series of optimization problems where we change the upper bound of the first nonlinear constraint. This is a typical scenario in industry, we are trying to explore the design space and often the constraints are not fully defined.\n", "\n", "We store all of the optimization problems in JSON files. This will allow us to fully describe the studies we are doing, and they can be version controlled. " ] }, { "cell_type": "code", "execution_count": 8, "id": "38f38d48", "metadata": { "metadata": {} }, "outputs": [], "source": [ "import json\n", "\n", "bound_values = [-3.5, -2.5, -1.5, -.5, -.4, -.3, -.2, -.1, 0.0, .1, .2]\n", "indx = 0\n", "for upper in bound_values:\n", " new_opt = original_opt.model_copy()\n", " new_opt.responses[0].bounds = (-np.inf, upper)\n", " optproblem_name = f'optproblem{indx:02}.json'\n", " indx += 1\n", " # Convert and write JSON object to file\n", " with open(optproblem_name, \"w\") as outfile:\n", " json.dump(new_opt.model_dump(exclude_unset=True), outfile, indent=2, skipkeys=True)\n" ] }, { "cell_type": "markdown", "id": "16cd0670", "metadata": {}, "source": [ "## Running a series of optimization problems\n", "\n", "In the next step we actually run the different optimization problems. To do this we first create a pattern for all the optimization files, and get the names of all of them.\n", "\n", "Then we run a loop through all the file names, first loading the optimization problem formulation into a dictionary, convert it into `OptProblem`'s, create a new instance of the Sellar problem, set the optimization problem, the optimzition parameters, and run the optimization. We store the results from the optimization in a Pandas DataFrame together with the name of the file that defined the specific optimization problem.\n", "\n", "After running all the optimizations we combine the results into a single DataFrame, and show the summary." ] }, { "cell_type": "code", "execution_count": 9, "id": "8a7cf9a4", "metadata": { "metadata": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 6.664694351674094\n", " Iterations: 5\n", " Function evaluations: 6\n", " Gradient evaluations: 5\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 5.667025907351937\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 4.670917299584386\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.677841549628989\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.5788057528834227\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.4798368284597276\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.380940701913713\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.2821239617838844\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.183393951729169\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 3.08475887838721\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n", "Optimization terminated successfully (Exit mode 0)\n", " Current function value: 2.9862279381209578\n", " Iterations: 6\n", " Function evaluations: 6\n", " Gradient evaluations: 6\n", "Optimization Complete\n", "-----------------------------------\n" ] }, { "data": { "text/html": [ "
| \n", " | y1 | \n", "y2 | \n", "z | \n", "x | \n", "con1 | \n", "con2 | \n", "obj | \n", "name | \n", "
|---|---|---|---|---|---|---|---|---|
| 0 | \n", "6.66 | \n", "5.361395 | \n", "[2.7806975800167444, 3.051394295571152e-12] | \n", "0.0 | \n", "-3.5 | \n", "-18.638605 | \n", "6.664694 | \n", "optproblem00.json | \n", "
| 0 | \n", "5.66 | \n", "4.958151 | \n", "[2.5790754506581415, 6.221918747285027e-13] | \n", "0.0 | \n", "-2.5 | \n", "-19.041849 | \n", "5.667026 | \n", "optproblem01.json | \n", "
| 0 | \n", "4.66 | \n", "4.517407 | \n", "[2.358703314489707, 1.2497789641957312e-13] | \n", "0.0 | \n", "-1.5 | \n", "-19.482593 | \n", "4.670917 | \n", "optproblem02.json | \n", "
| 0 | \n", "3.66 | \n", "4.026225 | \n", "[2.1131126469752384, 0.0] | \n", "0.0 | \n", "-0.5 | \n", "-19.973775 | \n", "3.677842 | \n", "optproblem03.json | \n", "
| 0 | \n", "3.56 | \n", "3.973592 | \n", "[2.086796226417705, 0.0] | \n", "0.0 | \n", "-0.4 | \n", "-20.026408 | \n", "3.578806 | \n", "optproblem04.json | \n", "
| 0 | \n", "3.46 | \n", "3.920215 | \n", "[2.060107523783193, 0.0] | \n", "0.0 | \n", "-0.3 | \n", "-20.079785 | \n", "3.479837 | \n", "optproblem05.json | \n", "
| 0 | \n", "3.36 | \n", "3.866061 | \n", "[2.0330302779941305, 6.990458292776596e-15] | \n", "0.0 | \n", "-0.2 | \n", "-20.133939 | \n", "3.380941 | \n", "optproblem06.json | \n", "
| 0 | \n", "3.26 | \n", "3.811094 | \n", "[2.0055470085439713, 5.500933528914423e-15] | \n", "0.0 | \n", "-0.1 | \n", "-20.188906 | \n", "3.282124 | \n", "optproblem07.json | \n", "
| 0 | \n", "3.16 | \n", "3.755278 | \n", "[1.977638883487764, 8.830566052859473e-15] | \n", "0.0 | \n", "-0.0 | \n", "-20.244722 | \n", "3.183394 | \n", "optproblem08.json | \n", "
| 0 | \n", "3.06 | \n", "3.698571 | \n", "[1.9492855684910377, 0.0] | \n", "0.0 | \n", "0.1 | \n", "-20.301429 | \n", "3.084759 | \n", "optproblem09.json | \n", "
| 0 | \n", "2.96 | \n", "3.64093 | \n", "[1.9204650534922432, 5.9424529525480574e-15] | \n", "0.0 | \n", "0.2 | \n", "-20.35907 | \n", "2.986228 | \n", "optproblem10.json | \n", "