osiris-utils 1.1.10__py3-none-any.whl → 1.2.0__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.
Files changed (52) hide show
  1. benchmarks/benchmark_hdf5_io.py +46 -0
  2. benchmarks/benchmark_load_all.py +54 -0
  3. docs/source/api/decks.rst +48 -0
  4. docs/source/api/postprocess.rst +66 -2
  5. docs/source/api/sim_diag.rst +1 -1
  6. docs/source/api/utilities.rst +1 -1
  7. docs/source/conf.py +2 -1
  8. docs/source/examples/example_Derivatives.md +78 -0
  9. docs/source/examples/example_FFT.md +152 -0
  10. docs/source/examples/example_InputDeck.md +148 -0
  11. docs/source/examples/example_Simulation_Diagnostic.md +213 -0
  12. docs/source/examples/quick_start.md +51 -0
  13. docs/source/examples.rst +14 -0
  14. docs/source/index.rst +8 -0
  15. examples/edited-deck.1d +1 -1
  16. examples/example_Derivatives.ipynb +24 -36
  17. examples/example_FFT.ipynb +44 -23
  18. examples/example_InputDeck.ipynb +24 -277
  19. examples/example_Simulation_Diagnostic.ipynb +27 -17
  20. examples/quick_start.ipynb +17 -1
  21. osiris_utils/__init__.py +10 -6
  22. osiris_utils/cli/__init__.py +6 -0
  23. osiris_utils/cli/__main__.py +85 -0
  24. osiris_utils/cli/export.py +199 -0
  25. osiris_utils/cli/info.py +156 -0
  26. osiris_utils/cli/plot.py +189 -0
  27. osiris_utils/cli/validate.py +247 -0
  28. osiris_utils/data/__init__.py +15 -0
  29. osiris_utils/data/data.py +41 -171
  30. osiris_utils/data/diagnostic.py +285 -274
  31. osiris_utils/data/simulation.py +20 -13
  32. osiris_utils/decks/__init__.py +4 -0
  33. osiris_utils/decks/decks.py +83 -8
  34. osiris_utils/decks/species.py +12 -9
  35. osiris_utils/postprocessing/__init__.py +28 -0
  36. osiris_utils/postprocessing/derivative.py +317 -106
  37. osiris_utils/postprocessing/fft.py +135 -24
  38. osiris_utils/postprocessing/field_centering.py +28 -14
  39. osiris_utils/postprocessing/heatflux_correction.py +39 -18
  40. osiris_utils/postprocessing/mft.py +10 -2
  41. osiris_utils/postprocessing/postprocess.py +8 -5
  42. osiris_utils/postprocessing/pressure_correction.py +29 -17
  43. osiris_utils/utils.py +26 -17
  44. osiris_utils/vis/__init__.py +3 -0
  45. osiris_utils/vis/plot3d.py +148 -0
  46. {osiris_utils-1.1.10.dist-info → osiris_utils-1.2.0.dist-info}/METADATA +55 -7
  47. {osiris_utils-1.1.10.dist-info → osiris_utils-1.2.0.dist-info}/RECORD +51 -34
  48. {osiris_utils-1.1.10.dist-info → osiris_utils-1.2.0.dist-info}/WHEEL +1 -1
  49. osiris_utils-1.2.0.dist-info/entry_points.txt +2 -0
  50. {osiris_utils-1.1.10.dist-info → osiris_utils-1.2.0.dist-info}/top_level.txt +1 -0
  51. osiris_utils/postprocessing/mft_for_gridfile.py +0 -55
  52. {osiris_utils-1.1.10.dist-info → osiris_utils-1.2.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,46 @@
1
+ import time
2
+
3
+ import osiris_utils as ou
4
+
5
+
6
+ def benchmark_hdf5_io():
7
+ """Benchmark HDF5 file opening and reading."""
8
+ print("=" * 60)
9
+ print("Benchmarking HDF5 I/O Performance")
10
+ print("=" * 60)
11
+
12
+ filepath = "examples/example_data/MS/FLD/e3/e3-000100.h5"
13
+
14
+ # Warm-up (file may be cached by OS)
15
+ _ = ou.OsirisGridFile(filepath)
16
+
17
+ # Benchmark multiple loads
18
+ n_iterations = 100
19
+ times = []
20
+
21
+ print(f"\nLoading file {n_iterations} times...")
22
+ for _ in range(n_iterations):
23
+ start = time.time()
24
+ data = ou.OsirisGridFile(filepath)
25
+ times.append(time.time() - start)
26
+
27
+ avg_time = sum(times) / len(times)
28
+ min_time = min(times)
29
+ max_time = max(times)
30
+
31
+ print("\nResults:")
32
+ print(f" Average load time: {avg_time * 1000:.2f}ms")
33
+ print(f" Min load time: {min_time * 1000:.2f}ms")
34
+ print(f" Max load time: {max_time * 1000:.2f}ms")
35
+ print(f" Data shape: {data.data.shape}")
36
+ print(f" Data size: {data.data.nbytes / 1024:.1f} KB")
37
+
38
+ # Calculate throughput
39
+ throughput = (data.data.nbytes / 1024 / 1024) / avg_time
40
+ print(f" Throughput: {throughput:.1f} MB/s")
41
+
42
+ print("\n" + "=" * 60)
43
+
44
+
45
+ if __name__ == "__main__":
46
+ benchmark_hdf5_io()
@@ -0,0 +1,54 @@
1
+ import sys
2
+ import time
3
+
4
+ import osiris_utils as ou
5
+
6
+ # Use example data if available
7
+ data_path = "examples/example_data"
8
+
9
+
10
+ def benchmark_loading():
11
+ """Benchmark parallel vs sequential loading."""
12
+ _ = f"{data_path}/MS/FLD/e3"
13
+
14
+ print("=" * 60)
15
+ print("Benchmarking Diagnostic.load_all() performance")
16
+ print("=" * 60)
17
+
18
+ # Test with sequential loading
19
+ print("\n1. Testing SEQUENTIAL loading...")
20
+ d = ou.Diagnostic(simulation_folder=data_path)
21
+ d.get_quantity("e3")
22
+
23
+ start = time.time()
24
+ data_seq = d.load_all(use_parallel=False)
25
+ seq_time = time.time() - start
26
+ print(f" Sequential time: {seq_time:.3f}s for {len(d)} timesteps")
27
+ print(f" Data shape: {data_seq.shape}")
28
+
29
+ # Unload to test parallel
30
+ d.unload()
31
+
32
+ # Test with parallel loading
33
+ print("\n2. Testing PARALLEL loading...")
34
+ start = time.time()
35
+ data_par = d.load_all(use_parallel=True)
36
+ par_time = time.time() - start
37
+ print(f" Parallel time: {par_time:.3f}s for {len(d)} timesteps")
38
+ print(f" Data shape: {data_par.shape}")
39
+
40
+ # Calculate speedup
41
+ speedup = seq_time / par_time if par_time > 0 else 0
42
+ print(f"\n✓ Speedup: {speedup:.2f}x faster with parallel loading")
43
+ print(f" Time saved: {seq_time - par_time:.3f}s ({100 * (seq_time - par_time) / seq_time:.1f}%)")
44
+
45
+ print("\n" + "=" * 60)
46
+
47
+
48
+ if __name__ == "__main__":
49
+ try:
50
+ benchmark_loading()
51
+ except Exception as e:
52
+ print(f"Error running benchmark: {e}")
53
+ print("Make sure example_data is available in the examples directory")
54
+ sys.exit(1)
@@ -0,0 +1,48 @@
1
+ Deck Handling
2
+ =============
3
+
4
+ This section documents the utilities for handling OSIRIS input decks.
5
+
6
+ InputDeckIO
7
+ -----------
8
+
9
+ .. autoclass:: osiris_utils.decks.decks.InputDeckIO
10
+ :members:
11
+ :special-members: __init__, __getitem__
12
+ :undoc-members:
13
+ :noindex:
14
+
15
+ Class to handle parsing and modifying OSIRIS input decks.
16
+
17
+ **Key Features:**
18
+
19
+ * Parse input decks into python objects
20
+ * Modify parameters programmatically
21
+ * Save modified decks to new files
22
+ * Automatic handling of OSIRIS syntax idiosyncrasies
23
+
24
+ **Usage Example:**
25
+
26
+ .. code-block:: python
27
+
28
+ from osiris_utils.decks import InputDeckIO
29
+
30
+ # Load an input deck
31
+ deck = InputDeckIO("osiris.inp")
32
+
33
+ # Get simulation dimension
34
+ print(f"Simulation dimension: {deck.dim}")
35
+
36
+ # Modify a parameter
37
+ # (Assuming there is a 'time_step' section with 'dt' parameter)
38
+ deck.set_param("time_step", "dt", "0.05")
39
+
40
+ # Save to a new file
41
+ deck.print_to_file("osiris_modified.inp")
42
+
43
+ Coordinate Conversion
44
+ ---------------------
45
+
46
+ .. function:: osiris_utils.decks.decks.deval
47
+
48
+ .. autofunction:: osiris_utils.decks.decks.deval
@@ -602,8 +602,72 @@ For large datasets, consider these performance optimizations:
602
602
  * Only call `load_all()` when analyzing the full time evolution
603
603
  * Use `delete()` and `delete_all()` to free memory when finished with results
604
604
 
605
- 2. **Computation Efficiency**:
605
+ 2. **Computation Efficiency**:
606
606
 
607
607
  * Averaging is computationally inexpensive compared to other operations
608
608
  * For 2D/3D data, consider which axis to average along based on your physics
609
- * For iterative analysis, calculate fluctuations only when needed
609
+ * For iterative analysis, calculate fluctuations only when needed
610
+
611
+ Field Centering
612
+ ===============
613
+
614
+ .. _field-centering-api:
615
+
616
+ The `FieldCentering` module provides tools to center fields on the grid cells, converting from the Yee mesh positions to cell centers.
617
+
618
+ FieldCentering_Simulation Class
619
+ -------------------------------
620
+
621
+ .. autoclass:: osiris_utils.postprocessing.field_centering.FieldCentering_Simulation
622
+ :members:
623
+ :special-members: __init__, __getitem__
624
+ :show-inheritance:
625
+ :noindex:
626
+
627
+ Post-processor for centering electromagnetic fields.
628
+
629
+ The FieldCentering_Simulation class provides a convenient interface for centering fields from the Yee mesh to cell centers.
630
+
631
+ **Key Features:**
632
+
633
+ * Centers fields from Yee mesh to cell centers
634
+ * Handles periodic boundaries
635
+ * Supports 1D, 2D, and 3D simulations
636
+ * Lazy evaluation
637
+
638
+ **Usage Examples:**
639
+
640
+ .. code-block:: python
641
+
642
+ from osiris_utils.data import Simulation
643
+ from osiris_utils.postprocessing import FieldCentering_Simulation
644
+
645
+ # Create a simulation interface
646
+ sim = Simulation('/path/to/input/deck')
647
+
648
+ # Create a field centering processor
649
+ centered_sim = FieldCentering_Simulation(sim)
650
+
651
+ # Get centered E1 field
652
+ e1_centered = centered_sim['e1']
653
+
654
+ # Access specific timestep (interpolated on-demand)
655
+ timestep_10 = e1_centered[10]
656
+
657
+ FieldCentering_Diagnostic Class
658
+ -------------------------------
659
+
660
+ .. autoclass:: osiris_utils.postprocessing.field_centering.FieldCentering_Diagnostic
661
+ :members:
662
+ :special-members: __init__, __getitem__
663
+ :show-inheritance:
664
+ :noindex:
665
+
666
+ Specialized diagnostic that represents the centered field.
667
+
668
+ This class handles the actual interpolation while maintaining the Diagnostic interface.
669
+
670
+ **Key Methods:**
671
+
672
+ * ``load_all()`` - Compute and store the complete centered field
673
+ * ``__getitem__(index)`` - Compute centered field for a specific timestep on-demand
@@ -110,7 +110,7 @@ Diagnostic Base Class
110
110
 
111
111
  **Key Attributes:**
112
112
 
113
- * ``species`` - The plasma species being analyzed (Specie object)
113
+ * ``species`` - The plasma species being analyzed (Species object)
114
114
  * ``dx`` - Grid spacing in each direction
115
115
  * ``nx`` - Number of grid points in each direction
116
116
  * ``x`` - Grid coordinates
@@ -1,7 +1,7 @@
1
1
  Utilities API
2
2
  =============
3
3
 
4
- This document provides a reference to the osiris_utils utilities API.
4
+ This document provides a reference to the `osiris_utils` utilities API, which includes tools for reading various OSIRIS data formats (Grid, Raw Particles, Tracks, HIST) and helper functions for common physics calculations and data operations.
5
5
 
6
6
  Data Readers Structures
7
7
  -----------------------
docs/source/conf.py CHANGED
@@ -26,7 +26,7 @@ sys.path.append(os.path.abspath("../.."))
26
26
  project = "osiris_utils"
27
27
  copyright = "2025, João Biu"
28
28
  author = "João Biu"
29
- version = "v1.1.10"
29
+ version = "v1.2.0"
30
30
  release = version
31
31
 
32
32
 
@@ -46,6 +46,7 @@ extensions = [
46
46
  "sphinx.ext.napoleon",
47
47
  "sphinx_copybutton",
48
48
  "sphinx_github_style",
49
+ "myst_parser",
49
50
  ]
50
51
 
51
52
  nb_execution_mode = "off" # use stored output; avoids long CI builds
@@ -0,0 +1,78 @@
1
+ ```python
2
+ %load_ext autoreload
3
+ %autoreload 2
4
+ ```
5
+
6
+
7
+ ```python
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+
11
+ import osiris_utils as ou
12
+ ```
13
+
14
+ # Derivatives using `Derivative_Simulation` object
15
+
16
+ In this notebook we will show how to use the `Derivative_Simulation` object to compute derivatives of a function with respect to the time and space coordinates.
17
+
18
+ As an example, we will compute the residual of the continuity equation of a thermal plasma, in the x-direction, this is, the LHS of:
19
+
20
+ $$
21
+ \frac{\partial n}{\partial t} + \frac{\partial}{\partial x}(n v_1) = 0
22
+ $$
23
+
24
+ For this, we need the derivatives of the density `n` and the velocity `v1` with respect to the time and space coordinates. The `Derivative_Simulation` object will compute these derivatives using finite differences.
25
+
26
+ Initialize the simulation object:
27
+
28
+
29
+ ```python
30
+ sim = ou.Simulation(input_deck_path="example_data/thermal.1d")
31
+ ```
32
+
33
+ Initialize the `Derivative_Simulation` objects with respect to the time and $x_1$ coordinate, since these are the derivatives that we want to compute.
34
+
35
+
36
+ ```python
37
+ d_dt = ou.Derivative_Simulation(sim, "t")
38
+ d_dx1 = ou.Derivative_Simulation(sim, "x1")
39
+ ```
40
+
41
+ Note that the derivative with respect to $x_1$ is applied not to one diagnostic, but to the product of two diagnostics. We can take advantage of the operations between diagnostics to compute this "new" diagnostic, the product of the density and the fluid velocity.
42
+
43
+
44
+ ```python
45
+ # Create a new diagnostic n * vfl1
46
+ nVfl1 = sim["electrons"]["n"] * sim["electrons"]["vfl1"]
47
+ # Add this to the simulation (electrons)
48
+ sim["electrons"].add_diagnostic(nVfl1, "nVfl1")
49
+ ```
50
+
51
+ Now that we have the diagnostics needed in our `Simulation` object, we can directly reconstruct the equation desired:
52
+
53
+
54
+ ```python
55
+ continuity = d_dt["electrons"]["n"] + d_dx1["electrons"]["nVfl1"]
56
+ ```
57
+
58
+ This is a new diagnostic, and we can now access to its iterations using indexing
59
+
60
+
61
+ ```python
62
+ plt.figure(figsize=(8, 6))
63
+ plt.title("Continuity equation residuals")
64
+ plt.xlabel(continuity.axis[0]["plot_label"])
65
+
66
+ plt.plot(continuity.x, continuity[10], label="Continuity equation")
67
+ plt.legend()
68
+ plt.xlim(continuity.x[0], continuity.x[-1])
69
+ plt.show()
70
+ ```
71
+
72
+
73
+ ```python
74
+ plt.plot(continuity.x, ou.integrate(continuity[10], continuity.dx), label=r"$\int \partial_t n + \partial_x( n v_{fl1}) dx$")
75
+ plt.legend()
76
+ plt.xlim(continuity.x[0], continuity.x[-1])
77
+ plt.show()
78
+ ```
@@ -0,0 +1,152 @@
1
+ # FFT Analysis
2
+
3
+ ```python
4
+ %load_ext autoreload
5
+ %autoreload 2
6
+ ```
7
+
8
+
9
+ ```python
10
+ import matplotlib.pyplot as plt
11
+ import numpy as np
12
+ from matplotlib.colors import LogNorm
13
+
14
+ import osiris_utils as ou
15
+ ```
16
+
17
+ ## Initialize an object of the Simulation class
18
+ - The Simulation class takes the `simulation_folder`, this is, the file where the input deck is located.
19
+ - It can also take the `species` when we want to use quantities related to a specific species, such as velocities, charge, temperature, etc.
20
+
21
+
22
+ ```python
23
+ # In this case, since we only want to study the electric field, no species is needed
24
+ sim = ou.Simulation(input_deck_path="example_data/thermal.1d")
25
+ ```
26
+
27
+ The `Simulation` object acts as a container for the simulation diagnostics, that can be easily accessed from the `Simulation` object using a dictionary-like syntax
28
+ In this case, to access the component of the electric field in the z direction, we can use `sim['e3']`. This is a `Diagnostic` object, that loads the data as requested using a data generator, using indices to choose the time step - a lazy loading approach.
29
+
30
+
31
+ ```python
32
+ # This is a Diagnostic object
33
+ sim["e3"]
34
+
35
+ # This is also a Diagnostic object but initialized directly and not through the Simulation object
36
+ e3 = ou.Diagnostic(simulation_folder="example_data")
37
+ e3.get_quantity("e3")
38
+
39
+ print("sim['e3'] class:", sim["e3"].__class__)
40
+ print("e3 class:", e3.__class__)
41
+ ```
42
+
43
+ The data of the diagnostic at a given index can be accessed by indexing the iteration number, e.g. `sim['e3'][0]` will return the electric field in the z direction at the first time step. The data is not stored in memory, but loaded from the file when requested, using a data generator (lazy loading). For example, to plot the electric field in the z direction at the 100th time step, we can use `sim['e3'][100]`, and use the other attributes of the `Diagnostic` object to get the time step, the grid, labels, etc.
44
+
45
+
46
+ ```python
47
+ plt.figure(figsize=(8, 3))
48
+ plt.plot(sim["e3"].x, sim["e3"][100])
49
+ plt.xlabel(sim["e3"].axis[0]["plot_label"])
50
+ plt.title(rf"${sim['e3'].label}$ @ $t = {sim['e3'].time(100)[0]:.2f}$ $[{sim['e3'].tunits}]$")
51
+ plt.show()
52
+ ```
53
+
54
+ If we want to load the data of the diagnostic at all time steps, we can use the `load_all()` method of the `Diagnostic` object, e.g. `sim['e3'].load_all()` will load the electric field in the z direction at all time steps and store it in memory. This is useful when we want to do the FFT of the data in the time domain, for example. Using the method `unload()` we can clear the data from memory.
55
+
56
+
57
+ ```python
58
+ sim["e3"].load_all()
59
+ print(sim["e3"].data.shape)
60
+
61
+ e3.load_all()
62
+ print(e3.data.shape)
63
+
64
+ np.isclose(sim["e3"].data, e3.data).all()
65
+
66
+ sim["e3"].unload()
67
+ e3.unload()
68
+ ```
69
+
70
+ Now we are going to use a post process on the simulation data and on the diagnostic. We can think of a `PostProcess` as an operator over the `Diagnostic`'s of the `Simulation`. We define it on the simulation, and to access a post-processed quantity of the simulation, we use the same dictionary-like syntax, e.g. `sim_fft["e3]` to access the FFT of the electric field in the z direction.
71
+
72
+ The data is loaded in memory when requested, using a data generator, and can be accessed using the same indexing syntax, e.g. `sim_fft["e3"][0]` to access the FFT of the electric field in the z direction at the first time step. The data can be loaded using the `load_all()` method, and cleared from memory using the `unload()` method.
73
+
74
+ The post-processing classes inherit from the `PostProcess` class and the `Diagnostic` class, and can be used as a `Diagnostic` object, with the same attributes and methods, ensuring that operations between diagnostics and post-processes are consistent.
75
+
76
+ Each diagnostic has a version to be applied to `Simulation` objects and another to be applied to `Diagnostic` objects. The first has the name `<name of post-process>`_Simulation, and the second has the name `<name of post-process>_Diagnostic`. For example, the post-process routines for Fast Fourier Transforms are called `FastFourierTransform_Diagnostic` and `FFT_Diagnostic`.
77
+
78
+
79
+ ```python
80
+ sim_fft = ou.FFT_Simulation(sim, (0, 1)) # "operator" FFT applied to Simulation object, axis 0 and 1
81
+
82
+ e3_fft = ou.FFT_Diagnostic(e3, (0, 1)) # "operator" FFT applied to Diagnostic object, axis 0 and 1
83
+ ```
84
+
85
+
86
+ ```python
87
+ sim_fft["e3"][100] # this will only return the kx axis, since we are only requesting the 100th time step of the FFT of e3
88
+
89
+ e3_fft[100] # this will return the kx and ky axis, since we are only requesting the 100th time step of the FFT of e3
90
+
91
+ np.isclose(sim_fft["e3"][100], e3_fft[100]).all() # They are the same!
92
+ ```
93
+
94
+ Since my goal is to plot the dispersion relation, I will need to use `.load_all()` to load all the time steps, and have access to the data in the frequency domain.
95
+
96
+
97
+ ```python
98
+ sim_fft["e3"].load_all()
99
+ e3_fft.load_all()
100
+
101
+ np.isclose(sim_fft["e3"].data, e3_fft.data).all() # They are the same!
102
+ ```
103
+
104
+ We can now plot!
105
+
106
+
107
+ ```python
108
+ plt.imshow(
109
+ sim_fft["e3"].data,
110
+ origin='lower',
111
+ norm=LogNorm(vmin=1e-7, vmax=0.01),
112
+ extent=(-sim_fft["e3"].kmax, sim_fft["e3"].kmax, -sim_fft["e3"].omega_max, sim_fft["e3"].omega_max),
113
+ aspect='auto',
114
+ cmap='gray',
115
+ )
116
+
117
+ # Plotting the dispersion relation
118
+ k = np.linspace(-e3_fft.kmax, e3_fft.kmax, num=512)
119
+ w = np.sqrt(1 + k**2)
120
+ plt.plot(k, w, label=r"$\omega^2 = \omega_p^2 + k^2c^2$", color='r', ls='-.')
121
+ plt.xlim(0, e3_fft.kmax)
122
+ plt.ylim(0, e3_fft.omega_max)
123
+
124
+ plt.xlabel(r"$k$ $[\omega_e/c]$")
125
+ plt.ylabel(r"$\omega$ $[\omega_e]$")
126
+ plt.legend()
127
+ plt.show()
128
+ ```
129
+
130
+
131
+ ```python
132
+ plt.imshow(
133
+ e3_fft.data,
134
+ origin='lower',
135
+ norm=LogNorm(vmin=1e-7, vmax=0.01),
136
+ extent=(-e3_fft.kmax, e3_fft.kmax, -e3_fft.omega_max, e3_fft.omega_max),
137
+ aspect='auto',
138
+ cmap='gray',
139
+ )
140
+
141
+ # Plotting the dispersion relation
142
+ k = np.linspace(-e3_fft.kmax, e3_fft.kmax, num=512)
143
+ w = np.sqrt(1 + k**2)
144
+ plt.plot(k, w, label=r"$\omega^2 = \omega_p^2 + k^2c^2$", color='r', ls='-.')
145
+ plt.xlim(0, e3_fft.kmax)
146
+ plt.ylim(0, e3_fft.omega_max)
147
+
148
+ plt.xlabel(r"$k$ $[\omega_e/c]$")
149
+ plt.ylabel(r"$\omega$ $[\omega_e]$")
150
+ plt.legend()
151
+ plt.show()
152
+ ```
@@ -0,0 +1,148 @@
1
+ ```python
2
+ %load_ext autoreload
3
+ %autoreload 2
4
+ ```
5
+
6
+
7
+ ```python
8
+ import osiris_utils as ou
9
+ ```
10
+
11
+ # Interface with Osiris Input Decks
12
+
13
+ For many tasks, it might be useful to read simulation parameters directly from the input deck, or algorithmically generate new Osiris input decks (e.g.to run large parameter scans).
14
+
15
+ The class `InputDeckIO` allows you to easily perform theses tasks.
16
+ You need to provide it two inputs:
17
+ - `filename`: path to OSIRIS input deck
18
+ - `verbose`: if `True` will print auxiliary information when parsing the input deck
19
+
20
+
21
+ ## Reading an input deck
22
+
23
+
24
+ ```python
25
+ deck = ou.InputDeckIO('example_data/thermal.1d', verbose=True)
26
+ ```
27
+
28
+ Not only does the object load the different module parameters, but also automatically determines other useful information for downstream tasks.
29
+
30
+ Examples include:
31
+ - `dim`: number of dimensions
32
+ - `n_species`: number of species
33
+ - `species`: dictionary with `Species` objects with relevant information (e.g. charge, rqm, etc.)
34
+
35
+
36
+ ```python
37
+ print("Simulation Dimensions:", deck.dim)
38
+ print("Number of species:", deck.n_species)
39
+ print("Species:", deck.species)
40
+ ```
41
+
42
+ `InputDeckIO` stores the state of the input deck in the parameter `self._sections`.
43
+
44
+ You should not access this value directly, instead you can use for example its getter function `self.sections` which returns a deepcopy.
45
+
46
+ The returned value corresponds to a list of pairs [section_name, dictionary] where the section_name matches the Osiris input deck section name and he dictionary contains the list of (key, values) for the parameters of the section.
47
+
48
+
49
+ ```python
50
+ print(deck.sections[0])
51
+ print(deck.sections[1])
52
+ print('Number of sections:', len(deck.sections))
53
+ ```
54
+
55
+ Another option is to query the object as if it is an iterator, where the key is the section name.
56
+
57
+ This will return a list of dictionaries, since multiple sections can have the same name (e.g. you might have multiple species).
58
+
59
+ Once again, a deepcopy is being returned so editing the returned values of the dictionaries will not change the original `InputDeckIO` object.
60
+
61
+
62
+ ```python
63
+ print(deck['simulation'])
64
+ print(deck['node_conf'])
65
+ ```
66
+
67
+ Finally you can also ask for a specific parameter directly with `get_param()`
68
+
69
+
70
+ ```python
71
+ # this one returns a list since there can be multiple sections with the same name
72
+ print(deck.get_param(section='simulation', param='random_seed')[0])
73
+ # which is equivalent to doing this
74
+ print(deck["simulation"][0]['random_seed'])
75
+ ```
76
+
77
+ ## Editing an input deck
78
+
79
+ To safely edit the value of a parameter in an input deck you can use `set_parameter()`.
80
+
81
+ **Note**: This re-writes the object values!
82
+
83
+
84
+
85
+ ```python
86
+ # edit a parameter already exists
87
+ print('Before', deck['simulation'])
88
+ deck.set_param('simulation', 'random_seed', value=42)
89
+ print('After', deck['simulation'])
90
+ ```
91
+
92
+
93
+ ```python
94
+ # add a parameter
95
+ print("Before", deck["simulation"])
96
+ deck.set_param("simulation", "new_parameter", value='HI!', unexistent_ok=True)
97
+ print("After", deck["simulation"])
98
+ ```
99
+
100
+ And you can also delete the parameter using `delete_param()`.
101
+
102
+
103
+ ```python
104
+ # delete a parameter
105
+ print("Before", deck["simulation"])
106
+ deck.delete_param("simulation", "new_parameter")
107
+ print("After", deck["simulation"])
108
+ ```
109
+
110
+ Something slighlty more powerful, is the ability to edit a string in the whole input deck (use with care!)
111
+
112
+ This can be done for example to automatically change multiple parameter values that are shared / depend on an external quantity.
113
+
114
+ We can do this using the function `set_tag()`.
115
+
116
+ **Note**: This only works on the parameter values, not section names / parameter names.
117
+
118
+
119
+ ```python
120
+ # this a dummy example where we change some values to #tag#
121
+ print("Before", deck["simulation"])
122
+ deck.set_tag(
123
+ '42', # this has to be a string
124
+ '#tag#',
125
+ )
126
+ deck.set_tag(
127
+ '23:50:00', # this has to be a string
128
+ '#tag#',
129
+ )
130
+ print("After 1", deck["simulation"])
131
+
132
+ # and here we change both values at once
133
+ deck.set_tag('#tag#', "BOTH CHANGED")
134
+ print("After 2", deck["simulation"])
135
+ # There is a reason why wall_clock_limit has an extra "" done worry
136
+ # it is because it should be a string, while random_seed is an int!
137
+ # InputDeckIO handles these things for you
138
+ ```
139
+
140
+ ## Writing changes to file
141
+
142
+ Once you did your changes, you can simply generate a new input deck with `print_to_file()`.
143
+
144
+
145
+ ```python
146
+ deck.print_to_file("edited-deck.1d")
147
+ ! cat edited-deck.1d
148
+ ```