diff --git a/sharpy/linear/assembler/linearaeroelastic.py b/sharpy/linear/assembler/linearaeroelastic.py index 95b146f62..d35f9c179 100644 --- a/sharpy/linear/assembler/linearaeroelastic.py +++ b/sharpy/linear/assembler/linearaeroelastic.py @@ -1,11 +1,13 @@ -import sharpy.linear.utils.ss_interface as ss_interface import numpy as np -import sharpy.linear.src.libss as libss import scipy.linalg as sclalg import warnings + +import sharpy.linear.utils.ss_interface as ss_interface +import sharpy.linear.src.libss as libss import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout import sharpy.utils.algebra as algebra +import sharpy.utils.generator_interface as gi @ss_interface.linear_system @@ -98,8 +100,44 @@ def initialise(self, data): # Create Linear UVLM self.uvlm = ss_interface.initialise_system('LinearUVLM') self.uvlm.initialise(data, custom_settings=self.settings['aero_settings']) + + # Look for the aerodynamic solver + if 'StaticUvlm' in data.settings: + aero_solver_name = 'StaticUvlm' + aero_solver_settings = data.settings['StaticUvlm'] + elif 'StaticCoupled' in data.settings: + aero_solver_name = data.settings['StaticCoupled']['aero_solver'] + aero_solver_settings = data.settings['StaticCoupled']['aero_solver_settings'] + elif 'StaticCoupledRBM' in data.settings: + aero_solver_name = data.settings['StaticCoupledRBM']['aero_solver'] + aero_solver_settings = data.settings['StaticCoupledRBM']['aero_solver_settings'] + elif 'DynamicCoupled' in data.settings: + aero_solver_name = data.settings['DynamicCoupled']['aero_solver'] + aero_solver_settings = data.settings['DynamicCoupled']['aero_solver_settings'] + elif 'StepUvlm' in data.settings: + aero_solver_name = 'StepUvlm' + aero_solver_settings = data.settings['StepUvlm'] + else: + raise RuntimeError("ERROR: aerodynamic solver not found") + + # Velocity generator + vel_gen_name = aero_solver_settings['velocity_field_generator'] + vel_gen_settings = aero_solver_settings['velocity_field_input'] + + # Get the minimum parameters needed to define the wake + vel_gen_type = gi.generator_from_string(vel_gen_name) + vel_gen = vel_gen_type() + vel_gen.initialise(vel_gen_settings) + + wake_prop_settings = {'dt': self.settings['aero_settings']['dt'], + 'ts': data.ts, + 't': data.ts*self.settings['aero_settings']['dt'], + 'for_pos': data.structure.timestep_info[-1].for_pos, + 'cfl1': self.settings['aero_settings']['cfl1'], + 'vel_gen': vel_gen} + if self.settings['uvlm_filename'] == '': - self.uvlm.assemble(track_body=self.settings['track_body']) + self.uvlm.assemble(track_body=self.settings['track_body'], wake_prop_settings=wake_prop_settings) else: self.load_uvlm_from_file = True diff --git a/sharpy/linear/assembler/linearuvlm.py b/sharpy/linear/assembler/linearuvlm.py index 79bcb46af..11b981653 100644 --- a/sharpy/linear/assembler/linearuvlm.py +++ b/sharpy/linear/assembler/linearuvlm.py @@ -128,6 +128,10 @@ class LinearUVLM(ss_interface.BaseElement): settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance below which inductions are not computed' + settings_types['cfl1'] = 'bool' + settings_default['cfl1'] = True + settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' + settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) @@ -238,7 +242,7 @@ def initialise(self, data, custom_settings=None): self.gust_assembler = lineargust.LinearGustGenerator() self.gust_assembler.initialise(data.aero) - def assemble(self, track_body=False): + def assemble(self, track_body=False, wake_prop_settings=None): r""" Assembles the linearised UVLM system, removes the desired inputs and adds linearised control surfaces (if present). @@ -252,7 +256,7 @@ def assemble(self, track_body=False): .. math:: [\delta_1, \delta_2, \dots, \dot{\delta}_1, \dot{\delta_2}] """ - self.sys.assemble_ss() + self.sys.assemble_ss(wake_prop_settings=wake_prop_settings) if self.scaled: self.sys.nondimss() diff --git a/sharpy/linear/src/assembly.py b/sharpy/linear/src/assembly.py index afc2cd2ce..191333004 100644 --- a/sharpy/linear/src/assembly.py +++ b/sharpy/linear/src/assembly.py @@ -24,6 +24,7 @@ import sharpy.linear.src.lib_dbiot as dbiot import sharpy.linear.src.lib_ucdncdzeta as lib_ucdncdzeta import sharpy.utils.algebra as algebra +import sharpy.utils.cout_utils as cout # local indiced panel/vertices as per self.maps dmver = [0, 1, 1, 0] # delta to go from (m,n) panel to (m,n) vertices @@ -1155,26 +1156,42 @@ def dfunstdgamma_dot(Surfs): return DerList -def wake_prop(Surfs, Surfs_star, use_sparse=False, sparse_format='lil'): +def wake_prop(MS, use_sparse=False, sparse_format='lil', settings=None): """ Assembly of wake propagation matrices, in sparse or dense matrices format - Note: wake propagation matrices are very sparse. Nonetheless, allocation - in dense format (from numpy.zeros) or sparse does not have important - differences in terms of cpu time and memory used as numpy.zeros does - not allocate memory until this is accessed. + Note: + Wake propagation matrices are very sparse. Nonetheless, allocation + in dense format (from numpy.zeros) or sparse does not have important + differences in terms of cpu time and memory used as numpy.zeros does + not allocate memory until this is accessed + + Args: + MS (MultiSurface): MultiSurface instance + use_sparse (bool (optional)): Use sparse matrices + sparse_format (str (optional)): Use either ``csc`` or ``lil`` format + settings (dict (optional)): Dictionary with aerodynamic settings containing: + cfl1 (bool): Defines if the wake shape complies with CFL=1 + dt (float): time step """ - n_surf = len(Surfs) - assert len(Surfs_star) == n_surf, 'No. of wake and bound surfaces not matching!' + try: + cfl1 = settings['cfl1'] + except (KeyError, TypeError): + # In case the key does not exist or settings=None + cfl1 = True + cout.cout_wrap("Computing wake propagation matrix with CFL1={}".format(cfl1), 1) + + n_surf = len(MS.Surfs) + assert len(MS.Surfs_star) == n_surf, 'No. of wake and bound surfaces not matching!' dimensions = [None]*n_surf dimensions_star = [None]*n_surf for ss in range(n_surf): - Surf = Surfs[ss] - Surf_star = Surfs_star[ss] + Surf = MS.Surfs[ss] + Surf_star = MS.Surfs_star[ss] N, M, K = Surf.maps.N, Surf.maps.M, Surf.maps.K M_star, K_star = Surf_star.maps.M, Surf_star.maps.K @@ -1184,10 +1201,64 @@ def wake_prop(Surfs, Surfs_star, use_sparse=False, sparse_format='lil'): dimensions[ss] = [M, N, K] dimensions_star[ss] = [M_star, N, K_star] - C_list, Cstar_list = wake_prop_from_dimensions(dimensions, - dimensions_star, - use_sparse=use_sparse, - sparse_format=sparse_format) + if not cfl1: + # allocate... + if use_sparse: + if sparse_format == 'csc': + C = libsp.csc_matrix((K_star, K)) + C_star = libsp.csc_matrix((K_star, K_star)) + elif sparse_format == 'lil': + C = sparse.lil_matrix((K_star, K)) + C_star = sparse.lil_matrix((K_star, K_star)) + else: + C = np.zeros((K_star, K)) + C_star = np.zeros((K_star, K_star)) + + C_list = [] + Cstar_list = [] + + # Compute flow velocity at wake + uext = [np.zeros((3, + dimensions_star[ss][0], + dimensions_star[ss][1]))] + + try: + Surf_star.zetac + except AttributeError: + Surf_star.generate_collocations() + # Compute induced velocities in the wake + Surf_star.u_ind_coll = np.zeros((3, M_star, N)) + + for iin in range(N): + # propagation from trailing edge + conv_vec = Surf_star.zetac[:, 0, iin] - Surf.zetac[:, -1, iin] + dist = np.linalg.norm(conv_vec) + conv_dir_te = conv_vec/dist + vel = Surf.u_input_coll[:, -1, iin] + vel_value = np.dot(vel, conv_dir_te) + cfl = settings['dt']*vel_value/dist + + C[iin, N * (M - 1) + iin] = cfl + C_star[iin, iin] = 1.0 - cfl + + # wake propagation + for mm in range(1, M_star): + conv_vec = Surf_star.zetac[:, mm, iin] - Surf_star.zetac[:, mm - 1, iin] + dist = np.linalg.norm(conv_vec) + conv_dir = conv_vec/dist + cfl = settings['dt']*vel_value/dist + + C_star[mm * N + iin, (mm - 1) * N + iin] = cfl + C_star[mm * N + iin, mm * N + iin] = 1.0 - cfl + + C_list.append(C) + Cstar_list.append(C_star) + + if cfl1: + C_list, Cstar_list = wake_prop_from_dimensions(dimensions, + dimensions_star, + use_sparse=use_sparse, + sparse_format=sparse_format) return C_list, Cstar_list diff --git a/sharpy/linear/src/lin_aeroelastic.py b/sharpy/linear/src/lin_aeroelastic.py index e5419b323..ea2c49928 100644 --- a/sharpy/linear/src/lin_aeroelastic.py +++ b/sharpy/linear/src/lin_aeroelastic.py @@ -203,7 +203,7 @@ def reshape_struct_input(self): # self.dq[-3:]=-0.5*(wa*tsdata.quat[0]+np.cross(wa,tsdata.quat[1:])) - def assemble_ss(self, beam_num_modes=None): + def assemble_ss(self, beam_num_modes=None, wake_prop_settings=None): """Assemble State Space formulation""" data = self.data @@ -213,7 +213,7 @@ def assemble_ss(self, beam_num_modes=None): tsstr = self.tsstr ### assemble linear uvlm - self.linuvlm.assemble_ss() + self.linuvlm.assemble_ss(wake_prop_settings=wake_prop_settings) SSaero = self.linuvlm.SS ### assemble gains and stiffening term due to non-zero forces diff --git a/sharpy/linear/src/linuvlm.py b/sharpy/linear/src/linuvlm.py index fe3ac75ff..a878f1694 100644 --- a/sharpy/linear/src/linuvlm.py +++ b/sharpy/linear/src/linuvlm.py @@ -42,6 +42,9 @@ settings_types_static['vortex_radius'] = 'float' settings_default_static['vortex_radius'] = vortex_radius_def +settings_types_static['cfl1'] = 'bool' +settings_default_static['cfl1'] = True + settings_types_dynamic = dict() settings_default_dynamic = dict() @@ -86,6 +89,9 @@ settings_types_dynamic['vortex_radius'] = 'float' settings_default_dynamic['vortex_radius'] = vortex_radius_def +settings_types_dynamic['cfl1'] = 'bool' +settings_default_dynamic['cfl1'] = True + class Static(): """ Static linear solver """ @@ -105,6 +111,7 @@ def __init__(self, tsdata, custom_settings=None, for_vel=np.zeros((6,))): settings_default_static) self.vortex_radius = settings_here['vortex_radius'] + self.cfl1 = settings_here['cfl1'] MS = multisurfaces.MultiAeroGridSurfaces(tsdata, self.vortex_radius, for_vel=for_vel) @@ -575,23 +582,32 @@ def __init__(self, tsdata, dt=None, dynamic_settings=None, integr_order=2, self.settings = dict() if dynamic_settings: self.settings = dynamic_settings + settings.to_custom_types(self.settings, + settings_types_dynamic, + settings_default_dynamic, + no_ctype=True) else: warnings.warn('No settings dictionary found. Using default. Individual parsing of settings is deprecated', DeprecationWarning) # Future: remove deprecation warning and make settings the only argument - settings.to_custom_types(self.settings, settings_types_dynamic, settings_default_dynamic) + settings.to_custom_types(self.settings, + settings_types_dynamic, + settings_default_dynamic, + no_ctype=True) self.settings['dt'] = dt self.settings['integr_order'] = integr_order self.settings['remove_predictor'] = RemovePredictor self.settings['use_sparse'] = UseSparse self.settings['ScalingDict'] = ScalingDict - static_dict = {'vortex_radius': self.settings['vortex_radius']} + static_dict = {'vortex_radius': self.settings['vortex_radius'], + 'cfl1': self.settings['cfl1']} super().__init__(tsdata, custom_settings=static_dict, for_vel=for_vel) self.dt = self.settings['dt'] self.integr_order = self.settings['integr_order'] self.vortex_radius = self.settings['vortex_radius'] + self.cfl1 = self.settings['cfl1'] if self.integr_order == 1: Nx = 2 * self.K + self.K_star @@ -717,7 +733,7 @@ def dimss(self): self.cpu_summary['dim'] = time.time() - t0 - def assemble_ss(self): + def assemble_ss(self, wake_prop_settings=None): r""" Produces state-space model of the form @@ -797,8 +813,9 @@ def assemble_ss(self): ### propagation of circ # fast and memory efficient with both dense and sparse matrices - List_C, List_Cstar = ass.wake_prop(MS.Surfs, MS.Surfs_star, - self.use_sparse, sparse_format='csc') + List_C, List_Cstar = ass.wake_prop(MS, + self.use_sparse, sparse_format='csc', + settings=wake_prop_settings) if self.use_sparse: Cgamma = libsp.csc_matrix(sparse.block_diag(List_C, format='csc')) CgammaW = libsp.csc_matrix(sparse.block_diag(List_Cstar, format='csc')) @@ -953,7 +970,7 @@ def assemble_ss(self): self.cpu_summary['assemble'] = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.cpu_summary['assemble']) - def freqresp(self, kv): + def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each @@ -1007,7 +1024,7 @@ def freqresp(self, kv): for kk in range(Nk): ### build Cw complex - Cw_cpx = self.get_Cw_cpx(zv[kk]) + Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) if self.remove_predictor: Ygamma = zv[kk] * \ @@ -1042,7 +1059,7 @@ def freqresp(self, kv): return Yfreq - def get_Cw_cpx(self, zval): + def get_Cw_cpx(self, zval, settings=None): r""" Produces a sparse matrix @@ -1058,30 +1075,10 @@ def get_Cw_cpx(self, zval): """ - MS = self.MS - K = self.K - K_star = self.K_star - - jjvec = [] - iivec = [] - valvec = [] - - K0tot, K0totstar = 0, 0 - for ss in range(MS.n_surf): - - M, N = self.MS.dimensions[ss] - Mstar, N = self.MS.dimensions_star[ss] - - for mm in range(Mstar): - jjvec += range(K0tot + N * (M - 1), K0tot + N * M) - iivec += range(K0totstar + mm * N, K0totstar + (mm + 1) * N) - valvec += N * [zval ** (-mm - 1)] - K0tot += MS.KK[ss] - K0totstar += MS.KK_star[ss] + return get_Cw_cpx(self.MS, self.K, self.K_star, zval, settings=settings) - return libsp.csc_matrix((valvec, (iivec, jjvec)), shape=(K_star, K), dtype=np.complex_) - def balfreq(self, DictBalFreq): + def balfreq(self, DictBalFreq, wake_prop_settings=None): """ Low-rank method for frequency limited balancing. The Observability ad controllability Gramians over the frequencies kv @@ -1293,7 +1290,7 @@ def balfreq(self, DictBalFreq): Intfact = wv[kk] # integration factor # build terms that will be recycled - Cw_cpx = self.get_Cw_cpx(zval) + Cw_cpx = self.get_Cw_cpx(zval, settings=wake_prop_settings) PwCw_T = Cw_cpx.T.dot(Pw.T) Kernel = np.linalg.inv(zval * Eye - P - PwCw_T.T) @@ -1399,7 +1396,7 @@ def balfreq(self, DictBalFreq): self.Zc = Zc self.Zo = Zo - def balfreq_profiling(self): + def balfreq_profiling(self, wake_prop_settings=None): """ Generate profiling report for balfreq function and saves it into ``self.prof_out.`` The function also returns a ``pstats.Stats`` object. @@ -1415,7 +1412,7 @@ def balfreq_profiling(self): import cProfile def wrap(): DictBalFreq = {'frequency': 0.5, 'check_stability': False} - self.balfreq(DictBalFreq) + self.balfreq(DictBalFreq, wake_prop_settings=wake_prop_settings) cProfile.runctx('wrap()', globals(), locals(), filename=self.prof_out) @@ -1747,7 +1744,7 @@ def __init__(self, tsdata, dt=None, warnings.warn('Individual parsing of settings is deprecated. Please use the settings dictionary', DeprecationWarning) - super().__init__(tsdata, vortex_radius, dt, + super().__init__(tsdata, dt, dynamic_settings=dynamic_settings, integr_order=integr_order, RemovePredictor=RemovePredictor, @@ -1839,7 +1836,7 @@ def dimss(self): self.SS.dt = self.SS.dt * self.ScalingFacts['time'] self.cpu_summary['dim'] = time.time() - t0 - def assemble_ss(self): + def assemble_ss(self, wake_prop_settings=None): r""" Produces block-form of state-space model @@ -1923,8 +1920,9 @@ def assemble_ss(self): ### propagation of circ # fast and memory efficient with both dense and sparse matrices - List_C, List_Cstar = ass.wake_prop(MS.Surfs, MS.Surfs_star, - self.use_sparse, sparse_format='csc') + List_C, List_Cstar = ass.wake_prop(MS, + self.use_sparse, sparse_format='csc', + settings=wake_prop_settings) if self.use_sparse: Cgamma = libsp.csc_matrix(sparse.block_diag(List_C, format='csc')) CgammaW = libsp.csc_matrix(sparse.block_diag(List_Cstar, format='csc')) @@ -2065,7 +2063,7 @@ def assemble_ss(self): self.cpu_summary['assemble'] = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.cpu_summary['assemble'], 1) - def freqresp(self, kv): + def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each @@ -2096,7 +2094,7 @@ def freqresp(self, kv): for kk in range(Nk): ### build Cw complex - Cw_cpx = self.get_Cw_cpx(zv[kk]) + Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) Ygamma = libsp.solve(zv[kk] * Eye - P - libsp.dot(Pw, Cw_cpx, type_out=libsp.csc_matrix), @@ -2121,7 +2119,7 @@ def freqresp(self, kv): return Yfreq - def balfreq(self, DictBalFreq): + def balfreq(self, DictBalFreq, wake_prop_settings=None): """ Low-rank method for frequency limited balancing. The Observability ad controllability Gramians over the frequencies kv @@ -2321,7 +2319,7 @@ def balfreq(self, DictBalFreq): Intfact = wv[kk] # integration factor # build terms that will be recycled - Cw_cpx = self.get_Cw_cpx(zval) + Cw_cpx = self.get_Cw_cpx(zval, settings=wake_prop_settings) P_PwCw = P + Cw_cpx.T.dot(Pw.T).T Kernel = np.linalg.inv(zval * Eye - P_PwCw) @@ -2844,7 +2842,7 @@ def addGain(self, K, where): self.Dss = libsp.dot(K, self.Dss) self.outputs = K.shape[0] - def freqresp(self, kv): + def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each @@ -2865,7 +2863,7 @@ def freqresp(self, kv): for kk in range(Nk): ### build Cw complex - Cw_cpx = self.get_Cw_cpx(zv[kk]) + Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) # get bound state freq response if self.remove_predictor: @@ -2896,7 +2894,7 @@ def freqresp(self, kv): return Yfreq - def get_Cw_cpx(self, zval): + def get_Cw_cpx(self, zval, settings=None): r""" Produces a sparse matrix @@ -2911,15 +2909,47 @@ def get_Cw_cpx(self, zval): .. math:: \bar{\goldsymbol{\Gamma}}_w = \bar{\mathbf{C}}(z) \bar{\boldsymbol{\Gamma}} """ + return get_Cw_cpx(self.MS, self.K, self.K_star, zval, settings=settings) - MS = self.MS - K = self.K - K_star = self.K_star - jjvec = [] - iivec = [] - valvec = [] + def assemble_profiling(self): + """ + Generate profiling report for assembly and save it in self.prof_out. + + To read the report: + import pstats + p=pstats.Stats(self.prof_out) + """ + + import cProfile + cProfile.runctx('self.assemble()', globals(), locals(), filename=self.prof_out) + + +def get_Cw_cpx(MS, K, K_star, zval, settings=None): + r""" + Produces a sparse matrix + + .. math:: \bar{\mathbf{C}}(z) + + where + + .. math:: z = e^{k \Delta t} + + such that the wake circulation frequency response at :math:`z` is + + .. math:: \bar{\boldsymbol{\Gamma}}_w = \bar{\mathbf{C}}(z) \bar{\mathbf{\Gamma}} + + """ + + try: + cfl1 = settings['cfl1'] + except (KeyError, TypeError): + # In case the key does not exist or settings=None + cfl1 = True + cout.cout_wrap("Computing wake propagation solution matrix if frequency domain with CFL1=%s" % cfl1, 1) + # print("Computing wake propagation solution matrix if frequency domain with CFL1=%s" % cfl1) + if cfl1: jjvec = [] iivec = [] valvec = [] @@ -2927,8 +2957,8 @@ def get_Cw_cpx(self, zval): K0tot, K0totstar = 0, 0 for ss in range(MS.n_surf): - M, N = self.MS.dimensions[ss] - Mstar, N = self.MS.dimensions_star[ss] + M, N = MS.dimensions[ss] + Mstar, N = MS.dimensions_star[ss] for mm in range(Mstar): jjvec += range(K0tot + N * (M - 1), K0tot + N * M) @@ -2936,21 +2966,87 @@ def get_Cw_cpx(self, zval): valvec += N * [zval ** (-mm - 1)] K0tot += MS.KK[ss] K0totstar += MS.KK_star[ss] + else: + # sum_m_n = 0 + sum_mstar_n = 0 + for ss in range(MS.n_surf): + # M, N = MS.dimensions[ss] + Mstar, N = MS.dimensions_star[ss] + # sum_m_n += M*N + sum_mstar_n += Mstar*N + + try: + MS.Surfs_star[ss].zetac + except AttributeError: + MS.Surfs_star[ss].zetac.generate_collocations() + jjvec = [None]*sum_mstar_n + iivec = [None]*sum_mstar_n + valvec = [None]*sum_mstar_n - return libsp.csc_matrix((valvec, (iivec, jjvec)), shape=(K_star, K), dtype=np.complex_) + K0tot, K0totstar = 0, 0 + ipoint = 0 + for ss in range(MS.n_surf): + M, N = MS.dimensions[ss] + Mstar, N = MS.dimensions_star[ss] + Surf = MS.Surfs[ss] + Surf_star = MS.Surfs_star[ss] + for iin in range(N): + for mm in range(Mstar): + # Value location in the sparse array + ipoint = K0totstar + mm * N + iin + # Compute CFL + if mm == 0: + conv_vec = Surf_star.zetac[:, 0, iin] - Surf.zetac[:, -1, iin] + dist = np.linalg.norm(conv_vec) + conv_dir_te = conv_vec/dist + vel = Surf.u_input_coll[:, -1, iin] + vel_value = np.dot(vel, conv_dir_te) + cfl = settings['dt']*vel_value/dist + else: + conv_vec = Surf_star.zetac[:, mm, iin] - Surf_star.zetac[:, mm - 1, iin] + dist = np.linalg.norm(conv_vec) + conv_dir = conv_vec/dist + vel = Surf.u_input_coll[:, -1, iin] + vel_value = np.dot(vel, conv_dir_te) + cfl = settings['dt']*vel_value/dist + # Compute coefficient + coef = get_Cw_cpx_coef_cfl_n1(cfl, zval) + # Assign values + jjvec[ipoint] = K0tot + N * (M - 1) + iin + iivec[ipoint] = K0totstar + mm * N + iin + if mm == 0: + # First row + valvec[ipoint] = coef + else: + ipoint_prev = K0totstar + (mm - 1) * N + iin + valvec[ipoint] = coef*valvec[ipoint_prev] + K0tot += MS.KK[ss] + K0totstar += MS.KK_star[ss] - def assemble_profiling(self): - """ - Generate profiling report for assembly and save it in self.prof_out. + return libsp.csc_matrix((valvec, (iivec, jjvec)), shape=(K_star, K), dtype=np.complex_) - To read the report: - import pstats - p=pstats.Stats(self.prof_out) - """ - import cProfile - cProfile.runctx('self.assemble()', globals(), locals(), filename=self.prof_out) +def get_Cw_cpx_coef_cfl_n1(cfl, zval): + # Convergence loop end criteria + tol = 1e-12 + rmax = 100 + + # Initial values + coef = 0. + r = 0 + + # Loop + error = 2*tol + while ((error > tol) and (r < rmax)): + delta_coef = ((1 - cfl)**r)*cfl*(zval**(-r-1)) + coef += delta_coef + error = np.abs(delta_coef/coef) + r += 1 + coef /= (1 - (1-cfl)**rmax*(zval**(-1))) + if (error > tol): + cout.cout_wrap(("WARNING computation of Cw_cpx did not reach desired accuracy. r: %d. error: %d" % (r, error)), 2) + return coef ################################################################################ diff --git a/sharpy/linear/src/multisurfaces.py b/sharpy/linear/src/multisurfaces.py index c27f9a9d0..0a79eaddd 100644 --- a/sharpy/linear/src/multisurfaces.py +++ b/sharpy/linear/src/multisurfaces.py @@ -87,6 +87,25 @@ def __init__(self, tsdata, vortex_radius, for_vel=np.zeros((6,))): self.KK_star.append(Map.K) self.KKzeta_star.append(Map.Kzeta) + + def get_ind_velocities_at_target_collocation_points(self, target): + """ + Computes normal induced velocities at target surface collocation points. + """ + # Loop input surfaces + for ss_in in range(self.n_surf): + # Bound + Surf_in = self.Surfs[ss_in] + target.u_ind_coll += \ + Surf_in.get_induced_velocity_over_surface(target, + target='collocation', Project=False) + + # Wake + Surf_in = self.Surfs_star[ss_in] + target.u_ind_coll += \ + Surf_in.get_induced_velocity_over_surface(target, + target='collocation', Project=False) + def get_ind_velocities_at_collocation_points(self): """ Computes normal induced velocities at collocation points. @@ -100,19 +119,8 @@ def get_ind_velocities_at_collocation_points(self): M_out, N_out = self.dimensions[ss_out] Surf_out.u_ind_coll = np.zeros((3, M_out, N_out)) - # Loop input surfaces - for ss_in in range(self.n_surf): - # Buond - Surf_in = self.Surfs[ss_in] - Surf_out.u_ind_coll += \ - Surf_in.get_induced_velocity_over_surface(Surf_out, - target='collocation', Project=False) + self.get_ind_velocities_at_target_collocation_points(Surf_out) - # Wake - Surf_in = self.Surfs_star[ss_in] - Surf_out.u_ind_coll += \ - Surf_in.get_induced_velocity_over_surface(Surf_out, - target='collocation', Project=False) def get_normal_ind_velocities_at_collocation_points(self): """ diff --git a/sharpy/solvers/steplinearuvlm.py b/sharpy/solvers/steplinearuvlm.py index bb39f5433..fff9bbd0c 100644 --- a/sharpy/solvers/steplinearuvlm.py +++ b/sharpy/solvers/steplinearuvlm.py @@ -109,6 +109,10 @@ class StepLinearUVLM(BaseSolver): settings_default['vortex_radius_wake_ind'] = vortex_radius_def settings_description['vortex_radius_wake_ind'] = 'Distance between points below which induction is not computed in the wake convection' + settings_types['cfl1'] = 'bool' + settings_default['cfl1'] = True + settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' + settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) @@ -171,6 +175,11 @@ def initialise(self, data, custom_settings=None): settings.to_custom_types(self.settings['ScalingDict'], self.scaling_settings_types, self.scaling_settings_default, no_ctype=True) + # Initialise velocity generator + velocity_generator_type = gen_interface.generator_from_string(self.settings['velocity_field_generator']) + self.velocity_generator = velocity_generator_type() + self.velocity_generator.initialise(self.settings['velocity_field_input']) + # Check whether linear UVLM has been initialised try: self.data.aero.linear @@ -238,7 +247,13 @@ def initialise(self, data, custom_settings=None): for ss in range(aero_tstep.n_surf)]) # Assemble the state space system - lin_uvlm_system.assemble_ss() + wake_prop_settings = {'dt': self.settings['dt'], + 'ts': self.data.ts, + 't': self.data.ts*self.settings['dt'], + 'for_pos':self.data.structure.timestep_info[-1].for_pos, + 'cfl1': self.settings['cfl1'], + 'vel_gen': self.velocity_generator} + lin_uvlm_system.assemble_ss(wake_prop_settings=wake_prop_settings) self.data.aero.linear['System'] = lin_uvlm_system self.data.aero.linear['SS'] = lin_uvlm_system.SS self.data.aero.linear['x_0'] = x_0 @@ -253,11 +268,6 @@ def initialise(self, data, custom_settings=None): # aero_tstep.linear.u = u_0 # aero_tstep.linear.y = f_0 - # Initialise velocity generator - velocity_generator_type = gen_interface.generator_from_string(self.settings['velocity_field_generator']) - self.velocity_generator = velocity_generator_type() - self.velocity_generator.initialise(self.settings['velocity_field_input']) - def run(self, aero_tstep, structure_tstep, diff --git a/tests/linear/assembly/test_assembly.py b/tests/linear/assembly/test_assembly.py index 7d581d693..7e6c8b008 100644 --- a/tests/linear/assembly/test_assembly.py +++ b/tests/linear/assembly/test_assembly.py @@ -16,6 +16,8 @@ import sharpy.linear.src.multisurfaces as multisurfaces import sharpy.linear.src.surface as surface import sharpy.utils.algebra as algebra +import sharpy.utils.cout_utils as cout +import sharpy.sharpy_main np.set_printoptions(linewidth=200, precision=3) @@ -1038,8 +1040,9 @@ def test_dfunstdgamma_dot(self): def test_wake_prop(self): + self.start_writer() MS = self.MS - C_list, Cstar_list = assembly.wake_prop(MS.Surfs, MS.Surfs_star) + C_list, Cstar_list = assembly.wake_prop(MS) n_surf = len(MS.Surfs) for ss in range(n_surf): @@ -1062,5 +1065,18 @@ def test_wake_prop(self): 'Prop. from trailing edge not correct' + def start_writer(self): + # Over write writer with print_file False to avoid I/O errors + global cout_wrap + cout_wrap = cout.Writer() + # cout_wrap.initialise(print_screen=False, print_file=False) + cout_wrap.cout_quiet() + sharpy.utils.cout_utils.cout_wrap = cout_wrap + + + def tearDown(self): + cout.finish_writer() + + if __name__ == '__main__': unittest.main() diff --git a/tests/linear/uvlm/test_infinite_span.py b/tests/linear/uvlm/test_infinite_span.py index 0210d6c09..b22001fb7 100644 --- a/tests/linear/uvlm/test_infinite_span.py +++ b/tests/linear/uvlm/test_infinite_span.py @@ -7,7 +7,6 @@ Modified: N. Goizueta, Sep 2019 """ -import sharpy.utils.sharpydir as sharpydir import unittest import os # import matplotlib.pyplot as plt diff --git a/tests/uvlm/dynamic/test_wake_cfl_n1.py b/tests/uvlm/dynamic/test_wake_cfl_n1.py new file mode 100755 index 000000000..52fa6040b --- /dev/null +++ b/tests/uvlm/dynamic/test_wake_cfl_n1.py @@ -0,0 +1,389 @@ +import numpy as np +import os +import sys +import unittest +# import shutil + +import sharpy.utils.generate_cases as gc +import sharpy.sharpy_main + +# maths +deg2rad = np.pi/180. + +# Generate the file containing the gust information +gust = np.array([[-1e12, 0, 0, 0], +[-1e-12, 0, 0, 0], +[0, -1.523086e-6, 0, 1.745328e-3], +[1e12, -1.523086e-6, 0, 1.745328e-3]]) +np.savetxt("0p1_step_gust.txt", gust, header="Time[s] DeltaUx[m/s] DeltaUy[m/s] DeltaUz[m/s]") + +# Analytical kussner function as a function of the non-dimensional time +kussner_function = np.array([[2.50000000e-01, 2.24788834e-01], + [5.00000000e-01, 3.08322030e-01], + [7.50000000e-01, 3.69409233e-01], + [1.00000000e+00, 4.18591199e-01], + [1.25000000e+00, 4.59608283e-01], + [1.50000000e+00, 4.94597861e-01], + [1.75000000e+00, 5.24998850e-01], + [2.00000000e+00, 5.51822632e-01], + [2.25000000e+00, 5.75792734e-01], + [2.50000000e+00, 5.97434419e-01], + [3.25000000e+00, 6.51811966e-01], + [4.00000000e+00, 6.94721054e-01], + [5.00000000e+00, 7.39586015e-01], + [7.50000000e+00, 8.13230851e-01], + [1.00000000e+01, 8.56395643e-01], + [1.25000000e+01, 8.84426328e-01], + [1.50000000e+01, 9.04257362e-01], + [1.75000000e+01, 9.19155200e-01], + [2.00000000e+01, 9.30758783e-01], + [0.00000000e+00, 6.43293001e-02], + [2.50000000e+01, 9.47358507e-01], + [3.00000000e+01, 9.58126128e-01], + [3.50000000e+01, 9.65279097e-01], + [4.00000000e+01, 9.70275111e-01], + [4.50000000e+01, 9.74053377e-01], + [5.00000000e+01, 9.77137592e-01], + [6.00000000e+01, 9.81903866e-01], + [7.00000000e+01, 9.84820831e-01], + [8.00000000e+01, 9.86297902e-01], + [9.00000000e+01, 9.87557134e-01], + [9.97500000e+01, 9.89298872e-01]]) + + +class TestWakeCFLn1(unittest.TestCase): + """ + Validate an airfoil response to a Kussner gust + Wake discretisation not complying with CFL=1 + Non-linear and linear responses + """ + + chord = 1. # + AR = 1e7 # Wing aspect ratio + + nodes_AR = 4 # even number of nodes in the spanwise direction + + num_chord_panels = 8 # Number of chord panels + + uinf = 1. # Flow velocity + uinf_dir = np.array([1., 0., 0.]) # Flow velocity direction + air_density = 1.225 # Flow density + + aoa_ini_deg = 1. # Initial angle of attack + + wake_chords = 100 # Length of the wake in airfoil chords units + + # Time discretization + final_ndtime = 12.0 # Final non-dimensional time + time_steps_per_chord = 8 # Number of time steps required by the flow to cover the airfoil chord + dt = chord/time_steps_per_chord/uinf # time step + nd_dt = dt*uinf/chord*2 # non-dimensional time step + offset_time_steps = 10 # Number of time steps before the gust gets to the airfoil + + offset = 0. + + def generate_geometry(self): + # offset is just the distance between the gust edge and the first colocation point + # It is just used for plotting purposes + len_first_panel = (self.chord - self.dt*self.uinf)/(self.num_chord_panels - 1) + self.offset = self.offset_time_steps*self.dt*self.uinf + (-0.25*self.chord + 0.75*len_first_panel)*np.cos(self.aoa_ini_deg*deg2rad) # should be 0.5*len_first_panel but to make sure it happens I increase it a bit + offset_LE = self.offset - 0.25*self.chord*np.cos(self.aoa_ini_deg*deg2rad) + + # Compute span + span = self.chord*self.AR + assert self.nodes_AR%2 == 0, "nodes_AR must be even" + + # Compute number of nodes + num_node = self.nodes_AR + 1 + num_node_semispan = int((num_node - 1)/2 + 1) + nodes_y_semispan = np.zeros((num_node_semispan,)) + + # Number of seminodes + n2 = int(self.nodes_AR/2 + 1) + + # Nodes coordinates in the spanwise direction + nodes_y_semispan[0:n2] = np.linspace(0, self.AR/2*self.chord, n2) + + # Nodes coordinates + nodes_y = np.zeros((num_node,)) + nodes_y[:num_node_semispan] = -1*nodes_y_semispan[::-1] + nodes_y[num_node_semispan - 1:] = nodes_y_semispan + + assert not (np.diff(nodes_y) == 0).any(), "Repited nodes" + + nodes = np.zeros((len(nodes_y), 3)) + nodes[:, 1] = nodes_y + + # Irrelevant structural properties + mass_per_unit_length = 1. + mass_iner = 1e-4 + EA = 1e9 + GA = 1e9 + GJ = 1e9 + EI = 1e9 + + # Airfoil camber + airfoil_camber = np.zeros((1, 100, 2)) + airfoil_camber[0,:,0] = np.linspace(0.,1.,100) + + # Structure + airfoil = gc.AeroelasticInformation() + airfoil.StructuralInformation.num_node = len(nodes) + airfoil.StructuralInformation.num_node_elem = 3 + airfoil.StructuralInformation.compute_basic_num_elem() + + airfoil.StructuralInformation.generate_uniform_sym_beam(nodes, + mass_per_unit_length, + mass_iner, + EA, + GA, + GJ, + EI, + num_node_elem = airfoil.StructuralInformation.num_node_elem, + y_BFoR = 'x_AFoR', + num_lumped_mass=0) + + airfoil.StructuralInformation.structural_twist = 0.*deg2rad*np.ones_like(airfoil.StructuralInformation.structural_twist) + airfoil.StructuralInformation.boundary_conditions = np.zeros((airfoil.StructuralInformation.num_node), dtype = int) + airfoil.StructuralInformation.boundary_conditions[0] = 1 # Clamped end + airfoil.StructuralInformation.boundary_conditions[-1] = -1 # Free end + + # Generate blade aerodynamics + airfoil.AerodynamicInformation.create_one_uniform_aerodynamics(airfoil.StructuralInformation, + chord = self.chord, + twist = self.aoa_ini_deg*deg2rad, + sweep = 0., + num_chord_panels = self.num_chord_panels, + m_distribution = 'uniform', + elastic_axis = 0.25, + num_points_camber = 100, + airfoil = airfoil_camber) + + return airfoil + + + def generate_files(self, case_header, airfoil): + + # Define the simulation + case = ('%s_aoa%.2f' % (case_header, self.aoa_ini_deg)).replace(".", "p") + route = os.path.dirname(os.path.realpath(__file__)) + '/' + + SimInfo = gc.SimulationInformation() + SimInfo.set_default_values() + + SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', + 'AerogridLoader', + 'StaticCoupled', + 'DynamicCoupled'] + + SimInfo.solvers['SHARPy']['case'] = case + SimInfo.solvers['SHARPy']['route'] = route + SimInfo.solvers['SHARPy']['log_folder'] = "./output/%s" % case + SimInfo.solvers['SHARPy']['write_log'] = False + SimInfo.solvers['SHARPy']['write_screen'] = False + SimInfo.set_variable_all_dicts('rho', self.air_density) + SimInfo.set_variable_all_dicts('dt', self.dt) + + SimInfo.solvers['BeamLoader']['unsteady'] = 'on' + + import sharpy.utils.generator_interface as gi + if case_header == 'traditional': + + SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' + SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': self.uinf, + 'u_inf_direction': self.uinf_dir, + 'dt': self.dt, + 'dx1': self.chord/self.num_chord_panels, + 'ndx1': int((self.wake_chords*self.chord)/(self.chord/self.num_chord_panels)), + 'r':1., + 'dxmax':10*self.chord} + + gi.dictionary_of_generators(print_info=False) + hw = gi.dict_of_generators['StraightWake'] + wsg_in = SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] # for simplicity + length = 0 + mstar = 0 + while length < (self.wake_chords*self.chord): + mstar += 1 + length += hw.get_deltax(mstar, wsg_in['dx1'], + wsg_in['ndx1'], + wsg_in['r'], + wsg_in['dxmax']) + + SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' + SimInfo.solvers['AerogridLoader']['mstar'] = mstar + SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0.,0.,0.]) + + elif case_header in ['new_nonlinear', 'new_linear']: + + SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': self.uinf, + 'u_inf_direction': self.uinf_dir, + 'dt': self.dt, + 'dx1': self.chord/self.num_chord_panels, + 'ndx1': int((1*self.chord)/(self.chord/self.num_chord_panels)), + 'r':2., + 'dxmax':10*self.chord} + + gi.dictionary_of_generators(print_info=False) + hw = gi.dict_of_generators['StraightWake'] + wsg_in = SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] # for simplicity + length = 0 + mstar = 0 + while length < (self.wake_chords*self.chord): + mstar += 1 + length += hw.get_deltax(mstar, wsg_in['dx1'], + wsg_in['ndx1'], + wsg_in['r'], + wsg_in['dxmax']) + + SimInfo.solvers['AerogridLoader']['mstar'] = mstar + + + SimInfo.solvers['SteadyVelocityField']['u_inf'] = self.uinf + SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = self.uinf_dir + + SimInfo.solvers['StaticUvlm']['horseshoe'] = False + SimInfo.solvers['StaticUvlm']['num_cores'] = 8 + SimInfo.solvers['StaticUvlm']['n_rollup'] = 0 + SimInfo.solvers['StaticUvlm']['rollup_dt'] = self.dt + SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField' + SimInfo.solvers['StaticUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField'] + + SimInfo.solvers['StaticCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' + SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] + SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' + SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] + + if case_header in ['traditional', 'new_nonlinear']: + SimInfo.solvers['StepUvlm']['convection_scheme'] = 0 + SimInfo.solvers['StepUvlm']['num_cores'] = 8 + SimInfo.solvers['StepUvlm']['velocity_field_generator'] = 'GustVelocityField' + SimInfo.solvers['StepUvlm']['velocity_field_input'] = {'u_inf' : self.uinf, + 'u_inf_direction': np.array([1.,0.,0.]), + 'gust_shape': 'time varying', + 'offset': self.offset, + 'relative_motion': True, + 'gust_parameters': {'file' : '0p1_step_gust.txt', + 'gust_length': 0., + 'gust_intensity': 0.}} + + SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' + SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] + elif case_header == 'new_linear': + + SimInfo.solvers['StepLinearUVLM']['dt'] = self.dt + SimInfo.solvers['StepLinearUVLM']['integr_order'] = 2 + SimInfo.solvers['StepLinearUVLM']['remove_predictor'] = True + SimInfo.solvers['StepLinearUVLM']['use_sparse'] = True + SimInfo.solvers['StepLinearUVLM']['density'] = self.air_density + SimInfo.solvers['StepLinearUVLM']['vortex_radius'] = 1e-6 + SimInfo.solvers['StepLinearUVLM']['vortex_radius_wake_ind'] = 1e-3 + SimInfo.solvers['StepLinearUVLM']['velocity_field_generator'] = 'GustVelocityField' + SimInfo.solvers['StepLinearUVLM']['velocity_field_input'] = {'u_inf' : self.uinf, + 'u_inf_direction': self.uinf_dir, + 'gust_shape': 'time varying', + 'offset': self.offset, + 'relative_motion': True, + 'gust_parameters': {'file' : '0p1_step_gust.txt', + 'gust_length': 0., + 'gust_intensity': 0.}} + + SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepLinearUVLM' + SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepLinearUVLM'] + + if case_header == 'new_nonlinear': + SimInfo.solvers['StaticUvlm']['cfl1'] = False + SimInfo.solvers['StepUvlm']['cfl1'] = False + + elif case_header == 'new_linear': + SimInfo.solvers['StaticUvlm']['cfl1'] = False + SimInfo.solvers['StepLinearUVLM']['cfl1'] = False + + elif case_header == 'traditional': + SimInfo.solvers['StaticUvlm']['cfl1'] = True + SimInfo.solvers['StepUvlm']['cfl1'] = True + + SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' + SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] + SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['Cleanup'] + SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'Cleanup': SimInfo.solvers['Cleanup']} + SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0 + SimInfo.solvers['DynamicCoupled']['include_unsteady_force_contribution'] = True + SimInfo.solvers['DynamicCoupled']['relaxation_factor'] = 0. + SimInfo.solvers['DynamicCoupled']['final_relaxation_factor'] = 0. + SimInfo.solvers['DynamicCoupled']['dynamic_relaxation'] = False + SimInfo.solvers['DynamicCoupled']['relaxation_steps'] = 0 + SimInfo.solvers['DynamicCoupled']['fsi_tolerance'] = 1e-6 + + # Time discretization + time_steps = int(self.final_ndtime / self.nd_dt - 1) + SimInfo.define_num_steps(time_steps) + + SimInfo.with_forced_vel = True + SimInfo.for_vel = np.zeros((time_steps,6), dtype=float) + SimInfo.for_acc = np.zeros((time_steps,6), dtype=float) + SimInfo.with_dynamic_forces = True + SimInfo.dynamic_forces = np.zeros((time_steps,airfoil.StructuralInformation.num_node,6), dtype=float) + + gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) + airfoil.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) + SimInfo.generate_solver_file() + SimInfo.generate_dyn_file(time_steps) + + return case, route + + + def run_test(self, case_header): + + airfoil = self.generate_geometry() + + case, route = self.generate_files(case_header, airfoil) + sharpy_file = route + case + '.sharpy' + sharpy_output = sharpy.sharpy_main.main(['', sharpy_file]) + + lift = sharpy_output.structure.timestep_info[-1].steady_applied_forces[self.nodes_AR//2, 2] + lift += sharpy_output.structure.timestep_info[-1].unsteady_applied_forces[self.nodes_AR//2, 2] + + nd_time = sharpy_output.ts*self.nd_dt + daoa = 0.1 + clsteady = 2*np.pi*(self.aoa_ini_deg + daoa)*deg2rad + offset_plot = 0.84375 + factor = 0.5*self.air_density*self.uinf**2*self.chord*2.5e6 + sharpy_cl_clss = -lift/factor/clsteady + kussner_cl_clss = 2*np.pi*self.aoa_ini_deg*deg2rad + kussner_cl_clss += 2*np.pi*daoa*deg2rad*np.interp(nd_time, + kussner_function[:, 0], + kussner_function[:, 1]) + kussner_cl_clss /= clsteady + + self.assertAlmostEqual(sharpy_cl_clss, kussner_cl_clss, 1) + + + def test_traditional(self): + self.run_test('traditional') + + + def test_new_nonlinear(self): + self.run_test('new_nonlinear') + + + def test_new_linear(self): + self.run_test('new_linear') + + @classmethod + def tearDownClass(cls): + solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + solver_path += '/' + files_to_delete = [] + for name in ['traditional_aoa1p00', 'new_nonlinear_aoa1p00', 'new_linear_aoa1p00']: + files_to_delete.extend((name + '.aero.h5', + name + '.dyn.h5', + name + '.fem.h5', + name + '.sharpy')) + files_to_delete.append("0p1_step_gust.txt") + + for f in files_to_delete: + os.remove(solver_path + f) + + # shutil.rmtree(solver_path + 'output/') +