示例#1
0
def test_ListRecorder():
    from openmdao.api import Problem, IndepVarComp
    from openmdao.test_suite.components.paraboloid import Paraboloid

    prob = Problem()
    model = prob.model

    model.add_subsystem('p1', IndepVarComp('x', 0.), promotes=['*'])
    model.add_subsystem('p2', IndepVarComp('y', 0.), promotes=['*'])
    model.add_subsystem('comp', Paraboloid(), promotes=['*'])

    model.add_design_var('x', lower=-10, upper=10)
    model.add_design_var('y', lower=-10, upper=10)
    model.add_objective('f_xy')

    xyf = [[0.98, 4.30, 74.1844],
           [2.06, 0.90, 23.7476],
           [-1.53, 2.92, 60.9397],
           [-1.25, 7.84, 145.4481]]
    prob.driver = DOEDriver(ListGenerator([[('x', xy[0]), ('y', xy[1])] for xy in xyf]))
    recorder = TopFarmListRecorder()
    recorder._initialize_database()
    recorder._cleanup_abs2meta()
    recorder.record_iteration_problem(None, None, None)
    recorder.record_iteration_system(None, None, None)
    recorder.record_iteration_solver(None, None, None)
    recorder.record_viewer_data(None)
    recorder.record_metadata_solver(None)
    recorder.record_derivatives_driver(None, None, None)
    recorder.shutdown()

    prob.driver.add_recorder(recorder)
    prob.driver.recording_options['record_desvars'] = True
    prob.driver.recording_options['includes'] = ['*']
    prob.driver.recording_options['record_inputs'] = True

    prob.setup()
    prob.run_driver()
    prob.cleanup()

    assert recorder.num_cases == 4

    npt.assert_array_equal(recorder.get('counter'), range(1, 5))
    npt.assert_array_equal(recorder['counter'], range(1, 5))

    npt.assert_array_almost_equal(recorder.get(['x', 'y', 'f_xy']), xyf, 4)
    for xyf, k in zip(xyf[0], ['x', 'y', 'f_xy']):
        npt.assert_allclose(recorder[k][0], xyf)

    with pytest.raises(KeyError, match="missing"):
        recorder.get('missing')
示例#2
0
def test_TopFarmListRecorderLoad(load_case, n, cost):
    fn = tfp + 'recordings/COBYLA_10iter:%s' % load_case
    rec = TopFarmListRecorder().load(fn)
    npt.assert_equal(rec.num_cases, n)
    npt.assert_almost_equal(rec.get('cost')[-1], cost)
示例#3
0
class TopFarmProblem(Problem):
    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()

    @property
    def cost(self):
        return self['cost'][0]

    @property
    def state(self):
        """Return current state"""
        self.setup()
        state = {k: self[k] for k in self.design_vars}
        state.update({k: self[k] for k in self.ext_vars})
        if hasattr(self.cost_comp, 'state'):
            state.update(self.cost_comp.state)
        if hasattr(self.cost_comp, 'additional_output'):
            state.update(
                {k: self[k]
                 for k, _ in self.cost_comp.additional_output})
        return state

    def state_array(self, keys):
        self.setup()
        return np.array([self[k] for k in keys]).T

    def update_state(self, state):
        for k, v in state.items():
            try:
                c = self[k]  # fail if k not exists
                v = np.array(v)
                if hasattr(c, 'shape') and c.shape != v.shape:
                    v = v.reshape(c.shape)
                self[k] = v
            except KeyError:
                pass

    def load_recorder(self):
        if hasattr(self.cost_comp, 'problem'):
            self.recorder = NestedTopFarmListRecorder(self.cost_comp,
                                                      self.record_id)
        else:
            self.recorder = TopFarmListRecorder(self.record_id)

    def setup(self):
        if self._setup_status == 0:
            Problem.setup(self, check=True)
        if self._setup_status < 2:
            with warnings.catch_warnings():
                warnings.filterwarnings('error')
                try:
                    if len(self.driver._rec_mgr._recorders) == 0:
                        tmp_recorder = ListRecorder()
                        self.driver.add_recorder(tmp_recorder)
                        Problem.final_setup(self)
                    else:
                        Problem.final_setup(self)
                except Warning as w:
                    if str(w).startswith(
                            'Inefficient choice of derivative mode'):
                        Problem.setup(self, check=True, mode='fwd')
                    else:
                        raise w
                finally:
                    try:
                        self.driver._rec_mgr._recorders.remove(tmp_recorder)
                    except Exception:
                        pass

    def evaluate(self, state={}, disp=False):
        """Evaluate the cost model.

        Parameters
        ----------
        state : dict, optional
            Initial state\n
            Ex: {'x': [1,2,3], 'y':[3,2,1]}\n
            The current state is used for unspecified variables
        disp : bool, optional
            if True, the time used for the optimization is printed

        Returns
        -------
        Current cost : float
        Current state : dict
        """
        tmp_recorder = ListRecorder()
        self.driver.add_recorder(tmp_recorder)
        self.setup()
        self.update_state(state)
        t = time.time()
        self.run_model()
        if disp:
            print("Evaluated in\t%.3fs" % (time.time() - t))
        self.driver._rec_mgr._recorders.remove(tmp_recorder)
        return self.cost, copy.deepcopy(self.state)

    def evaluate_gradients(self, disp=False):
        """Evaluate the gradients."""
        self.setup()
        t = time.time()
        rec = ListRecorder()
        self.driver.add_recorder(rec)
        res = self.compute_totals(['cost'],
                                  wrt=[topfarm.x_key, topfarm.y_key],
                                  return_format='dict')
        self.driver._rec_mgr._recorders.remove(rec)
        if disp:
            print("Gradients evaluated in\t%.3fs" % (time.time() - t))
        return res

    def optimize(self, state={}, disp=False):
        """Run the optimization problem

        Parameters
        ----------
        state : dict, optional
            Initial state\n
            Ex: {'x': [1,2,3], 'y':[3,2,1]}\n
            The current state is used to unspecified variables
        disp : bool, optional
            if True, the time used for the optimization is printed

        Returns
        -------
        Optimized cost : float
        state : dict
        recorder : TopFarmListRecorder or NestedTopFarmListRecorder
        """
        self.load_recorder()
        self.update_state(state)
        if self.recorder.num_cases > 0:
            try:
                self.update_state({
                    k: self.recorder[k][-1]
                    for k in self.state.keys() if k not in state
                })
            except (ValueError, KeyError):
                # Restart optimize with n
                self.record_id = split_record_id(self.record_id)[0] + ":0"
                return self.optimize(state, disp)

        self.driver.add_recorder(self.recorder)
        self.driver.recording_options['record_desvars'] = True
        self.driver.recording_options['includes'] = ['*']
        self.driver.recording_options['record_inputs'] = True
        self.setup()
        t = time.time()
        self.run_driver()
        self.cleanup()
        if disp:
            print("Optimized in\t%.3fs" % (time.time() - t))
        if self.driver._rec_mgr._recorders != []:  # in openmdao<2.4 cleanup does not delete recorders
            self.driver._rec_mgr._recorders.remove(self.recorder)
        if isinstance(self.driver, DOEDriver) or isinstance(
                self.driver, SimpleGADriver):
            costs = self.recorder.get('cost')
            cases = self.recorder.driver_cases
            costs = [
                cases.get_case(i).outputs['cost']
                for i in range(cases.num_cases)
            ]
            best_case_index = int(np.argmin(costs))
            best_case = cases.get_case(best_case_index)
            self.evaluate({k: best_case.outputs[k] for k in best_case.outputs})
        return self.cost, copy.deepcopy(self.state), self.recorder

    def check_gradients(self, check_all=False, tol=1e-3):
        """Check gradient computations"""
        self.setup()
        if check_all:
            comp_name_lst = [
                comp.pathname for comp in self.model.system_iter()
                if comp._has_compute_partials
            ]
        else:
            comp_name_lst = [self.cost_comp.pathname]
        print("checking %s" % ", ".join(comp_name_lst))
        res = self.check_partials(includes=comp_name_lst, compact_print=True)
        for comp in comp_name_lst:
            var_pair = [(x, dx) for x, dx in res[comp].keys()
                        if x not in ['cost_comp_eval']]
            worst = var_pair[np.argmax(
                [res[comp][k]['rel error'].forward for k in var_pair])]
            err = res[comp][worst]['rel error'].forward
            if err > tol:
                raise Warning(
                    "Mismatch between finite difference derivative of '%s' wrt. '%s' and derivative computed in '%s' is: %f"
                    % (worst[0], worst[1], comp, err))

    def as_component(self):
        return ProblemComponent(self)

    def get_DOE_list(self):
        self.setup()
        assert isinstance(
            self.driver, DOEDriver
        ), 'get_DOE_list only applies to DOEDrivers, and the current driver is: %s' % type(
            self.driver)
        case_gen = self.driver.options['generator']
        return [
            c for c in case_gen(self.model.get_design_vars(
                recurse=True), self.model)
        ]

    def get_DOE_array(self):
        return np.array([[v for _, v in c] for c in self.get_DOE_list()])

    @property
    def turbine_positions(self):
        return np.array([self[k] for k in [topfarm.x_key, topfarm.y_key]]).T

    def smart_start(self, XX, YY, ZZ, radius=None):
        assert XX.shape == YY.shape
        if len(XX.shape) == 1:
            XX, YY = np.meshgrid(XX, YY)
        ZZ_is_func = hasattr(ZZ, '__call__')
        spacing_comp_lst = [
            c for c in self.model.constraint_components
            if isinstance(c, SpacingComp)
        ]
        if len(spacing_comp_lst) == 1:
            min_spacing = spacing_comp_lst[0].min_spacing
        else:
            min_spacing = 0
        X, Y = XX.flatten(), YY.flatten()
        if not ZZ_is_func:
            Z = ZZ.flatten()
        else:
            Z = ZZ
        for comp in self.model.constraint_components:
            if isinstance(comp, BoundaryBaseComp):
                dist = comp.distances(X, Y)
                if len(dist.shape) == 2:
                    dist = dist.min(1)
                mask = dist >= 0
                X, Y = X[mask], Y[mask]
                if not ZZ_is_func:
                    Z = Z[mask]
        x, y = smart_start(X, Y, Z, self.n_wt, min_spacing, radius)
        self.update_state({topfarm.x_key: x, topfarm.y_key: y})
        return x, y