def __init__(self, design_vars, cost_comp=None, driver=EasyScipyOptimizeDriver(), constraints=[], plot_comp=NoPlot(), record_id=None, expected_cost=1, ext_vars={}, post_constraints=[], approx_totals=False): """Initialize TopFarmProblem Parameters ---------- design_vars : dict or list of key-initial_value-tuples Design variables for the problem.\n Ex: {'x': [1,2,3], 'y':([3,2,1],0,1), 'z':([4,5,6],[4,5,4], [6,7,6])}\n Ex: [('x', [1,2,3]), ('y',([3,2,1],0,1)), ('z',([4,5,6],[4,5,4], [6,7,6]))]\n Ex: [('x', ([1,2,3],0,3,'m')), ('y',([3,2,1],'m')), ('z',([4,5,6],[4,5,4], [6,7,6]))]\n Ex: zip('xy', pos.T)\n The keys (x, y, z) are the names of the design variable.\n The values are either\n - the initial value or\n - on of the following tuples: (initial value, unit) (initial value, lower bound, upper bound) (initial value, lower bound, upper bound, unit) cost_comp : ExplicitComponent or TopFarmProblem or TopFarmGroup Component that provides the cost function. It has to be the style of an OpenMDAO v2 ExplicitComponent. Pure python cost functions can be wrapped using ``CostModelComponent`` class in ``topfarm.cost_models.cost_model_wrappers``.\n ExplicitComponent are wrapped into a TopFarmGroup.\n For nested problems, the cost comp_comp is typically a TopFarmProblem driver : openmdao Driver, optinal Driver used to solve the optimization driver. For an example, see the ``EasyScipyOptimizeDriver`` class in ``topfarm.easy_drivers``. constraints : list of Constraint-objects E.g. XYBoundaryConstraint, SpacingConstraint plot_comp : ExplicitComponent, optional OpenMDAO ExplicitComponent used to plot the state (during optimization). For no plotting, pass in the ``topfarm.plotting.NoPlot`` class. record_id : string "<record_id>:<case>", optional Identifier for the optimization. Allows a user to restart an optimization where it left off.\n record_id can be name (saves as recordings/<name>.pkl), abs or relative path Case can be:\n - "", "latest", "-1": Continue from latest\n - "best": Continue from best case (minimum cost)\n - "0": Start from scratch (initial position)\n - "4": Start from case number 4\n expected_cost : int, float or None, optional Used to scale the cost, default is 1. This has influence on some drivers, e.g. SLSQP where it affects the step size\n If None, the value is found by evaluating the cost function ext_vars : dict or list of key-initial_value tuple Used for nested problems to propagate variables from parent problem\n Ex. {'type': [1,2,3]}\n Ex. [('type', [1,2,3])]\n post_constraints : list of Constraint-objects that needs the cost component to be evaluated, unlike (pre-)constraints which are evaluated before the cost component. E.g. LoadConstraint approx_totals : bool or dict If True, approximates the total derivative of the cost_comp group, skipping the partial ones. If it is a dictionary, it's elements are passed to the approx_totals function of an OpenMDAO Group. Examples -------- See main() in the bottom of this file """ if mpi.MPI: comm = None else: from openmdao.utils.mpi import FakeComm comm = FakeComm() Problem.__init__(self, comm=comm) if cost_comp: if isinstance(cost_comp, TopFarmProblem): cost_comp = cost_comp.as_component() elif isinstance(cost_comp, ExplicitComponent) and (len(post_constraints) > 0): cost_comp = TopFarmGroup([cost_comp]) cost_comp.parent = self self.cost_comp = cost_comp if isinstance(driver, list): driver = DOEDriver(ListGenerator(driver)) elif isinstance(driver, DOEGenerator): driver = DOEDriver(generator=driver) self.driver = driver self.driver.recording_options['record_desvars'] = True self.driver.recording_options['includes'] = ['*'] self.driver.recording_options['record_inputs'] = True self.plot_comp = plot_comp self.record_id = record_id self.load_recorder() if not isinstance(approx_totals, dict) and approx_totals: approx_totals = {'method': 'fd'} if not isinstance(design_vars, dict): design_vars = dict(design_vars) self.design_vars = design_vars self.indeps = self.model.add_subsystem('indeps', IndepVarComp(), promotes=['*']) for k, v in design_vars.items(): if isinstance(v, tuple): if (not isinstance(v[-1], str)) or (not v[-1]): design_vars[k] += (None, ) else: design_vars[k] = (design_vars[k], None) v = design_vars[k] self.indeps.add_output(k, v[0], units=v[-1]) for k in [topfarm.x_key, topfarm.y_key, topfarm.type_key]: if k in design_vars: self.n_wt = len(design_vars[k][0]) break else: self.n_wt = 0 constraints_as_penalty = ( (not self.driver.supports['inequality_constraints'] or isinstance(self.driver, SimpleGADriver) or isinstance(self.driver, EasySimpleGADriver)) and len(constraints) + len(post_constraints) > 0) if len(constraints) > 0: self.model.add_subsystem('pre_constraints', ParallelGroup(), promotes=['*']) for constr in constraints: if constraints_as_penalty: constr.setup_as_penalty(self) else: constr.setup_as_constraint(self) # Use the assembled Jacobian. self.model.pre_constraints.options[ 'assembled_jac_type'] = 'csc' self.model.pre_constraints.linear_solver.assemble_jac = True penalty_comp = PenaltyComponent(constraints, constraints_as_penalty) self.model.add_subsystem('penalty_comp', penalty_comp, promotes=['*']) self.model.constraint_components = [ constr.constraintComponent for constr in constraints ] for k, v in design_vars.items(): if isinstance(driver, EasyDriverBase): kwargs = driver.get_desvar_kwargs(self.model, k, v) else: kwargs = EasyDriverBase.get_desvar_kwargs( None, self.model, k, v) self.model.add_design_var(k, **kwargs) for k, v in ext_vars.items(): self.indeps.add_output(k, v) self.ext_vars = ext_vars if cost_comp: self.model.add_subsystem('cost_comp', cost_comp, promotes=['*']) if expected_cost is None: expected_cost = self.evaluate()[0] self._setup_status = 0 if isinstance( driver, EasyDriverBase) and driver.supports_expected_cost is False: expected_cost = 1 if isinstance(cost_comp, Group) and approx_totals: cost_comp.approx_totals(**approx_totals) # Use the assembled Jacobian. if 'assembled_jac_type' in self.model.cost_comp.options: self.model.cost_comp.options['assembled_jac_type'] = 'dense' self.model.cost_comp.linear_solver.assemble_jac = True else: self.indeps.add_output('cost') if len(post_constraints) > 0: if constraints_as_penalty: penalty_comp = PostPenaltyComponent(post_constraints, constraints_as_penalty) self.model.add_subsystem('post_penalty_comp', penalty_comp, promotes=['*']) else: for constr in post_constraints: self.model.add_constraint(constr[0], upper=np.full( self.n_wt, constr[1])) # Use the assembled Jacobian. # self.model.cost_comp.post_constraints.options['assembled_jac_type'] = 'csc' # self.model.cost_comp.post_constraints.linear_solver.assemble_jac = True aggr_comp = AggregatedCost(constraints_as_penalty, constraints, post_constraints) self.model.add_subsystem('aggr_comp', aggr_comp, promotes=['*']) self.model.add_objective('aggr_cost', scaler=1 / abs(expected_cost)) if plot_comp and not isinstance(plot_comp, NoPlot): self.model.add_subsystem('plot_comp', plot_comp, promotes=['*']) plot_comp.problem = self plot_comp.n_wt = self.n_wt self.setup()
def __init__(self, design_vars, cost_comp, driver=EasyScipyOptimizeDriver(), constraints=[], plot_comp=NoPlot(), record_id=None, expected_cost=1, ext_vars={}): """Initialize TopFarmProblem Parameters ---------- design_vars : dict or list of key-initial_value-tuples Design variables for the problem.\n Ex: {'x': [1,2,3], 'y':([3,2,1],0,1), 'z':([4,5,6],[4,5,4], [6,7,6])}\n Ex: [('x', [1,2,3]), ('y',([3,2,1],0,1)), ('z',([4,5,6],[4,5,4], [6,7,6]))]\n Ex: zip('xy', pos.T)\n The keys (x, y, z) are the names of the design variable.\n The values are either\n - the initial value or\n - a tuple of (initial value, lower bound, upper bound) cost_comp : ExplicitComponent or TopFarmProblem A cost component in the style of an OpenMDAO v2 ExplicitComponent. Pure python cost functions can be wrapped using ``CostModelComponent`` class in ``topfarm.cost_models.cost_model_wrappers``.\n For nested problems, the cost comp_comp is typically a TopFarmProblem driver : openmdao Driver, optinal Driver used to solve the optimization driver. For an example, see the ``EasyScipyOptimizeDriver`` class in ``topfarm.easy_drivers``. constraints : list of Constraint-objects E.g. XYBoundaryConstraint, SpacingConstraint plot_comp : ExplicitComponent, optional OpenMDAO ExplicitComponent used to plot the state (during optimization). For no plotting, pass in the ``topfarm.plotting.NoPlot`` class. record_id : string "<record_id>:<case>", optional Identifier for the optimization. Allows a user to restart an optimization where it left off.\n record_id can be name (saves as recordings/<name>.pkl), abs or relative path Case can be:\n - "", "latest", "-1": Continue from latest\n - "best": Continue from best case (minimum cost)\n - "0": Start from scratch (initial position)\n - "4": Start from case number 4\n expected_cost : int or float Used to scale the cost. This has influence on some drivers, e.g. SLSQP where it affects the step size ext_vars : dict or list of key-initial_value tuple Used for nested problems to propagate variables from parent problem\n Ex. {'type': [1,2,3]}\n Ex. [('type', [1,2,3])]\n Examples -------- See main() in the bottom of this file """ if mpi.MPI: comm = None else: from openmdao.utils.mpi import FakeComm comm = FakeComm() Problem.__init__(self, comm=comm) if isinstance(cost_comp, TopFarmProblem): cost_comp = cost_comp.as_component() cost_comp.parent = self self.cost_comp = cost_comp if isinstance(driver, list): driver = DOEDriver(ListGenerator(driver)) elif isinstance(driver, DOEGenerator): driver = DOEDriver(generator=driver) self.driver = driver self.plot_comp = plot_comp self.record_id = record_id self.load_recorder() if not isinstance(design_vars, dict): design_vars = dict(design_vars) self.design_vars = design_vars self.indeps = self.model.add_subsystem('indeps', IndepVarComp(), promotes=['*']) for k in [topfarm.x_key, topfarm.y_key, topfarm.type_key]: if k in design_vars: if isinstance(design_vars[k], tuple): self.n_wt = len(design_vars[k][0]) else: self.n_wt = len(design_vars[k]) break else: self.n_wt = 0 for constr in constraints: if self.driver.supports['inequality_constraints']: if isinstance(self.driver, SimpleGADriver): constr.setup_as_penalty(self) else: constr.setup_as_constraint(self) else: constr.setup_as_penalty(self) self.model.constraint_components = [ constr.constraintComponent for constr in constraints ] do = self.driver.options for k, v in design_vars.items(): if isinstance(v, tuple): assert len( v ) == 3, "Design_vars values must be either value or (value, lower, upper)" self.indeps.add_output(k, v[0]) if ('optimizer' in do and do['optimizer'] == 'COBYLA'): ref0 = np.min(v[1]) ref1 = np.max(v[2]) l, u = [lu * (ref1 - ref0) + ref0 for lu in [v[1], v[2]]] kwargs = { 'ref0': ref0, 'ref': ref1, 'lower': l, 'upper': u } else: kwargs = {'lower': v[1], 'upper': v[2]} else: self.indeps.add_output(k, v) kwargs = {} if 'optimizer' in do and do['optimizer'] == 'SLSQP': # Upper and lower disturbs SLSQP when running with constraints. Add limits as constraints self.model.add_constraint(k, kwargs.get('lower', None), kwargs.get('upper', None)) kwargs = { 'lower': np.nan, 'upper': np.nan } # Default +/- sys.float_info.max does not work for SLSQP self.model.add_design_var(k, **kwargs) for k, v in ext_vars.items(): self.indeps.add_output(k, v) self.ext_vars = ext_vars self.model.add_subsystem('cost_comp', cost_comp, promotes=['*']) self.model.add_objective('cost', scaler=1 / abs(expected_cost)) if plot_comp: self.model.add_subsystem('plot_comp', plot_comp, promotes=['*']) plot_comp.problem = self plot_comp.n_wt = self.n_wt self.setup()