.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/basics/ex3_3_tensorsens3d_strain3d.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_basics_ex3_3_tensorsens3d_strain3d.py: Basics: Custom tensor field sensors (strain gauges) in 3D ================================================================================ In this example we build a custom tensor field sensor array (i.e. a strain gauge array) in 3D. We will also demonstrate how to specify sensor angles and field errors based on sensor angles. Note that this tutorial assumes you are familiar with the use of `pyvale` for scalar fields as described in the first set of examples. Test case: point strain sensors on a 2D plate with hole loaded in tension .. GENERATED FROM PYTHON SOURCE LINES 20-27 .. code-block:: Python import numpy as np import matplotlib.pyplot as plt from scipy.spatial.transform import Rotation import mooseherder as mh import pyvale as pyv .. GENERATED FROM PYTHON SOURCE LINES 28-31 First we load our simulation asa `SimData` object. In this case we are loading a 10mm cube loaded in tension in the y direction with the addition of a thermal gradient in the y direction. .. GENERATED FROM PYTHON SOURCE LINES 31-34 .. code-block:: Python data_path = pyv.DataSet.element_case_path(pyv.EElemTest.HEX20) sim_data = mh.ExodusReader(data_path).read_all_sim_data() .. GENERATED FROM PYTHON SOURCE LINES 35-38 As we are creating a 3D tensor field sensor we now have a third displacement field component here for scaling. Note that you don't need to scale the displacements here if you only want to analyse strains. .. GENERATED FROM PYTHON SOURCE LINES 38-43 .. code-block:: Python disp_comps = ("disp_x","disp_y","disp_z") sim_data = pyv.scale_length_units(scale=1000.0, sim_data=sim_data, disp_comps=disp_comps) .. GENERATED FROM PYTHON SOURCE LINES 44-48 Here is the main difference when creating a tensor field sensor array. We create a tensor field where we need to specify the normal and deviatoric component string keys as they appear in our `SimData` object. We have a 3D simulation here so we have 3 normal components and 3 deviatoric (shear). .. GENERATED FROM PYTHON SOURCE LINES 48-57 .. code-block:: Python field_name = "strain" norm_comps = ("strain_xx","strain_yy","strain_zz") dev_comps = ("strain_xy","strain_yz","strain_xz") strain_field = pyv.FieldTensor(sim_data, field_name=field_name, norm_comps=norm_comps, dev_comps=dev_comps, elem_dims=3) .. GENERATED FROM PYTHON SOURCE LINES 58-61 Here we manually define our sensor positions to place a sensor on the centre of each face of our 10mm cube. From here everything is the same as for our 2D vector field sensor arrays. .. GENERATED FROM PYTHON SOURCE LINES 61-68 .. code-block:: Python sensor_positions = np.array(((5.0,0.0,5.0), # bottom (5.0,10.0,5.0), # top (5.0,5.0,0.0), # xy face (5.0,5.0,10.0), # xy face (0.0,5.0,5.0), # yz face (10.0,5.0,5.0),)) # yz face .. GENERATED FROM PYTHON SOURCE LINES 69-71 We set custom sensor sampling times here but we could also set this to None to have the sensors sample at the simulation time steps. .. GENERATED FROM PYTHON SOURCE LINES 71-73 .. code-block:: Python sample_times = np.linspace(0.0,np.max(sim_data.time),50) .. GENERATED FROM PYTHON SOURCE LINES 74-75 We are going to manually specify the sensor angles for all our sensors. .. GENERATED FROM PYTHON SOURCE LINES 75-87 .. code-block:: Python sens_angles = (Rotation.from_euler("zyx", [0, 0, 0], degrees=True), Rotation.from_euler("zyx", [0, 0, 0], degrees=True), Rotation.from_euler("zyx", [45, 0, 0], degrees=True), Rotation.from_euler("zyx", [45, 0, 0], degrees=True), Rotation.from_euler("zyx", [0, 0, 45], degrees=True), Rotation.from_euler("zyx", [0, 0, 45], degrees=True),) sens_data = pyv.SensorData(positions=sensor_positions, sample_times=sample_times, angles=sens_angles) .. GENERATED FROM PYTHON SOURCE LINES 88-92 Here we create a descriptor that will be used to label visualisations of the sensor locations and time traces for our sensors. For the strain gauges we are modelling here we could also use the descriptor factory to get these defaults. .. GENERATED FROM PYTHON SOURCE LINES 92-103 .. code-block:: Python descriptor = pyv.SensorDescriptor(name="Strain", symbol=r"\varepsilon", units=r"-", tag="SG", components=('xx','yy','zz','xy','yz','xz')) straingauge_array = pyv.SensorArrayPoint(sens_data, strain_field, descriptor) .. GENERATED FROM PYTHON SOURCE LINES 104-106 We can add any errors we like to our error chain. Here we add some basic percentage errors. .. GENERATED FROM PYTHON SOURCE LINES 106-110 .. code-block:: Python error_chain = [] error_chain.append(pyv.ErrSysUnif(low=-0.1e-3,high=0.1e-3)) error_chain.append(pyv.ErrRandNormPercent(std_percent=1.0)) .. GENERATED FROM PYTHON SOURCE LINES 111-113 Now we add a field error to perturb the positions of each sensor on its relevant face and then add a +/- 2deg angle error. .. GENERATED FROM PYTHON SOURCE LINES 113-124 .. code-block:: Python pos_uncert = 0.1 # units = mm pos_rand_xyz = (pyv.GenNormal(std=pos_uncert), pyv.GenNormal(std=pos_uncert), pyv.GenNormal(std=pos_uncert)) angle_uncert = 2.0 angle_rand_zyx = (pyv.GenUniform(low=-angle_uncert,high=angle_uncert), # units = deg pyv.GenUniform(low=-angle_uncert,high=angle_uncert), pyv.GenUniform(low=-angle_uncert,high=angle_uncert)) .. GENERATED FROM PYTHON SOURCE LINES 125-127 We are going to lock position perturbation so that the sensors stay on the faces of the cube they are positioned on. .. GENERATED FROM PYTHON SOURCE LINES 127-132 .. code-block:: Python pos_lock = np.full(sensor_positions.shape,False,dtype=bool) pos_lock[0:2,1] = True # Block translation in y pos_lock[2:4,2] = True # Block translation in z pos_lock[4:6,0] = True # Block translation in x .. GENERATED FROM PYTHON SOURCE LINES 133-135 We are also going to lock angular perturbation so that each sensor is only allowed to rotate on the plane it is on. .. GENERATED FROM PYTHON SOURCE LINES 135-153 .. code-block:: Python angle_lock = np.full(sensor_positions.shape,True,dtype=bool) angle_lock[0:2,1] = False # Allow rotation about y angle_lock[2:4,0] = False # Allow rotation about z angle_lock[4:6,2] = False # Allow rotation about x field_error_data = pyv.ErrFieldData(pos_rand_xyz=pos_rand_xyz, pos_lock_xyz=pos_lock, ang_rand_zyx=angle_rand_zyx, ang_lock_zyx=angle_lock) sys_err_field = pyv.ErrSysField(strain_field,field_error_data) error_chain.append(sys_err_field) error_int = pyv.ErrIntegrator(error_chain, sens_data, straingauge_array.get_measurement_shape()) straingauge_array.set_error_integrator(error_int) .. GENERATED FROM PYTHON SOURCE LINES 154-158 We run our virtual sensor simulation as normal. The only thing to note is that the second dimension of our measurement array will contain our tensor components in the order they are specified in the tuples with the normal components first followed by the deviatoric. .. GENERATED FROM PYTHON SOURCE LINES 158-160 .. code-block:: Python measurements = straingauge_array.calc_measurements() .. GENERATED FROM PYTHON SOURCE LINES 161-163 We print some of the results for one of the sensors so we can see the effect of the field errors. .. GENERATED FROM PYTHON SOURCE LINES 163-182 .. code-block:: Python print(80*"-") sens_print: int = 0 time_print: int = 5 comp_print: int = 1 # strain_yy based on order in tuple sens_print = 0 comp_print = 1 # strain_yy based on order in tuple time_last = 5 time_print = slice(measurements.shape[2]-time_last,measurements.shape[2]) print("ROTATED SENSORS WITH ANGLE ERRORS:") print(f"These are the last {time_last} virtual measurements of sensor " + f"{sens_print} for {norm_comps[comp_print]}:") pyv.print_measurements(straingauge_array,sens_print,comp_print,time_print) print(80*"-") .. GENERATED FROM PYTHON SOURCE LINES 183-185 We can plot a given component of our tensor field and display our sensor locations with respect to the field. .. GENERATED FROM PYTHON SOURCE LINES 185-189 .. code-block:: Python plot_field = "strain_yy" pv_plot = pyv.plot_point_sensors_on_sim(straingauge_array,plot_field) pv_plot.show(cpos="xy") .. GENERATED FROM PYTHON SOURCE LINES 190-191 We can also plot time traces for all components of the tensor field. .. GENERATED FROM PYTHON SOURCE LINES 191-195 .. code-block:: Python for cc in (norm_comps+dev_comps): pyv.plot_time_traces(straingauge_array,cc) plt.show() .. _sphx_glr_download_examples_basics_ex3_3_tensorsens3d_strain3d.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: ex3_3_tensorsens3d_strain3d.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: ex3_3_tensorsens3d_strain3d.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: ex3_3_tensorsens3d_strain3d.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_