solarc-eclipse 0.6.1.2__tar.gz → 0.6.1.4__tar.gz

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 (38) hide show
  1. {solarc_eclipse-0.6.1.2/solarc_eclipse.egg-info → solarc_eclipse-0.6.1.4}/PKG-INFO +76 -26
  2. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/README.md +68 -25
  3. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/__init__.py +1 -1
  4. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/analysis.py +74 -19
  5. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data_processing.py +17 -10
  6. solarc_eclipse-0.6.1.4/euvst_response/extern/__init__.py +0 -0
  7. solarc_eclipse-0.6.1.4/euvst_response/extern/mpfit.py +2388 -0
  8. solarc_eclipse-0.6.1.4/euvst_response/fitting.py +639 -0
  9. solarc_eclipse-0.6.1.4/euvst_response/main.py +566 -0
  10. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/monte_carlo.py +82 -34
  11. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/synthesis.py +183 -57
  12. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/utils.py +110 -0
  13. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/pyproject.toml +8 -0
  14. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/setup.py +1 -24
  15. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4/solarc_eclipse.egg-info}/PKG-INFO +76 -26
  16. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/SOURCES.txt +2 -0
  17. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/requires.txt +8 -0
  18. solarc_eclipse-0.6.1.2/euvst_response/fitting.py +0 -399
  19. solarc_eclipse-0.6.1.2/euvst_response/main.py +0 -500
  20. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/LICENSE +0 -0
  21. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/MANIFEST.in +0 -0
  22. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/cli.py +0 -0
  23. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/config.py +0 -0
  24. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/grating_reflection_efficiency.dat +0 -0
  25. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/primary_mirror_coating_reflectance.dat +0 -0
  26. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/source.txt +0 -0
  27. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/throughput_aluminium_1000_angstrom.dat +0 -0
  28. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/throughput_aluminium_oxide_1000_angstrom.dat +0 -0
  29. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/data/throughput/throughput_carbon_1000_angstrom.dat +0 -0
  30. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/pinhole_diffraction.py +0 -0
  31. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/psf.py +0 -0
  32. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/radiometric.py +0 -0
  33. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/euvst_response/synthesis_cli.py +0 -0
  34. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/setup.cfg +0 -0
  35. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/dependency_links.txt +0 -0
  36. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/entry_points.txt +0 -0
  37. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/not-zip-safe +0 -0
  38. {solarc_eclipse-0.6.1.2 → solarc_eclipse-0.6.1.4}/solarc_eclipse.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarc-eclipse
3
- Version: 0.6.1.2
3
+ Version: 0.6.1.4
4
4
  Summary: ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
5
5
  Home-page: https://github.com/jamesmckevitt/eclipse
6
6
  Author: James McKevitt
@@ -29,6 +29,13 @@ Requires-Dist: dill
29
29
  Requires-Dist: pyyaml
30
30
  Requires-Dist: reproject
31
31
  Requires-Dist: sunpy[all]
32
+ Requires-Dist: dask
33
+ Requires-Dist: psutil
34
+ Requires-Dist: mendeleev
35
+ Requires-Dist: h5py
36
+ Requires-Dist: fiasco
37
+ Provides-Extra: mpi
38
+ Requires-Dist: mpi4py; extra == "mpi"
32
39
  Provides-Extra: dev
33
40
  Requires-Dist: pytest; extra == "dev"
34
41
  Requires-Dist: black; extra == "dev"
@@ -71,7 +78,7 @@ After installation, you can run ECLIPSE from the command line:
71
78
 
72
79
  ```bash
73
80
  # Run synthesis script (convert 3D MHD data to synthetic spectra)
74
- synthesise-spectra --data-dir ./data/atmosphere --goft-file ./data/gofnt.sav --output-dir ./run/input
81
+ synthesise-spectra --data-dir ./data/atmosphere --lines Fe12_195.1190 --output-dir ./run/input
75
82
 
76
83
  # Run instrument response simulation
77
84
  eclipse --config ./run/input/config.yaml
@@ -95,7 +102,7 @@ detector = Detector_SWC()
95
102
  print(f"Telescope collecting area: {telescope.collecting_area:.4f}")
96
103
  print(f"Detector QE (EUV): {detector.qe_euv:.2f}")
97
104
 
98
- # Calculate effective area at Fe XII 195.119 Å
105
+ # Calculate effective area at Fe XII 195.119 Angstrom
99
106
  fe12_wl = 195.119 * u.AA
100
107
  effective_area = telescope.collecting_area * telescope.throughput(fe12_wl) * detector.qe_euv
101
108
 
@@ -125,22 +132,24 @@ from euvst_response import (
125
132
  create_sunpy_maps_from_combo,
126
133
  summary_table
127
134
  )
128
- ```
129
-
130
- ## Detailed instructions
131
135
 
132
- ### 1. Generate contribution functions for the desired emission lines
136
+ # Load results
137
+ results = load_instrument_response_results("run/result/my_results.pkl")
133
138
 
134
- Edit `make_gofnt.pro` to specify the desired emission lines and the location of the CHIANTI files. You can use CHIANTI to identify the required lines.
139
+ # Get results for a specific parameter combination:
140
+ combo = get_results_for_combination(
141
+ results, exposure=40*u.s, slit_width=0.4*u.arcsec, offchip_bin_slit=2
142
+ )
135
143
 
136
- Run the following command:
137
- ```bash
138
- idl -e "make_goft"
144
+ # Create SunPy maps
145
+ maps = create_sunpy_maps_from_combo(combo, rest_wavelength=195.119*u.AA, data_type='dn')
139
146
  ```
140
147
 
141
- ### 2. Run the line synthesis
148
+ ## Detailed instructions
149
+
150
+ ### 1. Run the line synthesis
142
151
 
143
- The synthesis script converts 3D MHD simulation data into synthetic solar spectra. It can be run directly from the command line with extensive configuration options.
152
+ The synthesis script converts 3D MHD simulation data into synthetic solar spectra. Contribution functions G(T, n_e) are computed on-the-fly using [fiasco](https://fiasco.readthedocs.io/) (a Python interface to the CHIANTI atomic database).
144
153
 
145
154
  #### Basic Usage
146
155
 
@@ -148,7 +157,9 @@ The synthesis script converts 3D MHD simulation data into synthetic solar spectr
148
157
  # Example using all available command line options
149
158
  synthesise-spectra \
150
159
  --data-dir ./data/atmosphere \
151
- --goft-file ./data/gofnt.sav \
160
+ --lines Fe12_195.1190 Fe12_195.1790 \
161
+ --abundance sun_coronal_2021_chianti \
162
+ --n-workers 4 \
152
163
  --output-dir ./run/input \
153
164
  --output-name synthesised_spectra.pkl \
154
165
  --temp-file temp/eosT.0270000 \
@@ -168,8 +179,7 @@ synthesise-spectra \
168
179
  --crop-z "0 Mm" "20 Mm" \
169
180
  --downsample 1 \
170
181
  --precision float64 \
171
- --mean-mol-wt 1.29 \
172
- --limit-lines Fe12_195.1190
182
+ --mean-mol-wt 1.29
173
183
 
174
184
  # Show all available options
175
185
  synthesise-spectra --help
@@ -179,10 +189,14 @@ synthesise-spectra --help
179
189
 
180
190
  **Input/Output Paths:**
181
191
  - `--data-dir`: Directory containing simulation data (default: `data/atmosphere`)
182
- - `--goft-file`: Path to CHIANTI G(T,N) save file (default: `./data/gofnt.sav`)
183
192
  - `--output-dir`: Output directory for results (default: `./run/input`)
184
193
  - `--output-name`: Output filename (default: `synthesised_spectra.pkl`)
185
194
 
195
+ **Line and Abundance Selection:**
196
+ - `--lines`: Emission lines to synthesise, e.g., `--lines Fe12_195.1190 Fe12_195.1790` (required)
197
+ - `--abundance`: CHIANTI abundance dataset name (default: `sun_coronal_2021_chianti`)
198
+ - `--n-workers`: Number of parallel workers for the fiasco G(T, n_e) computation. Each distinct ion is computed in a separate process. `0` uses all available CPUs (default: `0`). Set to `1` for serial execution.
199
+
186
200
  **Simulation Files:**
187
201
  - `--temp-file`: Temperature file relative to data-dir (default: `temp/eosT.0270000`)
188
202
  - `--rho-file`: Density file relative to data-dir (default: `rho/result_prim_0.0270000`)
@@ -215,9 +229,6 @@ synthesise-spectra --help
215
229
  - `--precision`: Numerical precision `float32` or `float64` (default: `float64`)
216
230
  - `--mean-mol-wt`: Mean molecular weight (default: `1.29`)
217
231
 
218
- **Line Selection:**
219
- - `--limit-lines`: Limit to specific lines, e.g., `--limit-lines Fe12_195.1190 Fe12_195.1790`
220
-
221
232
  #### Dynamic Mode (Time-varying Atmospheres)
222
233
 
223
234
  For simulating raster scans over evolving atmospheres, use dynamic mode which combines MHD timesteps based on instrument scanning:
@@ -225,7 +236,8 @@ For simulating raster scans over evolving atmospheres, use dynamic mode which co
225
236
  ```bash
226
237
  synthesise-spectra \
227
238
  --data-dir ./data/atmosphere \
228
- --goft-file ./data/gofnt.sav \
239
+ --lines Fe12_195.1190 \
240
+ --abundance sun_coronal_2021_chianti \
229
241
  --output-dir ./run/input \
230
242
  --slit-rest-time "40 s" \
231
243
  --slit-width "0.2 arcsec" \
@@ -263,7 +275,6 @@ The synthesis produces a pickle file containing:
263
275
 
264
276
  - Use `--downsample 2` or `--downsample 4` for initial testing
265
277
  - Use `--precision float32` to reduce memory usage (may affect accuracy)
266
- - Use `--limit-lines` to synthesise only specific lines for development
267
278
  - Use spatial cropping to focus on regions of interest and reduce computation time
268
279
  - Monitor memory usage - full resolution synthesis can require 50+ GB RAM
269
280
  - Side views (`--integration-axis x` or `y`) may require different velocity files
@@ -276,7 +287,7 @@ The synthesis results can be loaded and analyzed using the package API:
276
287
  import euvst_response
277
288
 
278
289
  # Load synthesis results - this sums all line cubes into a single cube
279
- # By default uses Fe XII 195.119 Å as reference for wavelength grid
290
+ # By default uses Fe XII 195.119 Angstrom as reference for wavelength grid
280
291
  cube = euvst_response.load_atmosphere("./run/input/synthesised_spectra.pkl")
281
292
  print(f"Combined cube shape: {cube.data.shape}")
282
293
 
@@ -300,7 +311,7 @@ This step can require a lot of memory at full resolution. A fully synthesised at
300
311
 
301
312
  **Important:** You can place the synthesised atmosphere file anywhere and specify its location using the `synthesis_file` parameter in your YAML configuration file. The default location is `./run/input/synthesised_spectra.pkl`.
302
313
 
303
- ### 3. Simulate the instrument response
314
+ ### 2. Simulate the instrument response
304
315
 
305
316
  #### Configuration File
306
317
 
@@ -324,7 +335,7 @@ instrument: SWC # Options: SWC (EUVST Short Wavelength) or EIS (Hinode/EIS)
324
335
  synthesis_file: ./run/input/synthesised_spectra.pkl # Default location
325
336
 
326
337
  # Reference line for wavelength grid and metadata when combining all spectral lines
327
- reference_line: Fe12_195.1190 # Default reference line (Fe XII 195.119 Å)
338
+ reference_line: Fe12_195.1190 # Default reference line (Fe XII 195.119 Angstrom)
328
339
 
329
340
  # Point Spread Function
330
341
  psf: False # Enable PSF convolution
@@ -358,20 +369,53 @@ aluminium_thickness: 1485 angstrom # Default (expected value)
358
369
  ccd_temperature: -60 Celsius # Default (expected operating temperature)
359
370
 
360
371
  # Visible stray light level
361
- vis_sl: 0 photon / (s * pixel) # Default, (ideal case, no stray light)
372
+ vis_sl: 0 photon / (s * cm**2) # Default, (ideal case, no stray light)
362
373
 
363
374
  # Multi-component Gaussian fitting (optional, omit for single-Gaussian)
364
375
  fitting:
365
376
  primary_component: 0
377
+ constrain_positive_intensity: true
366
378
  components:
367
379
  - wavelength: 195.119 angstrom
380
+ amplitude_greater_than: 1 # must be brighter than component 1
368
381
  - wavelength: 195.179 angstrom
369
382
  tie_center: 0 # Centroid offset tied to component 0
370
383
  tie_width: 0 # Same width as component 0
371
384
  ```
372
385
 
386
+ Off-chip binning in the slit direction can also be performed:
387
+
388
+ ```yaml
389
+ offchip_bin_slit: 2 # Bin every 2 slit pixels
390
+ offchip_bin_slit: [1, 2, 4] # Sweep over multiple binning factors
391
+ ```
392
+
393
+ Alternatively, you can specify paired slit width and off-chip binning values to only simulate specific combinations:
394
+
395
+ ```yaml
396
+ # Paired slit width / off-chip binning
397
+ # Only the listed (slit_width, offchip_bin_slit) pairs are simulated
398
+ slit_bin_pairs:
399
+ - slit_width: 0.2 arcsec
400
+ offchip_bin_slit: 1
401
+ - slit_width: 0.4 arcsec
402
+ offchip_bin_slit: 2
403
+ - slit_width: 0.8 arcsec
404
+ offchip_bin_slit: 5
405
+ - slit_width: 1.6 arcsec
406
+ offchip_bin_slit: 10
407
+ ```
408
+
373
409
  For guidance on recommended values, see McKevitt et al. (2025) (in prep.).
374
410
 
411
+ By default, both the DN and photon signals are fitted at every Monte Carlo iteration. To speed up the simulation when only one is needed, use the `fit_signals` option:
412
+
413
+ ```yaml
414
+ fit_signals: dn # Fit only the DN signal
415
+ fit_signals: photon # Fit only the photon signal
416
+ fit_signals: both # Fit both (default)
417
+ ```
418
+
375
419
  If you synthesised data in dynamic mode, your configuration must specify:
376
420
  - Exactly one slit width matching the synthesis slit width
377
421
  - Exactly one exposure time matching the synthesis exposure time
@@ -387,6 +431,12 @@ eclipse --config ./run/input/config.yaml
387
431
  - `--config`: Path to YAML configuration file (required)
388
432
  - `--debug`: Enable debug mode with IPython breakpoints on errors (optional)
389
433
 
434
+ #### Multi-node MPI parallelisation
435
+
436
+ When launched with multiple MPI ranks on a SLURM cluster (via `srun` or `mpirun`, and setting `--ntasks-per-node`), ECLIPSE automatically distributes Monte Carlo iterations across ranks and gathers results on rank 0. No code or configuration changes are needed - MPI is auto-detected at runtime. If `mpi4py` is not installed or only one rank is present, the code falls back to single-process mode.
437
+
438
+ Requirements: `mpi4py` and `intel-mpi` (load with `module load intel-mpi` before launching).
439
+
390
440
  #### Output
391
441
 
392
442
  Results are saved as pickle files in the `run/result/` directory with the same base name as the configuration file. The output includes:
@@ -30,7 +30,7 @@ After installation, you can run ECLIPSE from the command line:
30
30
 
31
31
  ```bash
32
32
  # Run synthesis script (convert 3D MHD data to synthetic spectra)
33
- synthesise-spectra --data-dir ./data/atmosphere --goft-file ./data/gofnt.sav --output-dir ./run/input
33
+ synthesise-spectra --data-dir ./data/atmosphere --lines Fe12_195.1190 --output-dir ./run/input
34
34
 
35
35
  # Run instrument response simulation
36
36
  eclipse --config ./run/input/config.yaml
@@ -54,7 +54,7 @@ detector = Detector_SWC()
54
54
  print(f"Telescope collecting area: {telescope.collecting_area:.4f}")
55
55
  print(f"Detector QE (EUV): {detector.qe_euv:.2f}")
56
56
 
57
- # Calculate effective area at Fe XII 195.119 Å
57
+ # Calculate effective area at Fe XII 195.119 Angstrom
58
58
  fe12_wl = 195.119 * u.AA
59
59
  effective_area = telescope.collecting_area * telescope.throughput(fe12_wl) * detector.qe_euv
60
60
 
@@ -84,22 +84,24 @@ from euvst_response import (
84
84
  create_sunpy_maps_from_combo,
85
85
  summary_table
86
86
  )
87
- ```
88
-
89
- ## Detailed instructions
90
87
 
91
- ### 1. Generate contribution functions for the desired emission lines
88
+ # Load results
89
+ results = load_instrument_response_results("run/result/my_results.pkl")
92
90
 
93
- Edit `make_gofnt.pro` to specify the desired emission lines and the location of the CHIANTI files. You can use CHIANTI to identify the required lines.
91
+ # Get results for a specific parameter combination:
92
+ combo = get_results_for_combination(
93
+ results, exposure=40*u.s, slit_width=0.4*u.arcsec, offchip_bin_slit=2
94
+ )
94
95
 
95
- Run the following command:
96
- ```bash
97
- idl -e "make_goft"
96
+ # Create SunPy maps
97
+ maps = create_sunpy_maps_from_combo(combo, rest_wavelength=195.119*u.AA, data_type='dn')
98
98
  ```
99
99
 
100
- ### 2. Run the line synthesis
100
+ ## Detailed instructions
101
+
102
+ ### 1. Run the line synthesis
101
103
 
102
- The synthesis script converts 3D MHD simulation data into synthetic solar spectra. It can be run directly from the command line with extensive configuration options.
104
+ The synthesis script converts 3D MHD simulation data into synthetic solar spectra. Contribution functions G(T, n_e) are computed on-the-fly using [fiasco](https://fiasco.readthedocs.io/) (a Python interface to the CHIANTI atomic database).
103
105
 
104
106
  #### Basic Usage
105
107
 
@@ -107,7 +109,9 @@ The synthesis script converts 3D MHD simulation data into synthetic solar spectr
107
109
  # Example using all available command line options
108
110
  synthesise-spectra \
109
111
  --data-dir ./data/atmosphere \
110
- --goft-file ./data/gofnt.sav \
112
+ --lines Fe12_195.1190 Fe12_195.1790 \
113
+ --abundance sun_coronal_2021_chianti \
114
+ --n-workers 4 \
111
115
  --output-dir ./run/input \
112
116
  --output-name synthesised_spectra.pkl \
113
117
  --temp-file temp/eosT.0270000 \
@@ -127,8 +131,7 @@ synthesise-spectra \
127
131
  --crop-z "0 Mm" "20 Mm" \
128
132
  --downsample 1 \
129
133
  --precision float64 \
130
- --mean-mol-wt 1.29 \
131
- --limit-lines Fe12_195.1190
134
+ --mean-mol-wt 1.29
132
135
 
133
136
  # Show all available options
134
137
  synthesise-spectra --help
@@ -138,10 +141,14 @@ synthesise-spectra --help
138
141
 
139
142
  **Input/Output Paths:**
140
143
  - `--data-dir`: Directory containing simulation data (default: `data/atmosphere`)
141
- - `--goft-file`: Path to CHIANTI G(T,N) save file (default: `./data/gofnt.sav`)
142
144
  - `--output-dir`: Output directory for results (default: `./run/input`)
143
145
  - `--output-name`: Output filename (default: `synthesised_spectra.pkl`)
144
146
 
147
+ **Line and Abundance Selection:**
148
+ - `--lines`: Emission lines to synthesise, e.g., `--lines Fe12_195.1190 Fe12_195.1790` (required)
149
+ - `--abundance`: CHIANTI abundance dataset name (default: `sun_coronal_2021_chianti`)
150
+ - `--n-workers`: Number of parallel workers for the fiasco G(T, n_e) computation. Each distinct ion is computed in a separate process. `0` uses all available CPUs (default: `0`). Set to `1` for serial execution.
151
+
145
152
  **Simulation Files:**
146
153
  - `--temp-file`: Temperature file relative to data-dir (default: `temp/eosT.0270000`)
147
154
  - `--rho-file`: Density file relative to data-dir (default: `rho/result_prim_0.0270000`)
@@ -174,9 +181,6 @@ synthesise-spectra --help
174
181
  - `--precision`: Numerical precision `float32` or `float64` (default: `float64`)
175
182
  - `--mean-mol-wt`: Mean molecular weight (default: `1.29`)
176
183
 
177
- **Line Selection:**
178
- - `--limit-lines`: Limit to specific lines, e.g., `--limit-lines Fe12_195.1190 Fe12_195.1790`
179
-
180
184
  #### Dynamic Mode (Time-varying Atmospheres)
181
185
 
182
186
  For simulating raster scans over evolving atmospheres, use dynamic mode which combines MHD timesteps based on instrument scanning:
@@ -184,7 +188,8 @@ For simulating raster scans over evolving atmospheres, use dynamic mode which co
184
188
  ```bash
185
189
  synthesise-spectra \
186
190
  --data-dir ./data/atmosphere \
187
- --goft-file ./data/gofnt.sav \
191
+ --lines Fe12_195.1190 \
192
+ --abundance sun_coronal_2021_chianti \
188
193
  --output-dir ./run/input \
189
194
  --slit-rest-time "40 s" \
190
195
  --slit-width "0.2 arcsec" \
@@ -222,7 +227,6 @@ The synthesis produces a pickle file containing:
222
227
 
223
228
  - Use `--downsample 2` or `--downsample 4` for initial testing
224
229
  - Use `--precision float32` to reduce memory usage (may affect accuracy)
225
- - Use `--limit-lines` to synthesise only specific lines for development
226
230
  - Use spatial cropping to focus on regions of interest and reduce computation time
227
231
  - Monitor memory usage - full resolution synthesis can require 50+ GB RAM
228
232
  - Side views (`--integration-axis x` or `y`) may require different velocity files
@@ -235,7 +239,7 @@ The synthesis results can be loaded and analyzed using the package API:
235
239
  import euvst_response
236
240
 
237
241
  # Load synthesis results - this sums all line cubes into a single cube
238
- # By default uses Fe XII 195.119 Å as reference for wavelength grid
242
+ # By default uses Fe XII 195.119 Angstrom as reference for wavelength grid
239
243
  cube = euvst_response.load_atmosphere("./run/input/synthesised_spectra.pkl")
240
244
  print(f"Combined cube shape: {cube.data.shape}")
241
245
 
@@ -259,7 +263,7 @@ This step can require a lot of memory at full resolution. A fully synthesised at
259
263
 
260
264
  **Important:** You can place the synthesised atmosphere file anywhere and specify its location using the `synthesis_file` parameter in your YAML configuration file. The default location is `./run/input/synthesised_spectra.pkl`.
261
265
 
262
- ### 3. Simulate the instrument response
266
+ ### 2. Simulate the instrument response
263
267
 
264
268
  #### Configuration File
265
269
 
@@ -283,7 +287,7 @@ instrument: SWC # Options: SWC (EUVST Short Wavelength) or EIS (Hinode/EIS)
283
287
  synthesis_file: ./run/input/synthesised_spectra.pkl # Default location
284
288
 
285
289
  # Reference line for wavelength grid and metadata when combining all spectral lines
286
- reference_line: Fe12_195.1190 # Default reference line (Fe XII 195.119 Å)
290
+ reference_line: Fe12_195.1190 # Default reference line (Fe XII 195.119 Angstrom)
287
291
 
288
292
  # Point Spread Function
289
293
  psf: False # Enable PSF convolution
@@ -317,20 +321,53 @@ aluminium_thickness: 1485 angstrom # Default (expected value)
317
321
  ccd_temperature: -60 Celsius # Default (expected operating temperature)
318
322
 
319
323
  # Visible stray light level
320
- vis_sl: 0 photon / (s * pixel) # Default, (ideal case, no stray light)
324
+ vis_sl: 0 photon / (s * cm**2) # Default, (ideal case, no stray light)
321
325
 
322
326
  # Multi-component Gaussian fitting (optional, omit for single-Gaussian)
323
327
  fitting:
324
328
  primary_component: 0
329
+ constrain_positive_intensity: true
325
330
  components:
326
331
  - wavelength: 195.119 angstrom
332
+ amplitude_greater_than: 1 # must be brighter than component 1
327
333
  - wavelength: 195.179 angstrom
328
334
  tie_center: 0 # Centroid offset tied to component 0
329
335
  tie_width: 0 # Same width as component 0
330
336
  ```
331
337
 
338
+ Off-chip binning in the slit direction can also be performed:
339
+
340
+ ```yaml
341
+ offchip_bin_slit: 2 # Bin every 2 slit pixels
342
+ offchip_bin_slit: [1, 2, 4] # Sweep over multiple binning factors
343
+ ```
344
+
345
+ Alternatively, you can specify paired slit width and off-chip binning values to only simulate specific combinations:
346
+
347
+ ```yaml
348
+ # Paired slit width / off-chip binning
349
+ # Only the listed (slit_width, offchip_bin_slit) pairs are simulated
350
+ slit_bin_pairs:
351
+ - slit_width: 0.2 arcsec
352
+ offchip_bin_slit: 1
353
+ - slit_width: 0.4 arcsec
354
+ offchip_bin_slit: 2
355
+ - slit_width: 0.8 arcsec
356
+ offchip_bin_slit: 5
357
+ - slit_width: 1.6 arcsec
358
+ offchip_bin_slit: 10
359
+ ```
360
+
332
361
  For guidance on recommended values, see McKevitt et al. (2025) (in prep.).
333
362
 
363
+ By default, both the DN and photon signals are fitted at every Monte Carlo iteration. To speed up the simulation when only one is needed, use the `fit_signals` option:
364
+
365
+ ```yaml
366
+ fit_signals: dn # Fit only the DN signal
367
+ fit_signals: photon # Fit only the photon signal
368
+ fit_signals: both # Fit both (default)
369
+ ```
370
+
334
371
  If you synthesised data in dynamic mode, your configuration must specify:
335
372
  - Exactly one slit width matching the synthesis slit width
336
373
  - Exactly one exposure time matching the synthesis exposure time
@@ -346,6 +383,12 @@ eclipse --config ./run/input/config.yaml
346
383
  - `--config`: Path to YAML configuration file (required)
347
384
  - `--debug`: Enable debug mode with IPython breakpoints on errors (optional)
348
385
 
386
+ #### Multi-node MPI parallelisation
387
+
388
+ When launched with multiple MPI ranks on a SLURM cluster (via `srun` or `mpirun`, and setting `--ntasks-per-node`), ECLIPSE automatically distributes Monte Carlo iterations across ranks and gathers results on rank 0. No code or configuration changes are needed - MPI is auto-detected at runtime. If `mpi4py` is not installed or only one rank is present, the code falls back to single-process mode.
389
+
390
+ Requirements: `mpi4py` and `intel-mpi` (load with `module load intel-mpi` before launching).
391
+
349
392
  #### Output
350
393
 
351
394
  Results are saved as pickle files in the `run/result/` directory with the same base name as the configuration file. The output includes:
@@ -4,7 +4,7 @@ ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
4
4
  This package provides tools for modeling the performance of the EUV spectrograph EUVST, on SOLAR-C.
5
5
  """
6
6
 
7
- __version__ = "0.6.1.2"
7
+ __version__ = "0.6.1.4"
8
8
  __author__ = "James McKevitt"
9
9
  __email__ = "jm2@mssl.ucl.ac.uk"
10
10
 
@@ -150,6 +150,11 @@ def analyse_fit_statistics(
150
150
  raise ValueError(f"No {fit_stats_key} found in combination results")
151
151
 
152
152
  fit_stats = combination_results[fit_stats_key]
153
+ if fit_stats is None:
154
+ raise ValueError(
155
+ f"'{data_type}' signal was not fitted for this combination. "
156
+ f"Check the 'fit_signals' setting in your YAML config."
157
+ )
153
158
  fit_truth_data = combination_results["ground_truth"]["fit_truth_data"]
154
159
  fit_truth_units = combination_results["ground_truth"]["fit_truth_units"]
155
160
 
@@ -214,6 +219,7 @@ def get_results_for_combination(
214
219
  exposure: u.Quantity = None,
215
220
  psf: bool = None,
216
221
  enable_pinholes: bool = None,
222
+ offchip_bin_slit: int = None,
217
223
  debug: bool = False
218
224
  ) -> Dict[str, Any]:
219
225
  """
@@ -241,6 +247,8 @@ def get_results_for_combination(
241
247
  PSF setting (True or False). If None, uses first available.
242
248
  enable_pinholes : bool, optional
243
249
  Pinhole effects setting (True or False). If None, uses first available.
250
+ offchip_bin_slit : int, optional
251
+ Off-chip slit binning factor. If None, uses first available.
244
252
  debug : bool, optional
245
253
  If True, print debugging information about available keys.
246
254
 
@@ -268,7 +276,7 @@ def get_results_for_combination(
268
276
 
269
277
  # Check if no parameters were specified at all
270
278
  no_params_specified = all(param is None for param in [slit_width, oxide_thickness, c_thickness,
271
- aluminium_thickness, ccd_temperature, vis_sl, exposure, psf, enable_pinholes])
279
+ aluminium_thickness, ccd_temperature, vis_sl, exposure, psf, enable_pinholes, offchip_bin_slit])
272
280
 
273
281
  if no_params_specified and len(all_combinations) > 1:
274
282
  print(f"Error: No parameters specified, but {len(all_combinations)} combinations are available!")
@@ -286,15 +294,24 @@ def get_results_for_combination(
286
294
  'vis_sl': vis_sl,
287
295
  'exposure': exposure,
288
296
  'psf': psf,
289
- 'enable_pinholes': enable_pinholes
297
+ 'enable_pinholes': enable_pinholes,
298
+ 'offchip_bin_slit': offchip_bin_slit,
290
299
  }
291
300
 
301
+ # Detect key format: 10-element (new, includes offchip_bin_slit) or 9-element (legacy)
302
+ sample_key = next(iter(all_combinations.keys()))
303
+ has_offchip_key = len(sample_key) == 10
304
+
292
305
  # FIRST: Check if the specified parameters match multiple combinations
293
306
  # before filling in any defaults
294
307
  matching_combinations = []
295
308
 
296
309
  for key in all_combinations.keys():
297
- key_slit, key_oxide, key_carbon, key_aluminium, key_ccd, key_vis_sl, key_exposure, key_psf, key_enable_pinholes = key
310
+ if has_offchip_key:
311
+ key_slit, key_oxide, key_carbon, key_aluminium, key_ccd, key_vis_sl, key_exposure, key_psf, key_enable_pinholes, key_offchip = key
312
+ else:
313
+ key_slit, key_oxide, key_carbon, key_aluminium, key_ccd, key_vis_sl, key_exposure, key_psf, key_enable_pinholes = key
314
+ key_offchip = 1 # legacy default
298
315
 
299
316
  # Check if this combination matches all specified (non-None) parameters
300
317
  matches = True
@@ -318,6 +335,8 @@ def get_results_for_combination(
318
335
  matches = False
319
336
  if enable_pinholes is not None and key_enable_pinholes != enable_pinholes:
320
337
  matches = False
338
+ if offchip_bin_slit is not None and key_offchip != offchip_bin_slit:
339
+ matches = False
321
340
 
322
341
  if matches:
323
342
  matching_combinations.append(key)
@@ -328,9 +347,14 @@ def get_results_for_combination(
328
347
  print(f"Use summary_table(results) to see all available parameter combinations.")
329
348
  print(f"Matching combinations found:")
330
349
  for i, combo in enumerate(matching_combinations[:5]): # Show first 5
331
- slit, oxide, carbon, aluminium, ccd, vis_sl, exp, psf_val, enable_pinholes_val = combo
350
+ if has_offchip_key:
351
+ slit, oxide, carbon, aluminium, ccd, vis_sl, exp, psf_val, enable_pinholes_val, offchip = combo
352
+ else:
353
+ slit, oxide, carbon, aluminium, ccd, vis_sl, exp, psf_val, enable_pinholes_val = combo
354
+ offchip = 1
332
355
  print(f" {i+1}: slit={slit:.2f}arcsec, oxide={oxide:.1f}nm, carbon={carbon:.1f}nm, "
333
- f"Al={aluminium:.0f}A, CCD={ccd:.1f}C, stray={vis_sl:.2g}, exp={exp:.1f}s, psf={psf_val}, pinholes={enable_pinholes_val}")
356
+ f"Al={aluminium:.0f}A, CCD={ccd:.1f}C, stray={vis_sl:.2g}, exp={exp:.1f}s, "
357
+ f"psf={psf_val}, pinholes={enable_pinholes_val}, offchip_bin={offchip}")
334
358
  if len(matching_combinations) > 5:
335
359
  print(f" ... and {len(matching_combinations) - 5} more")
336
360
  raise ValueError(f"Multiple combinations match your parameters. Please specify more parameters to select a unique combination.")
@@ -357,6 +381,9 @@ def get_results_for_combination(
357
381
  psf = param_ranges["psf_settings"][0]
358
382
  if enable_pinholes is None:
359
383
  enable_pinholes = param_ranges["enable_pinholes_vals"][0]
384
+ if offchip_bin_slit is None:
385
+ offchip_bin_slits = param_ranges.get("offchip_bin_slits", [1])
386
+ offchip_bin_slit = offchip_bin_slits[0]
360
387
 
361
388
  # Convert units to the same format as stored in keys (without units)
362
389
  slit_width_val = slit_width.to_value(u.arcsec)
@@ -367,9 +394,15 @@ def get_results_for_combination(
367
394
  vis_sl_val = vis_sl.to_value() if hasattr(vis_sl, 'to_value') else vis_sl
368
395
  exposure_val = exposure.to_value(u.s)
369
396
 
370
- # Find matching combination (9-element key format)
371
- target_key = (slit_width_val, oxide_thickness_val, c_thickness_val,
372
- aluminium_thickness_val, ccd_temperature_val, vis_sl_val, exposure_val, psf, enable_pinholes)
397
+ # Build target key in the appropriate format
398
+ if has_offchip_key:
399
+ target_key = (slit_width_val, oxide_thickness_val, c_thickness_val,
400
+ aluminium_thickness_val, ccd_temperature_val, vis_sl_val, exposure_val,
401
+ psf, enable_pinholes, offchip_bin_slit)
402
+ else:
403
+ target_key = (slit_width_val, oxide_thickness_val, c_thickness_val,
404
+ aluminium_thickness_val, ccd_temperature_val, vis_sl_val, exposure_val,
405
+ psf, enable_pinholes)
373
406
 
374
407
  if debug:
375
408
  print(f"Target key: {target_key}")
@@ -450,25 +483,38 @@ def summary_table(results: Dict[str, Any]) -> None:
450
483
  all_combinations = results["results"]["all_combinations"]
451
484
  param_ranges = results["results"]["parameter_ranges"]
452
485
 
486
+ # Detect key format
487
+ sample_key = next(iter(all_combinations.keys()))
488
+ has_offchip = len(sample_key) == 10
489
+
453
490
  print("Parameter Combination Summary")
454
- print("=" * 155)
455
- print(f"{'Slit (arcsec)':<12} {'Oxide (nm)':<12} {'Carbon (nm)':<12} {'Al (A)':<10} {'CCD (C)':<10} {'Stray Light':<12} {'Exp (s)':<10} {'PSF':<5} {'Pinholes':<8}")
456
- print("-" * 155)
491
+ print("=" * 165)
492
+ header = f"{'Slit (arcsec)':<12} {'Oxide (nm)':<12} {'Carbon (nm)':<12} {'Al (A)':<10} {'CCD (C)':<10} {'Stray Light':<12} {'Exp (s)':<10} {'PSF':<5} {'Pinholes':<8}"
493
+ if has_offchip:
494
+ header += f" {'Slit Bin':<8}"
495
+ print(header)
496
+ print("-" * 165)
457
497
 
458
498
  for key, combo_results in all_combinations.items():
459
- slit, oxide, carbon, aluminium, ccd_temp, vis_sl, exposure, psf, enable_pinholes = key
460
- params = combo_results["parameters"]
499
+ if has_offchip:
500
+ slit, oxide, carbon, aluminium, ccd_temp, vis_sl, exposure, psf, enable_pinholes, offchip = key
501
+ else:
502
+ slit, oxide, carbon, aluminium, ccd_temp, vis_sl, exposure, psf, enable_pinholes = key
503
+ offchip = 1
461
504
 
462
- print(f"{slit:<12.2f} {oxide:<12.1f} {carbon:<12.1f} {aluminium:<10.0f} {ccd_temp:<10.1f} {vis_sl:<12.2g} {exposure:<10.1f} {str(psf):<5} {str(enable_pinholes):<8}")
505
+ row = f"{slit:<12.2f} {oxide:<12.1f} {carbon:<12.1f} {aluminium:<10.0f} {ccd_temp:<10.1f} {vis_sl:<12.2g} {exposure:<10.1f} {str(psf):<5} {str(enable_pinholes):<8}"
506
+ if has_offchip:
507
+ row += f" {offchip:<8}"
508
+ print(row)
463
509
 
464
- print("-" * 155)
510
+ print("-" * 165)
465
511
  print(f"Total combinations: {len(all_combinations)}")
466
512
  print(f"Exposure times: {[exp.to_value(u.s) for exp in param_ranges['exposures']]}")
467
513
 
468
514
 
469
515
  def create_sunpy_maps_from_combo(
470
516
  combination_results: Dict[str, Any],
471
- cube_reb,
517
+ cube_reb=None,
472
518
  rest_wavelength: u.Quantity = 195.119 * u.AA,
473
519
  data_type: str = "dn",
474
520
  precision_requirement: u.Quantity = 2.0 * u.km / u.s,
@@ -482,8 +528,9 @@ def create_sunpy_maps_from_combo(
482
528
  ----------
483
529
  combination_results : dict
484
530
  Results for a specific parameter combination from get_results_for_combination().
485
- cube_reb : NDCube
531
+ cube_reb : NDCube, optional
486
532
  NDCube with helioprojective WCS to use for all maps.
533
+ If not provided, the WCS stored in the combination results is used.
487
534
  rest_wavelength : u.Quantity, optional
488
535
  Rest wavelength for velocity conversion (default: 195.119 A for Fe XII).
489
536
  data_type : str, optional
@@ -526,14 +573,22 @@ def create_sunpy_maps_from_combo(
526
573
  else:
527
574
  analysis_per_exp = None
528
575
 
529
- # Extract 2D helioprojective WCS from the cube
530
- wcs_2d = cube_reb.wcs.celestial.swapaxes(0, 1)
576
+ # Extract 2D helioprojective WCS from the cube or stored signal WCS
577
+ if cube_reb is not None:
578
+ wcs_2d = cube_reb.wcs.celestial.swapaxes(0, 1)
579
+ else:
580
+ wcs_2d = combination_results["first_signal_wcs"].celestial.swapaxes(0, 1)
531
581
 
532
582
  # Get the data arrays - now only first iteration is saved
533
583
  first_photon_signal = combination_results["first_photon_signal"] # Shape: (nx, ny, nwave)
534
584
  first_dn_signal = combination_results["first_dn_signal"] # Shape: (nx, ny, nwave)
535
585
  fit_stats_key = f"{data_type}_fit_stats"
536
586
  fit_stats = combination_results[fit_stats_key] # Contains first_fit_data, mean_data, std_data, units
587
+ if fit_stats is None:
588
+ raise ValueError(
589
+ f"'{data_type}' signal was not fitted for this combination. "
590
+ f"Check the 'fit_signals' setting in your YAML config."
591
+ )
537
592
 
538
593
  maps = {}
539
594