.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/basics/ex4_2_expsim3d_thermmech3d.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_ex4_2_expsim3d_thermmech3d.py: Basics: Multi-physics experiment simulation in 3D ================================================================================ In the previous example we performed a series of simulated experiments on a set of 2D multi-physics simulations. Here we use a 3D thermo-mechanical analysis of a divertor armour heatsink to show how we can run simulated experiments in 3D. Note that this tutorial assumes you are familiar with the use of `pyvale` for scalar and tensor fields as described in the previous examples. Test case: thermo-mechanical analysis of a divertor heatsink in 3D .. GENERATED FROM PYTHON SOURCE LINES 19-26 .. code-block:: Python from pathlib import Path import numpy as np import matplotlib.pyplot as plt import mooseherder as mh import pyvale as pyv .. GENERATED FROM PYTHON SOURCE LINES 27-30 First we get the path to simulation output file and then read the simulation into a `SimData` object. In this case our simulation is a thermomechanical model of a divertor heatsink. .. GENERATED FROM PYTHON SOURCE LINES 30-34 .. code-block:: Python sim_path = pyv.DataSet.thermomechanical_3d_path() sim_data = mh.ExodusReader(sim_path).read_all_sim_data() elem_dims: int = 3 .. GENERATED FROM PYTHON SOURCE LINES 35-37 We scale our length and displacement units to mm to help with visualisation. .. GENERATED FROM PYTHON SOURCE LINES 37-42 .. 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 43-46 If we are going to save figures showing where our sensors are and their simulated traces we need to create a directory. Set the flag below to save the figures when you run the script .. GENERATED FROM PYTHON SOURCE LINES 46-52 .. code-block:: Python save_figs = False save_tag = "thermomech3d" fig_save_path = Path.cwd()/"images" if not fig_save_path.is_dir(): fig_save_path.mkdir(parents=True, exist_ok=True) .. GENERATED FROM PYTHON SOURCE LINES 53-55 We specify manual sensor sampling times but we could also set this to None for the sensors to sample at the simulation time steps. .. GENERATED FROM PYTHON SOURCE LINES 55-66 .. code-block:: Python sample_times = np.linspace(0.0,np.max(sim_data.time),50) x_lims = (12.5,12.5) y_lims = (0.0,33.0) z_lims = (0.0,12.0) n_sens = (1,4,1) tc_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims) tc_sens_data = pyv.SensorData(positions=tc_sens_pos, sample_times=sample_times) .. GENERATED FROM PYTHON SOURCE LINES 67-69 We use the sensor array factory to create our thermocouple array with no errors. .. GENERATED FROM PYTHON SOURCE LINES 69-75 .. code-block:: Python tc_field_name = "temperature" tc_array = pyv.SensorArrayFactory \ .thermocouples_no_errs(sim_data, tc_sens_data, elem_dims=elem_dims, field_name=tc_field_name) .. GENERATED FROM PYTHON SOURCE LINES 76-78 Now we build our error chain starting with some basic errors on the order of 1 degree. .. GENERATED FROM PYTHON SOURCE LINES 78-82 .. code-block:: Python tc_err_chain = [] tc_err_chain.append(pyv.ErrSysUnif(low=1.0,high=1.0)) tc_err_chain.append(pyv.ErrRandNorm(std=1.0)) .. GENERATED FROM PYTHON SOURCE LINES 83-84 Now we add positioning error for our thermocouples. .. GENERATED FROM PYTHON SOURCE LINES 84-89 .. code-block:: Python tc_pos_uncert = 0.1 # units = mm tc_pos_rand = (pyv.GenNormal(std=tc_pos_uncert), pyv.GenNormal(std=tc_pos_uncert), pyv.GenNormal(std=tc_pos_uncert)) .. GENERATED FROM PYTHON SOURCE LINES 90-91 We block translation in x so the thermocouples stay attached. .. GENERATED FROM PYTHON SOURCE LINES 91-99 .. code-block:: Python tc_pos_lock = np.full(tc_sens_pos.shape,False,dtype=bool) tc_pos_lock[:,0] = True tc_field_err_data = pyv.ErrFieldData(pos_rand_xyz=tc_pos_rand, pos_lock_xyz=tc_pos_lock) tc_err_chain.append(pyv.ErrSysField(tc_array.get_field(), tc_field_err_data)) .. GENERATED FROM PYTHON SOURCE LINES 100-102 We have finished our error chain so we can build our error integrator and attach it to our thermocouple array. .. GENERATED FROM PYTHON SOURCE LINES 102-107 .. code-block:: Python tc_error_int = pyv.ErrIntegrator(tc_err_chain, tc_sens_data, tc_array.get_measurement_shape()) tc_array.set_error_integrator(tc_error_int) .. GENERATED FROM PYTHON SOURCE LINES 108-110 We visualise our thermcouple locations on our mesh to make sure they are in the correct positions. .. GENERATED FROM PYTHON SOURCE LINES 110-120 .. code-block:: Python pv_plot = pyv.plot_point_sensors_on_sim(tc_array,"temperature") pv_plot.camera_position = [(59.354, 43.428, 69.946), (-2.858, 13.189, 4.523), (-0.215, 0.948, -0.233)] if save_figs: pv_plot.save_graphic(fig_save_path/(save_tag+"_tc_vis.svg")) pv_plot.screenshot(fig_save_path/(save_tag+"_tc_vis.png")) pv_plot.show() .. GENERATED FROM PYTHON SOURCE LINES 121-126 Now we have finished with our thermocouple array we can move on to our strain gauge array. We use the same sampling time but we are going to place the strain gauges down the side of the monoblock where the pipe passes through. .. GENERATED FROM PYTHON SOURCE LINES 126-135 .. code-block:: Python x_lims = (9.4,9.4) y_lims = (0.0,33.0) z_lims = (12.0,12.0) n_sens = (1,4,1) sg_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims) sg_sens_data = pyv.SensorData(positions=sg_sens_pos, sample_times=sample_times) .. GENERATED FROM PYTHON SOURCE LINES 136-138 We use the sensor array factory to give us a strain gauge array with no errors. .. GENERATED FROM PYTHON SOURCE LINES 138-149 .. code-block:: Python sg_field_name = "strain" sg_norm_comps = ("strain_xx","strain_yy","strain_zz") sg_dev_comps = ("strain_xy","strain_yz","strain_xz") sg_array = pyv.SensorArrayFactory \ .strain_gauges_no_errs(sim_data, sg_sens_data, elem_dims=elem_dims, field_name=sg_field_name, norm_comps=sg_norm_comps, dev_comps=sg_dev_comps) .. GENERATED FROM PYTHON SOURCE LINES 150-152 Now we build our error chain starting with some basic errors on the order of 1 percent. .. GENERATED FROM PYTHON SOURCE LINES 152-156 .. code-block:: Python sg_err_chain = [] sg_err_chain.append(pyv.ErrSysUnifPercent(low_percent=1.0,high_percent=1.0)) sg_err_chain.append(pyv.ErrRandNormPercent(std_percent=1.0)) .. GENERATED FROM PYTHON SOURCE LINES 157-158 We are going to add +/-2 degree rotation uncertainty to our strain gauges. .. GENERATED FROM PYTHON SOURCE LINES 158-163 .. code-block:: Python 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 164-165 We only allow rotation on the face the strain gauges are on .. GENERATED FROM PYTHON SOURCE LINES 165-173 .. code-block:: Python angle_lock = np.full(sg_sens_pos.shape,True,dtype=bool) angle_lock[:,0] = False # Allow rotation about z sg_field_err_data = pyv.ErrFieldData(ang_rand_zyx=angle_rand_zyx, ang_lock_zyx=angle_lock) sg_err_chain.append(pyv.ErrSysField(sg_array.get_field(), sg_field_err_data)) .. GENERATED FROM PYTHON SOURCE LINES 174-176 We have finished our error chain so we can build our error integrator and attach it to our thermocouple array. .. GENERATED FROM PYTHON SOURCE LINES 176-181 .. code-block:: Python sg_error_int = pyv.ErrIntegrator(sg_err_chain, sg_sens_data, sg_array.get_measurement_shape()) sg_array.set_error_integrator(sg_error_int) .. GENERATED FROM PYTHON SOURCE LINES 182-184 Now we visualise the strain gauge locations to make sure they are where we expect them to be. .. GENERATED FROM PYTHON SOURCE LINES 184-194 .. code-block:: Python pv_plot = pyv.plot_point_sensors_on_sim(sg_array,"strain_yy") pv_plot.camera_position = [(59.354, 43.428, 69.946), (-2.858, 13.189, 4.523), (-0.215, 0.948, -0.233)] if save_figs: pv_plot.save_graphic(fig_save_path/(save_tag+"_sg_vis.svg")) pv_plot.screenshot(fig_save_path/(save_tag+"_sg_vis.png")) pv_plot.show() .. GENERATED FROM PYTHON SOURCE LINES 195-201 We have both our sensor arrays so we will create and run our experiment. Here we only have a single input simulation in our list and we only run 100 simulated experiments as we are going to plot all simulated data points on our traces. Note that if you are running more than 100 experiments here you will need to set the trace plots below to not show all points on the graph. .. GENERATED FROM PYTHON SOURCE LINES 201-207 .. code-block:: Python sim_list = [sim_data,] sensor_arrays = [tc_array,sg_array] exp_sim = pyv.ExperimentSimulator(sim_list, sensor_arrays, num_exp_per_sim=100) .. GENERATED FROM PYTHON SOURCE LINES 208-210 We run our experiments and calculate summary statistics as in the previous example .. GENERATED FROM PYTHON SOURCE LINES 210-213 .. code-block:: Python exp_data = exp_sim.run_experiments() exp_stats = exp_sim.calc_stats() .. GENERATED FROM PYTHON SOURCE LINES 214-216 We print the lengths of our exp_data and exp_stats lists along with the shape of the numpy arrays they contain so we can index into them easily. .. GENERATED FROM PYTHON SOURCE LINES 216-242 .. code-block:: Python print(80*"=") print("exp_data and exp_stats are lists where the index is the sensor array") print("position in the list as field components are not consistent dims:") print(f"{len(exp_data)=}") print(f"{len(exp_stats)=}") print() print(80*"-") print("Thermal sensor array @ exp_data[0]") print(80*"-") print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)") print(f"{exp_data[0].shape=}") print() print("Stats are calculated over all experiments (axis=1)") print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)") print(f"{exp_stats[0].max.shape=}") print() print(80*"-") print("Mechanical sensor array @ exp_data[1]") print(80*"-") print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)") print(f"{exp_data[1].shape=}") print() print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)") print(f"{exp_stats[1].max.shape=}") print(80*"=") .. GENERATED FROM PYTHON SOURCE LINES 243-249 Finally, we are going to plot the simulated sensor traces but we are going to control some of the plotting options using the options data class here. We set the plot to show all simulated experiment data points and to plot the median as the centre line and to fill between the min and max values. Note that the default here is to plot the mean and fill between 3 times the standard deviation. .. GENERATED FROM PYTHON SOURCE LINES 249-271 .. code-block:: Python trace_opts = pyv.TraceOptsExperiment(plot_all_exp_points=True, centre=pyv.EExpVisCentre.MEDIAN, fill_between=pyv.EExpVisBounds.MINMAX) (fig,ax) = pyv.plot_exp_traces(exp_sim, component="temperature", sens_array_num=0, sim_num=0, trace_opts=trace_opts) if save_figs: fig.savefig(fig_save_path/(save_tag+"_tc_traces.png"), dpi=300, format='png', bbox_inches='tight') (fig,ax) = pyv.plot_exp_traces(exp_sim, component="strain_yy", sens_array_num=1, sim_num=0, trace_opts=trace_opts) if save_figs: fig.savefig(fig_save_path/(save_tag+"_sg_traces.png"), dpi=300, format='png', bbox_inches='tight') plt.show() .. _sphx_glr_download_examples_basics_ex4_2_expsim3d_thermmech3d.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: ex4_2_expsim3d_thermmech3d.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: ex4_2_expsim3d_thermmech3d.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: ex4_2_expsim3d_thermmech3d.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_