pyvale 2025.4.0__py3-none-any.whl → 2025.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyvale might be problematic. Click here for more details.

Files changed (153) hide show
  1. pyvale/__init__.py +78 -64
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
  4. pyvale/analyticsimdatagenerator.py +323 -0
  5. pyvale/blendercalibrationdata.py +15 -0
  6. pyvale/blenderlightdata.py +26 -0
  7. pyvale/blendermaterialdata.py +15 -0
  8. pyvale/blenderrenderdata.py +30 -0
  9. pyvale/blenderscene.py +488 -0
  10. pyvale/blendertools.py +420 -0
  11. pyvale/{core/camera.py → camera.py} +15 -15
  12. pyvale/{core/cameradata.py → cameradata.py} +27 -22
  13. pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/{core/cameratools.py → cameratools.py} +220 -26
  16. pyvale/{core/cython → cython}/rastercyth.py +11 -7
  17. pyvale/data/__init__.py +5 -7
  18. pyvale/data/cal_target.tiff +0 -0
  19. pyvale/data/case00_HEX20_out.e +0 -0
  20. pyvale/data/case00_HEX27_out.e +0 -0
  21. pyvale/data/case00_HEX8_out.e +0 -0
  22. pyvale/data/case00_TET10_out.e +0 -0
  23. pyvale/data/case00_TET14_out.e +0 -0
  24. pyvale/data/case00_TET4_out.e +0 -0
  25. pyvale/{core/dataset.py → dataset.py} +91 -16
  26. pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
  27. pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
  28. pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
  29. pyvale/{core/errorrand.py → errorrand.py} +39 -46
  30. pyvale/errorsyscalib.py +134 -0
  31. pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
  32. pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
  33. pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
  34. pyvale/examples/__init__.py +5 -7
  35. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  36. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  37. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  38. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  39. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  40. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  41. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  42. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  43. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  44. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  45. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  46. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  47. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  48. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  49. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  50. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  51. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  52. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  53. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  54. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  55. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  56. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  57. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  58. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  59. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  60. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  61. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +6 -7
  62. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
  63. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
  64. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
  65. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
  66. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  67. pyvale/experimentsimulator.py +175 -0
  68. pyvale/{core/field.py → field.py} +6 -14
  69. pyvale/fieldconverter.py +351 -0
  70. pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
  71. pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
  72. pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
  73. pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
  74. pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
  75. pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
  76. pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
  77. pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
  78. pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
  79. pyvale/integratorrectangle.py +165 -0
  80. pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
  81. pyvale/{core/integratortype.py → integratortype.py} +7 -8
  82. pyvale/output.py +17 -0
  83. pyvale/pyvaleexceptions.py +11 -0
  84. pyvale/{core/raster.py → raster.py} +8 -8
  85. pyvale/{core/rastercy.py → rastercy.py} +11 -10
  86. pyvale/{core/rasternp.py → rasternp.py} +12 -13
  87. pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
  88. pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
  89. pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
  90. pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
  91. pyvale/{core/sensordata.py → sensordata.py} +7 -8
  92. pyvale/sensordescriptor.py +213 -0
  93. pyvale/{core/sensortools.py → sensortools.py} +8 -9
  94. pyvale/simcases/case00_HEX20.i +5 -5
  95. pyvale/simcases/case00_HEX27.i +5 -5
  96. pyvale/simcases/case00_HEX8.i +242 -0
  97. pyvale/simcases/case00_TET10.i +2 -2
  98. pyvale/simcases/case00_TET14.i +2 -2
  99. pyvale/simcases/case00_TET4.i +242 -0
  100. pyvale/simcases/run_1case.py +1 -1
  101. pyvale/simtools.py +67 -0
  102. pyvale/visualexpplotter.py +191 -0
  103. pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
  104. pyvale/{core/visualimages.py → visualimages.py} +10 -9
  105. pyvale/visualopts.py +493 -0
  106. pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
  107. pyvale/visualsimsensors.py +318 -0
  108. pyvale/visualtools.py +136 -0
  109. pyvale/visualtraceplotter.py +142 -0
  110. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
  111. pyvale-2025.5.1.dist-info/RECORD +172 -0
  112. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
  113. pyvale/core/__init__.py +0 -7
  114. pyvale/core/analyticmeshgen.py +0 -59
  115. pyvale/core/analyticsimdatagenerator.py +0 -160
  116. pyvale/core/cython/rastercyth.c +0 -32267
  117. pyvale/core/experimentsimulator.py +0 -99
  118. pyvale/core/fieldconverter.py +0 -154
  119. pyvale/core/integratorrectangle.py +0 -88
  120. pyvale/core/optimcheckfuncs.py +0 -153
  121. pyvale/core/sensordescriptor.py +0 -101
  122. pyvale/core/visualexpplotter.py +0 -151
  123. pyvale/core/visualopts.py +0 -180
  124. pyvale/core/visualsimplotter.py +0 -182
  125. pyvale/core/visualtools.py +0 -81
  126. pyvale/core/visualtraceplotter.py +0 -256
  127. pyvale/examples/analyticdatagen/__init__.py +0 -7
  128. pyvale/examples/ex1_1_thermal2d.py +0 -89
  129. pyvale/examples/ex1_2_thermal2d.py +0 -111
  130. pyvale/examples/ex1_3_thermal2d.py +0 -113
  131. pyvale/examples/ex1_5_thermal2d.py +0 -105
  132. pyvale/examples/ex2_1_thermal3d .py +0 -87
  133. pyvale/examples/ex2_2_thermal3d.py +0 -51
  134. pyvale/examples/ex2_3_thermal3d.py +0 -109
  135. pyvale/examples/ex3_1_displacement2d.py +0 -47
  136. pyvale/examples/ex3_2_displacement2d.py +0 -79
  137. pyvale/examples/ex3_3_displacement2d.py +0 -104
  138. pyvale/examples/ex3_4_displacement2d.py +0 -105
  139. pyvale/examples/ex4_1_strain2d.py +0 -57
  140. pyvale/examples/ex4_2_strain2d.py +0 -79
  141. pyvale/examples/ex4_3_strain2d.py +0 -100
  142. pyvale/examples/ex5_1_multiphysics2d.py +0 -78
  143. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
  144. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
  145. pyvale/examples/features/__init__.py +0 -7
  146. pyvale/examples/features/ex_area_avg.py +0 -89
  147. pyvale/examples/features/ex_calibration_error.py +0 -108
  148. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  149. pyvale/examples/features/ex_field_errs.py +0 -78
  150. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  151. pyvale-2025.4.0.dist-info/RECORD +0 -157
  152. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
  153. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
@@ -1,151 +0,0 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- from typing import Any
9
-
10
- import numpy as np
11
- import matplotlib.pyplot as plt
12
- from pyvale.core.visualopts import (PlotOptsGeneral,
13
- TraceOptsExperiment)
14
- from pyvale.core.experimentsimulator import ExperimentSimulator
15
-
16
- #TODO: Docstrings
17
-
18
- def plot_exp_traces(exp_sim: ExperimentSimulator,
19
- component: str,
20
- sens_array_num: int,
21
- sim_num: int,
22
- trace_opts: TraceOptsExperiment | None = None,
23
- plot_opts: PlotOptsGeneral | None = None) -> tuple[Any,Any]:
24
-
25
- if trace_opts is None:
26
- trace_opts = TraceOptsExperiment()
27
-
28
- if plot_opts is None:
29
- plot_opts = PlotOptsGeneral()
30
-
31
- descriptor = exp_sim.sensor_arrays[sens_array_num].descriptor
32
- comp_ind = exp_sim.sensor_arrays[sens_array_num].field.get_component_index(component)
33
- samp_time = exp_sim.sensor_arrays[sens_array_num].get_sample_times()
34
- num_sens = exp_sim.sensor_arrays[sens_array_num].get_measurement_shape()[0]
35
-
36
- exp_data = exp_sim.get_data()
37
- exp_stats = exp_sim.get_stats()
38
-
39
- if trace_opts.sensors_to_plot is None:
40
- sensors_to_plot = range(num_sens)
41
- else:
42
- sensors_to_plot = trace_opts.sensors_to_plot
43
-
44
- #---------------------------------------------------------------------------
45
- # Figure canvas setup
46
- fig, ax = plt.subplots(figsize=plot_opts.single_fig_size_portrait,
47
- layout='constrained')
48
- fig.set_dpi(plot_opts.resolution)
49
-
50
- #---------------------------------------------------------------------------
51
- # Plot all simulated experimental points
52
- if trace_opts.plot_all_exp_points:
53
- for ss in sensors_to_plot:
54
- for ee in range(exp_sim.num_exp_per_sim):
55
- ax.plot(samp_time,
56
- exp_data[sens_array_num][sim_num,ee,ss,comp_ind,:],
57
- "+",
58
- lw=plot_opts.lw,
59
- ms=plot_opts.ms,
60
- color=plot_opts.colors[ss % plot_opts.n_colors])
61
-
62
- for ss in sensors_to_plot:
63
- if trace_opts.centre == "median":
64
- ax.plot(samp_time,
65
- exp_stats[sens_array_num].median[sim_num,ss,comp_ind,:],
66
- trace_opts.exp_mean_line,
67
- lw=plot_opts.lw,
68
- ms=plot_opts.ms,
69
- color=plot_opts.colors[ss % plot_opts.n_colors])
70
- else:
71
- ax.plot(samp_time,
72
- exp_stats[sens_array_num].mean[sim_num,ss,comp_ind,:],
73
- trace_opts.exp_mean_line,
74
- lw=plot_opts.lw,
75
- ms=plot_opts.ms,
76
- color=plot_opts.colors[ss % plot_opts.n_colors])
77
-
78
- if trace_opts is not None:
79
- upper = np.zeros_like(exp_stats[sens_array_num].min)
80
- lower = np.zeros_like(exp_stats[sens_array_num].min)
81
-
82
- if trace_opts.fill_between == 'max':
83
- upper = exp_stats[sens_array_num].min
84
- lower = exp_stats[sens_array_num].max
85
-
86
- elif trace_opts.fill_between == 'quartile':
87
- upper = exp_stats[sens_array_num].q25
88
- lower = exp_stats[sens_array_num].q75
89
-
90
- elif trace_opts.fill_between == '2std':
91
- upper = exp_stats[sens_array_num].mean + \
92
- 2*exp_stats[sens_array_num].std
93
- lower = exp_stats[sens_array_num].mean - \
94
- 2*exp_stats[sens_array_num].std
95
-
96
- elif trace_opts.fill_between == '3std':
97
- upper = exp_stats[sens_array_num].mean + \
98
- 3*exp_stats[sens_array_num].std
99
- lower = exp_stats[sens_array_num].mean - \
100
- 3*exp_stats[sens_array_num].std
101
-
102
- ax.fill_between(samp_time,
103
- upper[sim_num,ss,comp_ind,:],
104
- lower[sim_num,ss,comp_ind,:],
105
- color=plot_opts.colors[ss % plot_opts.n_colors],
106
- alpha=0.2)
107
-
108
- #---------------------------------------------------------------------------
109
- # Plot simulation and truth line
110
- if trace_opts.sim_line is not None:
111
- sim_time = exp_sim.sensor_arrays[sens_array_num].field.get_time_steps()
112
- sim_vals = exp_sim.sensor_arrays[sens_array_num].field.sample_field(
113
- exp_sim.sensor_arrays[sens_array_num].positions)
114
-
115
- for ss in sensors_to_plot:
116
- ax.plot(sim_time,
117
- sim_vals[ss,comp_ind,:],
118
- trace_opts.sim_line,
119
- lw=plot_opts.lw,
120
- ms=plot_opts.ms)
121
-
122
- if trace_opts.truth_line is not None:
123
- truth = exp_sim.sensor_arrays[sens_array_num].get_truth()
124
- for ss in sensors_to_plot:
125
- ax.plot(samp_time,
126
- truth[ss,comp_ind,:],
127
- trace_opts.truth_line,
128
- lw=plot_opts.lw,
129
- ms=plot_opts.ms,
130
- color=plot_opts.colors[ss % plot_opts.n_colors])
131
-
132
- #---------------------------------------------------------------------------
133
- # Axis / legend labels and options
134
- ax.set_xlabel(trace_opts.time_label,
135
- fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
136
- ax.set_ylabel(descriptor.create_label(comp_ind),
137
- fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
138
-
139
- if trace_opts.time_min_max is None:
140
- ax.set_xlim((np.min(samp_time),np.max(samp_time))) # type: ignore
141
- else:
142
- ax.set_xlim(trace_opts.time_min_max)
143
-
144
- trace_opts.legend = False
145
- if trace_opts.legend:
146
- ax.legend(prop={"size":plot_opts.font_leg_size},loc='best')
147
-
148
- plt.grid(True)
149
- plt.draw()
150
-
151
- return (fig,ax)
pyvale/core/visualopts.py DELETED
@@ -1,180 +0,0 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- from pathlib import Path
9
- import enum
10
- from dataclasses import dataclass
11
- import numpy as np
12
- import matplotlib as plt
13
-
14
-
15
- @dataclass(slots=True)
16
- class PlotOptsGeneral:
17
- """ Dataclass for controlling the properties of figures and graphs such as
18
- figure size, resolution, font sizes, marker sizes, line widths and
19
- colormaps.
20
- """
21
- aspect_ratio: float = 1.62
22
- # These are in inches because of matplotlib
23
- a4_width: float = 8.25
24
- a4_height: float = 11.75
25
- a4_margin_width: float = 0.5
26
- a4_margin_height: float = 0.5
27
- a4_print_width: float = a4_width-2*a4_margin_width
28
- a4_print_height: float = a4_height-2*a4_margin_height
29
-
30
- single_fig_scale: float = 0.5
31
-
32
- single_fig_size_square: tuple[float,float] = (
33
- a4_print_width*single_fig_scale,
34
- a4_print_width*single_fig_scale
35
- )
36
- single_fig_size_portrait: tuple[float,float] = (
37
- a4_print_width*single_fig_scale/aspect_ratio,
38
- a4_print_width*single_fig_scale
39
- )
40
- single_fig_size_landscape: tuple[float,float] = (
41
- a4_print_width*single_fig_scale,
42
- a4_print_width*single_fig_scale/aspect_ratio
43
- )
44
-
45
- resolution: float = 300.0
46
-
47
- font_name: str = 'Liberation Sans'
48
- font_def_weight: str = 'normal'
49
- font_def_size: float = 8.0
50
- font_tick_size: float = 8.0
51
- font_head_size: float = 9.0
52
- font_ax_size: float = 8.0
53
- font_leg_size: float = 8.0
54
-
55
- ms: float = 3.2
56
- lw: float = 0.8
57
-
58
- cmap_seq: str = "cividis"
59
- cmap_div: str = "RdBu"
60
-
61
- colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
62
- n_colors = len(plt.rcParams['axes.prop_cycle'].by_key()['color'])
63
-
64
- def __post_init__(self) -> None:
65
- plt.rc('font', size=self.font_def_size)
66
- plt.rc('axes', titlesize=self.font_def_size)
67
-
68
-
69
- @dataclass(slots=True)
70
- class TraceOptsSensor:
71
- """Dataclass for controlling the properties of sensor trace plots.
72
- """
73
- legend: bool = True
74
-
75
- x_label: str = r"x [$m$]"
76
- y_label: str = r"y [$m$]"
77
- z_label: str = r"z [$m$]"
78
- time_label: str = r"Time, $t$ [$s$]"
79
-
80
- truth_line: str | None = "-"
81
- sim_line: str | None = "-"
82
- meas_line: str = "--o"
83
-
84
- sensors_to_plot: np.ndarray | None = None
85
- time_min_max: tuple[float,float] | None = None
86
-
87
-
88
- @dataclass(slots=True)
89
- class TraceOptsExperiment:
90
- """Dataclass for contorlling the properties of sensor trace plots from
91
- batches of simulated experiments.
92
- """
93
- legend: bool = True
94
-
95
- x_label: str = r"x [$m$]"
96
- y_label: str = r"y [$m$]"
97
- z_label: str = r"z [$m$]"
98
- time_label: str = r"Time, $t$ [$s$]"
99
-
100
- truth_line: str | None = None
101
- sim_line: str | None = None
102
- exp_mean_line: str = "-"
103
- exp_marker_line: str = "+"
104
-
105
- sensors_to_plot: np.ndarray | None = None
106
- time_min_max: tuple[float,float] | None = None
107
-
108
- centre: str = "mean"
109
- plot_all_exp_points: bool = False
110
- fill_between: str | None = "3std"
111
-
112
-
113
- @dataclass(slots=True)
114
- class VisOptsSimSensors:
115
- """Dataclass for controlling displays of the simulation mesh and sensor
116
- locations using pyvista.
117
- """
118
- # pyvista ops
119
- window_size_px: tuple[int,int] = (1280,800)
120
- camera_position: np.ndarray | str = "xy"
121
- show_edges: bool = True
122
- interactive: bool = True
123
-
124
- font_colour: str = "black"
125
- background_colour: str = "white" # "white"
126
-
127
- time_label_font_size: float = 12
128
- time_label_position: str = "upper_left"
129
- time_label_show: bool = True
130
-
131
- colour_bar_font_size: float = 18
132
- colour_bar_show: bool = True
133
- colour_bar_lims: tuple[float,float] | None = None
134
- colour_bar_vertical: bool = True
135
-
136
- # pyvale ops
137
- show_perturbed_pos: bool = True
138
- sens_colour_nom: str = "red"
139
- sens_colour_pert: str = "blue"
140
- sens_point_size: float = 20
141
- sens_label_font_size: float = 30
142
- sens_label_colour: str = "grey"
143
-
144
-
145
- class EImageType(enum.Enum):
146
- PNG = enum.auto()
147
- SVG = enum.auto()
148
-
149
- @dataclass(slots=True)
150
- class VisOptsImageSave:
151
- """Dataclass for image save options.
152
- """
153
- path: Path | None = None
154
- image_type: EImageType = EImageType.PNG
155
- transparent_background: bool = False
156
-
157
-
158
- class EAnimationType(enum.Enum):
159
- MP4 = enum.auto()
160
- GIF = enum.auto()
161
-
162
- @dataclass(slots=True)
163
- class VisOptsAnimation:
164
- """Dataclass for animation save options.
165
- """
166
- frames_per_second: float = 10.0
167
- off_screen: bool = False
168
-
169
- # save options
170
- save_animation: EAnimationType | None = None
171
- save_path: Path | None = None
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
@@ -1,182 +0,0 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- # import vtk #NOTE: has to be here to fix latex bug in pyvista/vtk
9
- # See: https://github.com/pyvista/pyvista/discussions/2928
10
- #NOTE: causes output to console to be suppressed unfortunately
11
- import pyvista as pv
12
-
13
- import mooseherder as mh
14
-
15
- from pyvale.core.sensorarraypoint import SensorArrayPoint
16
- from pyvale.core.fieldconverter import simdata_to_pyvista
17
- from pyvale.core.visualopts import (VisOptsSimSensors,VisOptsImageSave)
18
- from pyvale.core.visualtools import (create_pv_plotter,
19
- get_colour_lims,
20
- save_image)
21
-
22
-
23
- #TODO: Docstrings
24
-
25
- def add_sim_field(pv_plot: pv.Plotter,
26
- sensor_array: SensorArrayPoint,
27
- component: str,
28
- time_step: int,
29
- vis_opts: VisOptsSimSensors,
30
- ) -> tuple[pv.Plotter,pv.UnstructuredGrid]:
31
-
32
- sim_vis = sensor_array.field.get_visualiser()
33
- sim_data = sensor_array.field.get_sim_data()
34
- sim_vis[component] = sim_data.node_vars[component][:,time_step]
35
- comp_ind = sensor_array.field.get_component_index(component)
36
-
37
- scalar_bar_args = {"title":sensor_array.descriptor.create_label(comp_ind),
38
- "vertical":vis_opts.colour_bar_vertical,
39
- "title_font_size":vis_opts.colour_bar_font_size,
40
- "label_font_size":vis_opts.colour_bar_font_size}
41
-
42
- pv_plot.add_mesh(sim_vis,
43
- scalars=component,
44
- label="sim-data",
45
- show_edges=vis_opts.show_edges,
46
- show_scalar_bar=vis_opts.colour_bar_show,
47
- scalar_bar_args=scalar_bar_args,
48
- lighting=False,
49
- clim=vis_opts.colour_bar_lims)
50
-
51
- if vis_opts.time_label_show:
52
- pv_plot.add_text(f"Time: {sim_data.time[time_step]} " + \
53
- f"{sensor_array.descriptor.time_units}",
54
- position=vis_opts.time_label_position,
55
- font_size=vis_opts.time_label_font_size,
56
- name='time-label')
57
-
58
- return (pv_plot,sim_vis)
59
-
60
-
61
- def add_sensor_points_nom(pv_plot: pv.Plotter,
62
- sensor_array: SensorArrayPoint,
63
- vis_opts: VisOptsSimSensors,
64
- ) -> pv.Plotter:
65
-
66
- vis_sens_nominal = pv.PolyData(sensor_array.sensor_data.positions)
67
- vis_sens_nominal["labels"] = sensor_array.descriptor.create_sensor_tags(
68
- sensor_array.get_measurement_shape()[0])
69
-
70
- # Add points to show sensor locations
71
- pv_plot.add_point_labels(vis_sens_nominal,"labels",
72
- font_size=vis_opts.sens_label_font_size,
73
- shape_color=vis_opts.sens_label_colour,
74
- point_color=vis_opts.sens_colour_nom,
75
- render_points_as_spheres=True,
76
- point_size=vis_opts.sens_point_size,
77
- always_visible=True)
78
-
79
- return pv_plot
80
-
81
-
82
- def add_sensor_points_pert(pv_plot: pv.Plotter,
83
- sensor_array: SensorArrayPoint,
84
- vis_opts: VisOptsSimSensors,
85
- ) -> pv.Plotter:
86
-
87
- sens_data_perturbed = sensor_array.get_sensor_data_perturbed()
88
-
89
- if sens_data_perturbed is not None and vis_opts.show_perturbed_pos:
90
- vis_sens_perturbed = pv.PolyData(sens_data_perturbed.positions)
91
- vis_sens_perturbed["labels"] = ["",]*sensor_array.get_measurement_shape()[0]
92
-
93
- pv_plot.add_point_labels(vis_sens_perturbed,"labels",
94
- font_size=vis_opts.sens_label_font_size,
95
- shape_color=vis_opts.sens_label_colour,
96
- point_color=vis_opts.sens_colour_pert,
97
- render_points_as_spheres=True,
98
- point_size=vis_opts.sens_point_size,
99
- always_visible=True)
100
-
101
- return pv_plot
102
-
103
-
104
- def plot_sim_mesh(sim_data: mh.SimData,
105
- vis_opts: VisOptsSimSensors | None = None,
106
- ) -> pv.Plotter:
107
-
108
- if vis_opts is None:
109
- vis_opts = VisOptsSimSensors()
110
-
111
- pv_simdata = simdata_to_pyvista(sim_data,
112
- None,
113
- sim_data.num_spat_dims)
114
-
115
- pv_plot = create_pv_plotter(vis_opts)
116
-
117
- pv_plot.add_mesh(pv_simdata,
118
- label='sim-data',
119
- show_edges=True,
120
- show_scalar_bar=False)
121
-
122
- return pv_plot
123
-
124
-
125
- def plot_sim_data(sim_data: mh.SimData,
126
- component: str,
127
- time_step: int = -1,
128
- vis_opts: VisOptsSimSensors | None = None
129
- ) -> pv.Plotter:
130
-
131
- if vis_opts is None:
132
- vis_opts = VisOptsSimSensors()
133
-
134
- pv_simdata = simdata_to_pyvista(sim_data,
135
- (component,),
136
- sim_data.num_spat_dims)
137
-
138
- pv_plot = create_pv_plotter(vis_opts)
139
-
140
- pv_plot.add_mesh(pv_simdata,
141
- scalars=pv_simdata[component][:,time_step],
142
- label="sim-data",
143
- show_edges=True,
144
- show_scalar_bar=True,
145
- scalar_bar_args={"title":component},)
146
-
147
-
148
- return pv_plot
149
-
150
-
151
- def plot_point_sensors_on_sim(sensor_array: SensorArrayPoint,
152
- component: str,
153
- time_step: int = -1,
154
- vis_opts: VisOptsSimSensors | None = None,
155
- image_save_opts: VisOptsImageSave | None = None,
156
- ) -> pv.Plotter:
157
-
158
- if vis_opts is None:
159
- vis_opts = VisOptsSimSensors()
160
-
161
- sim_data = sensor_array.field.get_sim_data()
162
- vis_opts.colour_bar_lims = get_colour_lims(
163
- sim_data.node_vars[component][:,time_step],
164
- vis_opts.colour_bar_lims)
165
-
166
- pv_plot = create_pv_plotter(vis_opts)
167
-
168
- pv_plot = add_sensor_points_pert(pv_plot,sensor_array,vis_opts)
169
- pv_plot = add_sensor_points_nom(pv_plot,sensor_array,vis_opts)
170
- (pv_plot,_) = add_sim_field(pv_plot,
171
- sensor_array,
172
- component,
173
- time_step,
174
- vis_opts)
175
-
176
- pv_plot.camera_position = vis_opts.camera_position
177
-
178
- if image_save_opts is not None:
179
- save_image(pv_plot,image_save_opts)
180
-
181
- return pv_plot
182
-
@@ -1,81 +0,0 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
8
- from pathlib import Path
9
- import numpy as np
10
- #import vtk #NOTE: has to be here to fix latex bug in pyvista/vtk
11
- # See: https://github.com/pyvista/pyvista/discussions/2928
12
- #NOTE: causes output to console to be suppressed unfortunately
13
- import pyvista as pv
14
- from pyvale.core.visualopts import (VisOptsSimSensors,
15
- VisOptsImageSave,
16
- EImageType,
17
- VisOptsAnimation,
18
- EAnimationType)
19
-
20
- #TODO: Docstrings
21
-
22
- def create_pv_plotter(vis_opts: VisOptsSimSensors) -> pv.Plotter:
23
- pv_plot = pv.Plotter(window_size=vis_opts.window_size_px)
24
- pv_plot.set_background(vis_opts.background_colour)
25
- pv.global_theme.font.color = vis_opts.font_colour
26
- pv_plot.add_axes_at_origin(labels_off=True)
27
- return pv_plot
28
-
29
-
30
- def get_colour_lims(component_data: np.ndarray,
31
- colour_bar_lims: tuple[float,float] | None
32
- ) -> tuple[float,float]:
33
-
34
- if colour_bar_lims is None:
35
- min_comp = np.min(component_data.flatten())
36
- max_comp = np.max(component_data.flatten())
37
- colour_bar_lims = (min_comp,max_comp)
38
-
39
- return colour_bar_lims
40
-
41
-
42
- def save_image(pv_plot: pv.Plotter,
43
- image_save_opts: VisOptsImageSave) -> None:
44
- if image_save_opts.path is None:
45
- image_save_opts.path = Path.cwd() / "pyvale-image"
46
-
47
- if image_save_opts.image_type == EImageType.PNG:
48
- image_save_opts.path = image_save_opts.path.with_suffix(".png")
49
- pv_plot.screenshot(image_save_opts.path,
50
- image_save_opts.transparent_background)
51
-
52
- elif image_save_opts.image_type == EImageType.SVG:
53
- image_save_opts.path = image_save_opts.path.with_suffix(".svg")
54
- pv_plot.save_graphic(image_save_opts.path)
55
-
56
-
57
- def set_animation_writer(pv_plot: pv.Plotter,
58
- anim_opts: VisOptsAnimation) -> pv.Plotter:
59
- if anim_opts.save_animation is None:
60
- return pv_plot
61
-
62
- if anim_opts.save_path is None:
63
- anim_opts.save_path = Path.cwd() / "pyvale-animation"
64
-
65
- if anim_opts.save_animation == EAnimationType.GIF:
66
- anim_opts.save_path = anim_opts.save_path.with_suffix(".gif")
67
- pv_plot.open_gif(anim_opts.save_path,
68
- loop=0,
69
- fps=anim_opts.frames_per_second)
70
-
71
- elif anim_opts.save_animation == EAnimationType.MP4:
72
- anim_opts.save_path = anim_opts.save_path.with_suffix(".mp4")
73
- print(80*"=")
74
- print(f"{anim_opts.save_path=}")
75
- print(80*"=")
76
- pv_plot.open_movie(anim_opts.save_path,
77
- anim_opts.frames_per_second)
78
-
79
- return pv_plot
80
-
81
-