diff --git a/requirements.txt b/requirements.txt index 35c4519a8d810178a7ab4194268283be6a056b2a..24a4f4bc3f379b3e16a993590cf807e8e86d9d0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ rsa~=4.9 pyasn1~=0.4.8 toolz~=0.12.0 utm~=0.7.0 -numpy~=1.22.4 +numpy~=1.23.5 pyzmq~=23.2.1 pexpect~=4.8.0 pymongo~=4.2.0 @@ -49,7 +49,7 @@ HeapDict~=1.0.1 anyio~=3.6.1 sniffio~=1.2.0 Babel~=2.10.3 -setuptools>=65.3.0 +setuptools~=59.6.0 isort~=5.10.1 tomli~=2.0.1 tensorflow~=2.9.1 @@ -64,7 +64,7 @@ ipykernel nbformat~=5.4.0 cloudpickle~=2.1.0 Cython~=0.29.32 -matplotlib~=3.5.3 +matplotlib~=3.6.2 mpmath sympy decorator~=5.1.1 @@ -107,9 +107,9 @@ mistune~=2.0.4 Markdown~=3.4.1 ptyprocess~=0.7.0 autograd~=1.4 -scikit-learn -tqdm -Shapely +scikit-learn~=1.1.2 +tqdm~=4.64.1 +Shapely~=2.0.0 threadpoolctl~=3.1.0 openmdao~=3.16.0 nbclient~=0.6.7 @@ -152,3 +152,4 @@ json5~=0.9.10 click~=8.1.3 windIO~=0.1 fusedwake~=0.1.0 +geopandas~=0.12.2 \ No newline at end of file diff --git a/topfarm/constraint_components/boundary.py b/topfarm/constraint_components/boundary.py index 84884910a6ebfe07349a3b1f02a638c509718f44..2b09607ab60cf2e21ea1a526584e7b80db56ce99 100644 --- a/topfarm/constraint_components/boundary.py +++ b/topfarm/constraint_components/boundary.py @@ -18,8 +18,8 @@ import matplotlib.pyplot as plt from matplotlib.pyplot import Circle # from shapely.ops import cascaded_union -from shapely.geometry import MultiPoint, Polygon, MultiPolygon -from shapely.ops import unary_union +from shapely.geometry import MultiPoint, Polygon, MultiPolygon, LineString +# from shapely.ops import unary_union import geopandas as gpd import topfarm @@ -33,7 +33,7 @@ class XYBoundaryConstraint(Constraint): TODO: missing-class-docstring """ - def __init__(self, boundary, boundary_type: str = 'convex_hull', units=None, relaxation: bool = False): + def __init__(self, boundary, boundary_type: str = 'convex_hull', units=None, relaxation: bool = False, **kwargs): """Initialize XYBoundaryConstraint Parameters @@ -49,11 +49,13 @@ class XYBoundaryConstraint(Constraint): - 'polygon': Polygon boundary (may be non-convex). Less suitable for gradient-based optimization \n - 'rectangle': Smallest axis-aligned rectangle covering the boundary points \n - 'square': Smallest axis-aligned square covering the boundary points - - 'multi_polygon': Mulitple polygon boundaries incl. exclusion zones (may be non convex). \n + - 'multi_polygon': Multiple polygon boundaries incl. exclusion zones (may be non convex). \n - 'type_specific': Set of multiple polygon boundaries that depend on the wind turbine type. \n units : relaxation : + kwargs : + """ self.alpha = None @@ -109,8 +111,8 @@ class XYBoundaryConstraint(Constraint): """ if self.boundary_type in ['multi_polygon', 'turbine_specific']: - bound_min = np.vstack([(bound).min(0) for bound, _ in self.boundary_comp.boundaries]).min(0) - bound_max = np.vstack([(bound).max(0) for bound, _ in self.boundary_comp.boundaries]).max(0) + bound_min = np.vstack([bound.min(0) for bound, _ in self.boundary_comp.boundaries]).min(0) + bound_max = np.vstack([bound.max(0) for bound, _ in self.boundary_comp.boundaries]).max(0) else: bound_min = self.boundary_comp.xy_boundary.min(0) bound_max = self.boundary_comp.xy_boundary.max(0) @@ -222,6 +224,7 @@ class BoundaryBaseComp(ConstraintComponent, ABC): self.const_id = const_id self.units = units self.relaxation = relaxation + if np.any(self.xy_boundary[0] != self.xy_boundary[-1]): self.xy_boundary = np.r_[self.xy_boundary, self.xy_boundary[:1]] @@ -260,9 +263,9 @@ class BoundaryBaseComp(ConstraintComponent, ABC): # calculate distances from each point to each face args = {x: inputs[x] for x in [topfarm.x_key, topfarm.y_key, topfarm.type_key] if x in inputs} - boundaryDistances = self.distances(**args) - outputs['boundaryDistances'] = boundaryDistances - outputs['penalty_' + self.const_id] = np.sum(np.minimum(boundaryDistances, 0) ** 2) + boundary_distances = self.distances(**args) + outputs['boundaryDistances'] = boundary_distances + outputs['penalty_' + self.const_id] = np.sum(np.minimum(boundary_distances, 0) ** 2) def compute_partials(self, inputs, partials): """ return Jacobian dict """ @@ -280,24 +283,31 @@ class BoundaryBaseComp(ConstraintComponent, ABC): def plot(self, ax): """Plot boundary""" + if isinstance(self, TurbineSpecificBoundaryComp): linestyles = ['--', '-'] colors = np.array(['b', 'r', 'm', 'c', 'g', 'y', 'orange', 'indigo', 'grey'] * 10) for n, t in enumerate(self.types): - line, = ax.plot(*self.ts_merged_xy_boundaries[n][0][0][0, :], color=colors[t], linewidth=1, label=f'{self.wind_turbines._names[n]} boundary') + ax.plot(*self.ts_merged_xy_boundaries[n][0][0][0, :], color=colors[t], linewidth=1, + label=f'{self.wind_turbines._names[n]} boundary') + for bound, io in self.ts_merged_xy_boundaries[n]: ax.plot(np.asarray(bound)[:, 0].tolist() + [np.asarray(bound)[0, 0]], - np.asarray(bound)[:, 1].tolist() + [np.asarray(bound)[0, 1]], color=colors[t], linewidth=1, linestyle=linestyles[io]) + np.asarray(bound)[:, 1].tolist() + [np.asarray(bound)[0, 1]], color=colors[t], linewidth=1, + linestyle=linestyles[io]) + elif isinstance(self, MultiPolygonBoundaryComp): colors = ['--k', 'k'] if self.relaxation != 0: for bound, io in self.relaxed_polygons(): ax.plot(np.asarray(bound)[:, 0].tolist() + [np.asarray(bound)[0, 0]], - np.asarray(bound)[:, 1].tolist() + [np.asarray(bound)[0, 1]], c='r', linewidth=1, linestyle='--') + np.asarray(bound)[:, 1].tolist() + [np.asarray(bound)[0, 1]], c='r', linewidth=1, + linestyle='--') ax.plot([], c='r', linewidth=1, linestyle='--', label='Relaxed boundaries') for bound, io in self.boundaries: ax.plot(np.asarray(bound)[:, 0].tolist() + [np.asarray(bound)[0, 0]], np.asarray(bound)[:, 1].tolist() + [np.asarray(bound)[0, 1]], colors[io], linewidth=1) + else: ax.plot(self.xy_boundary[:, 0].tolist() + [self.xy_boundary[0, 0]], self.xy_boundary[:, 1].tolist() + [self.xy_boundary[0, 1]], 'k', linewidth=1) @@ -399,8 +409,8 @@ class ConvexBoundaryComp(BoundaryBaseComp): unit_normals = self.unit_normals # initialize array to hold distances from each point to each face - dfaceDistance_dx = np.zeros([self.n_wt * self.nVertices, self.n_wt]) - dfaceDistance_dy = np.zeros([self.n_wt * self.nVertices, self.n_wt]) + dface_distance_dx = np.zeros([self.n_wt * self.nVertices, self.n_wt]) + dface_distance_dy = np.zeros([self.n_wt * self.nVertices, self.n_wt]) for i in range(0, self.n_wt): # Determine whether the point is inside or outside each face, and the distances from each face @@ -411,43 +421,40 @@ class ConvexBoundaryComp(BoundaryBaseComp): # Determination of the derivatives of the perpendicular distances from a point to the current surface # (vector projection) - ddistanceVec_dx = np.vdot(dpa_dx, unit_normals[j]) * unit_normals[j] - ddistanceVec_dy = np.vdot(dpa_dy, unit_normals[j]) * unit_normals[j] + diff_dist_vec_dx = np.vdot(dpa_dx, unit_normals[j]) * unit_normals[j] + diff_dist_vec_dy = np.vdot(dpa_dy, unit_normals[j]) * unit_normals[j] # Calculate derivatives for the sign of perpendicular distances from the point to the current face - dfaceDistance_dx[i * self.nVertices + j, i] = np.vdot(ddistanceVec_dx, unit_normals[j]) - dfaceDistance_dy[i * self.nVertices + j, i] = np.vdot(ddistanceVec_dy, unit_normals[j]) + dface_distance_dx[i * self.nVertices + j, i] = np.vdot(diff_dist_vec_dx, unit_normals[j]) + dface_distance_dy[i * self.nVertices + j, i] = np.vdot(diff_dist_vec_dy, unit_normals[j]) # return Jacobian dict - self.dfaceDistance_dx = dfaceDistance_dx - self.dfaceDistance_dy = dfaceDistance_dy + self.dfaceDistance_dx = dface_distance_dx + self.dfaceDistance_dy = dface_distance_dy - def calculate_distance_to_boundary(self, points): + def calculate_distance_to_boundary(self, points) -> NDArray: """ :param points: points that you want to calculate the distances from to the faces of the convex hull - :return face_distace: signed perpendicular distances from each point to each face; + is inside + :return face_distance: signed perpendicular distances from each point to each face; + is inside """ - nPoints = np.array(points).shape[0] xy_boundary = self.xy_boundary[:-1] - nVertices = xy_boundary.shape[0] vertices = xy_boundary unit_normals = self.unit_normals - # initialize array to hold distances from each point to each face - face_distance = np.zeros([nPoints, nVertices]) # define the vector from the point of interest to the first point of the face - PA = (vertices[:, na] - points[na]) + pa = (vertices[:, na] - points[na]) # find perpendicular distances from point to current surface (vector projection) - dist = np.sum(PA * unit_normals[:, na], 2) + dist = np.sum(pa * unit_normals[:, na], 2) + # calculate the sign of perpendicular distances from point to current face (+ is inside, - is outside) d_vec = dist[:, :, na] * unit_normals[:, na] face_distance = np.sum(d_vec * unit_normals[:, na], 2) return face_distance.T - def distances(self, x: NDArray, y: NDArray): + def distances(self, x: NDArray, y: NDArray) -> NDArray: """ TODO: missing-method-docstring """ @@ -467,25 +474,25 @@ class ConvexBoundaryComp(BoundaryBaseComp): """ x, y = [np.asarray(state[xyz], dtype=float) for xyz in [topfarm.x_key, topfarm.y_key]] - dist = self.distances(x, y) - dist = np.where(dist < 0, np.minimum(dist, -.01), dist) + np_dist = self.distances(x, y) + np_dist = np.where(np_dist < 0, np.minimum(np_dist, -.01), np_dist) dx, dy = self.gradients(x, y) # independent of position dx = dx[:self.nVertices, 0] dy = dy[:self.nVertices, 0] - for i in np.where(dist.min(1) < 0)[0]: # loop over turbines that violate edges + for i in np.where(np_dist.min(1) < 0)[0]: # loop over turbines that violate edges # find the smallest movement that where the constraints are satisfied - d = dist[i] + d = np_dist[i] v = np.linspace(-np.abs(d.min()), np.abs(d.min()), 100) - X, Y = np.meshgrid(v, v) - m = np.ones_like(X) + np_grid_x, np_grid_y = np.meshgrid(v, v) + m = np.ones_like(np_grid_x) - for dx_, dy_, d in zip(dx, dy, dist.T): - m = np.logical_and(m, X * dx_ + Y * dy_ >= -d[i]) + for dx_, dy_, d in zip(dx, dy, np_dist.T): + m = np.logical_and(m, np_grid_x * dx_ + np_grid_y * dy_ >= -d[i]) - index = np.argmin(X[m] ** 2 + Y[m] ** 2) - x[i] += X[m][index] - y[i] += Y[m][index] + index = np.argmin(np_grid_x[m] ** 2 + np_grid_y[m] ** 2) + x[i] += np_grid_x[m][index] + y[i] += np_grid_y[m][index] state[topfarm.x_key] = x state[topfarm.y_key] = y @@ -517,7 +524,7 @@ class PolygonBoundaryComp(BoundaryBaseComp): @staticmethod def get_boundary_properties(xy_boundary, inclusion_zone=True): """ - TODO: missing-method-docstring + TODO: missing-staticmethod-docstring """ vertices = np.array(xy_boundary) @@ -561,6 +568,7 @@ class PolygonBoundaryComp(BoundaryBaseComp): boundary_properties = boundary_properties or self.boundary_properties[1:] A, B, AB, AB_len, edge_unit_normal, A_normal, B_normal = boundary_properties + """ A: edge start point B: edge end point @@ -599,12 +607,12 @@ class PolygonBoundaryComp(BoundaryBaseComp): # Perpendicular distances to edge (AP dot edge_unit_normal product). # This is the distance to the edge if not use_A or use_B - distance = np.sum((AP) * edge_unit_normal, 0) + distance = np.sum(AP * edge_unit_normal, 0) # Update distance for points closer to A - good_side_of_A = (np.sum((AP * A_normal)[:, use_A], 0) > 0) - sign_use_A = np.where(good_side_of_A, 1, -1) - distance[use_A] = (vec_len(AP[:, use_A]) * sign_use_A) + good_side_of_a = (np.sum((AP * A_normal)[:, use_A], 0) > 0) + sign_use_a = np.where(good_side_of_a, 1, -1) + distance[use_A] = (vec_len(AP[:, use_A]) * sign_use_a) # Update distance for points closer to B good_side_of_B = np.sum((BP * B_normal)[:, use_B], 0) > 0 @@ -617,14 +625,14 @@ class PolygonBoundaryComp(BoundaryBaseComp): # Gradient of perpendicular distances to edge. # This is the gradient if not use_A or use_B - ddist_dxy = np.tile(edge_unit_normal, (1, len(x), 1)) + diff_dist_dxy = np.tile(edge_unit_normal, (1, len(x), 1)) # Update gradient for points closer to A or B - ddist_dxy[:, use_A] = sign_use_A * (AP[:, use_A] / vec_len(AP[:, use_A])) - ddist_dxy[:, use_B] = sign_use_B * (BP[:, use_B] / vec_len(BP[:, use_B])) - ddist_dX, ddist_dY = ddist_dxy + diff_dist_dxy[:, use_A] = sign_use_a * (AP[:, use_A] / vec_len(AP[:, use_A])) + diff_dist_dxy[:, use_B] = sign_use_B * (BP[:, use_B] / vec_len(BP[:, use_B])) + diff_dist_dx, diff_dist_dy = diff_dist_dxy - return distance, ddist_dX, ddist_dY + return distance, diff_dist_dx, diff_dist_dy def calc_distance_and_gradients(self, x: NDArray, y: NDArray): """ @@ -633,10 +641,10 @@ class PolygonBoundaryComp(BoundaryBaseComp): if np.all(np.array([x, y]) == self._cache_input): return self._cache_output - distance, ddist_dX, ddist_dY = self._calc_distance_and_gradients(x, y) + distance, diff_dist_dx, diff_dist_dy = self._calc_distance_and_gradients(x, y) closest_edge_index = np.argmin(np.abs(distance), 1) self._cache_input = np.array([x, y]) - self._cache_output = [np.choose(closest_edge_index, v.T) for v in [distance, ddist_dX, ddist_dY]] + self._cache_output = [np.choose(closest_edge_index, v.T) for v in [distance, diff_dist_dx, diff_dist_dy]] return self._cache_output @@ -706,14 +714,14 @@ class CircleBoundaryComp(PolygonBoundaryComp): circle = Circle(self.center, self.radius, color='k', fill=False) ax.add_artist(circle) - def distances(self, x, y): + def distances(self, x: NDArray, y: NDArray) -> NDArray: """ TODO: missing-method-docstring """ return self.radius - np.sqrt((x - self.center[0]) ** 2 + (y - self.center[1]) ** 2) - def gradients(self, x, y): + def gradients(self, x: NDArray, y: NDArray) -> (NDArray, NDArray): """ TODO: missing-method-docstring """ @@ -729,7 +737,23 @@ class CircleBoundaryComp(PolygonBoundaryComp): class Zone(object): + """ + TODO: missing-class-docstring + """ def __init__(self, boundary, dist2wt, geometry_type, incl, name): + """ + TODO: missing-init-docstring + + Parameters + ---------- + boundary: + dist2wt: + geometry_type: + incl: + name: + + """ + self.name = name self.boundary = boundary self.dist2wt = dist2wt @@ -738,11 +762,17 @@ class Zone(object): class InclusionZone(Zone): + """ + TODO: missing-class-docstring + """ def __init__(self, boundary, dist2wt=None, geometry_type='polygon', name=''): super().__init__(np.asarray(boundary), dist2wt, geometry_type, incl=1, name=name) class ExclusionZone(Zone): + """ + TODO: missing-class-docstring + """ def __init__(self, boundary, dist2wt=None, geometry_type='polygon', name=''): super().__init__(np.asarray(boundary), dist2wt, geometry_type, incl=0, name=name) @@ -758,71 +788,59 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): Parameters ---------- - n_wt : TYPE - DESCRIPTION. - zones : list + n_wt : + turbine identification + zones : list of InclusionZone and ExclusionZone objects const_id : TYPE, optional - DESCRIPTION. The default is None. + The default is None. units : TYPE, optional - DESCRIPTION. The default is None. + The default is None. method : {'nearest' or 'smooth_min'}, optional - 'nearest' calculate the distance to the nearest edge or point'smooth_min' + 'nearest' calculate the distance to the nearest edge or point 'smooth_min' calculates the weighted minimum distance to all edges/points. The default is 'nearest'. simplify_geometry : bool or dict - if float, simplification tolerance. if dict, shapely simplify keyword arguments - - Returns - ------- - None. + if is a float, simplification tolerance. if dict, shapely simplify keyword arguments """ self.zones = zones - self.bounds_poly, xy_boundaries = self.get_xy_boundaries() - PolygonBoundaryComp.__init__(self, n_wt, xy_boundary=xy_boundaries[0], const_id=const_id, units=units, relaxation=relaxation) - # self.bounds_poly = [Polygon(x) for x in xy_boundaries] - self.incl_excls = [x.incl for x in zones] - self._setup_boundaries() self.relaxation = relaxation self.method = method - self.xy_multi_boundary = xy_multi_boundary + self.bounds_poly, xy_boundaries = self.get_xy_boundaries() + + # inclusion zones only + PolygonBoundaryComp.__init__(self, n_wt, + xy_boundary=xy_boundaries[0], + const_id=const_id, + units=units, + relaxation=relaxation) + self.incl_excls = [x.incl for x in zones] + self._setup_boundaries() + + ################################################################### + # Geopandas pre processing list_mpolygon = [] list_zn_type = [] - for poly_i, zn_i in xy_multi_boundary: + for poly_i, zn_i in zones: list_mpolygon.append(Polygon(poly_i)) if zn_i == 'i' or zn_i is True or zn_i == 1: list_zn_type.append(True) else: list_zn_type.append(False) - geo_mpolygon = gpd.GeoDataFrame(dict(inclusion=list_zn_type, geometry=list_mpolygon)) - geo_mpolygon_inz = gpd.GeoSeries(geo_mpolygon[geo_mpolygon.inclusion == True].unary_union) - - shp_cvx_hull = geo_mpolygon_inz.convex_hull - np_multi_boundary = np.asarray(shp_cvx_hull[0].boundary.xy).T - - # inclusion zones only - PolygonBoundaryComp.__init__(self, n_wt, - xy_boundary=np_multi_boundary, - const_id=const_id, - units=units, - relaxation=relaxation) - - self.bounds_poly = list_mpolygon + self.geo_mpolygon = gpd.GeoDataFrame(dict(inclusion=list_zn_type, geometry=list_mpolygon)) self.types_bool = np.array(list_zn_type, dtype=int).tolist() - self._setup_boundaries() - self.relaxation = relaxation - self.method = method + ################################################################### # FIXME : shapely/shapely/geometry/base.py -> line 452, def simplify(self, tolerance, preserve_topology=True) if simplify_geometry: self.simplify(simplify_geometry) - def simplify(self, simplify_geometry): + def simplify(self, simplify_geometry: bool = False): """ TODO: missing-method-docstring """ @@ -831,6 +849,7 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): self.bounds_poly = [rp.simplify(**simplify_geometry) for rp in self.bounds_poly] else: self.bounds_poly = [rp.simplify(simplify_geometry) for rp in self.bounds_poly] + self._setup_boundaries() def get_xy_boundaries(self): @@ -846,10 +865,12 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): buffer = z.dist2wt(**{k: 100 for k in z.dist2wt.__code__.co_varnames}) else: buffer = 0 + if z.geometry_type == 'line': poly = Polygon(LineString(z.boundary).buffer(buffer, join_style=2).exterior) elif z.geometry_type == 'polygon': poly = Polygon(z.boundary).buffer(buffer, join_style=2) + polygons.append(poly) bounds.append(np.asarray(poly.exterior.coords)) @@ -871,13 +892,16 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): @staticmethod def _poly_to_bound(polygons): + boundaries = [] for bound in polygons: x, y = bound.exterior.xy boundaries.append((np.asarray([x, y]).T[:-1, :], 1)) + for interior in bound.interiors: x, y = interior.xy boundaries.append((np.asarray([x, y]).T[:-1, :], 0)) + return boundaries def _calc_resulting_polygons(self, boundary_polygons: list): @@ -885,7 +909,7 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): Parameters ---------- boundary_polygons : - list of shapely polygons as specifed or inferred from user input + list of shapely polygons as specified or inferred from user input Returns ------- list of merged shapely polygons. Resolve issues arising if any are overlapping, touching, or contained in @@ -950,7 +974,7 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): return domain @staticmethod - def sign(dist_ij): + def sign(dist_ij: NDArray) -> NDArray: """ TODO: docstring """ @@ -981,26 +1005,28 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): if np.all(np.array([x, y]) == self._cache_input) & (not self.relaxation): return self._cache_output - dist_ij, ddist_d_x, ddist_d_y = self._calc_distance_and_gradients(x, y, self.boundary_properties_list_all) + dist_ij, diff_dist_d_x, diff_dist_d_y = self._calc_distance_and_gradients(x, y, + self.boundary_properties_list_all) - dDdk_ijk = np.moveaxis([ddist_d_x, ddist_d_y], 0, -1) + diff_dk_ijk = np.moveaxis([diff_dist_d_x, diff_dist_d_y], 0, -1) sign_i = self.sign(dist_ij) self._cache_input = np.array([x, y]) - self._cache_output = [dist_ij, dDdk_ijk, sign_i] + self._cache_output = [dist_ij, diff_dk_ijk, sign_i] return self._cache_output - def calc_relaxation(self, iteration_no=None): + def calc_relaxation(self, iteration_no=None) -> float: """ - The tupple relaxation contains a first term for the penalty constant + The tuple relaxation contains a first term for the penalty constant and a second term for the n first iterations to apply relaxation. """ if iteration_no is None: iteration_no = self.problem.cost_comp.n_grad_eval + 1 + return max(0, self.relaxation[0] * (self.relaxation[1] - iteration_no)) - def distances(self, x, y): + def distances(self, x: NDArray, y: NDArray) -> NDArray: """ TODO: docstring @@ -1030,9 +1056,9 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): return dist_i - def gradients(self, x, y): + def gradients(self, x: NDArray, y: NDArray) -> NDArray: """ - The derivate of the smooth maximum with respect to x and y is calculated with the chain rule: + The derivative of the smooth maximum with respect to x and y is calculated with the chain rule: dS/dk = dS/dD * dD/dk where S is smooth maximum, D is distance to edge and k is the spacial dimension @@ -1047,7 +1073,7 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): dist_ij, d_ddk_ijk, _ = self.calc_distance_and_gradients(x, y) if self.relaxation: - Dist_ij += self.calc_relaxation() + dist_ij += self.calc_relaxation() if self.method == 'smooth_min': d_sd_dist_ij = smooth_max_gradient(np.abs(dist_ij), -np.abs(dist_ij).max(), axis=1) @@ -1063,13 +1089,175 @@ class MultiPolygonBoundaryComp(PolygonBoundaryComp): return gradients +class TurbineSpecificBoundaryComp(MultiPolygonBoundaryComp): + """ + TODO: docstring + """ + + def __init__(self, n_wt: int, wind_turbines, zones: list, const_id: int = None, + units=None, relaxation: bool = False, method: str = 'nearest', simplify_geometry: bool = False): + """ + TODO: docstring + """ + + # self.dependencies = [d or {'type': None, 'multiplier': None, 'ref': None} for d in dependencies] + # self.multi_boundary = xy_boundaries = self.get_xy_boundaries(boundaries, geometry_types, incl_excls) + self.wind_turbines = wind_turbines + self.types = wind_turbines.types() + + self.n_wt = n_wt + self.zones = zones + self.ts_polygon_boundaries, ts_xy_boundaries = self.get_ts_boundaries() + MultiPolygonBoundaryComp.__init__(self, n_wt=n_wt, zones=zones, const_id=const_id, units=units, + relaxation=relaxation, method=method, simplify_geometry=simplify_geometry) + + self.ts_merged_polygon_boundaries = self.merge_boundaries() + self.ts_merged_xy_boundaries = self.get_ts_xy_boundaries() + self.ts_boundary_properties = self.get_ts_boundary_properties() + self.ts_item_indices = self.get_ts_item_indices() + + def get_ts_boundaries(self): + """ + TODO: docstring + """ + + polygons, bounds = [], [] + + for t in set(self.types): + temp1 = [] + temp2 = [] + dist2wt_input = dict(D=self.wind_turbines.diameter(t), + H=self.wind_turbines.hub_height(t)) + for z in self.zones: + if hasattr(z.dist2wt, '__code__'): + buffer = z.dist2wt(**{k: dist2wt_input[k] for k in z.dist2wt.__code__.co_varnames}) + else: + buffer = 0 + if z.geometry_type == 'line': + poly = Polygon(LineString(z.boundary).buffer(buffer, join_style=2).exterior) + elif z.geometry_type == 'polygon': + poly = Polygon(z.boundary).buffer(buffer, join_style=2) + bound = np.asarray(poly) + temp1.append(poly) + temp2.append(bound) + polygons.append(temp1) + bounds.append(temp2) + + return polygons, bounds + + def get_ts_xy_boundaries(self): + """ + TODO: docstring + """ + + return [self._poly_to_bound(b) for b in self.ts_merged_polygon_boundaries] + + def merge_boundaries(self): + """ + TODO: docstring + """ + + return [self._calc_resulting_polygons(bounds) for bounds in self.ts_polygon_boundaries] + + def get_ts_boundary_properties(self, ): + """ + TODO: docstring + """ + + return [[self.get_boundary_properties(bound) for bound, _ in bounds] for bounds in self.ts_merged_xy_boundaries] + + def get_ts_item_indices(self): + """ + TODO: docstring + """ + + temp = [] + for bounds in self.ts_merged_xy_boundaries: + n_edges = np.asarray([len(bound) for bound, _ in bounds]) + n_edges_tot = np.sum(n_edges) + start_at = np.cumsum(n_edges) - n_edges + end_at = start_at + n_edges + item_indices = [n_edges_tot, start_at, end_at] + temp.append(item_indices) + + return temp + + def calc_distance_and_gradients(self, x: NDArray, y: NDArray, types=None): + """ + TODO: docstring + """ + + if not isinstance(self._cache_input, type(None)): + if np.all(np.array([x, y]) == self._cache_input[0]) & (not self.relaxation) & np.all( + np.asarray([types]) == self._cache_input[1]): + return self._cache_output + if isinstance(types, type(None)): + types = np.zeros(self.n_wt) + + dist_i = np.zeros(self.n_wt) + sign_i = np.zeros(self.n_wt) + diff_dx_i = np.zeros(self.n_wt) + diff_dy_i = np.zeros(self.n_wt) + + for t in set(types): + t = int(t) + idx = (types == t) + n_edges_tot, start_at, end_at = self.ts_item_indices[t] + dist_ij = np.zeros((sum(idx), n_edges_tot)) + diff_dk_ijk = np.zeros((sum(idx), n_edges_tot, 2)) + for n, (bound, bound_type) in enumerate(self.ts_merged_xy_boundaries[t]): + sa = start_at[n] + ea = end_at[n] + distance, diff_dist_dX, diff_dist_dY = self._calc_distance_and_gradients(x[idx], y[idx], + self.ts_boundary_properties[t][n][1:]) + if bound_type == 0: + distance *= -1 + diff_dist_dX *= -1 + diff_dist_dY *= -1 + dist_ij[:, sa:ea] = distance + diff_dk_ijk[:, sa:ea, 0] = diff_dist_dX + diff_dk_ijk[:, sa:ea, 1] = diff_dist_dY + + sign_i[idx] = self.sign(dist_ij) + dist_i[idx] = dist_ij[np.arange(sum(idx)), np.argmin(np.abs(dist_ij), axis=1)] + diff_dx_i[idx], diff_dy_i[idx] = diff_dk_ijk[np.arange(sum(idx)), np.argmin(np.abs(dist_ij), axis=1), :].T + + self._cache_input = (np.array([x, y]), np.asarray(types)) + self._cache_output = [dist_i, diff_dx_i, diff_dy_i, sign_i] + return self._cache_output + + def distances(self, x: NDArray, y: NDArray, type=None): + """ + TODO: docstring + """ + + dist_i, _, _, _ = self.calc_distance_and_gradients(x, y, types=type) + if self.relaxation: + dist_i += self.calc_relaxation() + return dist_i + + def gradients(self, x: NDArray, y: NDArray, type=None) -> NDArray: + """ + TODO: docstring + """ + + dist_i, diff_dx_i, diff_dy_i, _ = self.calc_distance_and_gradients(x, y, types=type) + if self.relaxation: + dist_i += self.calc_relaxation() + diff_dt = -self.relaxation[0] + if self.relaxation: + gradients = np.diagflat(diff_dx_i), np.diagflat(diff_dy_i), np.ones(self.n_wt) * diff_dt + else: + gradients = np.diagflat(diff_dx_i), np.diagflat(diff_dy_i) + + return gradients class LevelSetBoundary: """ TODO: docstring """ - def __init__(self, xy_multi_boundary: [Polygon, bool], ndiv: int = 100, bounds=[]): + def __init__(self, xy_multi_boundary: [Polygon, bool], ndiv: int = 100, bounds: list = [float]): """ TODO: docstring @@ -1094,7 +1282,7 @@ class LevelSetBoundary: self.np_xgrid: NDArray = np.empty([], dtype=float) self.np_ygrid: NDArray = np.empty([], dtype=float) - # Equivalent Geomtry Polygon (Inclusion/ Exclusion) - GeoPandas Polygon/MultiPolygon + # Equivalent Geometry Polygon (Inclusion/ Exclusion) - GeoPandas Polygon/MultiPolygon self.geo_inz = gpd.GeoSeries() self.geo_exz = gpd.GeoSeries() self.geo_eqv = gpd.GeoSeries() @@ -1210,7 +1398,7 @@ class LevelSetBoundary: TODO: docstring """ - # FIXME, shoud be supplied by BoundaryXY Class (in caso of only exclusions zones) + # FIXME, should be supplied by BoundaryXY Class (in caso of only exclusions zones) if len(self.bounds) == 0: pd_coords = self.geo_eqv.bounds else: @@ -1276,7 +1464,7 @@ class LevelSetBoundary: return x, y @staticmethod - def _ks_aggreation(np_gx: NDArray, stab: float = 0., rho: float = 5.) -> float: + def _ks_aggregation(np_gx: NDArray, stab: float = 0., rho: float = 5.) -> float: """ The Kreisselmeier-Steinhauser (KS) function for aggregate constraints @@ -1320,9 +1508,9 @@ class LevelSetBoundary: np_dist_eq = np.zeros(np_dist.shape[1], dtype=float) for i in range(np_dist.shape[1]): - np_dist_eq[i] = -self._ks_aggreation(-np_dist[:, i], rho=self.rho) + np_dist_eq[i] = -self._ks_aggregation(-np_dist[:, i], rho=self.rho) - return -self._ks_aggreation(-np_dist.min(axis=0), rho=self.rho) + return -self._ks_aggregation(-np_dist.min(axis=0), rho=self.rho) def ks_dist_diff(self, x: NDArray, y: NDArray, fdtype: str = 'second-order', **kwargs) -> Tuple[NDArray, NDArray]: """ @@ -1523,25 +1711,25 @@ def main(): ExclusionZone(e5, name='e5', dist2wt=lambda: 1, geometry_type='line'), ] - N_points = 50 - xs = np.linspace(-1, 30, N_points) - ys = np.linspace(-1, 30, N_points) + n_points = 50 + xs = np.linspace(-1, 30, n_points) + ys = np.linspace(-1, 30, n_points) y_grid, x_grid = np.meshgrid(xs, ys) x = x_grid.ravel() y = y_grid.ravel() n_wt = len(x) - MPBC = MultiPolygonBoundaryComp(n_wt, zones, method='nearest') - distances = MPBC.distances(x, y) + mpbc = MultiPolygonBoundaryComp(n_wt, zones, method='nearest') + distances = mpbc.distances(x, y) delta = 1e-9 - distances2 = MPBC.distances(x + delta, y) + distances2 = mpbc.distances(x + delta, y) dx_fd = (distances2 - distances) / delta - dx = np.diag(MPBC.gradients(x + delta / 2, y)[0]) + dx = np.diag(mpbc.gradients(x + delta / 2, y)[0]) plt.figure() plt.plot(dx_fd, dx, '.') plt.figure() - for n, bound in enumerate(MPBC.boundaries): + for n, bound in enumerate(mpbc.boundaries): x_bound, y_bound = bound[0].T x_bound = np.append(x_bound, x_bound[0]) y_bound = np.append(y_bound, y_bound[0]) @@ -1551,26 +1739,26 @@ def main(): plt.legend() plt.grid() plt.axis('square') - plt.contourf(x_grid, y_grid, distances.reshape(N_points, N_points), np.linspace(-10, 10, 100), cmap='seismic') + plt.contourf(x_grid, y_grid, distances.reshape(n_points, n_points), np.linspace(-10, 10, 100), cmap='seismic') plt.colorbar() plt.figure() ax = plt.axes(projection='3d') ax.contour3D( x.reshape( - N_points, N_points), y.reshape( - N_points, N_points), distances.reshape( - N_points, N_points), np.linspace(-10, 10, 100), cmap='seismic') + n_points, n_points), y.reshape( + n_points, n_points), distances.reshape( + n_points, n_points), np.linspace(-10, 10, 100), cmap='seismic') ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('z') if 0: for smpl in [0, 1, 2, 3, 4, 5, 6, 7, 8]: - MPBC = MultiPolygonBoundaryComp(n_wt, zones, simplify_geometry=smpl) + mpbc = MultiPolygonBoundaryComp(n_wt, zones, simplify_geometry=smpl) plt.figure() ax = plt.gca() - MPBC.plot(ax) + mpbc.plot(ax) wind_turbines = WindTurbines(names=['tb1', 'tb2'], diameters=[80, 120], @@ -1610,28 +1798,29 @@ def main(): ExclusionZone(b3, geometry_type='line', dist2wt=lambda D: 3 * D, name='river'), ExclusionZone(b4, geometry_type='line', dist2wt=lambda D, H: max(D * 2, H * 3), name='road'), ] - N_points = 50 - xs = np.linspace(0, 3000, N_points) - ys = np.linspace(0, 3000, N_points) + n_points = 50 + xs = np.linspace(0, 3000, n_points) + ys = np.linspace(0, 3000, n_points) y_grid, x_grid = np.meshgrid(xs, ys) x = x_grid.ravel() y = y_grid.ravel() n_wt = len(x) types = np.zeros(n_wt) - TSBC = TurbineSpecificBoundaryComp(n_wt, wind_turbines, zones) - distances = TSBC.distances(x, y, type=types) + + tsbc = TurbineSpecificBoundaryComp(n_wt, wind_turbines, zones) + distances = tsbc.distances(x, y, type=types) delta = 1e-9 - distances2 = TSBC.distances(x + delta, y, type=types) + distances2 = tsbc.distances(x + delta, y, type=types) dx_fd = (distances2 - distances) / delta - dx = np.diag(TSBC.gradients(x + delta / 2, y, type=types)[0]) + dx = np.diag(tsbc.gradients(x + delta / 2, y, type=types)[0]) plt.figure() plt.plot(dx_fd, dx, '.') plt.figure() - for ll, t in enumerate(TSBC.types): - line, = plt.plot(*TSBC.ts_merged_xy_boundaries[ll][0][0][0, :], label=f'type {ll}') - for n, bound in enumerate(TSBC.ts_merged_xy_boundaries[ll]): + for ll, t in enumerate(tsbc.types): + line, = plt.plot(*tsbc.ts_merged_xy_boundaries[ll][0][0][0, :], label=f'type {ll}') + for n, bound in enumerate(tsbc.ts_merged_xy_boundaries[ll]): x_bound, y_bound = bound[0].T x_bound = np.append(x_bound, x_bound[0]) y_bound = np.append(y_bound, y_bound[0]) @@ -1641,9 +1830,9 @@ def main(): plt.grid() plt.axis('square') - for ll, t in enumerate(TSBC.types): + for ll, t in enumerate(tsbc.types): plt.figure() - for n, bound in enumerate(TSBC.ts_merged_xy_boundaries[ll]): + for n, bound in enumerate(tsbc.ts_merged_xy_boundaries[ll]): x_bound, y_bound = bound[0].T x_bound = np.append(x_bound, x_bound[0]) y_bound = np.append(y_bound, y_bound[0]) @@ -1651,7 +1840,7 @@ def main(): plt.grid() plt.title(f'type {ll}') plt.axis('square') - plt.contourf(x_grid, y_grid, TSBC.distances(x, y, type=t * np.ones(n_wt)).reshape(N_points, N_points), 50) + plt.contourf(x_grid, y_grid, tsbc.distances(x, y, type=t * np.ones(n_wt)).reshape(n_points, n_points), 50) plt.colorbar() plt.show() diff --git a/topfarm/drivers/stochastic_gradient_descent_driver.py b/topfarm/drivers/stochastic_gradient_descent_driver.py index aa5ebbd8683845fc47f4bba7eba359216cceb56b..fab100660b6f107b5a1b4b649dbb9cb4dc931ae7 100644 --- a/topfarm/drivers/stochastic_gradient_descent_driver.py +++ b/topfarm/drivers/stochastic_gradient_descent_driver.py @@ -9,16 +9,30 @@ from openmdao.core.driver import Driver, RecordingDebugging from six import iteritems import numpy as np from openmdao.core.analysis_error import AnalysisError -import topfarm import time -from openmdao.utils.concurrent import concurrent_eval -from openmdao.utils.record_util import create_local_meta class SGDDriver(Driver): - def __init__(self, maxiter, **kwargs): + def __init__(self, maxiter: int, **kwargs): + + self.obj_list = [] + self.con_list = [] + self.obj_and_con_list = [] + + self.upper = None + self.lower = None + + self.l0 = None + self.mid = None + + self.alpha0 = None + self._dvlist = None + self.abs2prom = None self.maxiter = maxiter + + self.is_converged = None + super().__init__(**kwargs) # What we support @@ -73,7 +87,7 @@ class SGDDriver(Driver): def run(self): """ - Excute the stochastic gradient descent algorithm. + Execute the stochastic gradient descent algorithm. Returns ------- @@ -102,6 +116,7 @@ class SGDDriver(Driver): lower_bound[i:j] = meta['lower'] upper_bound[i:j] = meta['upper'] x0[i:j] = desvar_vals[name] + maxiter = self.maxiter max_time = self.options['max_time'] or 1e20 assert maxiter < 1e20 or max_time < 1e20, "maxiter or max_time must be set" @@ -125,7 +140,7 @@ class SGDDriver(Driver): desvar_info = [(abs2prom[name], *self._desvar_idx[name], lower_bound, upper_bound) for name, meta in iteritems(desvars)] desvar_dict = {name: (x0[i:j].copy(), lbound[i:j], ubound[i:j]) for (name, i, j, lbound, ubound) in desvar_info} - start = time.time() + # start = time.time() self.obj_list = list(self._objs) self.con_list = [] @@ -137,6 +152,7 @@ class SGDDriver(Driver): else: size = meta['global_size'] if meta['distributed'] else meta['size'] self.con_list.append(name) + self.obj_and_con_list = self.obj_list + self.con_list start = time.time() @@ -146,6 +162,7 @@ class SGDDriver(Driver): jac_time = time.time() - start max_iters_from_max_time = int(max_time / jac_time) self.maxiter = min(self.maxiter, max_iters_from_max_time) + j = jac[0] learning_rate = float(np.copy(self.learning_rate)) self.alpha0 = np.mean(np.abs(j)) / learning_rate @@ -166,19 +183,22 @@ class SGDDriver(Driver): self.upper = mid elif etaM > self.gamma_min: self.lower = mid + self.mid = mid m = np.zeros(x0.size) v = np.zeros(x0.size) self.is_converged = False + while (n_iter < self.maxiter) and (not self.is_converged): obj_value_x1, x1, alpha, learning_rate, m, v, success = self.objective_callback(x1, alpha, learning_rate, m, v, record=True) n_iter += 1 if disp: print(n_iter, obj_value_x1) + return False - def objective_callback(self, x, alpha, learning_rate, m, v, record=False): + def objective_callback(self, x: np.ndarray, alpha, learning_rate, m, v, record=False): """ Evaluate problem objective at the requested point. @@ -198,6 +218,7 @@ class SGDDriver(Driver): int Case number, used for identification when run in parallel. """ + model = self._problem().model success = 1 diff --git a/topfarm/easy_drivers.py b/topfarm/easy_drivers.py index a496a779810e3dada2f175602d751b7deca76f91..f1d998e89a84580c795e727c4099080b025edb41 100644 --- a/topfarm/easy_drivers.py +++ b/topfarm/easy_drivers.py @@ -19,17 +19,20 @@ import pandas as pd import openmdao import scipy from scipy import optimize +from scipy.optimize.optimize import OptimizeResult # from scipy.spatial import distance from openmdao.drivers import scipy_optimizer as drv from openmdao.core.driver import Driver, RecordingDebugging from openmdao.core.constants import INF_BOUND -from openmdao.drivers.scipy_optimizer import ScipyOptimizeDriver +from openmdao.drivers import scipy_optimizer +# from openmdao.drivers.scipy_optimizer import ScipyOptimizeDriver # from openmdao.drivers.genetic_algorithm_driver import SimpleGADriver # version 2.5.0 has bug see Issue #874 from topfarm.utils import ks_aggreation from topfarm.drivers.genetic_algorithm_driver import SimpleGADriver from topfarm.drivers.random_search_driver import RandomSearchDriver +from topfarm.drivers.stochastic_gradient_descent_driver import SGDDriver from topfarm.constraint_components.boundary import LevelSetBoundary try: @@ -61,6 +64,13 @@ CITATIONS = """ # from watchpoints import watch +def fmt_option(v): + if isinstance(v, str): + return v.encode() + else: + return v + + # noinspection PyCompatibility class EasyDriverBase(Driver): """ @@ -235,7 +245,7 @@ class EasyDriverBase(Driver): return True -class EasyScipyOptimizeDriver(ScipyOptimizeDriver, EasyDriverBase): +class EasyScipyOptimizeDriver(scipy_optimizer.ScipyOptimizeDriver, EasyDriverBase): """ TODO: missing-class-docstring """ @@ -245,8 +255,8 @@ class EasyScipyOptimizeDriver(ScipyOptimizeDriver, EasyDriverBase): """ Parameters ---------- - optimizer : {'COBYLA', 'SLSQP'} - Gradients are only supported by SLSQP + optimizer : {'COBYLA', 'SLSQP', 'SGD'} + Gradients are only supported by SLSQP and SGD maxiter : int Maximum number of iterations. tol : float @@ -254,33 +264,56 @@ class EasyScipyOptimizeDriver(ScipyOptimizeDriver, EasyDriverBase): disp : bool Set False to prevent printing of Scipy convergence messages auto_scale : bool - Set to true to set ref0 and ref1 to boundaries of the desig variables for the drivers which support it (SLSQP, ). + Set to true to set ref0 and ref1 to boundaries of the design variables for the drivers which + support it (SLSQP, ). """ - ScipyOptimizeDriver.__init__(self) - self.auto_scale = auto_scale + scipy_optimizer.ScipyOptimizeDriver.__init__(self) + + self.auto_scale = auto_scale self.cite = CITATIONS - if optimizer == 'SGD': - from topfarm.drivers.SGD import SGD - from openmdao.drivers import scipy_optimizer - from scipy.optimize.optimize import OptimizeResult + if optimizer == 'IPOPT': + try: + from cyipopt.scipy_interface import minimize_ipopt + except ImportError: + raise ImportError("""Cannot import ipopt wrapper. Please install cyipopt, e.g. via conda + Windows: conda install -c pycalphad cyipopt \n Linux/OSX: conda install -c conda-forge cyipopt """) + + ipopt_options = {k: fmt_option(v) for k, v in kwargs.items()} + + def minimize_ipopt_wrapper(*args, maxiter=200, disp=True, **kwargs): + ipopt_options.update({'max_iter': self.max_iter or maxiter, 'print_level': int(disp)}) + return minimize_ipopt(*args, options=ipopt_options, **kwargs) + + kwargs = {} + for lst in [scipy_optimizer._optimizers, scipy_optimizer._gradient_optimizers, + scipy_optimizer._bounds_optimizers, + scipy_optimizer._all_optimizers, scipy_optimizer._constraint_optimizers, + scipy_optimizer._constraint_grad_optimizers]: + lst.add(minimize_ipopt_wrapper) + optimizer = minimize_ipopt_wrapper + + elif optimizer == 'SGD': sgd_options = {k: fmt_option(v) for k, v in kwargs.items()} - sgd_opt = SGD(**kwargs) + sgd_opt = SGDDriver(**kwargs) - def minimize_sgd_wrapper(*args, maxiter=200, disp=True, **kwargs): + def minimize_sgd_wrapper(*args, maxiter: int =200, disp: bool = True, **kwargs): sgd_options.update({'max_iter': self.max_iter or maxiter, 'print_level': int(disp)}) s = sgd_opt.run(*args, options=sgd_options, **kwargs) return OptimizeResult(x=s, fun=args[0](s), jac=kwargs['jac'](s), nit=int(sgd_opt.T), nfev=None, njev=None, status=1, message='hello world!', success=1) kwargs = {} - for lst in [scipy_optimizer._optimizers, scipy_optimizer._gradient_optimizers, scipy_optimizer._bounds_optimizers, - scipy_optimizer._all_optimizers, scipy_optimizer._constraint_optimizers, scipy_optimizer._constraint_grad_optimizers]: + for lst in [scipy_optimizer._optimizers, scipy_optimizer._gradient_optimizers, + scipy_optimizer._bounds_optimizers, scipy_optimizer._all_optimizers, + scipy_optimizer._constraint_optimizers, scipy_optimizer._constraint_grad_optimizers]: lst.add(minimize_sgd_wrapper) + optimizer = minimize_sgd_wrapper self.options.update({'optimizer': optimizer, 'maxiter': self.max_iter or maxiter, 'tol': tol, 'disp': disp}) + if kwargs: self.options.update(kwargs) @@ -295,7 +328,6 @@ class EasyScipyOptimizeDriver(ScipyOptimizeDriver, EasyDriverBase): if tuple([int(v) for v in scipy.__version__.split(".")]) < (1, 5, 0): # Upper and lower disturbs SLSQP when running with constraints. Add limits as constraints model.add_constraint(desvar_name, 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 ref0 = 0 ref1 = 1 @@ -317,6 +349,10 @@ class EasyScipyOptimizeDriver(ScipyOptimizeDriver, EasyDriverBase): l, u = desvar_values[1], desvar_values[2] kwargs = {'ref0': ref0, 'ref': ref1, 'lower': l, 'upper': u} + else: + # Default +/- sys.float_info.max does not work for SLSQP + kwargs = {'lower': np.nan, 'upper': np.nan} + return kwargs @property @@ -367,6 +403,7 @@ def fd_diff(fun: Any, xi_new: NDArray, idx: int = None, perturbation: float = 1. np_dx = np.abs(dtp * xi_new) np_zidx = np.where(np_dx < 1.e-9)[0] + if len(np_zidx) > 0: np_dx[np_zidx] = np_dx.mean() @@ -513,13 +550,13 @@ class EasyIpOptOptimizeDriver(drv.ScipyOptimizeDriver, EasyDriverBase): multistart : Number of MultiStart Looping lsearch : - Number of Iteration throung the local optimization search + Number of Iteration through the local optimization search perturbation : Perturbation value for the multistart fd: Finite difference method for approximating the first-order derivatives of the objective function - **kwargs : Another paramters that could be included + **kwargs : Another parameters that could be included """ if Problem is not None: @@ -2075,22 +2112,21 @@ class EasyRandomSearchDriver(RandomSearchDriver, EasyDriverBase): class EasySGDDriver(SGDDriver, EasyDriverBase): - def __init__(self, maxiter=100, max_time=600, disp=False, run_parallel=False, - learning_rate=10, upper=0.1, lower=0, beta1=0.1, beta2=0.2, gamma_min_factor=1e-2, - speedupSGD=False, sgd_thresh=0.1): + def __init__(self, maxiter: int = 100, max_time: int = 600, disp: bool = False, run_parallel: bool = False, + learning_rate:float = 10., upper:float = 0.1, lower:float = 0., beta1:float = 0.1, beta2:float = 0.2, + gamma_min_factor:float = 1.e-2, speedupSGD:bool = False, sgd_thresh:float = 0.1): """Easy initialization of RandomSearchDriver Parameters ---------- - randomize_func : f(desvar_dict) - Function to randomize desired variables of desvar_dict maxiter : int, optional Maximum iterations max_time : int, optional Maximum time in seconds disp : bool + """ - # self.T = T + self.learning_rate = learning_rate self.upper = upper self.lower = lower @@ -2098,5 +2134,6 @@ class EasySGDDriver(SGDDriver, EasyDriverBase): self.beta2 = beta2 self.gamma_min_factor = gamma_min_factor self.gamma_min = gamma_min_factor # * learning_rate + SGDDriver.__init__(self, maxiter=maxiter, max_time=max_time, disp=disp, run_parallel=run_parallel, speedupSGD=speedupSGD, sgd_thresh=sgd_thresh)