Processing Geometric Imperfection Data

This part of the turorial will explain in a step-by-step example how one can use the developed modules to post-process measured imperfection data.

The following steps will be discussed:

  • raw data from measurement system

  • finding

Raw Data from Mesurement Systems

The raw files obtained from measured geometric imperfections usually come with three columns:

x1 y1 z1
x2 y2 z2
...
xn yn zn

The following sample has been used to illustrate this tutorial (first 10 lines shown below):

310.909   1174.136   261.8
146.126   754.321   378.119
216.694   1111.78   342.928
-261.395   1031.417   -314.052
-5.499   99.519   -407.764
-70.556   95.452   -401.672
-336.88   1148.365   -229.665
-351.651   705.414   204.226
403.703   454.238   -37.619
122.444   55.322   386.451

The following script is used to plot the sample with the imperfection exagerated. Note that best_fit_raw_data.py has to be executed before.

plot_sample_3d.py

import numpy as np
import mayavi.mlab as mlab

from desicos.conecylDB.read_write import xyz2thetazimp

R = 406.4 # nominal radius
H = 1219.2 # nominal height
R_best_fit = 407.193 # radius obtained with the best-fit routine

thetas, zfit, dR = np.genfromtxt('sample_theta_z_imp.txt', unpack=True)

xpos = R_best_fit*np.cos(thetas)
ypos = R_best_fit*np.sin(thetas)
sf = 20
x2 = xpos + sf*dR*np.cos(thetas)
y2 = ypos + sf*dR*np.sin(thetas)
z2 = zfit

Tinv = np.loadtxt('tmp_Tinv.txt')
x, y, z = Tinv.dot(np.vstack((x2, y2, z2, np.ones_like(x2))))

black = (0,0,0)
white = (1,1,1)
mlab.figure(bgcolor=white)
mlab.points3d(x, y, z, color=(0,1,0))
mlab.plot3d([0, 600], [0, 0], [0, 0], color=black, tube_radius=10.)
mlab.plot3d([0, 0], [0, 1600], [0, 0], color=black, tube_radius=10.)
mlab.plot3d([0, 0], [0, 0], [0, 600], color=black, tube_radius=10.)
mlab.text3d(650, -50, +50, 'x', color=black, scale=100.)
mlab.text3d(0, 1650, +50, 'y', color=black, scale=100.)
mlab.text3d(0, -50, 650, 'z', color=black, scale=100.)
mlab.savefig('plot_sample_3d.png', size=(500, 500))

giving:

../../../_images/plot_sample_3d.png

In order to transform the raw data to the finite element coordinate system, the recommended procedure is the function xyz2thetazimp(), exemplified below. This function converts the “x y z” data to a to convert them to a “\theta z imp” format. This function is a wrapper that calls funcion best_fit_cylinder() or best_fit_cone(), where the right coordinate transformation is calculated.

best_fit_raw_data.py

import numpy as np

from desicos.conecylDB.read_write import xyz2thetazimp

mps, out = xyz2thetazimp('sample.txt', alphadeg_measured=0, R_expected=406.4,
        H_measured=1219.2, clip_bottom=10, clip_top=10, use_best_fit=True,
        best_fit_output=True, rotatedeg=0.)

np.savetxt('tmp_Tinv.txt', out['Tinv'])

It will create an output file with a prefix "_theta_z_imp.txt" where the imperfection data is stored in the format:

theta1 z1 imp1
theta2 z2 imp2
...
thetan zn impn

where thetai and zi are the coordinates \theta and z, while impi represents the imperfection amplitude at this coordinate. An example of the transformed imperfection data is given below (first 10 lines), and the full output is given here:

0.701704 44.752223 -0.299107
1.202029 464.737892 -0.772438
1.008550 107.201740 -0.834017
-2.266294 188.347008 0.697946
-1.581399 1119.953672 -0.574269
-1.742285 1124.099646 -0.728166
-2.545140 71.468489 0.131696
2.612788 514.312597 -0.417002
-0.090133 764.620654 -1.094824
1.261926 1163.763315 -0.307154

Note

It is also possible to use xyz2thetazimp() in other ways, as detailed in the function’s documentation

The transformed data can also be visualized in the 3-D domain, which was accomplished using the following script:

plot_sample_3d_tranformed.py

import numpy as np
import mayavi.mlab as mlab

from desicos.conecylDB.read_write import xyz2thetazimp

R = 406.4
R_best_fit = 407.168

data = np.genfromtxt('sample_theta_z_imp.txt')
thetas, zfit, dR = data.T

xpos = R_best_fit*np.cos(thetas)
ypos = R_best_fit*np.sin(thetas)
sf = 20
x2 = xpos + sf*dR*np.cos(thetas)
y2 = ypos + sf*dR*np.sin(thetas)
z2 = zfit

black = (0,0,0)
white = (1,1,1)
mlab.figure(bgcolor=white)
mlab.points3d(x2, y2, z2, color=(0.5,0.5,0.5))
mlab.plot3d([0, 600], [0, 0], [0, 0], color=black, tube_radius=10.)
mlab.plot3d([0, 0], [0, 600], [0, 0], color=black, tube_radius=10.)
mlab.plot3d([0, 0], [0, 0], [0, 1600], color=black, tube_radius=10.)
mlab.text3d(650, -50, +50, 'x', color=black, scale=100.)
mlab.text3d(0, 650, +50, 'y', color=black, scale=100.)
mlab.text3d(0, -50, 1650, 'z', color=black, scale=100.)
mlab.savefig('plot_sample_3d_transformed.png')

giving:

../../../_images/plot_sample_3d_transformed.png

Note that due to the axisymmetry it may happen that the final result has some offset rotation. For this reason the interpolation routines that will be discussed in the next sessions of this tutorial include a rotatedeg parameter that the analyst should use in order to correct this angular offset.

Inverse-Weighted Interpolation

Function iterp_theta_z_imp() is the one used to interpolate the measured data into a given mesh. In the example below the mesh creates using np.meshgrid() is used for plotting purposes. Note that the an angular offset of -88.5 degrees was used in order to adjust the imperfection.

interpolate_inv_weighted.py

import numpy as np

from desicos.conecylDB.interpolate import interp_theta_z_imp

R_model = 406.4 + 0.8128/2.
H_measured = 1219.2
ignore_bot_h = 25.4
ignore_top_h = 25.4

nz = 180
nt = 560

theta = np.linspace(-np.pi, np.pi, nt)
z = np.linspace(0, H_measured, nz)
T, Z = np.meshgrid(theta, z, copy=False)

data = np.loadtxt('sample_theta_z_imp.txt')
ts, zs, imps = data.T
a = np.vstack((ts, zs, imps)).T
mesh = np.vstack((T.ravel(), Z.ravel())).T
w0 = interp_theta_z_imp(data=a, mesh=mesh, semi_angle=0,
        H_measured=H_measured, H_model=H_measured, R_model=R_model,
        stretch_H=False, rotatedeg=-88.5, num_sub=100, ncp=5,
        power_parameter=2, ignore_bot_h=ignore_bot_h,
        ignore_top_h=ignore_top_h)
np.savetxt('tmp_inv_weighted.txt',
           np.vstack((T.ravel(), Z.ravel(), w0)).T, fmt='%1.8f')

An the generated data can be plotted using the following code:

plot_inv_weighted.py

import numpy as np
from numpy import pi
import matplotlib.cm as cm
import matplotlib.pyplot as plt

plt.rcParams['axes.labelsize'] = 9.
plt.rcParams['font.size'] = 9.

path = 'tmp_inv_weighted.txt'

nz = 180
nt = 560

R = 407.168

#cmap = cm.jet
cmap = cm.gist_rainbow_r

theta, z, w0 = np.loadtxt(path, unpack=True)

fig = plt.figure(figsize=(3.5, 3.5), dpi=220)
levels = np.linspace(w0.min(), w0.max(), 400)
contour = plt.contourf(theta.reshape(nz, nt)*R, z.reshape(nz, nt),
        w0.reshape(nz, nt), levels=levels, cmap=cmap)

ax = plt.gca()
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)

ax.set_aspect('equal')
ax.xaxis.set_ticks([-pi*R, pi*R])
ax.xaxis.set_ticklabels([r'$-\pi$', r'$\pi$'])
ax.set_xlabel(r'$\theta$ $[rad]$', ha='center', va='center')
ax.xaxis.labelpad=-7

ax.yaxis.set_ticks([0, 1219.2])
ax.set_ylabel('$x$\n$[mm]$', rotation='horizontal', ha='center',
        va='center')
ax.yaxis.labelpad=-10

colorbar=True
if colorbar:
    from mpl_toolkits.axes_grid1 import make_axes_locatable

    fsize = 10
    cbar_nticks = 2
    divider = make_axes_locatable(ax)
    cax = divider.append_axes('right', size='5%', pad=0.05)
    cbarticks = np.linspace(w0.min(), w0.max(), cbar_nticks)
    cbar = plt.colorbar(contour, ticks=cbarticks, format='%1.3f',
                        cax=cax, cmap=cmap)
    cax.text(0.5, 1.1, '$w_0$ $[mm]$', ha='left', va='bottom',
            fontsize=fsize)
    cbar.outline.remove()
    cbar.ax.tick_params(labelsize=fsize, pad=0., tick2On=False)

fig.savefig('plot_inv_weighted.png', transparent=True,
                  bbox_inches='tight', pad_inches=0.05, dpi=220)


giving:

../../../_images/plot_inv_weighted.png

Half-Cosine Function

Function calc_c0() is used to interpolate the measured data using Fourier series. The half-cosine function corresponds to the parameter funcnum = 2, as can be seen in the function’s documentation.

The following script was used to calculate the coefficients \{c_0\}. Note how the angular offset is applied using the rotatedeg parameter:

interpolate_half_cosine.py

import numpy as np

from desicos.conecylDB.fit_data import calc_c0

method = 'points'
funcnum= 2

path = 'sample_theta_z_imp.txt'

R = 406.4 + 0.8128/2.
H = 1219.2
m0 = 10
n0 = 10

c0, residues = calc_c0(path, m0=m0, n0=n0, funcnum=funcnum, rotatedeg=-88.5)
outname = 'tmp_c0_{0}_f{1:d}_m0_{2:03d}_n0_{3:03d}.txt'.format(method,
        funcnum, m0, n0)
np.savetxt(outname, c0)

and the following routine was used to plot the corresponding imperfection field:

plot_half_cosine.py

import numpy as np
from numpy import pi
import matplotlib.cm as cm
import matplotlib.pyplot as plt

from compmech.conecyl.imperfections import fw0

plt.rcParams['axes.labelsize'] = 9.
plt.rcParams['font.size'] = 9.

method = 'points'
funcnum = 2
names = ['C01']

nz = 180
nt = 560

H = 1219.2
zpts_min = 25.4
zpts_max = H - 25.4
R = 406.4 + 0.8128/2.

#cmap = cm.jet
cmap = cm.gist_rainbow_r

m0 = 10
n0 = 10

filename = 'tmp_c0_{0}_f{1:d}_m0_{2:03d}_n0_{3:03d}.txt'.format(method,
        funcnum, m0, n0)
c0 = np.loadtxt(filename)
ts = np.linspace(-pi, pi, nt)
xs = np.linspace(0, 1., nz)
zs = xs*H
check = (zs >= zpts_min) & (zs <= zpts_max)
test = xs[check]
xs -= test.min()
xs /= xs.max()
xs[xs<0] = 0
xs[xs>1] = 1

dummy, xs = np.meshgrid(ts, xs, copy=False)
ts, zs = np.meshgrid(ts, zs, copy=False)

w0 = fw0(m0, n0, c0, xs, ts, funcnum=funcnum)
w0.flat[(zs.flat < zpts_min) | (zs.flat > zpts_max)] = 0

fig = plt.figure(figsize=(3.5, 3.5), dpi=220)

levels = np.linspace(w0.min(), w0.max(), 400)
contour = plt.contourf(ts*R, zs, w0.reshape(ts.shape), levels=levels,
        cmap=cmap)

ax = plt.gca()
ax.set_aspect('equal')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.spines['bottom'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.xaxis.set_ticks([-pi*R, pi*R])
ax.xaxis.set_ticklabels([r'$-\pi$', r'$\pi$'])
ax.set_xlabel(r'$\theta$ $[rad]$', va='center', ha='center')
ax.xaxis.labelpad = -7

ax.yaxis.set_ticks([0, H])
ax.set_ylabel('$z$\n$[mm]$', rotation='horizontal', va='center',
        ha='center')
ax.yaxis.labelpad = -10


colorbar = True
if colorbar:
    from mpl_toolkits.axes_grid1 import make_axes_locatable

    fsize = 10
    cbar_nticks = 2
    divider = make_axes_locatable(ax)
    cax = divider.append_axes('right', size='5%', pad=0.05)
    cbarticks = np.linspace(w0.min(), w0.max(), cbar_nticks)
    cbar = plt.colorbar(contour, ticks=cbarticks, format='%1.3f',
                        cax=cax, cmap=cmap)
    cax.text(0.5, 1.1, '$w_0$ $[mm]$', ha='left', va='bottom',
            fontsize=fsize)
    cbar.outline.remove()
    cbar.ax.tick_params(labelsize=fsize, pad=0., tick2On=False)

fig.savefig('plot_half_cosine.png', transparent=True, bbox_inches='tight',
        pad_inches=0.05, dpi=220)



giving:

../../../_images/plot_half_cosine.png

The relatively poor correlation between the half-cosine and the inverse_weighted is primarily due to the reduced amount of measured data used for this tutorial. Using real imperfection data the correlation is much closer, as shown in the additional examples below.

Additional Examples using the Half-Cosine Function

The example below shows how to use this function and then plot the displacement patterns using different approximation levels:

half_cosine_additional_example.py

from numpy import *

from desicos.conecylDB.fit_data import fw0

ntheta = 420
nz = 180
funcnum = 2
path = 'degenhardt_2010_z25_msi_theta_z_imp.txt'

theta = linspace(-pi, pi, ntheta)
z = linspace(0., 1., nz)
theta, z = meshgrid(theta, z, copy=False)

for m0, n0 in [[20, 30], [30, 45], [40, 60], [50, 75]]:
    c, residues = calc_c0(path, m0=m0, n0=n0, sample_size=(10*2*m*n),
                          funcnum=funcnum)

    w0 = fw0(m0, n0, z.ravel(), theta.ravel(), funcnum=funcnum)

    plt.figure(figsize=(3.5, 2))

    levels = np.linspace(w0.min(), w0.max(), 400)
    plt.contourf(theta, z*H_measured, w0.reshape(theta.shape),
                 levels=levels)

    ax = plt.gca()
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)

    ax.xaxis.set_ticks([-pi, pi])
    ax.xaxis.set_ticklabels([r'$-\pi$', r'$\pi$'])
    ax.set_xlabel('Circumferential Position, $rad$')
    ax.xaxis.labelpad=-10

    ax.yaxis.set_ticks([0, 500])
    ax.set_ylabel('Height, $mm$')
    ax.yaxis.labelpad=-15

    filename = 'fw0_f{0}_z25_m_{1:03d}_n_{2:03d}.png'.format(
                funcnum, m0, n0)

    plt.gcf().savefig(filename, transparent=True, bbox_inches='tight',
                      pad_inches=0.05, dpi=90)

which will result in the following figures for funcnum=1:

m_0=20, n_0=30:

../../../_images/fw0_f1_z25_m_020_n_030.png

m_0=30, n_0=45:

../../../_images/fw0_f1_z25_m_030_n_045.png

m_0=40, n_0=60:

../../../_images/fw0_f1_z25_m_040_n_060.png

m_0=50, n_0=75:

../../../_images/fw0_f1_z25_m_050_n_075.png

and for funcnum=2:

m_0=20, n_0=30:

../../../_images/fw0_f2_z25_m_020_n_030.png

m_0=30, n_0=45:

../../../_images/fw0_f2_z25_m_030_n_045.png

m_0=40, n_0=60:

../../../_images/fw0_f2_z25_m_040_n_060.png

m_0=50, n_0=75:

../../../_images/fw0_f2_z25_m_050_n_075.png

It can be seen how the w_0 function approximates the measured imperfection pattern with the increase of m_0 and m_0. The measured imperfection pattern is shown below, and it was plotted after performing the inverse-weighted interpolation already described.

../../../_images/measured_z25.png

Comparing the results using funcnum=1 and funcnum=2, one see that the latter approaches closer the real measurements, since it relaxes the condition w_0 = 0 at the edges.

Processing Measured Thickness Imperfection Data

The procedure described for geometric imperfections is also applicable for thickness imperfections. The measured imperfection data usually comes in a 4 columns format where the thickness measurement is given for each spatial position: x_i, y_i, z_i:

x1 y1 z1 thick1
x2 y2 z2 thick2
...
xn yn zn thickn

The recommended procedure to transform the raw data to the finite element coordinate system is the function xyzthick2thetazthick(), that also calls internally the best_fit_cylinder() or the best_fit_cone() routines:

>>> from desicos.conecylDB.read_write import xyzthick2thetazthick

>>> xyzthick2thetazthick(path, alphadeg_measured, R_measured, H_measured,
                         fmt='%1.8f')

Note

It is possible to use function xyzthick2thetazthick`() in other ways, as detailed in the corresponding documentation