-
Mikkel Friis-Møller authoredMikkel Friis-Møller authored
_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()