Skip to content
Snippets Groups Projects
_topfarm.py 7.13 KiB
from topfarm.constraint_components.boundary_component import BoundaryComp,\
    PolygonBoundaryComp
from topfarm.constraint_components.spacing_component import SpacingComp
from topfarm.plotting import PlotComp
from topfarm.utils import pos_from_case, latest_id
import os
import time
import numpy as np
import warnings
with warnings.catch_warnings():
    warnings.simplefilter('ignore', FutureWarning)
    from openmdao.api import Problem, ScipyOptimizeDriver, IndepVarComp, \
        SqliteRecorder

class TopFarm(object):
    """Optimize wind farm layout in terms of
    - Position of turbines
    [- Type of turbines: Not implemented yet]
    [- Height of turbines: Not implemented yet]
    [- Number of turbines: Not implemented yet]
    """

    def __init__(self, turbines, cost_comp, min_spacing, boundary,
                 boundary_type='convex_hull', plot_comp=None,
                 driver=ScipyOptimizeDriver(), record=False,
                 case_recorder_dir=os.getcwd(), rerun_case_id=None):
        if rerun_case_id is None:
            self.initial_positions = turbines = np.array(turbines)
        elif rerun_case_id is 'latest':
            rerun_case_id = latest_id(case_recorder_dir)
            self.initial_positions = turbines = pos_from_case(rerun_case_id)
            print('*Initial positions loaded from file: {}\n'.format(
                    rerun_case_id))
        else:
            self.initial_positions = turbines = pos_from_case(rerun_case_id)
        n_wt = turbines.shape[0]
        if boundary_type == 'polygon':
            self.boundary_comp = PolygonBoundaryComp(boundary, n_wt)
        else:
            self.boundary_comp = BoundaryComp(boundary, n_wt, boundary_type)
        self.problem = prob = Problem()
        indeps = prob.model.add_subsystem('indeps', IndepVarComp(),
                                          promotes=['*'])
        min_x, min_y = self.boundary_comp.vertices.min(0)
        mean_x, mean_y = self.boundary_comp.vertices.mean(0)
        design_var_kwargs = {}

        if 'optimizer' in driver.options and driver.options['optimizer'] == 'SLSQP':
            min_x, min_y, mean_x, mean_y = 0, 0, 1, 1  # scaling disturbs SLSQP
            # Default +/- sys.float_info.max does not work for SLSQP
            design_var_kwargs = {'lower': np.nan, 'upper': np.nan}
        indeps.add_output('turbineX', turbines[:, 0], units='m', ref0=min_x, ref=mean_x)
        indeps.add_output('turbineY', turbines[:, 1], units='m', ref0=min_y, ref=mean_y)
        indeps.add_output('boundary', self.boundary_comp.vertices, units='m')
        prob.model.add_subsystem('cost_comp', cost_comp, promotes=['*'])
        prob.driver = driver

        if record:
            timestr = time.strftime("%Y%m%d_%H%M%S")
            filename = 'cases_{}.sql'.format(timestr)
            case_recorder_filename = os.path.join(case_recorder_dir, filename)
            recorder = SqliteRecorder(case_recorder_filename)
            prob.driver.add_recorder(recorder)
            prob.driver.recording_options['record_desvars'] = True
            prob.driver.recording_options['record_responses'] = True
            prob.driver.recording_options['record_objectives'] = True
            prob.driver.recording_options['record_constraints'] = True

        prob.model.add_design_var('turbineX', **design_var_kwargs)
        prob.model.add_design_var('turbineY', **design_var_kwargs)
        prob.model.add_objective('cost')

        prob.model.add_subsystem('spacing_comp', SpacingComp(nTurbines=n_wt),
                                 promotes=['*'])
        prob.model.add_subsystem('bound_comp', self.boundary_comp,
                                 promotes=['*'])
        if plot_comp == "default":
            plot_comp = PlotComp()
        if plot_comp:
            plot_comp.n_wt = n_wt
            plot_comp.n_vertices = self.boundary_comp.vertices.shape[0]
            prob.model.add_subsystem('plot_comp', plot_comp, promotes=['*'])

        self.plot_comp = plot_comp
        prob.model.add_constraint('wtSeparationSquared', lower=np.zeros(int(((n_wt - 1.) * n_wt / 2.))) + (min_spacing)**2)
        prob.model.add_constraint('boundaryDistances', lower=np.zeros(self.boundary_comp.nVertices * n_wt))

        prob.setup(check=True, mode='fwd')


    def check(self, all=False, tol=1e-3):
        """Check gradient computations"""
        comp_name_lst = [comp.pathname for comp in self.problem.model.system_iter()
                         if comp._has_compute_partials and
                         (comp.pathname not in ['spacing_comp', 'bound_comp', 'plot_comp'] or (all and comp.pathname != 'plot_comp'))]
        print("checking %s" % ", ".join(comp_name_lst))
        res = self.problem.check_partials(comps=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 evaluate(self):
        t = time.time()
        self.problem.run_model()
        print("Evaluated in\t%.3fs" % (time.time() - t))
        return self.get_cost(), self.turbine_positions

    def evaluate_gradients(self):
        t = time.time()
        res = self.problem.compute_totals(['cost'], wrt=['turbineX', 'turbineY'], return_format='dict')
        print("Gradients evaluated in\t%.3fs" % (time.time() - t))
        return res

    def optimize(self):
        t = time.time()
        self.problem.run_driver()
        print("Optimized in\t%.3fs" % (time.time() - t))
        return np.array([self.problem['turbineX'], self.problem['turbineY']]).T

    def get_cost(self):
        return self.problem['cost'][0]

    @property
    def boundary(self):
        b = self.boundary_comp.vertices
        return np.r_[b, b[:1]]

    @property
    def turbine_positions(self):
        return np.array([self.problem['turbineX'], self.problem['turbineY']]).T


    def post_process(self, anim_time=10, verbose=True):
        if self.plot_comp.animate:
           self.plot_comp.run_animate(anim_time, verbose)
        for file in os.listdir(self.plot_comp.temp):
            if file.startswith('plot_') and file.endswith('.png'):
                os.remove(os.path.join(self.plot_comp.temp,file))


def try_me():
    if __name__ == '__main__':
        from topfarm.cost_models.dummy import DummyCostPlotComp, DummyCost

        n_wt = 4
        random_offset = 5
        optimal = [(3, -3), (7, -7), (4, -3), (3, -7), (-3, -3), (-7, -7), (-4, -3), (-3, -7)][:n_wt]
        rotorDiameter = 1.0
        minSpacing = 2.0

        turbines = np.array(optimal) + np.random.randint(-random_offset, random_offset, (n_wt, 2))
        plot_comp = DummyCostPlotComp(optimal)
        plot_comp.animate = True

        boundary = [(0, 0), (6, 0), (6, -10), (0, -10)]
        tf = TopFarm(turbines, DummyCost(optimal), minSpacing * rotorDiameter, boundary=boundary, plot_comp=plot_comp)
        # tf.check()
        tf.optimize()
        tf.post_process()

try_me()