osiris-utils 1.1.4__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.
@@ -31,15 +31,7 @@ class Simulation:
31
31
  __getitem__(key)
32
32
  Get a diagnostic.
33
33
 
34
- Example
35
- -------
36
- >>> sim = Simulation('electrons', 'path/to/simulation')
37
- >>> diag = sim['e1']
38
- >>> diag.load_all()
39
-
40
- >>> sim = Simulation('electrons', 'path/to/simulation')
41
- >>> diag = sim['e1']
42
- >>> diag[<index>]
34
+
43
35
  '''
44
36
  def __init__(self, input_deck_path):
45
37
  folder_path = os.path.dirname(input_deck_path)
@@ -135,6 +127,10 @@ class Simulation:
135
127
  @property
136
128
  def species(self):
137
129
  return self._species
130
+
131
+ @property
132
+ def loaded_diagnostics(self):
133
+ return self._diagnostics
138
134
 
139
135
  # This is to handle species related diagnostics
140
136
  class Species_Handler:
@@ -180,13 +176,7 @@ class Species_Handler:
180
176
  -------
181
177
  str
182
178
  The name (key) used to store the diagnostic
183
-
184
- Example
185
- -------
186
- >>> sim = Simulation('path/to/simulation', 'input_deck.txt')
187
- >>> nT = sim['electrons']['n'] * sim['electrons']['T11']
188
- >>> sim.add_diagnostic(nT, 'nT')
189
- >>> sim['nT'] # Access the custom diagnostic
179
+
190
180
  """
191
181
  # Generate a name if none provided
192
182
  if name is None:
@@ -200,4 +190,27 @@ class Species_Handler:
200
190
  if isinstance(diagnostic, Diagnostic):
201
191
  self._diagnostics[name] = diagnostic
202
192
  else:
203
- raise ValueError("Only Diagnostic objects are supported for now")
193
+ raise ValueError("Only Diagnostic objects are supported for now")
194
+
195
+ def delete_diagnostic(self, key):
196
+ """
197
+ Delete a diagnostic.
198
+ """
199
+ if key in self._diagnostics:
200
+ del self._diagnostics[key]
201
+ else:
202
+ print(f"Diagnostic {key} not found in species {self._species_name}")
203
+ return None
204
+
205
+ def delete_all_diagnostics(self):
206
+ """
207
+ Delete all diagnostics.
208
+ """
209
+ self._diagnostics = {}
210
+
211
+ @property
212
+ def species(self):
213
+ return self._species_name
214
+ @property
215
+ def loaded_diagnostics(self):
216
+ return self._diagnostics
@@ -12,7 +12,7 @@ class Derivative_Simulation(PostProcess):
12
12
  ----------
13
13
  simulation : Simulation
14
14
  The simulation object.
15
- type : str
15
+ deriv_type : str
16
16
  The type of derivative to compute. Options are:
17
17
  - 't' for time derivative.
18
18
  - 'x1' for first spatial derivative.
@@ -24,19 +24,14 @@ class Derivative_Simulation(PostProcess):
24
24
  axis : int or tuple
25
25
  The axis to compute the derivative. Only used for 'xx', 'xt' and 'tx' types.
26
26
 
27
- Example
28
- -------
29
- >>> sim = Simulation('electrons', 'path/to/simulation')
30
- >>> derivative = Derivative(sim, 'x1')
31
- >>> deriv_e1_wrt_x1 = derivative['e1']
32
27
  """
33
28
 
34
- def __init__(self, simulation, type, axis=None):
35
- super().__init__(f"Derivative({type})")
29
+ def __init__(self, simulation, deriv_type, axis=None):
30
+ super().__init__(f"Derivative({deriv_type})")
36
31
  if not isinstance(simulation, Simulation):
37
32
  raise ValueError("Simulation must be a Simulation object.")
38
33
  self._simulation = simulation
39
- self._type = type
34
+ self._deriv_type = deriv_type
40
35
  self._axis = axis
41
36
  self._derivatives_computed = {}
42
37
  self._species_handler = {}
@@ -44,12 +39,12 @@ class Derivative_Simulation(PostProcess):
44
39
  def __getitem__(self, key):
45
40
  if key in self._simulation._species:
46
41
  if key not in self._species_handler:
47
- self._species_handler[key] = Derivative_Species_Handler(self._simulation[key], self._type, self._axis)
42
+ self._species_handler[key] = Derivative_Species_Handler(self._simulation[key], self._deriv_type, self._axis)
48
43
  return self._species_handler[key]
49
44
 
50
45
  if key not in self._derivatives_computed:
51
46
  self._derivatives_computed[key] = Derivative_Diagnostic(diagnostic=self._simulation[key],
52
- type=self._type, axis=self._axis)
47
+ deriv_type=self._deriv_type, axis=self._axis)
53
48
  return self._derivatives_computed[key]
54
49
 
55
50
  def delete_all(self):
@@ -63,7 +58,7 @@ class Derivative_Simulation(PostProcess):
63
58
 
64
59
  def process(self, diagnostic):
65
60
  """Apply derivative to a diagnostic"""
66
- return Derivative_Diagnostic(diagnostic, self._type, self._axis)
61
+ return Derivative_Diagnostic(diagnostic, self._deriv_type, self._axis)
67
62
 
68
63
 
69
64
  class Derivative_Diagnostic(Diagnostic):
@@ -75,7 +70,7 @@ class Derivative_Diagnostic(Diagnostic):
75
70
  ----------
76
71
  diagnostic : Diagnostic
77
72
  The diagnostic object.
78
- type : str
73
+ deriv_type : str
79
74
  The type of derivative to compute. Options are: 't', 'x1', 'x2', 'x3', 'xx', 'xt' and 'tx'.
80
75
  axis : int or tuple
81
76
  The axis to compute the derivative. Only used for 'xx', 'xt' and 'tx' types
@@ -87,45 +82,30 @@ class Derivative_Diagnostic(Diagnostic):
87
82
  __getitem__(index)
88
83
  Get data at a specific index.
89
84
 
90
- Example
91
- -------
92
- >>> sim = Simulation('electrons', 'path/to/simulation')
93
- >>> diag = sim['e1']
94
- >>> derivative = Derivative_Diagnostic(diag, 'x1')
95
85
  """
96
86
 
97
- def __init__(self, diagnostic, type, axis=None):
87
+ def __init__(self, diagnostic, deriv_type, axis=None):
98
88
  # Initialize using parent's __init__ with the same species
99
89
  if hasattr(diagnostic, '_species'):
100
90
  super().__init__(simulation_folder=diagnostic._simulation_folder if hasattr(diagnostic, '_simulation_folder') else None,
101
91
  species=diagnostic._species)
102
92
  else:
103
93
  super().__init__(None)
94
+
95
+ self.postprocess_name = f"DERIV"
104
96
 
105
97
  # self._name = f"D[{diagnostic._name}, {type}]"
106
98
  self._diag = diagnostic
107
- self._type = type
99
+ self._deriv_type = deriv_type
108
100
  self._axis = axis if axis is not None else diagnostic._axis
109
101
  self._data = None
110
102
  self._all_loaded = False
111
103
 
112
104
  # Copy all relevant attributes from diagnostic
113
- for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter']:
105
+ for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter', '_type']:
114
106
  if hasattr(diagnostic, attr):
115
107
  setattr(self, attr, getattr(diagnostic, attr))
116
108
 
117
- def load_metadata(self):
118
- """Copy metadata from original diagnostic to ensure consistency"""
119
- self._dt = self._diag._dt
120
- self._dx = self._diag._dx
121
- self._ndump = self._diag._ndump
122
- self._axis = self._diag._axis
123
- self._nx = self._diag._nx
124
- self._x = self._diag._x
125
- self._grid = self._diag._grid
126
- self._dim = self._diag._dim
127
- self._maxiter = self._diag._maxiter
128
-
129
109
  def load_all(self):
130
110
  """Load all data and compute the derivative"""
131
111
  if self._data is not None:
@@ -135,34 +115,34 @@ class Derivative_Diagnostic(Diagnostic):
135
115
  if not hasattr(self._diag, '_data') or self._diag._data is None:
136
116
  self._diag.load_all()
137
117
 
138
- if self._type == "t":
118
+ if self._deriv_type == "t":
139
119
  result = np.gradient(self._diag._data, self._diag._dt * self._diag._ndump, axis=0, edge_order=2)
140
120
 
141
- elif self._type == "x1":
121
+ elif self._deriv_type == "x1":
142
122
  if self._dim == 1:
143
123
  result = np.gradient(self._diag._data, self._diag._dx, axis=1, edge_order=2)
144
124
  else:
145
125
  result = np.gradient(self._diag._data, self._diag._dx[0], axis=1, edge_order=2)
146
126
 
147
- elif self._type == "x2":
148
- result = np.gradient(self._diag._data, self._diag._dx[0], axis=2, edge_order=2)
127
+ elif self._deriv_type == "x2":
128
+ result = np.gradient(self._diag._data, self._diag._dx[1], axis=2, edge_order=2)
149
129
 
150
- elif self._type == "x3":
151
- result = np.gradient(self._diag._data, self._diag._dx[0], axis=3, edge_order=2)
130
+ elif self._deriv_type == "x3":
131
+ result = np.gradient(self._diag._data, self._diag._dx[2], axis=3, edge_order=2)
152
132
 
153
- elif self._type == "xx":
133
+ elif self._deriv_type == "xx":
154
134
  if len(self._axis) != 2:
155
135
  raise ValueError("Axis must be a tuple with two elements.")
156
136
  result = np.gradient(np.gradient(self._diag._data, self._diag._dx[self._axis[0]-1], axis=self._axis[0], edge_order=2),
157
137
  self._diag._dx[self._axis[1]-1], axis=self._axis[1], edge_order=2)
158
138
 
159
- elif self._type == "xt":
139
+ elif self._deriv_type == "xt":
160
140
  if not isinstance(self._axis, int):
161
141
  raise ValueError("Axis must be an integer.")
162
142
  result = np.gradient(np.gradient(self._diag._data, self._diag._dt, axis=0, edge_order=2),
163
143
  self._diag._dx[self._axis-1], axis=self._axis[0], edge_order=2)
164
144
 
165
- elif self._type == "tx":
145
+ elif self._deriv_type == "tx":
166
146
  if not isinstance(self._axis, int):
167
147
  raise ValueError("Axis must be an integer.")
168
148
  result = np.gradient(np.gradient(self._diag._data, self._diag._dx[self._axis-1], axis=self._axis, edge_order=2),
@@ -177,19 +157,19 @@ class Derivative_Diagnostic(Diagnostic):
177
157
 
178
158
  def _data_generator(self, index):
179
159
  """Generate data for a specific index on-demand"""
180
- if self._type == "x1":
160
+ if self._deriv_type == "x1":
181
161
  if self._dim == 1:
182
162
  yield np.gradient(self._diag[index], self._diag._dx, axis=0, edge_order=2)
183
163
  else:
184
164
  yield np.gradient(self._diag[index], self._diag._dx[0], axis=0, edge_order=2)
185
165
 
186
- elif self._type == "x2":
166
+ elif self._deriv_type == "x2":
187
167
  yield np.gradient(self._diag[index], self._diag._dx[1], axis=1, edge_order=2)
188
168
 
189
- elif self._type == "x3":
169
+ elif self._deriv_type == "x3":
190
170
  yield np.gradient(self._diag[index], self._diag._dx[2], axis=2, edge_order=2)
191
171
 
192
- elif self._type == "t":
172
+ elif self._deriv_type == "t":
193
173
  if index == 0:
194
174
  yield (-3 * self._diag[index] + 4 * self._diag[index + 1] - self._diag[index + 2]) / (2 * self._diag._dt * self._diag._ndump)
195
175
  elif index == self._diag._maxiter - 1:
@@ -230,14 +210,14 @@ class Derivative_Species_Handler:
230
210
  axis : int or tuple
231
211
  The axis to compute the derivative. Only used for 'xx', 'xt' and 'tx' types.
232
212
  """
233
- def __init__(self, species_handler, type, axis=None):
213
+ def __init__(self, species_handler, deriv_type, axis=None):
234
214
  self._species_handler = species_handler
235
- self._type = type
215
+ self._deriv_type = deriv_type
236
216
  self._axis = axis
237
217
  self._derivatives_computed = {}
238
218
 
239
219
  def __getitem__(self, key):
240
220
  if key not in self._derivatives_computed:
241
221
  diag = self._species_handler[key]
242
- self._derivatives_computed[key] = Derivative_Diagnostic(diag, self._type, self._axis)
222
+ self._derivatives_computed[key] = Derivative_Diagnostic(diag, self._deriv_type, self._axis)
243
223
  return self._derivatives_computed[key]
@@ -19,11 +19,6 @@ class FastFourierTransform_Simulation(PostProcess):
19
19
  axis : int
20
20
  The axis to compute the FFT.
21
21
 
22
- Example
23
- -------
24
- >>> sim = Simulation('electrons', 'path/to/simulation')
25
- >>> fft = FastFourierTransform(sim, 1)
26
- >>> fft_e1 = fft['e1']
27
22
  """
28
23
  def __init__(self, simulation, fft_axis):
29
24
  super().__init__("FFT")
@@ -78,12 +73,7 @@ class FFT_Diagnostic(Diagnostic):
78
73
  Get the angular frequency array for the FFT.
79
74
  __getitem__(index)
80
75
  Get data at a specific index.
81
-
82
- Example
83
- -------
84
- >>> sim = Simulation('electrons', 'path/to/simulation')
85
- >>> diag = sim['e1']
86
- >>> fft = FFT_Diagnostic(diag, 1)
76
+
87
77
  """
88
78
  def __init__(self, diagnostic, fft_axis):
89
79
  if hasattr(diagnostic, '_species'):
@@ -92,6 +82,8 @@ class FFT_Diagnostic(Diagnostic):
92
82
  else:
93
83
  super().__init__(None)
94
84
 
85
+ self.postprocess_name = f"FFT"
86
+
95
87
  self._name = f"FFT[{diagnostic._name}, {fft_axis}]"
96
88
  self._diag = diagnostic
97
89
  self._fft_axis = fft_axis
@@ -99,7 +91,7 @@ class FFT_Diagnostic(Diagnostic):
99
91
  self._all_loaded = False
100
92
 
101
93
  # Copy all relevant attributes from diagnostic
102
- for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter']:
94
+ for attr in ['_dt', '_dx', '_ndump', '_axis', '_nx', '_x', '_grid', '_dim', '_maxiter', '_type']:
103
95
  if hasattr(diagnostic, attr):
104
96
  setattr(self, attr, getattr(diagnostic, attr))
105
97
 
@@ -110,7 +102,7 @@ class FFT_Diagnostic(Diagnostic):
110
102
 
111
103
  def load_all(self):
112
104
  if self._data is not None:
113
- print("Using cached derivative")
105
+ print("Using cached data.")
114
106
  return self._data
115
107
 
116
108
  if not hasattr(self._diag, '_data') or self._diag._data is None:
@@ -119,7 +111,9 @@ class FFT_Diagnostic(Diagnostic):
119
111
 
120
112
  # Apply appropriate windows based on which axes we're transforming
121
113
  if isinstance(self._fft_axis, (list, tuple)):
122
- # Multiple axes FFT
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
+
123
117
  result = self._diag._data.copy()
124
118
 
125
119
  for axis in self._fft_axis:
@@ -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}.")