Source code for bladex.deform

"""
Module to deform blade's parameters, based on a given parameter file.
"""

import numpy as np
import matplotlib.pyplot as plt
from .ndinterpolator import reconstruct_f, scipy_bspline
from .params import ParamFile


[docs]class Deformation(object): """ Deform parameter curves {chord, pitch, rake, skew, camber} according to specific information passed through a parameter file. The module contains several methods that are able to: 1. compute coordinates of the optimal control points, provided their number from the parameter file. 2. update control points Y coordinates, provided the magnitude of the Y deformation from the parameter file. 3. generate B-spline curve using the computed control points (before or after their deformation), provided npoints from the parameter file for spline estimation. 4. plot parametric curves, with several options. Finally export a new parameter file containing the new deformed parameters. :param str paramfile: parameter file name :cvar str paramfile: parameter file name :cvar class param: class object instantiated by the module params, and contains all the attributes assigned by reading the parameter file. Possible attributes are: - `param.radii` - `param.parameters` - `param.nbasis` - `param.degree` - `param.deformations` First attribute is array defines the radial sections, while the remaining attributes are dictionaries with possible keys: `chord`, `pitch`, `rake`, `skew`, `camber`. :cvar dict deformed_parameters: dictionary that contains the deformed parameters at the same radial sections, provided some tolerance due to the spline interpolation. Possible dictionary keys are the parameters `chord`, `pitch`, `rake`, `skew`, `camber`. Default value is array of zeros with length equal to the radial sections. :cvar dict control_points: dictionary that contains the 2D coordinates of the control points associated with the B-spline parametric curve. The dictionary possible keys are the parameters `chord`, `pitch`, `rake`, `skew`, `camber`. Default value is None :cvar dict spline: dictionary that contains the B-spline interpolation for the parametric curves. The dictionary possible keys are the parameters `chord`, `pitch`, `rake`, `skew`, `camber`. Each value is a 2D numpy array containing the radii interpolations against the interpolations for one of the parameters mentioned in the dictionary keys. Default value is None. """ def __init__(self, paramfile): self.paramfile = paramfile self.param = ParamFile() self.param.read_parameters(filename=paramfile) self.deformed_parameters = { 'chord': np.zeros(self.param.radii.size), 'pitch': np.zeros(self.param.radii.size), 'rake': np.zeros(self.param.radii.size), 'skew': np.zeros(self.param.radii.size), 'camber': np.zeros(self.param.radii.size) } self.control_points = { 'chord': None, 'pitch': None, 'rake': None, 'skew': None, 'camber': None } self.spline = { 'chord': None, 'pitch': None, 'rake': None, 'skew': None, 'camber': None } @staticmethod
[docs] def _optimum_control_points(X, Y, degree, nbasis, rbf_points): """ Private static method that computes the optimum coordinates of the B-spline control points. :param array_like X: Array of original points of the parametric curve X-axis, usually array of the radii sections :param array_like Y: radial distribution of parameter `chord` or `pitch` or `rake` or `skew` or `camber`, corresponding to the radial sections in X :param int degree: degree of the B-spline construction for the parametric curve :param int nbasis: number of control points associated with the parametric curve :param int rbf_points: if specified greater than zero, then the X and Y arrays are interpolated using the Wendland C2 radial basis function to produce X and Y arrays with length = rbf_points. The larger number of rbf_points implies better estimation of the optimum control coordinates. To turn it off (i.e. compute control points based on original X, Y arrays) then insert 0. (Negative values or None results in same effect as zero) :return: control points 2D coordinates :rtype: numpy.ndarray """ if not isinstance(rbf_points, int): # in case inserted as None, then converts to zero, # otherwise returns the inserted value. Useful when dealing with # the parameter as a flag rbf_points = int(rbf_points or 0) if rbf_points > 0: xx = np.linspace(X[0], X[-1], num=rbf_points) yy = np.zeros(rbf_points) reconstruct_f( original_input=X, original_output=Y, rbf_input=xx, rbf_output=yy, basis='beckert_wendland_c2_basis', radius=2.0) X = xx Y = yy A = np.zeros((len(X), nbasis)) At = np.zeros((len(X), nbasis - 2)) for i in range(nbasis): cv_new = np.zeros((nbasis, 3)) cv_new[i, 0] = 1. # i-th basis function in the reference space A[:, i] = scipy_bspline(cv_new, A.shape[0], degree)[:, 0] # A tilde for the constraints on the first and last point At = A[:, 1:-1] # x and y of the ctrl points with constrained least square. # we subtract the contribution of the first and last basis function cvt_x = np.linalg.lstsq( At, X - A[:, 0] * X[0] - A[:, -1] * X[-1], rcond=-1)[0] cvt_y = np.linalg.lstsq( At, Y - A[:, 0] * Y[0] - A[:, -1] * Y[-1], rcond=-1)[0] # fill with the constraints the first and last point opt_ctrl = np.zeros((nbasis, 2)) opt_ctrl[0, 0] = X[0] opt_ctrl[-1, 0] = X[-1] opt_ctrl[0, 1] = Y[0] opt_ctrl[-1, 1] = Y[-1] opt_ctrl[1:-1, 0] = cvt_x opt_ctrl[1:-1, 1] = cvt_y return opt_ctrl
@staticmethod
[docs] def _check_param(param): """ Private static method that checks the passed parameter. :param str param: passed parameter to check. Valid values are: `chord`, `pitch`, `rake`, `skew`, `camber` :raises ValueError: if the param value is not one of the previous """ params = ['chord', 'pitch', 'rake', 'skew', 'camber'] if not param in params: raise ValueError( 'Valid param values are: "chord", "pitch", "rake", "skew",'\ ' "camber".')
[docs] def _check_control_points(self, param): """ Private method to check if control points are computed. :param str param: passed parameter to check. Valid values are: `chord`, `pitch`, `rake`, `skew`, `camber` :raises ValueError: if the control points have None value, i.e. not computed """ if self.control_points[param] is None: raise ValueError( 'control_points has None value. You must compute them first.')
[docs] def _check_spline(self, param): """ Private method to check if spline interpolation is computed. :param str param: passed parameter to check. Valid values are: `chord`, `pitch`, `rake`, `skew`, `camber` :raises ValueError: if the spline of that parameter curve has None value, i.e. not computed """ if self.spline[param] is None: raise ValueError( param + ' spline is None. You must first generate spline.')
[docs] def _check_deformed(self, param): """ Private method to check if the deformed parameters are computed. :param str param: passed parameter to check. Valid values are: `chord`, `pitch`, `rake`, `skew`, `camber` :raises ValueError: if the deformed parameters have array of zeros, i.e. not computed """ if self.deformed_parameters[param].all() == 0: raise ValueError(param + ' deformed points are not computed.')
[docs] def compute_control_points(self, param, rbf_points=1000): """ Compute the control points 2D coordinates for one of the parametric curves. :param str param: parameter corresponding to the parametric curve. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` :param int rbf_points: if greater than zero then the Wendland C2 radial basis function is used to interpolate the original arrays for the parametric curve, so that the control points are computed according to the interpolated arrays. Needless to mention that longer arrays would produce better estimation of the control points optimum coordinates. In order to turn off the rbf interpolation: specify either 0 or -1 (Also a None value can be used too). Default value is 1000 """ self._check_param(param=param) self.control_points[param] = self._optimum_control_points( X=self.param.radii, Y=self.param.parameters[param], degree=self.param.degree[param], nbasis=self.param.nbasis[param], rbf_points=rbf_points)
[docs] def update_control_points(self, param): """ Update the control point Y coordinate with the deformation values specified in the parameter file. :param str param: parameter corresponding to the parametric curve. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` """ self._check_param(param=param) self._check_control_points(param=param) if not self.control_points[param].shape[0] == len( self.param.deformations[param]): raise ValueError( 'array of deformations must equal to number of control points') for i in range(self.control_points[param].shape[0]): self.control_points[param][i, 1] += self.param.deformations[param][ i]
[docs] def generate_spline(self, param): """ Generate the B-spline interpolations, using the information: `degree`, `npoints` from the parameter file, as well as the computed 2D coordinates of the control points. :param str param: parameter corresponding to the parametric curve. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` """ self._check_param(param=param) self._check_control_points(param=param) self.spline[param] = scipy_bspline( cv=self.control_points[param], npoints=self.param.npoints[param], degree=self.param.degree[param])
[docs] def compute_deformed_parameters(self, param, tol=1e-3): """ This method uses the spline npoints interpolation of the parametric curve to extract the parameters corresponding to the radial distribution of the original undeformed array. Therefore the resulting deformed parameters should be arrays of same length like that of the original parameters. :param str param: parameter corresponding to the parametric curve. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` :param float tol: tolerance required to find the B-spline estimation within the neighborhood of each of the radii sections. It is important to specify the value carefully as it depends on the order of the original array values, as well as the number of points for the spline interpolations. Default value is 1e-3 """ self._check_param(param=param) self._check_spline(param=param) for i, val in enumerate(self.param.radii): index = np.where(np.fabs(self.spline[param][:, 0] - val) < tol)[0] if len(index) == 0: raise ValueError( 'Could not compute deformed parameter "' + param + '" at radius "' + str(val) + '". Either increase the tolerance for that parameter, or'\ ' increase the spline npoints in the parameter file.' ) if index.shape[0] > 1: # In case more neighbors are found, then take first value only. index = index[0] self.deformed_parameters[param][i] = self.spline[param][index, 1]
[docs] def compute_all(self, rbf_points=1000, tol_chord=1e-3, tol_pitch=1e-3, tol_rake=1e-3, tol_skew=1e-3, tol_camber=1e-3): """ Computes everything: - control points 2D coordinates - deformed control points - spline npoints interpolations - deformed parameters of the original arrays The previous procedure is applied for all the parameters: `chord`, `pitch`, `rake`, `skew`, `camber` :param int rbf_points: if greater than zero then the Wendland C2 radial basis function is used to interpolate the original arrays for the parametric curve, so that the control points are computed according to the interpolated arrays. Needless to mention that longer arrays would produce better estimation of the control points optimum coordinates. In order to turn off the rbf interpolation then specify either 0 or -1 (Also a None value can be used too). Default value is 1000 :param float tol_chord: tolerance used to extract the chord radial distribution for the deformed B-spline interpolation. Default value is 1e-3 :param float tol_pitch: tolerance used to extract the pitch radial distribution for the deformed B-spline interpolation. Default value is 1e-3 :param float tol_rake: tolerance used to extract the rake radial distribution for the deformed B-spline interpolation. Default value is 1e-3 :param float tol_skew: tolerance used to extract the skew radial distribution for the deformed B-spline interpolation. Default value is 1e-3 :param float tol_camber: tolerance used to extract the camber radial distribution for the deformed B-spline interpolation. Default value is 1e-3 """ tols = { 'chord': tol_chord, 'pitch': tol_pitch, 'rake': tol_rake, 'skew': tol_skew, 'camber': tol_camber } params = ['chord', 'pitch', 'rake', 'skew', 'camber'] for param in params: self.compute_control_points(param=param, rbf_points=rbf_points) self.update_control_points(param=param) self.generate_spline(param=param) self.compute_deformed_parameters(param=param, tol=tols[param])
[docs] def _plot_parametric_curve(self, param, original=True, ctrl_points=True, spline=True, rbf=False, rbf_points=500, deformed=False, outfile=None): """ Private method to plot the parametric curve. Several options can be specified. :param str param: parameter corresponding to the parametric curve needs to be plotted. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` :param bool original: if True, then plot the original points of the parameter at the radii sections. :param bool ctrl_points: if True, then plot the control points of that parametric curve. :param bool spline: If True, then plot the B-spline interpolation of the parametric curve. :param bool rbf: if True, then plot the radial basis functions interpolation of the parametric curve. :param int rbf_points: number of points used for the rbf interpolation, if the flag `rbf` is set True. Beware that this argument does not have the same function of that when computing the control points, although both uses the radial basis function interpolation with the Wendland basis. :param bool deformed: if True, then plot the deformed points of the parameter radial distribution, estimated using the B-spline interpolations within a given tolerance. :param str outfile: if string is passed, then the plot is saved with that name. If the value is None, then the plot is shown on the screen. """ self._check_param(param=param) plt.figure() if original: plt.plot( self.param.radii, self.param.parameters[param], 'o', label='original points') if ctrl_points: self._check_control_points(param=param) plt.plot( self.control_points[param][:, 0], self.control_points[param][:, 1], '*-', label='control points') if spline: self._check_spline(param=param) plt.plot( self.spline[param][:, 0], self.spline[param][:, 1], label='spline') if rbf: xx = np.linspace( self.param.radii[0], self.param.radii[-1], num=rbf_points) yy = np.zeros(rbf_points) reconstruct_f( original_input=self.param.radii, original_output=self.param.parameters[param], rbf_input=xx, rbf_output=yy, basis='beckert_wendland_c2_basis', radius=2.0) plt.plot(xx, yy, label='rbf') if deformed: self._check_deformed(param=param) plt.plot( self.param.radii, self.deformed_parameters[param], '+', label='deformed points') plt.grid(linestyle='dotted') plt.title(param + ' curve') plt.legend() if outfile: if not isinstance(outfile, str): raise ValueError('Output file name must be string.') plt.savefig(outfile) else: plt.show()
[docs] def plot(self, param, original=True, ctrl_points=True, spline=True, rbf=False, rbf_points=500, deformed=False, outfile=None): """ Plot the parametric curve. Several options can be specified. :param array_like param: array_like of strings corresponding to the parametric curve that needs to be plotted. possible values are `chord`, `pitch`, `rake`, `skew`, `camber` :param bool original: if True, then plot the original points of the parameter at the radii sections. Default value is True :param bool ctrl_points: if True, then plot the control points of that parametric curve. Default value is True :param bool spline: If True, then plot the B-spline interpolation of the parametric curve. Default value is True :param bool rbf: if True, then plot the radial basis functions interpolation of the parametric curve. Default value is True :param int rbf_points: number of points used for the rbf interpolation, if the flag `rbf` is set True. Beware that this argument does not have the same function of that when computing the control points, although both uses the radial basis function interpolation with the Wendland basis. Default value is 500 :param bool deformed: if True, then plot the deformed points of the parameter radial distribution, estimated using the B-spline interpolations within a given tolerance. Default value is False :param str outfile: if string is passed, then the plot is saved with that name. If the value is None, then the plot is shown on the screen. Default value is None """ if not isinstance(param, (list, tuple, np.ndarray)): param = [param] for par in param: self._plot_parametric_curve( param=par, original=original, ctrl_points=ctrl_points, spline=spline, rbf=rbf, rbf_points=rbf_points, deformed=deformed, outfile=outfile)
[docs] def export_param_file(self, outfile='parameters_mod.prm'): """ Export a new parameter file with the new deformed parameters, while all other values are kept the same as in the original parameter file with the undeformed parameters. In the new parameter file (i.e. with deformed parameters) the deformations arrays become array of zeros. :param str outfile: file name to be written out """ prm = ParamFile() prm.radii = self.param.radii params = ['chord', 'pitch', 'rake', 'skew', 'camber'] for param in params: if np.all(self.param.deformations[param] == 0): prm.parameters[param] = self.param.parameters[param] else: prm.parameters[param] = self.deformed_parameters[param] prm.nbasis[param] = self.param.nbasis[param] prm.degree[param] = self.param.nbasis[param] prm.npoints[param] = self.param.npoints[param] prm.deformations[param] = np.zeros(self.param.nbasis[param]) prm.write_parameters(filename=outfile)