Skip to content
Snippets Groups Projects
_topfarm.py 16.7 KiB
Newer Older
from topfarm.constraint_components.boundary_component import BoundaryComp
from topfarm.constraint_components.spacing_component import SpacingComp
from topfarm.plotting import PlotComp
from topfarm.utils import smart_start
from openmdao.drivers.doe_generators import DOEGenerator, ListGenerator
from openmdao.drivers.doe_driver import DOEDriver
from openmdao.core.explicitcomponent import ExplicitComponent
from topfarm.recorders import ListRecorder, NestedTopFarmListRecorder,\
    TopFarmListRecorder
from openmdao.api import Problem, ScipyOptimizeDriver, IndepVarComp
from openmdao.drivers.genetic_algorithm_driver import SimpleGADriver
from topfarm.drivers.random_search_driver import RandomSearchDriver
Mads M. Pedersen's avatar
Mads M. Pedersen committed
from topfarm.easy_drivers import EasyRandomSearchDriver
from topfarm.drivers import random_search_driver
class TopFarmProblem(Problem):
    def __init__(self, cost_comp, driver, plot_comp, record_id, expected_cost):
        Problem.__init__(self)
        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
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        if hasattr(self.driver, 'supports') and self.driver.supports['inequality_constraints']:
            self.mode = 'fwd'
        else:
            self.mode = 'rev'

        self.plot_comp = plot_comp

        self.record_id = record_id
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        self.load_recorder()
        self.indeps = self.model.add_subsystem('indeps', IndepVarComp(), promotes=['*'])
        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

    @property
    def cost(self):
        return self['cost'][0]
    @property
    def state(self):
        if hasattr(self.cost_comp, 'state'):
            return self.cost_comp.state
            return {}

    def state_array(self, keys):
        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

Mads M. Pedersen's avatar
Mads M. Pedersen committed
    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 evaluate(self, state={}):
        t = time.time()
        self.update_state(state)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        tmp_recorder = ListRecorder()
        self.driver.add_recorder(tmp_recorder)
        self.run_model()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        self.driver._rec_mgr._recorders.remove(tmp_recorder)
        print("Evaluated in\t%.3fs" % (time.time() - t))
        return self.cost, self.state
    def evaluate_gradients(self):
        t = time.time()
        rec = ListRecorder()
        self.driver.add_recorder(rec)
        res = self.compute_totals(['cost'], wrt=['turbineX', 'turbineY'], return_format='dict')
        self.driver._rec_mgr._recorders.remove(rec)
        print("Gradients evaluated in\t%.3fs" % (time.time() - t))
        return res
    def optimize(self, state={}):
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        self.load_recorder()
        self.update_state(state)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        if self.recorder.num_cases > 0:
Mads M. Pedersen's avatar
Mads M. Pedersen committed
            try:
                self.update_state({k: self.recorder.get(k)[-1] for k in self.state.keys() if k not in state})
            except ValueError:
                pass  # loaded state does not fit into dimension of current state

        self.driver.add_recorder(self.recorder)
        self.run_driver()
        self.cleanup()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        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):
            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.update_state({k: best_case.outputs[k] for k in best_case.outputs})
        return self.cost, self.state, self.recorder

    def check_gradients(self, all=False, tol=1e-3):
        """Check gradient computations"""
        if all:
            comp_name_lst = [comp.pathname for comp in self.model.system_iter()
                             if comp._has_compute_partials]
            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 = list(res[comp].keys())
            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):
        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):
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        return np.array([[v for _, v in c] for c in self.get_DOE_list()])
class TurbineTypeOptimizationProblem(TopFarmProblem):
    def __init__(self, cost_comp, turbineTypes, lower, upper, driver, plot_comp=None, record_id=None, expected_cost=1):
        TopFarmProblem.__init__(self, cost_comp, driver, plot_comp, record_id, expected_cost)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        self.initialize(turbineTypes, lower, upper)
        self.setup(check=True, mode=self.mode)

    def initialize(self, turbineTypes, lower, upper,):
        self.turbineTypes = turbineTypes
        self.lower = lower
        self.upper = upper
        n_wt = len(turbineTypes)
Mads M. Pedersen's avatar
Mads M. Pedersen committed

        lim = np.zeros((n_wt, 2))
        lim[:, 0] += lower
        lim[:, 1] += upper
        assert np.all(lim[:, 0] < lim[:, 1])
        self.indeps.add_output('turbineType', np.array(turbineTypes).astype(np.int))
        self.model.add_design_var('turbineType', lower=lim[:, 0], upper=lim[:, 1])
Mads M. Pedersen's avatar
Mads M. Pedersen committed
            self.plot_comp.n_wt = n_wt
    @property
    def state(self):
        state = {'turbineType': np.round(self['turbineType']).astype(np.int)}
        state.update(TopFarmProblem.state.fget(self))
        return state
class TurbineXYZOptimizationProblem(TopFarmProblem):
    def __init__(self, cost_comp, turbineXYZ, boundary_comp, min_spacing=None,
                 driver=ScipyOptimizeDriver(), plot_comp=None, record_id=None, expected_cost=1):
        if plot_comp:
            if plot_comp == "default":
                plot_comp = PlotComp()

        TopFarmProblem.__init__(self, cost_comp, driver, plot_comp, record_id, expected_cost)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        self.initialize(turbineXYZ, boundary_comp, min_spacing)
        self.setup(check=True, mode=self.mode)

    def initialize(self, turbineXYZ, boundary_comp, min_spacing=None):
        turbineXYZ = np.array(turbineXYZ)
        self.turbineXYZ = turbineXYZ
        self.n_wt = n_wt = turbineXYZ.shape[0]
        turbineXYZ = np.hstack((turbineXYZ, np.zeros((n_wt, 4 - turbineXYZ.shape[1]))))
        self.min_spacing = min_spacing
        spacing_comp = SpacingComp(n_wt, min_spacing)
        self.boundary_comp = boundary_comp
        if self.driver.supports['inequality_constraints']:
            spacing_comp.setup_as_constraints(self)
            boundary_comp.setup_as_constraints(self)
        else:
            spacing_comp.setup_as_penalty(self)
            boundary_comp.setup_as_penalty(self)
        dont_scale = (('optimizer' in do and do['optimizer'] == 'SLSQP') or  # scaling disturbs SLSQP
Mads M. Pedersen's avatar
Mads M. Pedersen committed
                      isinstance(self.driver, DOEDriver) or
                      isinstance(self.driver, SimpleGADriver) or
                      isinstance(self.driver, RandomSearchDriver))
        if len(boundary_comp.xy_boundary) > 0:

            ref0_x, ref0_y = self.boundary_comp.xy_boundary.min(0)
            ref_x, ref_y = self.boundary_comp.xy_boundary.max(0)
                ref0_x, ref0_y, ref_x, ref_y = 0, 0, 1, 1
            vertices = self.boundary_comp.xy_boundary
            self.indeps.add_output('boundary', vertices, units='m')

            if 'optimizer' in do and do['optimizer'] == 'SLSQP':
                # Default +/- sys.float_info.max does not work for SLSQP
                self.model.add_design_var('turbineX', lower=np.nan, upper=np.nan)
                self.model.add_design_var('turbineY', lower=np.nan, upper=np.nan)
            else:
                l, u = [f(vertices[:, 0]) * (ref_x - ref0_x) + ref0_x for f in [np.min, np.max]]
                self.model.add_design_var('turbineX', lower=l, upper=u, ref0=ref0_x, ref=ref_x)
                l, u = [f(vertices[:, 1]) * (ref_y - ref0_y) + ref0_y for f in [np.min, np.max]]
                self.model.add_design_var('turbineY', lower=l, upper=u, ref0=ref0_y, ref=ref_y)

        if len(boundary_comp.z_boundary) > 0:
            ref0_z, ref_z = np.min(self.boundary_comp.z_boundary), np.max(self.boundary_comp.z_boundary)
                ref0_z, ref_z = 0, 1
Mads M. Pedersen's avatar
Mads M. Pedersen committed
            l, u = [self.boundary_comp.z_boundary[:, i] * (ref_z - ref0_z) + ref0_z for i in range(2)]
            if 'optimizer' in do and do['optimizer'] == 'SLSQP':
                self.model.add_design_var('turbineZ', lower=np.nan, upper=np.nan)
            else:
                self.model.add_design_var('turbineZ', lower=l, upper=u, ref0=ref0_z, ref=ref_z)

        self.indeps.add_output('turbineX', turbineXYZ[:, 0], units='m')
        self.indeps.add_output('turbineY', turbineXYZ[:, 1], units='m')
        self.indeps.add_output('turbineZ', turbineXYZ[:, 2], units='m')

Mads M. Pedersen's avatar
Mads M. Pedersen committed
        if self.plot_comp:
            self.plot_comp.n_wt = n_wt
            if self.boundary_comp:
Mads M. Pedersen's avatar
Mads M. Pedersen committed
                self.plot_comp.n_vertices = len(self.boundary_comp.xy_boundary)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
                self.plot_comp.n_vertices = 0
    def turbine_positions(self):
        return self.state_array(['turbineX', 'turbineY', 'turbineZ'])
    def state(self):
        state = {'turbine%s' % xyz: self['turbine%s' % xyz] for xyz in 'XYZ'}
        state.update(TopFarmProblem.state.fget(self))
        return state
    def xy_boundary(self):
        xy_b = self.boundary_comp.xy_boundary
        if len(xy_b) > 0:
            return np.r_[xy_b, xy_b[:1]]
    @property
    def z_boundary(self):
        return self.boundary_comp.z_boundary

    def smart_start(self, x, y, val):
        x, y = smart_start(x, y, val, self.n_wt, self.min_spacing)
        self.update_state({'turbineX': x, 'turbineY': y})
        return x, y

Mads M. Pedersen's avatar
Mads M. Pedersen committed
class TurbineTypeXYZOptimizationProblem(TurbineTypeOptimizationProblem, TurbineXYZOptimizationProblem):
    def __init__(self, cost_comp, turbineTypes, lower, upper, turbineXYZ, boundary_comp, min_spacing=None,
                 driver=EasyRandomSearchDriver(random_search_driver.RandomizeTurbineTypeAndPosition()), plot_comp=None, record_id=None, expected_cost=1):

        TopFarmProblem.__init__(self, cost_comp, driver, plot_comp, record_id, expected_cost)
        TurbineTypeOptimizationProblem.initialize(self, turbineTypes, lower, upper)
        TurbineXYZOptimizationProblem.initialize(self, turbineXYZ, boundary_comp, min_spacing)
        self.setup(check=True, mode=self.mode)

    @property
    def state(self):
        state = {k: self[k] for k in ['turbineX', 'turbineY', 'turbineZ']}
        state['turbineType'] = self['turbineType'].astype(np.int)
        state.update(TopFarmProblem.state.fget(self))
        return state


class InitialXYZOptimizationProblem(TurbineXYZOptimizationProblem):
    def __init__(self, cost_comp, turbineXYZ, boundary_comp=None, min_spacing=None,
                 driver=None, plot_comp=None):
        #          if driver is None:
        #              driver = DOEDriver(shuffle_generator(self, 10))
        TurbineXYZOptimizationProblem.__init__(self, cost_comp, turbineXYZ,
                                               boundary_comp, min_spacing,
                                               driver=driver, plot_comp=plot_comp)

#     def shuffle_positions(self, shuffle_type='rel', n_iter=1000,
#                           step_size=0.1, pad=1.1, offset=5, plot=False,
#                           verbose=False):
#         if shuffle_type is not None:
#             turbines = spos(self.boundary, self.n_wt, self.min_spacing,
#                             self.turbine_positions, shuffle_type, n_iter,
#                             step_size, pad, offset, plot, verbose)
#             self.problem['turbineX'] = turbines.T[0]
#             self.problem['turbineY'] = turbines.T[1]


class TopFarm(TurbineXYZOptimizationProblem):
    """Wrapper of TurbineXYZOPtimizationProblem for backward compatibility
    """
    def __init__(self, turbines, cost_comp, min_spacing, boundary,
                 boundary_type='convex_hull', plot_comp=None,
                 driver=ScipyOptimizeDriver(),
                 record_id="Opt_%s" % time.strftime("%Y%m%d_%H%M%S"),
                 expected_cost=1):
        TurbineXYZOptimizationProblem.__init__(self, cost_comp, turbines,
                                               boundary_comp=BoundaryComp(len(turbines), boundary, None, boundary_type),
                                               min_spacing=min_spacing, driver=driver, plot_comp=plot_comp,
                                               record_id=record_id, expected_cost=expected_cost)
class ProblemComponent(ExplicitComponent):
    """class used to wrap a TopFarmProblem as a cost_component"""
    def __init__(self, problem):
        ExplicitComponent.__init__(self)
        self.problem = problem
    def setup(self):
        missing_in_problem = (set([c[0] for c in self.parent.indeps._indep_external]) -
                              set([c[0] for c in self.problem.indeps._indep_external]))
        for name, val, kwargs in self.parent.indeps._indep_external:
            self.add_input(name, val=val, **{k: kwargs[k] for k in ['units']})
            if name in missing_in_problem:
                self.problem.indeps.add_output(name, val, **kwargs)
        self.problem.setup(check=True, mode='fwd')
        self.add_output('cost', val=0.0)
    @property
    def state(self):
        return self.problem.state
Mads M. Pedersen's avatar
Mads M. Pedersen committed
    def cost_function(self, **kwargs):
        return self.problem.optimize(kwargs)[0]

    def compute(self, inputs, outputs):
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        outputs['cost'] = self.cost_function(**inputs)
Mads M. Pedersen's avatar
Mads M. Pedersen committed

def try_me():
    if __name__ == '__main__':
        from openmdao.drivers.doe_generators import FullFactorialGenerator
        from topfarm.cost_models.dummy import DummyCost, DummyCostPlotComp
        from topfarm.easy_drivers import EasyScipyOptimizeDriver
Mads M. Pedersen's avatar
Mads M. Pedersen committed

Mads M. Pedersen's avatar
Mads M. Pedersen committed
        optimal = [(0, 2, 4, 1), (4, 2, 1, 0)]

        plot_comp = DummyCostPlotComp(optimal)

        cost_comp = DummyCost(
            optimal_state=optimal,
            inputs=['turbineX', 'turbineY', 'turbineZ', 'turbineType'])
        xyz_opt_problem = TurbineXYZOptimizationProblem(
            cost_comp,
            turbineXYZ=[(0, 0, 0), (1, 1, 1)],
            min_spacing=2,
            boundary_comp=BoundaryComp(n_wt=2,
                                       xy_boundary=[(0, 0), (4, 4)],
                                       z_boundary=(0, 4),
                                       xy_boundary_type='square'),
            plot_comp=plot_comp,
            driver=EasyScipyOptimizeDriver(disp=False),
            record_id='test:latest')
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        cost, state, recorder = xyz_opt_problem.optimize()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        print(cost)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        recorder.save()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        tf = TurbineTypeOptimizationProblem(
            cost_comp=xyz_opt_problem,
            turbineTypes=[0, 0], lower=0, upper=1,
            driver=DOEDriver(FullFactorialGenerator(2)))
        cost, state, recorder = tf.optimize()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        print(state)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        plot_comp.show()
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        print("Done")