def fa(m0, n0, zs_norm, thetas, funcnum=2): """Calculates the matrix with the base functions for `w_0` The calculated matrix is directly used to calculate the `w_0` displacement field, when the corresponding coefficients `c_0` are known, through:: a = fa(m0, n0, zs_norm, thetas, funcnum) w0 = a.dot(c0) Parameters ---------- m0 : int The number of terms along the meridian. n0 : int The number of terms along the circumference. zs_norm : np.ndarray The normalized `z` coordinates (from 0. to 1.) used to compute the base functions. thetas : np.ndarray The angles in radians representing the circumferential positions. funcnum : int, optional The function used for the approximation (see function :func:`.calc_c0`) """ try: import _fit_data return _fit_data.fa(m0, n0, zs_norm, thetas, funcnum) except: warn('_fit_data.pyx could not be imported, executing in Python/NumPy' + '\n\t\tThis mode is slower and needs more memory than the' + '\n\t\tPython/NumPy/Cython mode', level=1) zs = zs_norm.ravel() ts = thetas.ravel() n = zs.shape[0] zsmin = zs.min() zsmax = zs.max() if zsmin < 0 or zsmax > 1: log('zs.min()={0}'.format(zsmin)) log('zs.max()={0}'.format(zsmax)) raise ValueError('The zs array must be normalized!') if funcnum==1: a = np.array([[sin(i*pi*zs)*sin(j*ts), sin(i*pi*zs)*cos(j*ts)] for j in range(n0) for i in range(1, m0+1)]) a = a.swapaxes(0,2).swapaxes(1,2).reshape(n,-1) elif funcnum==2: a = np.array([[cos(i*pi*zs)*sin(j*ts), cos(i*pi*zs)*cos(j*ts)] for j in range(n0) for i in range(m0)]) a = a.swapaxes(0,2).swapaxes(1,2).reshape(n,-1) elif funcnum==3: a = np.array([[sin(i*pi*zs)*sin(j*ts), sin(i*pi*zs)*cos(j*ts), cos(i*pi*zs)*sin(j*ts), cos(i*pi*zs)*cos(j*ts)] for j in range(n0) for i in range(m0)]) a = a.swapaxes(0,2).swapaxes(1,2).reshape(n,-1) return a
def fw0(m0, n0, c0, xs_norm, ts, funcnum=2): r"""Calculates the imperfection field `w_0` for a given input Parameters ---------- m0 : int The number of terms along the meridian. n0 : int The number of terms along the circumference. c0 : np.ndarray The coefficients of the imperfection pattern. xs_norm : np.ndarray The meridian coordinate (`x`) normalized to be between ``0.`` and ``1.``. ts : np.ndarray The angles in radians representing the circumferential coordinate (`\theta`). funcnum : int, optional The function used for the approximation (see function :func:`.calc_c0`) Returns ------- w0s : np.ndarray An array with the same shape of ``xs_norm`` containing the calculated imperfections. Notes ----- The inputs ``xs_norm`` and ``ts`` must be of the same size. The inputs must satisfy ``c0.shape[0] == size*m0*n0``, where: - ``size=2`` if ``funcnum==1 or funcnum==2`` - ``size=4`` if ``funcnum==3`` """ if xs_norm.shape != ts.shape: raise ValueError('xs_norm and ts must have the same shape') if funcnum==1: size = 2 elif funcnum==2: size = 2 elif funcnum==3: size = 4 if c0.shape[0] != size*m0*n0: raise ValueError('Invalid c0 for the given m0 and n0!') try: import _fit_data w0s = _fit_data.fw0(m0, n0, c0, xs_norm.ravel(), ts.ravel(), funcnum) except: a = fa(m0, n0, xs_norm.ravel(), ts.ravel(), funcnum) w0s = a.dot(c0) return w0s.reshape(xs_norm.shape)
def calc_c0(path, m0=50, n0=50, funcnum=2, fem_meridian_bot2top=True, rotatedeg=None, filter_m0=None, filter_n0=None, sample_size=None, maxmem=8): r"""Find the coefficients that best fit the `w_0` imperfection The measured data will be fit using one of the following functions, selected using the ``funcnum`` parameter: 1) Half-Sine Function .. math:: w_0 = \sum_{i=1}^{m_0}{ \sum_{j=0}^{n_0}{ {c_0}_{ij}^a sin{b_z} sin{b_\theta} +{c_0}_{ij}^b sin{b_z} cos{b_\theta} }} 2) Half-Cosine Function (default) .. math:: w_0 = \sum_{i=0}^{m_0}{ \sum_{j=0}^{n_0}{ {c_0}_{ij}^a cos{b_z} sin{b_\theta} +{c_0}_{ij}^b cos{b_z} cos{b_\theta} }} 3) Complete Fourier Series .. math:: w_0 = \sum_{i=0}^{m_0}{ \sum_{j=0}^{n_0}{ {c_0}_{ij}^a sin{b_z} sin{b_\theta} +{c_0}_{ij}^b sin{b_z} cos{b_\theta} +{c_0}_{ij}^c cos{b_z} sin{b_\theta} +{c_0}_{ij}^d cos{b_z} cos{b_\theta} }} where: .. math:: b_z = i \pi \frac z H_{points} b_\theta = j \theta where `H_{points}` represents the difference between the maximum and the minimum `z` values in the imperfection file. The approximation can be written in matrix form as: .. math:: w_0 = [g] \{c_0\} where `[g]` carries the base functions and `{c_0}` the respective amplitudes. The solution consists on finding the best `{c_0}` that minimizes the least-square error between the measured imperfection pattern and the `w_0` function. Parameters ---------- path : str or np.ndarray The path of the file containing the data. Can be a full path using ``r"C:\Temp\inputfile.txt"``, for example. The input file must have 3 columns "`\theta` `z` `imp`" expressed in Cartesian coordinates. This input can also be a ``np.ndarray`` object, with `\theta`, `z`, `imp` in each corresponding column. m0 : int Number of terms along the meridian (`z`). n0 : int Number of terms along the circumference (`\theta`). funcnum : int, optional As explained above, selects the base functions used for the approximation. fem_meridian_bot2top : bool, optional A boolean indicating if the finite element has the `x` axis starting at the bottom or at the top. rotatedeg : float or None, optional Rotation angle in degrees telling how much the imperfection pattern should be rotated about the `X_3` (or `Z`) axis. filter_m0 : list, optional The values of ``m0`` that should be filtered (see :func:`.filter_c0`). filter_n0 : list, optional The values of ``n0`` that should be filtered (see :func:`.filter_c0`). sample_size : int or None, optional An in specifying how many points of the imperfection file should be used. If ``None`` is used all points file will be used in the computations. maxmem : int, optional Maximum RAM memory in GB allowed to compute the base functions. The ``scipy.interpolate.lstsq`` will go beyond this limit. Returns ------- out : np.ndarray A 1-D array with the best-fit coefficients. Notes ----- If a similar imperfection pattern is expected along the meridian and along the circumference, the analyst can use an optimized relation between ``m0`` and ``n0`` in order to achieve a higher accuracy for a given computational cost, as proposed by Castro et al. (2014): .. math:: n_0 = m_0 \frac{\pi(R_{bot}+R_{top})}{2H} """ from scipy.linalg import lstsq if isinstance(path, np.ndarray): input_pts = path path = 'unmamed.txt' else: input_pts = np.loadtxt(path) if input_pts.shape[1] != 3: raise ValueError('Input does not have the format: "theta, z, imp"') if (input_pts[:,0].min() < -2*np.pi or input_pts[:,0].max() > 2*np.pi): raise ValueError( 'In the input: "theta, z, imp"; "theta" must be in radians!') log('Finding c0 coefficients for {0}'.format(str(os.path.basename(path)))) log('using funcnum {0}'.format(funcnum), level=1) if sample_size: num = input_pts.shape[0] if sample_size < num: input_pts = input_pts[sample(range(num), int(sample_size))] if funcnum==1: size = 2 elif funcnum==2: size = 2 elif funcnum==3: size = 4 else: raise ValueError('Valid values for "funcnum" are 1, 2 or 3') # the least-squares algorithm uses approximately the double the memory # used by the coefficients matrix. This is non-linear though. memfac = 2.2 maxnum = int(maxmem*1024*1024*1024*8/(64.*size*m0*n0)/memfac) num = input_pts.shape[0] if num >= maxnum: input_pts = input_pts[sample(range(num), int(maxnum))] warn('Using {0} measured points due to the "maxmem" specified'. format(maxnum), level=1) ts = input_pts[:, 0].copy() if rotatedeg is not None: ts += deg2rad(rotatedeg) zs = input_pts[:, 1] w0pts = input_pts[:, 2] #NOTE using `H_measured` did not allow a good fitting result #zs /= H_measured zs = (zs - zs.min())/(zs.max() - zs.min()) if not fem_meridian_bot2top: #TODO zs *= -1 zs += 1 a = fa(m0, n0, zs, ts, funcnum) log('Base functions calculated', level=1) c0, residues, rank, s = lstsq(a, w0pts) log('Finished scipy.linalg.lstsq', level=1) if filter_m0 is not None or filter_n0 is not None: c0 = filter_c0(m0, n0, c0, filter_m0, filter_n0, funcnum=funcnum) return c0, residues
[-cos(b)*sin(g), (cos(a)*cos(g) - sin(a)*sin(b)*sin(g)), (sin(a)*cos(g) + cos(a)*sin(b)*sin(g)), y0], [sin(b), -sin(a)*cos(b), cos(a)*cos(b), z0]]) if __name__=='__main__': import matplotlib.pyplot as plt from _fit_data import fa path = r'C:\clones\desicos\desicos\conecylDB\files\dlr\degenhardt_2010_z25\degenhardt_2010_z25_msi_theta_z_imp.txt' m0 = 20 n0 = 20 c0, residues = calc_c0(path, m0=m0, n0=n0, sample_size=0.75) theta = np.linspace(-pi, pi, 1000) z = np.linspace(0, 1., 400) theta, z = np.meshgrid(theta, z, copy=False) a = fa(m0, n0, z.ravel(), theta.ravel(), funcnum=1) w = a.dot(c0).reshape(1000, 400) levels = np.linspace(w.min(), w.max(), 400) plt.contourf(theta, z, w.reshape(theta.shape), levels=levels) plt.gcf().savefig('plot.png', transparent=True, bbox_inches='tight', pad_inches=0.05)