osiris-utils 1.1.3__py3-none-any.whl → 1.1.6__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.
- osiris_utils/__init__.py +25 -6
- osiris_utils/data/__init__.py +0 -0
- osiris_utils/data/data.py +692 -0
- osiris_utils/data/diagnostic.py +1437 -0
- osiris_utils/data/simulation.py +216 -0
- osiris_utils/decks/__init__.py +0 -0
- osiris_utils/decks/decks.py +288 -0
- osiris_utils/decks/species.py +55 -0
- osiris_utils/gui/__init__.py +0 -0
- osiris_utils/gui/gui.py +266 -0
- osiris_utils/postprocessing/__init__.py +0 -0
- osiris_utils/postprocessing/derivative.py +223 -0
- osiris_utils/postprocessing/fft.py +234 -0
- osiris_utils/postprocessing/field_centering.py +168 -0
- osiris_utils/postprocessing/heatflux_correction.py +193 -0
- osiris_utils/postprocessing/mft.py +334 -0
- osiris_utils/postprocessing/mft_for_gridfile.py +52 -0
- osiris_utils/postprocessing/postprocess.py +42 -0
- osiris_utils/postprocessing/pressure_correction.py +171 -0
- osiris_utils/utils.py +141 -41
- {osiris_utils-1.1.3.dist-info → osiris_utils-1.1.6.dist-info}/METADATA +20 -2
- osiris_utils-1.1.6.dist-info/RECORD +25 -0
- {osiris_utils-1.1.3.dist-info → osiris_utils-1.1.6.dist-info}/WHEEL +1 -1
- osiris_utils-1.1.3.dist-info/RECORD +0 -7
- {osiris_utils-1.1.3.dist-info → osiris_utils-1.1.6.dist-info}/licenses/LICENSE.txt +0 -0
- {osiris_utils-1.1.3.dist-info → osiris_utils-1.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
from ..data.simulation import Simulation
|
|
3
|
+
from .postprocess import PostProcess
|
|
4
|
+
from ..data.diagnostic import Diagnostic
|
|
5
|
+
import numpy as np
|
|
6
|
+
import tqdm as tqdm
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FastFourierTransform_Simulation(PostProcess):
|
|
10
|
+
"""
|
|
11
|
+
Class to handle the Fast Fourier Transform on data. Works as a wrapper for the FFT_Diagnostic class.
|
|
12
|
+
Inherits from PostProcess to ensure all operation overloads work properly.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
|
|
17
|
+
simulation : Simulation
|
|
18
|
+
The simulation object.
|
|
19
|
+
axis : int
|
|
20
|
+
The axis to compute the FFT.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, simulation, fft_axis):
|
|
24
|
+
super().__init__("FFT")
|
|
25
|
+
if not isinstance(simulation, Simulation):
|
|
26
|
+
raise ValueError("Simulation must be a Simulation object.")
|
|
27
|
+
self._simulation = simulation
|
|
28
|
+
self._fft_axis = fft_axis
|
|
29
|
+
self._fft_computed = {}
|
|
30
|
+
self._species_handler = {}
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, key):
|
|
33
|
+
if key in self._simulation._species:
|
|
34
|
+
if key not in self._species_handler:
|
|
35
|
+
self._species_handler[key] = FFT_Species_Handler(self._simulation[key], self._fft_axis)
|
|
36
|
+
return self._species_handler[key]
|
|
37
|
+
|
|
38
|
+
if key not in self._fft_computed:
|
|
39
|
+
self._fft_computed[key] = FFT_Diagnostic(self._simulation[key], self._fft_axis)
|
|
40
|
+
return self._fft_computed[key]
|
|
41
|
+
|
|
42
|
+
def delete_all(self):
|
|
43
|
+
self._fft_computed = {}
|
|
44
|
+
|
|
45
|
+
def delete(self, key):
|
|
46
|
+
if key in self._fft_computed:
|
|
47
|
+
del self._fft_computed[key]
|
|
48
|
+
else:
|
|
49
|
+
print(f"FFT {key} not found in simulation")
|
|
50
|
+
|
|
51
|
+
def process(self, diagnostic):
|
|
52
|
+
"""Apply FFT to a diagnostic"""
|
|
53
|
+
return FFT_Diagnostic(diagnostic, self._fft_axis)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FFT_Diagnostic(Diagnostic):
|
|
57
|
+
"""
|
|
58
|
+
Auxiliar class to compute the FFT of a diagnostic, for it to be similar in behavior to a Diagnostic object.
|
|
59
|
+
Inherits directly from Diagnostic to ensure all operation overloads work properly.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
diagnostic : Diagnostic
|
|
64
|
+
The diagnostic to compute the FFT.
|
|
65
|
+
axis : int
|
|
66
|
+
The axis to compute the FFT.
|
|
67
|
+
|
|
68
|
+
Methods
|
|
69
|
+
-------
|
|
70
|
+
load_all()
|
|
71
|
+
Load all the data and compute the FFT.
|
|
72
|
+
omega()
|
|
73
|
+
Get the angular frequency array for the FFT.
|
|
74
|
+
__getitem__(index)
|
|
75
|
+
Get data at a specific index.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
def __init__(self, diagnostic, fft_axis):
|
|
79
|
+
if hasattr(diagnostic, '_species'):
|
|
80
|
+
super().__init__(simulation_folder=diagnostic._simulation_folder if hasattr(diagnostic, '_simulation_folder') else None,
|
|
81
|
+
species=diagnostic._species)
|
|
82
|
+
else:
|
|
83
|
+
super().__init__(None)
|
|
84
|
+
|
|
85
|
+
self.postprocess_name = f"FFT"
|
|
86
|
+
|
|
87
|
+
self._name = f"FFT[{diagnostic._name}, {fft_axis}]"
|
|
88
|
+
self._diag = diagnostic
|
|
89
|
+
self._fft_axis = fft_axis
|
|
90
|
+
self._data = None
|
|
91
|
+
self._all_loaded = False
|
|
92
|
+
|
|
93
|
+
# Copy all relevant attributes from diagnostic
|
|
94
|
+
for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter', '_type']:
|
|
95
|
+
if hasattr(diagnostic, attr):
|
|
96
|
+
setattr(self, attr, getattr(diagnostic, attr))
|
|
97
|
+
|
|
98
|
+
if isinstance(self._dx, (int, float)):
|
|
99
|
+
self._kmax = np.pi / (self._dx)
|
|
100
|
+
else:
|
|
101
|
+
self._kmax = np.pi / np.array([self._dx[ax-1] for ax in self._fft_axis if ax != 0])
|
|
102
|
+
|
|
103
|
+
def load_all(self):
|
|
104
|
+
if self._data is not None:
|
|
105
|
+
print("Using cached data.")
|
|
106
|
+
return self._data
|
|
107
|
+
|
|
108
|
+
if not hasattr(self._diag, '_data') or self._diag._data is None:
|
|
109
|
+
self._diag.load_all()
|
|
110
|
+
self._diag._data = np.nan_to_num(self._diag._data)
|
|
111
|
+
|
|
112
|
+
# Apply appropriate windows based on which axes we're transforming
|
|
113
|
+
if isinstance(self._fft_axis, (list, tuple)):
|
|
114
|
+
if self._diag._data is None:
|
|
115
|
+
raise ValueError(f"Unable to load data for diagnostic {self._diag._name}. The data is None even after loading.")
|
|
116
|
+
|
|
117
|
+
result = self._diag._data.copy()
|
|
118
|
+
|
|
119
|
+
for axis in self._fft_axis:
|
|
120
|
+
if axis == 0: # Time axis
|
|
121
|
+
window = np.hanning(result.shape[0]).reshape(-1, *([1] * (result.ndim - 1)))
|
|
122
|
+
result = result * window
|
|
123
|
+
else: # Spatial axis
|
|
124
|
+
window = self._get_window(result.shape[axis], axis)
|
|
125
|
+
result = self._apply_window(result, window, axis)
|
|
126
|
+
|
|
127
|
+
with tqdm.tqdm(total=1, desc="FFT calculation") as pbar:
|
|
128
|
+
data_fft = np.fft.fftn(result, axes=self._fft_axis)
|
|
129
|
+
pbar.update(0.5)
|
|
130
|
+
result = np.fft.fftshift(data_fft, axes=self._fft_axis)
|
|
131
|
+
pbar.update(0.5)
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
if self._fft_axis == 0:
|
|
135
|
+
hanning_window = np.hanning(self._diag._data.shape[0]).reshape(-1, *([1] * (self._diag._data.ndim - 1)))
|
|
136
|
+
data_windowed = hanning_window * self._diag._data
|
|
137
|
+
else:
|
|
138
|
+
window = self._get_window(self._diag._data.shape[self._fft_axis], self._fft_axis)
|
|
139
|
+
data_windowed = self._apply_window(self._diag._data, window, self._fft_axis)
|
|
140
|
+
|
|
141
|
+
with tqdm.tqdm(total=1, desc="FFT calculation") as pbar:
|
|
142
|
+
data_fft = np.fft.fft(data_windowed, axis=self._fft_axis)
|
|
143
|
+
pbar.update(0.5)
|
|
144
|
+
result = np.fft.fftshift(data_fft, axes=self._fft_axis)
|
|
145
|
+
pbar.update(0.5)
|
|
146
|
+
|
|
147
|
+
self.omega_max = np.pi / self._dt / self._ndump
|
|
148
|
+
|
|
149
|
+
self._all_loaded = True
|
|
150
|
+
self._data = np.abs(result)**2
|
|
151
|
+
return self._data
|
|
152
|
+
|
|
153
|
+
def _data_generator(self, index):
|
|
154
|
+
# Get the data for this index
|
|
155
|
+
original_data = self._diag[index]
|
|
156
|
+
|
|
157
|
+
if self._fft_axis == 0:
|
|
158
|
+
raise ValueError("Cannot generate FFT along time axis for a single timestep. Use load_all() instead.")
|
|
159
|
+
|
|
160
|
+
# For spatial FFT, we can apply a spatial window if desired
|
|
161
|
+
if isinstance(self._fft_axis, (list, tuple)):
|
|
162
|
+
result = original_data
|
|
163
|
+
for axis in self._fft_axis:
|
|
164
|
+
if axis != 0: # Skip time axis
|
|
165
|
+
# Apply window along this spatial dimension
|
|
166
|
+
window = self._get_window(original_data.shape[axis-1], axis-1)
|
|
167
|
+
result = self._apply_window(result, window, axis-1)
|
|
168
|
+
|
|
169
|
+
# Compute FFT
|
|
170
|
+
result_fft = np.fft.fftn(result, axes=[ax-1 for ax in self._fft_axis if ax != 0])
|
|
171
|
+
result_fft = np.fft.fftshift(result_fft, axes=[ax-1 for ax in self._fft_axis if ax != 0])
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
if self._fft_axis > 0: # Spatial axis
|
|
175
|
+
window = self._get_window(original_data.shape[self._fft_axis-1], self._fft_axis-1)
|
|
176
|
+
windowed_data = self._apply_window(original_data, window, self._fft_axis-1)
|
|
177
|
+
|
|
178
|
+
result_fft = np.fft.fft(windowed_data, axis=self._fft_axis-1)
|
|
179
|
+
result_fft = np.fft.fftshift(result_fft, axes=self._fft_axis-1)
|
|
180
|
+
|
|
181
|
+
yield np.abs(result_fft)**2
|
|
182
|
+
|
|
183
|
+
def _get_window(self, length, axis):
|
|
184
|
+
return np.hanning(length)
|
|
185
|
+
|
|
186
|
+
def _apply_window(self, data, window, axis):
|
|
187
|
+
ndim = data.ndim
|
|
188
|
+
window_shape = [1] * ndim
|
|
189
|
+
window_shape[axis] = len(window)
|
|
190
|
+
|
|
191
|
+
reshaped_window = window.reshape(window_shape)
|
|
192
|
+
|
|
193
|
+
return data * reshaped_window
|
|
194
|
+
|
|
195
|
+
def __getitem__(self, index):
|
|
196
|
+
if self._all_loaded and self._data is not None:
|
|
197
|
+
return self._data[index]
|
|
198
|
+
|
|
199
|
+
if isinstance(index, int):
|
|
200
|
+
return next(self._data_generator(index))
|
|
201
|
+
elif isinstance(index, slice):
|
|
202
|
+
start = 0 if index.start is None else index.start
|
|
203
|
+
step = 1 if index.step is None else index.step
|
|
204
|
+
stop = self._diag._maxiter if index.stop is None else index.stop
|
|
205
|
+
return np.array([next(self._data_generator(i)) for i in range(start, stop, step)])
|
|
206
|
+
else:
|
|
207
|
+
raise ValueError("Invalid index type. Use int or slice.")
|
|
208
|
+
|
|
209
|
+
def omega(self):
|
|
210
|
+
"""
|
|
211
|
+
Get the angular frequency array for the FFT.
|
|
212
|
+
"""
|
|
213
|
+
if not self._all_loaded:
|
|
214
|
+
raise ValueError("Load the data first using load_all() method.")
|
|
215
|
+
|
|
216
|
+
omega = np.fft.fftfreq(self._data.shape[self._fft_axis], d=self._dx[self._fft_axis-1])
|
|
217
|
+
omega = np.fft.fftshift(omega)
|
|
218
|
+
return omega
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def kmax(self):
|
|
222
|
+
return self._kmax
|
|
223
|
+
|
|
224
|
+
class FFT_Species_Handler:
|
|
225
|
+
def __init__(self, species_handler, fft_axis):
|
|
226
|
+
self._species_handler = species_handler
|
|
227
|
+
self._fft_axis = fft_axis
|
|
228
|
+
self._fft_computed = {}
|
|
229
|
+
|
|
230
|
+
def __getitem__(self, key):
|
|
231
|
+
if key not in self._fft_computed:
|
|
232
|
+
diag = self._species_handler[key]
|
|
233
|
+
self._fft_computed[key] = FFT_Diagnostic(diag, self._fft_axis)
|
|
234
|
+
return self._fft_computed[key]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
from ..data.simulation import Simulation
|
|
3
|
+
from .postprocess import PostProcess
|
|
4
|
+
from ..data.diagnostic import Diagnostic
|
|
5
|
+
|
|
6
|
+
OSIRIS_FLD = ["e1", "e2", "e3", "b1", "b2", "b3"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FieldCentering_Simulation(PostProcess):
|
|
10
|
+
"""
|
|
11
|
+
Class to handle the field centering on data. Works as a wrapper for the FieldCentering_Diagnostic class.
|
|
12
|
+
Inherits from PostProcess to ensure all operation overloads work properly.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
simulation : Simulation
|
|
17
|
+
The simulation object.
|
|
18
|
+
field : str
|
|
19
|
+
The field to center.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, simulation: Simulation):
|
|
23
|
+
super().__init__(f"FieldCentering Simulation")
|
|
24
|
+
"""
|
|
25
|
+
Class to center the field in the simulation.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
sim : Simulation
|
|
30
|
+
The simulation object.
|
|
31
|
+
field : str
|
|
32
|
+
The field to center.
|
|
33
|
+
"""
|
|
34
|
+
if not isinstance(simulation, Simulation):
|
|
35
|
+
raise ValueError("Simulation must be a Simulation object.")
|
|
36
|
+
self._simulation = simulation
|
|
37
|
+
|
|
38
|
+
self._field_centered = {}
|
|
39
|
+
# no need to create a species handler for field centering since fields are not species related
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, key):
|
|
42
|
+
if key not in OSIRIS_FLD:
|
|
43
|
+
raise ValueError(f"Does it make sense to center {key} field? Only {OSIRIS_FLD} are supported.")
|
|
44
|
+
if key not in self._field_centered:
|
|
45
|
+
self._field_centered[key] = FieldCentering_Diagnostic(self._simulation[key])
|
|
46
|
+
return self._field_centered[key]
|
|
47
|
+
|
|
48
|
+
def delete_all(self):
|
|
49
|
+
self._field_centered = {}
|
|
50
|
+
|
|
51
|
+
def delete(self, key):
|
|
52
|
+
if key in self._field_centered:
|
|
53
|
+
del self._field_centered[key]
|
|
54
|
+
else:
|
|
55
|
+
print(f"Field {key} not found in simulation")
|
|
56
|
+
|
|
57
|
+
def process(self, diagnostic):
|
|
58
|
+
"""Apply field centering to a diagnostic"""
|
|
59
|
+
return FieldCentering_Diagnostic(diagnostic)
|
|
60
|
+
|
|
61
|
+
class FieldCentering_Diagnostic(Diagnostic):
|
|
62
|
+
def __init__(self, diagnostic):
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
Class to center the field in the simulation.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
diagnostic : Diagnostic
|
|
70
|
+
The diagnostic object.
|
|
71
|
+
"""
|
|
72
|
+
if hasattr(diagnostic, '_species'):
|
|
73
|
+
super().__init__(simulation_folder=diagnostic._simulation_folder if hasattr(diagnostic, '_simulation_folder') else None,
|
|
74
|
+
species=diagnostic._species)
|
|
75
|
+
else:
|
|
76
|
+
super().__init__(None)
|
|
77
|
+
|
|
78
|
+
self.postprocess_name = "FLD_CTR"
|
|
79
|
+
|
|
80
|
+
if diagnostic._name not in OSIRIS_FLD:
|
|
81
|
+
raise ValueError(f"Does it make sense to center {diagnostic._name} field? Only {OSIRIS_FLD} are supported.")
|
|
82
|
+
|
|
83
|
+
self._diag = diagnostic
|
|
84
|
+
|
|
85
|
+
for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter']:
|
|
86
|
+
if hasattr(diagnostic, attr):
|
|
87
|
+
setattr(self, attr, getattr(diagnostic, attr))
|
|
88
|
+
|
|
89
|
+
self._original_name = diagnostic._name
|
|
90
|
+
self._name = diagnostic._name + "_centered"
|
|
91
|
+
|
|
92
|
+
self._data = None
|
|
93
|
+
self._all_loaded = False
|
|
94
|
+
|
|
95
|
+
def load_all(self):
|
|
96
|
+
if self._data is not None:
|
|
97
|
+
return self._data
|
|
98
|
+
|
|
99
|
+
if not hasattr(self._diag, '_data') or self._diag._data is None:
|
|
100
|
+
self._diag.load_all()
|
|
101
|
+
|
|
102
|
+
if self._dim == 1:
|
|
103
|
+
if self._original_name.lower() in ['b2', 'b3', 'e1']:
|
|
104
|
+
result = 0.5 * (np.roll(self._diag.data, shift=1, axis=1) + self._diag.data)
|
|
105
|
+
elif self._original_name.lower() in ['b1', 'e2', 'e3']:
|
|
106
|
+
result = self._diag.data
|
|
107
|
+
|
|
108
|
+
elif self._dim == 2:
|
|
109
|
+
if self._original_name.lower() in ['e1', 'b2']:
|
|
110
|
+
result = 0.5 * (np.roll(self._diag.data, shift=1, axis=1) + self._diag.data)
|
|
111
|
+
elif self._original_name.lower() in ['e2', 'b1']:
|
|
112
|
+
result = 0.5 * (np.roll(self._diag.data, shift=1, axis=2) + self._diag.data)
|
|
113
|
+
elif self._original_name.lower() in ['b3']:
|
|
114
|
+
result = 0.5 * (np.roll((0.5 * (np.roll(self._diag.data, shift=1, axis=1) + self._diag.data)), shift=1, axis=2) + (0.5 * (np.roll(self._diag.data, shift=1, axis=1) + self._diag.data)))
|
|
115
|
+
elif self._original_name.lower() in ['e3']:
|
|
116
|
+
result = self._diag.data
|
|
117
|
+
|
|
118
|
+
elif self._dim == 3:
|
|
119
|
+
raise NotImplementedError("3D field centering is not implemented yet.")
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
raise ValueError(f"Unknown dimension {self._dim}.")
|
|
123
|
+
|
|
124
|
+
self._data = result
|
|
125
|
+
self._all_loaded = True
|
|
126
|
+
return self._data
|
|
127
|
+
|
|
128
|
+
def __getitem__(self, index):
|
|
129
|
+
"""Get data at a specific index"""
|
|
130
|
+
if self._all_loaded and self._data is not None:
|
|
131
|
+
return self._data[index]
|
|
132
|
+
|
|
133
|
+
if isinstance(index, int):
|
|
134
|
+
return next(self._data_generator(index))
|
|
135
|
+
elif isinstance(index, slice):
|
|
136
|
+
start = 0 if index.start is None else index.start
|
|
137
|
+
step = 1 if index.step is None else index.step
|
|
138
|
+
stop = self._diag._maxiter if index.stop is None else index.stop
|
|
139
|
+
return np.array([next(self._data_generator(i)) for i in range(start, stop, step)])
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError("Invalid index type. Use int or slice.")
|
|
142
|
+
|
|
143
|
+
def _data_generator(self, index):
|
|
144
|
+
if self._dim == 1:
|
|
145
|
+
if self._original_name.lower() in ['b2', 'b3', 'e1']:
|
|
146
|
+
yield 0.5 * (np.roll(self._diag[index], shift=1) + self._diag[index])
|
|
147
|
+
elif self._original_name.lower() in ['b1', 'e2', 'e3']: # it's already centered but self._data does not exist
|
|
148
|
+
yield self._diag[index]
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError(f"Unknown field {self._original_name}.")
|
|
151
|
+
|
|
152
|
+
elif self._dim == 2:
|
|
153
|
+
if self._original_name in ['e1', 'b2']:
|
|
154
|
+
yield 0.5 * (np.roll(self._diag[index], shift=1, axis=0) + self._diag[index])
|
|
155
|
+
elif self._original_name in ['e2', 'b1']:
|
|
156
|
+
yield 0.5 * (np.roll(self._diag[index], shift=1, axis=1) + self._diag[index])
|
|
157
|
+
elif self._original_name in ['b3']:
|
|
158
|
+
yield 0.5 * (np.roll((0.5 * (np.roll(self._diag[index], shift=1, axis=0) + self._diag[index])), shift=1, axis=1) + (0.5 * (np.roll(self._diag[index], shift=1, axis=0) + self._diag[index])))
|
|
159
|
+
elif self._original_name in ['e3']:
|
|
160
|
+
yield self._diag[index]
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError(f"Unknown field {self._original_name}.")
|
|
163
|
+
|
|
164
|
+
elif self._dim == 3:
|
|
165
|
+
raise NotImplementedError("3D field centering is not implemented yet.")
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
raise ValueError(f"Unknown dimension {self._dim}.")
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
from ..data.simulation import Simulation
|
|
3
|
+
from .postprocess import PostProcess
|
|
4
|
+
from ..data.diagnostic import Diagnostic
|
|
5
|
+
|
|
6
|
+
from .pressure_correction import *
|
|
7
|
+
|
|
8
|
+
OSIRIS_H = ["q1", "q2", "q3"]
|
|
9
|
+
|
|
10
|
+
class HeatfluxCorrection_Simulation(PostProcess):
|
|
11
|
+
def __init__(self, simulation):
|
|
12
|
+
super().__init__(f"HeatfluxCorrection Simulation")
|
|
13
|
+
"""
|
|
14
|
+
Class to correct pressure tensor components by subtracting Reynolds stress.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
sim : Simulation
|
|
19
|
+
The simulation object.
|
|
20
|
+
heatflux : str
|
|
21
|
+
The heatflux component to center.
|
|
22
|
+
"""
|
|
23
|
+
if not isinstance(simulation, Simulation):
|
|
24
|
+
raise ValueError("Simulation must be a Simulation object.")
|
|
25
|
+
self._simulation = simulation
|
|
26
|
+
self._heatflux_corrected = {}
|
|
27
|
+
self._species_handler = {}
|
|
28
|
+
|
|
29
|
+
def __getitem__(self, key):
|
|
30
|
+
if key in self._simulation._species:
|
|
31
|
+
if key not in self._species_handler:
|
|
32
|
+
self._species_handler[key] = HeatfluxCorrection_Species_Handler(self._simulation[key], self._simulation)
|
|
33
|
+
return self._species_handler[key]
|
|
34
|
+
if key not in OSIRIS_H:
|
|
35
|
+
raise ValueError(f"Invalid heatflux component {key}. Supported: {OSIRIS_H}.")
|
|
36
|
+
if key not in self._heatflux_corrected:
|
|
37
|
+
print("Weird that it got here - heatflux is always species dependent on OSIRIS")
|
|
38
|
+
self._heatflux_corrected[key] = HeatfluxCorrection_Diagnostic(self._simulation[key], self._simulation)
|
|
39
|
+
return self._heatflux_corrected[key]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def delete_all(self):
|
|
43
|
+
self._heatflux_corrected = {}
|
|
44
|
+
|
|
45
|
+
def delete(self, key):
|
|
46
|
+
if key in self._heatflux_corrected:
|
|
47
|
+
del self._heatflux_corrected[key]
|
|
48
|
+
else:
|
|
49
|
+
print(f"Heatflux {key} not found in simulation")
|
|
50
|
+
|
|
51
|
+
def process(self, diagnostic):
|
|
52
|
+
"""Apply heatflux correction to a diagnostic"""
|
|
53
|
+
return HeatfluxCorrection_Diagnostic(diagnostic, self._simulation)
|
|
54
|
+
|
|
55
|
+
class HeatfluxCorrection_Diagnostic(Diagnostic):
|
|
56
|
+
def __init__(self, diagnostic, vfl_i, Pjj_list, vfl_j_list, Pji_list):
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
Class to correct the pressure in the simulation.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
diagnostic : Diagnostic
|
|
64
|
+
The diagnostic object.
|
|
65
|
+
"""
|
|
66
|
+
if hasattr(diagnostic, '_species'):
|
|
67
|
+
super().__init__(simulation_folder=diagnostic._simulation_folder if hasattr(diagnostic, '_simulation_folder') else None,
|
|
68
|
+
species=diagnostic._species)
|
|
69
|
+
else:
|
|
70
|
+
super().__init__(None)
|
|
71
|
+
|
|
72
|
+
self.postprocess_name = "HFL_CORR"
|
|
73
|
+
|
|
74
|
+
if diagnostic._name not in OSIRIS_H:
|
|
75
|
+
raise ValueError(f"Invalid heatflux component {diagnostic._name}. Supported: {OSIRIS_H}")
|
|
76
|
+
|
|
77
|
+
self._diag = diagnostic
|
|
78
|
+
|
|
79
|
+
# The density and velocities are now passed as arguments (so it can doesn't depend on the simulation)
|
|
80
|
+
self._vfl_i = vfl_i
|
|
81
|
+
self._Pjj_list = Pjj_list
|
|
82
|
+
self._vfl_j_list = vfl_j_list
|
|
83
|
+
self._Pji_list = Pji_list
|
|
84
|
+
|
|
85
|
+
for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter', '_type']:
|
|
86
|
+
if hasattr(diagnostic, attr):
|
|
87
|
+
setattr(self, attr, getattr(diagnostic, attr))
|
|
88
|
+
|
|
89
|
+
self._original_name = diagnostic._name
|
|
90
|
+
self._name = diagnostic._name + "_corrected"
|
|
91
|
+
|
|
92
|
+
self._data = None
|
|
93
|
+
self._all_loaded = False
|
|
94
|
+
|
|
95
|
+
def load_all(self):
|
|
96
|
+
if self._data is not None:
|
|
97
|
+
return self._data
|
|
98
|
+
|
|
99
|
+
if not hasattr(self._diag, '_data') or self._diag._data is None:
|
|
100
|
+
self._diag.load_all()
|
|
101
|
+
|
|
102
|
+
print(f"Loading {self._species._name} {self._original_name} diagnostic")
|
|
103
|
+
|
|
104
|
+
self._vfl_i.load_all()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
for vfl_j in self._vfl_j_list:
|
|
108
|
+
vfl_j.load_all()
|
|
109
|
+
for Pji in self._Pji_list:
|
|
110
|
+
Pji.load_all()
|
|
111
|
+
for Pjj in self._Pjj_list:
|
|
112
|
+
Pjj.load_all()
|
|
113
|
+
|
|
114
|
+
q = self._diag.data
|
|
115
|
+
vfl_i = self._vfl_i.data
|
|
116
|
+
|
|
117
|
+
trace_P = sum(Pjj.data for Pjj in self._Pjj_list)
|
|
118
|
+
|
|
119
|
+
# Sum over j: vfl_j * Pji
|
|
120
|
+
vfl_dot_Pji = sum(vfl_j.data * Pji.data for vfl_j, Pji in zip(self._vfl_j_list, self._Pji_list))
|
|
121
|
+
|
|
122
|
+
self._data = 2 * q - 0.5 * vfl_i * trace_P - vfl_dot_Pji
|
|
123
|
+
self._all_loaded = True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
return self._data
|
|
127
|
+
|
|
128
|
+
def __getitem__(self, index):
|
|
129
|
+
"""Get data at a specific index"""
|
|
130
|
+
if self._all_loaded and self._data is not None:
|
|
131
|
+
return self._data[index]
|
|
132
|
+
|
|
133
|
+
if isinstance(index, int):
|
|
134
|
+
return next(self._data_generator(index))
|
|
135
|
+
elif isinstance(index, slice):
|
|
136
|
+
start = 0 if index.start is None else index.start
|
|
137
|
+
step = 1 if index.step is None else index.step
|
|
138
|
+
stop = self._diag._maxiter if index.stop is None else index.stop
|
|
139
|
+
return np.array([next(self._data_generator(i)) for i in range(start, stop, step)])
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError("Invalid index type. Use int or slice.")
|
|
142
|
+
|
|
143
|
+
def _data_generator(self, index):
|
|
144
|
+
q = self._diag[index]
|
|
145
|
+
vfl_i = self._vfl_i[index]
|
|
146
|
+
trace_P = sum(Pjj[index] for Pjj in self._Pjj_list)
|
|
147
|
+
vfl_dot_Pji = sum(vfl_j[index] * Pji[index] for vfl_j, Pji in zip(self._vfl_j_list, self._Pji_list))
|
|
148
|
+
yield 2 * q - 0.5 * vfl_i * trace_P - vfl_dot_Pji
|
|
149
|
+
|
|
150
|
+
class HeatfluxCorrection_Species_Handler:
|
|
151
|
+
"""
|
|
152
|
+
Class to handle heatflux correction for a species.
|
|
153
|
+
Acts as a wrapper for the HeatfluxCorrection_Diagnostic class.
|
|
154
|
+
|
|
155
|
+
Not intended to be used directly, but through the HeatfluxCorrection_Simulation class.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
species_handler : Species_Handler
|
|
160
|
+
The species handler object.
|
|
161
|
+
simulation : Simulation
|
|
162
|
+
The simulation object.
|
|
163
|
+
"""
|
|
164
|
+
def __init__(self, species_handler, simulation):
|
|
165
|
+
self._species_handler = species_handler
|
|
166
|
+
self._simulation = simulation
|
|
167
|
+
self._heatflux_corrected = {}
|
|
168
|
+
|
|
169
|
+
def __getitem__(self, key):
|
|
170
|
+
if key not in self._heatflux_corrected:
|
|
171
|
+
diag = self._species_handler[key]
|
|
172
|
+
|
|
173
|
+
# Velocities alwayes depend on the species so this can be done here
|
|
174
|
+
|
|
175
|
+
i = int(key[-1]) # Get i from 'q1', 'q2', etc.
|
|
176
|
+
|
|
177
|
+
vfl_i = self._species_handler[f"vfl{i}"]
|
|
178
|
+
|
|
179
|
+
# Load trace(P): sum over Pjj
|
|
180
|
+
Pjj_list = [self._species_handler[f"P{j}{j}"] for j in range(1, diag._dim + 1)]
|
|
181
|
+
|
|
182
|
+
# Compute quantities for vfl_j * P_{ji}
|
|
183
|
+
vfl_j_list = [self._species_handler[f"vfl{j}"] for j in range(1, diag._dim + 1)]
|
|
184
|
+
Pji_list = [PressureCorrection_Simulation(self._simulation)[diag._species._name][f"P{j}{i}"] for j in range(1, diag._dim + 1)]
|
|
185
|
+
|
|
186
|
+
self._heatflux_corrected[key] = HeatfluxCorrection_Diagnostic(
|
|
187
|
+
diag,
|
|
188
|
+
vfl_i,
|
|
189
|
+
Pjj_list,
|
|
190
|
+
vfl_j_list,
|
|
191
|
+
Pji_list
|
|
192
|
+
)
|
|
193
|
+
return self._heatflux_corrected[key]
|