diff --git a/topfarm/_topfarm.py b/topfarm/_topfarm.py index 1344dc02e0bafa9e3bc155fa5bb451ad1770346b..c210f2fb057927648120dae2789e9d8d945640d6 100644 --- a/topfarm/_topfarm.py +++ b/topfarm/_topfarm.py @@ -10,7 +10,7 @@ from topfarm.constraint_components.boundary_component import BoundaryComp,\ PolygonBoundaryComp from topfarm.constraint_components.spacing_component import SpacingComp from topfarm.plotting import PlotComp -from topfarm.recording import pos_from_case, latest_id +from topfarm.utils import pos_from_case, latest_id class TopFarm(object): diff --git a/topfarm/constraint_components/spacing_component.py b/topfarm/constraint_components/spacing_component.py index 318948d9a75fa8a03b96718bb0a4db37125525e9..7d05698157636ec8121f3d903aa72ba924e07159 100644 --- a/topfarm/constraint_components/spacing_component.py +++ b/topfarm/constraint_components/spacing_component.py @@ -33,34 +33,39 @@ class SpacingComp(ExplicitComponent): self.declare_partials('wtSeparationSquared', ['turbineX', 'turbineY']) def compute(self, inputs, outputs): - # print 'in dist const' - turbineX = inputs['turbineX'] turbineY = inputs['turbineY'] + separation_squared = self._compute(turbineX, turbineY) + outputs['wtSeparationSquared'] = separation_squared + + + def _compute(self, turbineX, turbineY): nTurbines = self.nTurbines separation_squared = np.zeros(int((nTurbines - 1) * nTurbines / 2)) - k = 0 for i in range(0, nTurbines): for j in range(i + 1, nTurbines): separation_squared[k] = (turbineX[j] - turbineX[i])**2 + (turbineY[j] - turbineY[i])**2 k += 1 - outputs['wtSeparationSquared'] = separation_squared -# print("wt sep") -# print(separation_squared) -# print() - + return separation_squared + + def compute_partials(self, inputs, J): - # obtain necessary inputs turbineX = inputs['turbineX'] turbineY = inputs['turbineY'] + dSdx, dSdy = self._compute_partials(turbineX, turbineY) + # populate Jacobian dict + J['wtSeparationSquared', 'turbineX'] = dSdx + J['wtSeparationSquared', 'turbineY'] = dSdy + + + def _compute_partials(self, turbineX, turbineY): # get number of turbines nTurbines = self.nTurbines # initialize gradient calculation array - dS = np.zeros((int((nTurbines - 1.) * nTurbines / 2.), 2 * nTurbines)) dSdx = np.zeros((int((nTurbines - 1.) * nTurbines / 2.), nTurbines)) #col: dx_1-dx_n, row: d12, d13,..,d1n, d23..d2n,.. dSdy = np.zeros((int((nTurbines - 1.) * nTurbines / 2.), nTurbines)) @@ -71,20 +76,13 @@ class SpacingComp(ExplicitComponent): for i in range(0, nTurbines): for j in range(i + 1, nTurbines): # separation wrt Xj - dS[k, j] = 2 * (turbineX[j] - turbineX[i]) dSdx[k,j]= 2 * (turbineX[j] - turbineX[i]) # separation wrt Xi - dS[k, i] = -2 * (turbineX[j] - turbineX[i]) dSdx[k, i] = -2 * (turbineX[j] - turbineX[i]) # separation wrt Yj - dS[k, j + nTurbines] = 2 * (turbineY[j] - turbineY[i]) dSdy[k, j] = 2 * (turbineY[j] - turbineY[i]) # separation wrt Yi - dS[k, i + nTurbines] = -2 * (turbineY[j] - turbineY[i]) dSdy[k, i] = -2 * (turbineY[j] - turbineY[i]) # increment turbine pair counter k += 1 - - # populate Jacobian dict - J['wtSeparationSquared', 'turbineX'] = dS[:, :nTurbines] - J['wtSeparationSquared', 'turbineY'] = dS[:, nTurbines:] + return dSdx, dSdy diff --git a/topfarm/recording.py b/topfarm/recording.py deleted file mode 100644 index 3b3672767db07552b4fafccdbbfebb36ba2255d1..0000000000000000000000000000000000000000 --- a/topfarm/recording.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import numpy as np -from openmdao.api import CaseReader - - -def pos_from_case(case_recorder_filename): - cr = CaseReader(case_recorder_filename) - case_list = cr.driver_cases.list_cases() - case_len = len(case_list) - case_arg = 'rank0:SLSQP|{:d}'.format(case_len-1) - case = cr.driver_cases.get_case(case_arg) - x = np.array(case.desvars['turbineX']) - y = np.array(case.desvars['turbineY']) - turbines = np.column_stack((x, y)) - return turbines - - -def latest_id(case_recorder_dir): - files = os.listdir(case_recorder_dir) - files = [x for x in files if x.startswith('cases_') and x.endswith('.sql')] - if len(files) == 0: - string = 'No recorded files found in the specified directory: ' - string += case_recorder_dir + '\n' + 9*' ' - string += 'Start a new optimization or specify another directory ' - string += 'for resumed optimization' - raise Warning(string) - latest = max(files) - latest = os.path.join(case_recorder_dir,latest) - return latest - -if __name__ == '__main__': - crf = r"C:\Sandbox\Git\TopFarm2\topfarm\cases_20180621_104446.sql" - case_recorder_filename = crf - turbines = pos_from_case(case_recorder_filename) - print(turbines) - - case_recorder_dir = r'C:\Sandbox\Git\TopFarm2\topfarm' - latest_id = latest_id(case_recorder_dir) - print(latest_id) diff --git a/topfarm/utils.py b/topfarm/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..df16d39da6460122fe00afe2927a734509e3e396 --- /dev/null +++ b/topfarm/utils.py @@ -0,0 +1,147 @@ +from topfarm.constraint_components.boundary_component import \ + PolygonBoundaryComp +from topfarm.constraint_components.spacing_component import SpacingComp +import os +from copy import copy +import numpy as np +from openmdao.api import CaseReader +import matplotlib.pyplot as plt + + +def pos_from_case(case_recorder_filename): + ''' + Input: a recorded optimization case + Returns the positions at the last step of that optimization + ''' + if not os.path.exists(case_recorder_filename): + string = 'The specified optimization recording does not exists: ' + string += case_recorder_filename + raise Warning(string) + cr = CaseReader(case_recorder_filename) + case_list = cr.driver_cases.list_cases() + case_len = len(case_list) + case_arg = 'rank0:SLSQP|{:d}'.format(case_len-1) + case = cr.driver_cases.get_case(case_arg) + x = np.array(case.desvars['turbineX']) + y = np.array(case.desvars['turbineY']) + turbines = np.column_stack((x, y)) + return turbines + + +def latest_id(case_recorder_dir): + ''' + Input: path to the directory of recorded optimizations + Returns the absolute path to the most recent recording in that directory + ''' + files = os.listdir(case_recorder_dir) + files = [x for x in files if x.startswith('cases_') and x.endswith('.sql')] + if len(files) == 0: + string = 'No recorded files found in the specified directory: ' + string += case_recorder_dir + '\n' + 9*' ' + string += 'Start a new optimization or specify another directory ' + string += 'for resumed optimization' + raise Warning(string) + latest = max(files) + latest = os.path.join(case_recorder_dir, latest) + return latest + + +def random_positions(boundary, n_wt, n_iter, step_size, min_space, + pad = 1.1, plot=False, verbose=True): + ''' + Input: + boundary: list of tuples, e.g.: [(0, 0), (6, 1), (7, -11), (-1, -10)] + n_wt: number of wind turbines + n_iter: number of iterations allowed to try and satisfy the minimum + spacing constraint + step_size: the multiplier on the spacing gradient that the turbines + are moved in each step + min_space: the minimum spacing between turbines + pad: the multiplier on the boundary gradient + plot: plot the generated random layout + verbose: print helpful text to the console + Returns an array of xy coordinates of the wind turbines + ''' + x, y = map(_random, np.array(boundary).T) + turbines = _random_positions(x, y, boundary, n_wt, n_iter, step_size, + min_space, pad, plot, verbose) + return turbines + + +def _random_positions(x, y, boundary, n_wt, n_iter, step_size, min_space, + pad, plot, verbose): + boundary_comp = PolygonBoundaryComp(boundary, n_wt) + spacing_comp = SpacingComp(nTurbines=n_wt) + turbineX = copy(x) + turbineY = copy(y) + min_space2 = min_space**2 + ba = np.array(boundary).T + bx = np.append(ba[0], ba[0][0]) + by = np.append(ba[1], ba[1][0]) + if plot: + plt.cla() + plt.plot(bx, by) + plt.plot(x, y, '.') + for j in range(n_iter): + dist = spacing_comp._compute(turbineX, turbineY) + dx, dy = spacing_comp._compute_partials(turbineX, turbineY) + index = np.argmin(dist) + if dist[index] < min_space2: + turbineX += dx[index]*step_size + turbineY += dy[index]*step_size + turbineX, turbineY = _contain(n_wt, turbineX, turbineY, + boundary_comp, pad) + else: + if verbose: + print('Obtained required spacing after {} iterations'.format( + j)) + break + if plot: + plt.plot(turbineX, turbineY, 'o') + dist = spacing_comp._compute(turbineX, turbineY) + index = np.argmin(dist) + if verbose: + print('Spacing obtained: {}'.format(dist[index]**0.5)) + print('Spacing required: {}'.format(min_space)) + turbines = np.array([turbineX, turbineY]).T + return turbines + + +def _random(b): + return np.random.rand(n_wt) * (max(b) - min(b)) + min(b) + + +def _contain(n_wt, turbineX, turbineY, boundary_comp, pad): + for i in range(0, n_wt): + dng = boundary_comp.calc_distance_and_gradients(turbineX, + turbineY) + dist = dng[0][i] + if dist < 0: + dx = dng[1][i] + dy = dng[2][i] + turbineX[i] -= dx*dist*pad + turbineY[i] -= dy*dist*pad + return turbineX, turbineY + + +if __name__ == '__main__': +# this_dir = os.path.dirname(os.path.abspath(__file__)) +# crf = r"C:\Sandbox\Git\TopFarm2\topfarm\cases_20180621_111710.sql" +# case_recorder_filename = crf +# turbines = pos_from_case(case_recorder_filename) +# print(turbines) +# +# case_recorder_dir = r'C:\Sandbox\Git\TopFarm2\topfarm' +# latest_id = latest_id(case_recorder_dir) +# print(latest_id) + + boundary = [(0, 0), (6, 1), (7, -11), (-1, -10)] + n_wt = 20 + n_iter = 1000 + step_size = 0.1 + min_space = 2.1 + plot = True + verbose = True + turbines = random_positions(boundary, n_wt, n_iter, step_size, min_space, + plot, verbose) + print(turbines)