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,216 @@
|
|
|
1
|
+
from matplotlib.style import available
|
|
2
|
+
from ..data.diagnostic import *
|
|
3
|
+
from ..utils import *
|
|
4
|
+
from ..decks.decks import InputDeckIO
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Simulation:
|
|
8
|
+
'''
|
|
9
|
+
Class to handle the simulation data. It is a wrapper for the Diagnostic class.'
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
input_deck_path : str
|
|
14
|
+
Path to the input deck (It must be in the folder where the simulation was run)
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
simulation_folder : str
|
|
19
|
+
The simulation folder.
|
|
20
|
+
species : Specie object
|
|
21
|
+
The species to analyze.
|
|
22
|
+
diagnostics : dict
|
|
23
|
+
Dictionary to store diagnostics for each quantity when `load_all` method is used.
|
|
24
|
+
|
|
25
|
+
Methods
|
|
26
|
+
-------
|
|
27
|
+
delete_all_diagnostics()
|
|
28
|
+
Delete all diagnostics.
|
|
29
|
+
delete_diagnostic(key)
|
|
30
|
+
Delete a diagnostic.
|
|
31
|
+
__getitem__(key)
|
|
32
|
+
Get a diagnostic.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
'''
|
|
36
|
+
def __init__(self, input_deck_path):
|
|
37
|
+
folder_path = os.path.dirname(input_deck_path)
|
|
38
|
+
self._input_deck_path = input_deck_path
|
|
39
|
+
self._input_deck = InputDeckIO(self._input_deck_path, verbose=False)
|
|
40
|
+
|
|
41
|
+
self._species = list(self._input_deck.species.keys())
|
|
42
|
+
|
|
43
|
+
self._simulation_folder = folder_path
|
|
44
|
+
self._diagnostics = {} # Dictionary to store diagnostics for each quantity
|
|
45
|
+
self._species_handler = {}
|
|
46
|
+
|
|
47
|
+
def delete_all_diagnostics(self):
|
|
48
|
+
"""
|
|
49
|
+
Delete all diagnostics.
|
|
50
|
+
"""
|
|
51
|
+
self._diagnostics = {}
|
|
52
|
+
|
|
53
|
+
def delete_diagnostic(self, key):
|
|
54
|
+
"""
|
|
55
|
+
Delete a diagnostic."
|
|
56
|
+
"""
|
|
57
|
+
if key in self._diagnostics:
|
|
58
|
+
del self._diagnostics[key]
|
|
59
|
+
else:
|
|
60
|
+
print(f"Diagnostic {key} not found in simulation")
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, key):
|
|
63
|
+
# check if key is a species
|
|
64
|
+
if key in self._species:
|
|
65
|
+
# check if species handler already exists
|
|
66
|
+
if key not in self._species_handler:
|
|
67
|
+
self._species_handler[key] = Species_Handler(self._simulation_folder, self._input_deck.species[key], self._input_deck)
|
|
68
|
+
return self._species_handler[key]
|
|
69
|
+
|
|
70
|
+
if key in self._diagnostics:
|
|
71
|
+
return self._diagnostics[key]
|
|
72
|
+
|
|
73
|
+
# Create a temporary diagnostic for this quantity - this is for quantities that are not species related
|
|
74
|
+
diag = Diagnostic(simulation_folder=self._simulation_folder, species=None, input_deck=self._input_deck)
|
|
75
|
+
diag.get_quantity(key)
|
|
76
|
+
|
|
77
|
+
original_load_all = diag.load_all
|
|
78
|
+
|
|
79
|
+
def patched_load_all(*args, **kwargs):
|
|
80
|
+
result = original_load_all(*args, **kwargs)
|
|
81
|
+
self._diagnostics[key] = diag
|
|
82
|
+
return diag
|
|
83
|
+
|
|
84
|
+
diag.load_all = patched_load_all
|
|
85
|
+
|
|
86
|
+
return diag
|
|
87
|
+
|
|
88
|
+
def add_diagnostic(self, diagnostic, name=None):
|
|
89
|
+
"""
|
|
90
|
+
Add a custom diagnostic to the simulation.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
diagnostic : Diagnostic or array-like
|
|
95
|
+
The diagnostic to add. If not a Diagnostic object, it will be wrapped
|
|
96
|
+
in a Diagnostic object.
|
|
97
|
+
name : str, optional
|
|
98
|
+
The name to use as the key for accessing the diagnostic.
|
|
99
|
+
If None, an auto-generated name will be used.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
str
|
|
104
|
+
The name (key) used to store the diagnostic
|
|
105
|
+
|
|
106
|
+
Example
|
|
107
|
+
-------
|
|
108
|
+
>>> sim = Simulation('path/to/simulation', 'input_deck.txt')
|
|
109
|
+
>>> nT = sim['electrons']['n'] * sim['electrons']['T11']
|
|
110
|
+
>>> sim.add_diagnostic(nT, 'nT')
|
|
111
|
+
>>> sim['nT'] # Access the custom diagnostic
|
|
112
|
+
"""
|
|
113
|
+
# Generate a name if none provided
|
|
114
|
+
if name is None:
|
|
115
|
+
# Find an unused name
|
|
116
|
+
i = 1
|
|
117
|
+
while f"custom_diag_{i}" in self._diagnostics:
|
|
118
|
+
i += 1
|
|
119
|
+
name = f"custom_diag_{i}"
|
|
120
|
+
|
|
121
|
+
# If already a Diagnostic, store directly
|
|
122
|
+
if isinstance(diagnostic, Diagnostic):
|
|
123
|
+
self._diagnostics[name] = diagnostic
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError("Only Diagnostic objects are supported for now")
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def species(self):
|
|
129
|
+
return self._species
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def loaded_diagnostics(self):
|
|
133
|
+
return self._diagnostics
|
|
134
|
+
|
|
135
|
+
# This is to handle species related diagnostics
|
|
136
|
+
class Species_Handler:
|
|
137
|
+
def __init__(self, simulation_folder, species_name, input_deck):
|
|
138
|
+
self._simulation_folder = simulation_folder
|
|
139
|
+
self._species_name = species_name
|
|
140
|
+
self._input_deck = input_deck
|
|
141
|
+
self._diagnostics = {}
|
|
142
|
+
|
|
143
|
+
def __getitem__(self, key):
|
|
144
|
+
if key in self._diagnostics:
|
|
145
|
+
return self._diagnostics[key]
|
|
146
|
+
|
|
147
|
+
# Create a temporary diagnostic for this quantity
|
|
148
|
+
diag = Diagnostic(simulation_folder=self._simulation_folder, species=self._species_name, input_deck=self._input_deck)
|
|
149
|
+
diag.get_quantity(key)
|
|
150
|
+
|
|
151
|
+
original_load_all = diag.load_all
|
|
152
|
+
|
|
153
|
+
def patched_load_all(*args, **kwargs):
|
|
154
|
+
result = original_load_all(*args, **kwargs)
|
|
155
|
+
self._diagnostics[key] = diag
|
|
156
|
+
return diag
|
|
157
|
+
|
|
158
|
+
diag.load_all = patched_load_all
|
|
159
|
+
|
|
160
|
+
return diag
|
|
161
|
+
|
|
162
|
+
def add_diagnostic(self, diagnostic, name=None):
|
|
163
|
+
"""
|
|
164
|
+
Add a custom diagnostic to the simulation.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
diagnostic : Diagnostic or array-like
|
|
169
|
+
The diagnostic to add. If not a Diagnostic object, it will be wrapped
|
|
170
|
+
in a Diagnostic object.
|
|
171
|
+
name : str, optional
|
|
172
|
+
The name to use as the key for accessing the diagnostic.
|
|
173
|
+
If None, an auto-generated name will be used.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
str
|
|
178
|
+
The name (key) used to store the diagnostic
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
# Generate a name if none provided
|
|
182
|
+
if name is None:
|
|
183
|
+
# Find an unused name
|
|
184
|
+
i = 1
|
|
185
|
+
while f"custom_diag_{i}" in self._diagnostics:
|
|
186
|
+
i += 1
|
|
187
|
+
name = f"custom_diag_{i}"
|
|
188
|
+
|
|
189
|
+
# If already a Diagnostic, store directly
|
|
190
|
+
if isinstance(diagnostic, Diagnostic):
|
|
191
|
+
self._diagnostics[name] = diagnostic
|
|
192
|
+
else:
|
|
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
|
|
File without changes
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import ast
|
|
3
|
+
import copy
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .species import Specie
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def deval(x):
|
|
10
|
+
"""
|
|
11
|
+
Auxiliar to handle eval of Fortran formatted numbers (e.g. 1.4d-5)
|
|
12
|
+
"""
|
|
13
|
+
if "d" in x:
|
|
14
|
+
x = x.replace("d", "e")
|
|
15
|
+
return float(x)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InputDeckIO:
|
|
19
|
+
"""
|
|
20
|
+
Class to handle parsing/re-writing of OSIRIS input decks
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
filename : str
|
|
25
|
+
Path to OSIRIS input deck file.
|
|
26
|
+
|
|
27
|
+
verbose : bool
|
|
28
|
+
If True, prints additional information when parsing file.
|
|
29
|
+
Helpful for debugging issues if input deck parsing fails.
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
filename : str
|
|
34
|
+
Path to original input file used to create the InputDeckIO object.
|
|
35
|
+
|
|
36
|
+
sections : list[dict]
|
|
37
|
+
List of pairs (section_name: str, section_dict: dict) which contain
|
|
38
|
+
current state of InputDeckIO object.
|
|
39
|
+
|
|
40
|
+
dim : int
|
|
41
|
+
Number of dimensions in the simulation (1, 2, or 3).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, filename: str, verbose: bool = True):
|
|
45
|
+
self._filename = str(filename)
|
|
46
|
+
self._sections = self._parse_input_deck(verbose)
|
|
47
|
+
self._dim = self._get_dim()
|
|
48
|
+
self._species = self._get_species()
|
|
49
|
+
|
|
50
|
+
def _parse_input_deck(self, verbose):
|
|
51
|
+
|
|
52
|
+
section_list = []
|
|
53
|
+
|
|
54
|
+
if verbose:
|
|
55
|
+
print(f"\nParsing input deck : {self._filename}")
|
|
56
|
+
|
|
57
|
+
with open(self._filename, "r", encoding="utf-8") as f:
|
|
58
|
+
lines = f.readlines()
|
|
59
|
+
|
|
60
|
+
# remove comments
|
|
61
|
+
lines = [l[: l.find("!")] if "!" in l else l for l in lines]
|
|
62
|
+
|
|
63
|
+
# join into single string (makes it easier to parse using regex)
|
|
64
|
+
lines = "".join(lines)
|
|
65
|
+
|
|
66
|
+
# remove tabs/spaces/paragraphs (except spaces inside "")
|
|
67
|
+
lines = re.sub(
|
|
68
|
+
r'"[^"]*"|(\s+)', lambda x: "" if x.group(1) else x.group(0), lines
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# split sections
|
|
72
|
+
# get name before brackets
|
|
73
|
+
section_names = re.findall(r"(?:^|\})(.*?)(?:\{)", lines)
|
|
74
|
+
# get content inside brackets
|
|
75
|
+
section_infos = re.findall(r"(?:\{)(.*?)(?:\})", lines)
|
|
76
|
+
|
|
77
|
+
if len(section_names) != len(section_infos):
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
"Unexpected problems parsing the document!\n"
|
|
80
|
+
f"Number of section names detected ({len(section_names)}) "
|
|
81
|
+
f"is different from number of sections ({len(section_infos)}).\n"
|
|
82
|
+
"Might be a bug in the code, or problem with input deck format!"
|
|
83
|
+
"Check if you could run the deck with OSIRIS."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# parse section information
|
|
87
|
+
for section, info in zip(section_names, section_infos):
|
|
88
|
+
if verbose:
|
|
89
|
+
print(f"Reading {section}")
|
|
90
|
+
|
|
91
|
+
# split section contents at commas (unless comma inside brackets e.g. pmax(1:2, 1))
|
|
92
|
+
info = re.split(r",(?![^()]*\))\s*", info)
|
|
93
|
+
info = list(filter(None, info))
|
|
94
|
+
|
|
95
|
+
# save pairs of (param, values) to dict
|
|
96
|
+
section_dict = {}
|
|
97
|
+
param = ""
|
|
98
|
+
value = ""
|
|
99
|
+
|
|
100
|
+
for i in info:
|
|
101
|
+
aux = i.split("=")
|
|
102
|
+
# solution to deal with parameters given by multiple values
|
|
103
|
+
# (e.g. ps_np(1:3) = 512,128,128 which was split into:
|
|
104
|
+
# ['ps_np(1:3)=512', '128', '128'])
|
|
105
|
+
# need to be able to regroup '128's with the previous value
|
|
106
|
+
if len(aux) == 1 and param != "":
|
|
107
|
+
value = ",".join([value, aux[0]])
|
|
108
|
+
section_dict[param] = value
|
|
109
|
+
# simplest case where we simply have "param=value"
|
|
110
|
+
elif len(aux) == 2:
|
|
111
|
+
param, value = aux
|
|
112
|
+
section_dict[param] = value
|
|
113
|
+
# case where we have multipel '=' inside strings
|
|
114
|
+
# happens for e.g. with mathfuncs in density profiles
|
|
115
|
+
else:
|
|
116
|
+
param = aux[0]
|
|
117
|
+
value = "".join(aux[1:])
|
|
118
|
+
# check that value is actually wrapped inside a string
|
|
119
|
+
if value[0] in ['"', "'"] and value[-1] in ['"', "'"]:
|
|
120
|
+
section_dict[param] = value
|
|
121
|
+
# because if not, then there is an error in the parser
|
|
122
|
+
# or a problem with the input deck
|
|
123
|
+
else:
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f'Error parsing section: "{section}".\n'
|
|
126
|
+
"Might be a bug in the code, or problem with input deck format!\n"
|
|
127
|
+
"Check if you could run the deck with OSIRIS."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
section_list.append([section, section_dict])
|
|
131
|
+
|
|
132
|
+
if verbose:
|
|
133
|
+
for k, v in section_dict.items():
|
|
134
|
+
print(f" {k} = {v}")
|
|
135
|
+
|
|
136
|
+
if verbose:
|
|
137
|
+
print("Input deck successfully parsed\n")
|
|
138
|
+
|
|
139
|
+
return section_list
|
|
140
|
+
|
|
141
|
+
def _get_dim(self):
|
|
142
|
+
dim = None
|
|
143
|
+
for i in range(1, 4):
|
|
144
|
+
try:
|
|
145
|
+
self.get_param(section="grid", param=f"nx_p(1:{i})")
|
|
146
|
+
except KeyError:
|
|
147
|
+
pass
|
|
148
|
+
else:
|
|
149
|
+
dim = i
|
|
150
|
+
break
|
|
151
|
+
if dim is None:
|
|
152
|
+
raise RuntimeError(
|
|
153
|
+
"Error parsing grid dimension. Grid dimension could not be estabilished."
|
|
154
|
+
)
|
|
155
|
+
return dim
|
|
156
|
+
|
|
157
|
+
def _get_species(self):
|
|
158
|
+
s_names = self.get_param("species", "name")
|
|
159
|
+
s_rqm = self.get_param("species", "rqm")
|
|
160
|
+
# real charge is optional in OSIRIS
|
|
161
|
+
# if real charge not provided assume electron charge
|
|
162
|
+
try:
|
|
163
|
+
s_qreal = self.get_param("species", "q_real")
|
|
164
|
+
s_qreal = np.array([float(q) for q in s_qreal])
|
|
165
|
+
except KeyError:
|
|
166
|
+
s_qreal = np.ones(len(s_names))
|
|
167
|
+
# check if we have information for all species
|
|
168
|
+
if len(s_names) != self.n_species:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
"Number of specie names does not match number of species: "
|
|
171
|
+
f"{len(s_names)} != {len(self.n_species)}."
|
|
172
|
+
)
|
|
173
|
+
if len(s_rqm) != self.n_species:
|
|
174
|
+
raise RuntimeError(
|
|
175
|
+
"Number of specie rqm does not match number of species: "
|
|
176
|
+
f"{len(s_rqm)} != {len(self.n_species)}."
|
|
177
|
+
)
|
|
178
|
+
if len(s_qreal) != self.n_species:
|
|
179
|
+
raise RuntimeError(
|
|
180
|
+
"Number of specie rqm does not match number of species: "
|
|
181
|
+
f"{len(s_qreal)} != {len(self.n_species)}."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
ast.literal_eval(s_names[i]): Specie(
|
|
186
|
+
name=ast.literal_eval(s_names[i]),
|
|
187
|
+
rqm=float(s_rqm[i]),
|
|
188
|
+
q=int(s_qreal[0]) * np.sign(float(s_rqm[i])),
|
|
189
|
+
)
|
|
190
|
+
for i in range(self.n_species)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def set_param(self, section, param, value, i_use=None, unexistent_ok=False):
|
|
194
|
+
# get all sections with the same name
|
|
195
|
+
# (e.g. there might be multiple 'species')
|
|
196
|
+
i_sections = [i for i, m in enumerate(self._sections) if m[0] == section]
|
|
197
|
+
|
|
198
|
+
if len(i_sections) == 0:
|
|
199
|
+
raise KeyError(f'section "{section}" not found')
|
|
200
|
+
|
|
201
|
+
if i_use is not None:
|
|
202
|
+
try:
|
|
203
|
+
i_sections = [im for i, im in enumerate(i_sections) if i in i_use]
|
|
204
|
+
except TypeError:
|
|
205
|
+
i_sections = [i_sections[i_use]]
|
|
206
|
+
|
|
207
|
+
for i in i_sections:
|
|
208
|
+
if not unexistent_ok and param not in self._sections[i][1]:
|
|
209
|
+
raise KeyError(
|
|
210
|
+
f'"{param}" not yet inside section "{section}" '
|
|
211
|
+
"(set unexistent_ok=True to ignore)."
|
|
212
|
+
)
|
|
213
|
+
if isinstance(value, str):
|
|
214
|
+
self._sections[i][1][param] = str(f'"{value}"')
|
|
215
|
+
elif isinstance(value, list):
|
|
216
|
+
self._sections[i][1][param] = ",".join(map(str, value))
|
|
217
|
+
else:
|
|
218
|
+
self._sections[i][1][param] = str(value)
|
|
219
|
+
|
|
220
|
+
def set_tag(self, tag, value):
|
|
221
|
+
for im, (_, params) in enumerate(self._sections):
|
|
222
|
+
for p, v in params.items():
|
|
223
|
+
self._sections[im][1][p] = v.replace(tag, str(value))
|
|
224
|
+
|
|
225
|
+
def get_param(self, section, param):
|
|
226
|
+
i_sections = [i for i, m in enumerate(self._sections) if m[0] == section]
|
|
227
|
+
|
|
228
|
+
if len(i_sections) == 0:
|
|
229
|
+
print("section not found")
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
values = []
|
|
233
|
+
for i in i_sections:
|
|
234
|
+
if param not in self._sections[i][1]:
|
|
235
|
+
raise KeyError(f'"{param}" not found inside section "{section}"')
|
|
236
|
+
values.append(copy.deepcopy(self._sections[i][1][param]))
|
|
237
|
+
|
|
238
|
+
return values
|
|
239
|
+
|
|
240
|
+
def delete_param(self, section, param):
|
|
241
|
+
sections_new = []
|
|
242
|
+
for m_name, m_dict in self._sections:
|
|
243
|
+
if m_name == section and param in m_dict:
|
|
244
|
+
m_dict.pop(param)
|
|
245
|
+
sections_new.append([m_name, m_dict])
|
|
246
|
+
self._sections = sections_new
|
|
247
|
+
|
|
248
|
+
def print_to_file(self, filename):
|
|
249
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
250
|
+
for section, section_dict in self._sections:
|
|
251
|
+
f.write(f"{section}\n{{\n")
|
|
252
|
+
for k, v in section_dict.items():
|
|
253
|
+
f.write(f'\t{k} = {v.replace(",", ", ")},\n')
|
|
254
|
+
f.write("}\n\n")
|
|
255
|
+
|
|
256
|
+
def __getitem__(self, section):
|
|
257
|
+
return copy.deepcopy([m[1] for m in self._sections if m[0] == section])
|
|
258
|
+
|
|
259
|
+
# Getters
|
|
260
|
+
@property
|
|
261
|
+
def filename(self):
|
|
262
|
+
return self._filename
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def sections(self):
|
|
266
|
+
return self._sections
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def dim(self):
|
|
270
|
+
return self._dim
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def n_species(self):
|
|
274
|
+
try:
|
|
275
|
+
return int(self["particles"][0]["num_species"])
|
|
276
|
+
except (KeyError, IndexError):
|
|
277
|
+
# If num_species doesn't exist, try num_cathode
|
|
278
|
+
try:
|
|
279
|
+
return int(self["particles"][0]["num_cathode"])
|
|
280
|
+
except (KeyError, IndexError):
|
|
281
|
+
# If neither exists, raise an informative error
|
|
282
|
+
raise KeyError(
|
|
283
|
+
"Could not find 'num_species' or 'num_cathode' in the particles section"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def species(self):
|
|
288
|
+
return self._species
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class Specie:
|
|
2
|
+
"""
|
|
3
|
+
Class to store OSIRIS species object.
|
|
4
|
+
|
|
5
|
+
Parameters
|
|
6
|
+
----------
|
|
7
|
+
name : str
|
|
8
|
+
Specie name.
|
|
9
|
+
|
|
10
|
+
rqm : float
|
|
11
|
+
Specie charge to mass ratio.
|
|
12
|
+
|
|
13
|
+
q : int
|
|
14
|
+
Specie charge in units of the electron charge.
|
|
15
|
+
Electrons would be represented by q=-1 and protons q=1.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
name : str
|
|
20
|
+
Specie name.
|
|
21
|
+
|
|
22
|
+
rqm : float
|
|
23
|
+
Specie charge to mass ratio.
|
|
24
|
+
|
|
25
|
+
q : int
|
|
26
|
+
Specie charge in units of the electron charge.
|
|
27
|
+
|
|
28
|
+
m : float
|
|
29
|
+
Specie mass in units of the electron mass.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, name, rqm, q: int = 1):
|
|
33
|
+
self._name = name
|
|
34
|
+
self._rqm = rqm
|
|
35
|
+
self._q = q
|
|
36
|
+
self._m = rqm * q
|
|
37
|
+
|
|
38
|
+
def __repr__(self):
|
|
39
|
+
return f"Specie(name={self._name}, rqm={self._rqm}, q={self._q}, m={self._m})"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self):
|
|
43
|
+
return self._name
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def rqm(self):
|
|
47
|
+
return self._rqm
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def q(self):
|
|
51
|
+
return self._q
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def m(self):
|
|
55
|
+
return self._m
|
|
File without changes
|