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 driver(self): # type: () -> Driver """Method to return a preconfigured driver. Returns ------- Driver A preconfigured driver element to be used for the Problem instance. Raises ------ ValueError Value error are raised if unsupported settings are encountered. """ if self.driver_type == 'optimizer': # Find optimizer element in CMDOWS file opt_uid = self.driver_uid opt_elem = get_element_by_uid(self.elem_cmdows, opt_uid) # Load settings from CMDOWS file opt_package = get_opt_setting_safe(opt_elem, 'package', 'SciPy') opt_algo = get_opt_setting_safe(opt_elem, 'algorithm', 'SLSQP') opt_maxiter = get_opt_setting_safe(opt_elem, 'maximumIterations', 50, expected_type='int') opt_convtol = get_opt_setting_safe(opt_elem, 'convergenceTolerance', 1e-6, expected_type='float') # Apply settings to the driver # driver if opt_package == 'SciPy': driver = ScipyOptimizeDriver() elif opt_package == 'pyOptSparse': try: from openmdao.api import pyOptSparseDriver except ImportError: raise PyOptSparseImportError() driver = pyOptSparseDriver() else: raise ValueError( 'Unsupported package {} encountered in CMDOWS file for "{}".' .format(opt_package, opt_uid)) # optimization algorithm if opt_algo == 'SLSQP': driver.options['optimizer'] = 'SLSQP' elif opt_algo == 'COBYLA': driver.options['optimizer'] = 'COBYLA' elif opt_algo == 'L-BFGS-B': driver.options['optimizer'] = 'L-BFGS-B' else: raise ValueError( 'Unsupported algorithm {} encountered in CMDOWS file for "{}".' .format(opt_algo, opt_uid)) # maximum iterations and tolerance if isinstance(driver, ScipyOptimizeDriver): driver.options['maxiter'] = opt_maxiter driver.options['tol'] = opt_convtol elif isinstance(driver, pyOptSparseDriver): driver.opt_settings['MAXIT'] = opt_maxiter driver.opt_settings['ACC'] = opt_convtol # Set default display and output settings if isinstance(driver, ScipyOptimizeDriver): driver.options['disp'] = False # Print the result return driver elif self.driver_type == 'doe': # Find DOE element in CMDOWS file doe_uid = self.driver_uid doe_elem = get_element_by_uid(self.elem_cmdows, doe_uid) # Load settings from CMDOWS file doe_method = get_doe_setting_safe(doe_elem, 'method', 'Uniform design') # type: str doe_runs = get_doe_setting_safe(doe_elem, 'runs', 5, expected_type='int', doe_method=doe_method, required_for_doe_methods=[ 'Latin hypercube design', 'Uniform design', 'Monte Carlo design' ]) doe_center_runs = get_doe_setting_safe( doe_elem, 'centerRuns', 2, expected_type='int', doe_method=doe_method, required_for_doe_methods=['Box-Behnken design']) doe_seed = get_doe_setting_safe(doe_elem, 'seed', None, expected_type='int', doe_method=doe_method, required_for_doe_methods=[ 'Latin hypercube design', 'Uniform design', 'Monte Carlo design' ]) doe_levels = get_doe_setting_safe( doe_elem, 'levels', 2, expected_type='int', doe_method=doe_method, required_for_doe_methods=['Full factorial design']) # table doe_data = [] if isinstance(doe_elem.find('settings/table'), _Element): doe_table = doe_elem.find('settings/table') doe_table_rows = [row for row in doe_table.iterchildren()] n_samples = len( [exp for exp in doe_table_rows[0].iterchildren()]) for idx in range(n_samples): data_sample = [] for row_elem in doe_table_rows: value = float( row_elem.find( 'tableElement[@experimentID="{}"]'.format( idx)).text) data_sample.append( [row_elem.attrib['relatedParameterUID'], value]) doe_data.append(data_sample) else: if doe_method in ['Custom design table']: raise ValueError( 'Table element with data for custom design table missing in ' 'CMDOWS file.') # Apply settings to the driver # define driver driver = DOEDriver() # define generator if doe_method in ['Uniform design', 'Monte Carlo design']: driver.options['generator'] = UniformGenerator( num_samples=doe_runs, seed=doe_seed) elif doe_method == 'Full factorial design': driver.options['generator'] = FullFactorialGenerator( levels=doe_levels) elif doe_method == 'Box-Behnken design': driver.options['generator'] = BoxBehnkenGenerator( center=doe_center_runs) elif doe_method == 'Latin hypercube design': driver.options['generator'] = LatinHypercubeGenerator( samples=doe_runs, criterion='maximin', seed=doe_seed) elif doe_method == 'Custom design table': driver.options['generator'] = ListGenerator(data=doe_data) else: raise ValueError( 'Could not match the doe_method {} with methods from OpenMDAO.' .format(doe_method)) return driver else: return Driver()