solarc-eclipse 0.5.0__tar.gz → 0.6.0__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 (34) hide show
  1. {solarc_eclipse-0.5.0/solarc_eclipse.egg-info → solarc_eclipse-0.6.0}/PKG-INFO +58 -18
  2. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/README.md +57 -17
  3. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/__init__.py +1 -1
  4. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/analysis.py +3 -0
  5. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data_processing.py +12 -7
  6. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/main.py +45 -1
  7. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/radiometric.py +7 -3
  8. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/synthesis.py +578 -133
  9. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0/solarc_eclipse.egg-info}/PKG-INFO +58 -18
  10. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/LICENSE +0 -0
  11. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/MANIFEST.in +0 -0
  12. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/cli.py +0 -0
  13. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/config.py +0 -0
  14. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/grating_reflection_efficiency.dat +0 -0
  15. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/primary_mirror_coating_reflectance.dat +0 -0
  16. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/source.txt +0 -0
  17. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/throughput_aluminium_1000_angstrom.dat +0 -0
  18. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/throughput_aluminium_oxide_1000_angstrom.dat +0 -0
  19. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/data/throughput/throughput_carbon_1000_angstrom.dat +0 -0
  20. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/fitting.py +0 -0
  21. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/monte_carlo.py +0 -0
  22. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/pinhole_diffraction.py +0 -0
  23. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/psf.py +0 -0
  24. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/synthesis_cli.py +0 -0
  25. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/euvst_response/utils.py +0 -0
  26. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/pyproject.toml +0 -0
  27. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/setup.cfg +0 -0
  28. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/setup.py +0 -0
  29. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/solarc_eclipse.egg-info/SOURCES.txt +0 -0
  30. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/solarc_eclipse.egg-info/dependency_links.txt +0 -0
  31. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/solarc_eclipse.egg-info/entry_points.txt +0 -0
  32. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/solarc_eclipse.egg-info/not-zip-safe +0 -0
  33. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/solarc_eclipse.egg-info/requires.txt +0 -0
  34. {solarc_eclipse-0.5.0 → solarc_eclipse-0.6.0}/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.5.0
3
+ Version: 0.6.0
4
4
  Summary: ECLIPSE: Emission Calculation and Line Intensity Prediction for SOLAR-C EUVST
5
5
  Home-page: https://github.com/jamesmckevitt/eclipse
6
6
  Author: James McKevitt
@@ -49,6 +49,8 @@ Contact: James McKevitt (jm2@mssl.ucl.ac.uk). License: Contact for permission to
49
49
 
50
50
  The instrument response generated by this code will be updated during instrument development, testing, and commissioning.
51
51
 
52
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17543844.svg)](https://doi.org/10.5281/zenodo.17543844)
53
+
52
54
  ## Installation
53
55
 
54
56
  ### From PyPI (recommended)
@@ -158,15 +160,15 @@ synthesise-spectra \
158
160
  --vy-file vy/result_prim_3.0270000 \
159
161
  --vz-file vz/result_prim_2.0270000 \
160
162
  --cube-shape 512 768 256 \
161
- --voxel-dx 0.192 \
162
- --voxel-dy 0.192 \
163
- --voxel-dz 0.064 \
164
- --vel-res 5.0 \
165
- --vel-lim 300.0 \
163
+ --voxel-dx "0.192 Mm" \
164
+ --voxel-dy "0.192 Mm" \
165
+ --voxel-dz "0.064 Mm" \
166
+ --vel-res "5.0 km/s" \
167
+ --vel-lim "300.0 km/s" \
166
168
  --integration-axis z \
167
- --crop-x -50 50 \
168
- --crop-y -50 50 \
169
- --crop-z 0 20 \
169
+ --crop-x "-50 Mm" "50 Mm" \
170
+ --crop-y "-50 Mm" "50 Mm" \
171
+ --crop-z "0 Mm" "20 Mm" \
170
172
  --downsample 1 \
171
173
  --precision float64 \
172
174
  --mean-mol-wt 1.29 \
@@ -193,11 +195,11 @@ synthesise-spectra --help
193
195
 
194
196
  **Grid Parameters:**
195
197
  - `--cube-shape`: Cube dimensions as three integers (default: `512 768 256`)
196
- - `--voxel-dx`, `--voxel-dy`, `--voxel-dz`: Voxel sizes in Mm (default: `0.192 0.192 0.064`)
198
+ - `--voxel-dx`, `--voxel-dy`, `--voxel-dz`: Voxel sizes with units (default: `"0.192 Mm"`, `"0.192 Mm"`, `"0.064 Mm"`)
197
199
 
198
200
  **Velocity Grid:**
199
- - `--vel-res`: Velocity resolution in km/s (default: `5.0`)
200
- - `--vel-lim`: Velocity limit ±km/s (default: `300.0`)
201
+ - `--vel-res`: Velocity resolution with units (default: `"5.0 km/s"`)
202
+ - `--vel-lim`: Velocity limit +-km/s with units (default: `"300.0 km/s"`)
201
203
 
202
204
  **Integration and Viewing:**
203
205
  - `--integration-axis`: Integration axis: `x`, `y`, or `z` (default: `z`)
@@ -205,10 +207,10 @@ synthesise-spectra --help
205
207
  - `x`: Side view from the left (integrates left-to-right)
206
208
  - `y`: Side view from the front (integrates front-to-back)
207
209
 
208
- **Spatial Cropping (Heliocentric coordinates in Mm):**
209
- - `--crop-x`: X-range to crop, e.g., `--crop-x -50 50` (optional)
210
- - `--crop-y`: Y-range to crop, e.g., `--crop-y -50 50` (optional)
211
- - `--crop-z`: Z-range to crop, e.g., `--crop-z 0 20` (optional)
210
+ **Spatial Cropping (Heliocentric coordinates with units):**
211
+ - `--crop-x`: X-range to crop with units, e.g., `--crop-x "-50 Mm" "50 Mm"` (optional)
212
+ - `--crop-y`: Y-range to crop with units, e.g., `--crop-y "-50 Mm" "50 Mm"` (optional)
213
+ - `--crop-z`: Z-range to crop with units, e.g., `--crop-z "0 Mm" "20 Mm"` (optional)
212
214
  - Omit any crop option to use the full range in that dimension
213
215
 
214
216
  **Processing Options:**
@@ -219,6 +221,40 @@ synthesise-spectra --help
219
221
  **Line Selection:**
220
222
  - `--limit-lines`: Limit to specific lines, e.g., `--limit-lines Fe12_195.1190 Fe12_195.1790`
221
223
 
224
+ #### Dynamic Mode (Time-varying Atmospheres)
225
+
226
+ For simulating raster scans over evolving atmospheres, use dynamic mode which combines MHD timesteps based on instrument scanning:
227
+
228
+ ```bash
229
+ synthesise-spectra \
230
+ --data-dir ./data/atmosphere \
231
+ --goft-file ./data/gofnt.sav \
232
+ --output-dir ./run/input \
233
+ --slit-rest-time "40 s" \
234
+ --slit-width "0.2 arcsec" \
235
+ --temp-dir temp \
236
+ --temp-filename eosT \
237
+ --rho-dir rho \
238
+ --rho-filename result_prim_0 \
239
+ --vz-dir vz \
240
+ --vz-filename result_prim_2 \
241
+ --time-dir time \
242
+ --time-filename tau_slice_0.100 \
243
+ --cube-shape 512 768 256 \
244
+ --voxel-dx "0.192 Mm" \
245
+ --voxel-dy "0.192 Mm" \
246
+ --voxel-dz "0.064 Mm" \
247
+ --vel-res "5.0 km/s" \
248
+ --vel-lim "300.0 km/s" \
249
+ --integration-axis z
250
+ ```
251
+
252
+ **Dynamic Mode Options:**
253
+ - `--slit-rest-time`: Slit rest time per position - enables dynamic mode
254
+ - `--slit-width`: Slit width
255
+ - `--temp-dir`, `--rho-dir`, `--vx-dir`, `--vy-dir`, `--vz-dir`, `--time-dir`: Directories containing timestep files
256
+ - `--temp-filename`, `--rho-filename`, `--vx-filename`, `--vy-filename`, `--vz-filename`, `--time-filename`: Filename prefix before timestep suffix
257
+
222
258
  #### Output
223
259
 
224
260
  The synthesis produces a pickle file containing:
@@ -230,7 +266,7 @@ The synthesis produces a pickle file containing:
230
266
 
231
267
  - Use `--downsample 2` or `--downsample 4` for initial testing
232
268
  - Use `--precision float32` to reduce memory usage (may affect accuracy)
233
- - Use `--limit-lines` to synthesize only specific lines for development
269
+ - Use `--limit-lines` to synthesise only specific lines for development
234
270
  - Use spatial cropping to focus on regions of interest and reduce computation time
235
271
  - Monitor memory usage - full resolution synthesis can require 50+ GB RAM
236
272
  - Side views (`--integration-axis x` or `y`) may require different velocity files
@@ -328,7 +364,11 @@ ccd_temperature: -60 Celsius # Default (expected operating temperature)
328
364
  vis_sl: 0 photon / (s * pixel) # Default, (ideal case, no stray light)
329
365
  ```
330
366
 
331
- For guidence on recommended values, see McKevitt et al. (2025) (in prep.).
367
+ For guidance on recommended values, see McKevitt et al. (2025) (in prep.).
368
+
369
+ If you synthesised data in dynamic mode, your configuration must specify:
370
+ - Exactly one slit width matching the synthesis slit width
371
+ - Exactly one exposure time matching the synthesis exposure time
332
372
 
333
373
  #### Running Simulations
334
374
 
@@ -6,6 +6,8 @@ Contact: James McKevitt (jm2@mssl.ucl.ac.uk). License: Contact for permission to
6
6
 
7
7
  The instrument response generated by this code will be updated during instrument development, testing, and commissioning.
8
8
 
9
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17543844.svg)](https://doi.org/10.5281/zenodo.17543844)
10
+
9
11
  ## Installation
10
12
 
11
13
  ### From PyPI (recommended)
@@ -115,15 +117,15 @@ synthesise-spectra \
115
117
  --vy-file vy/result_prim_3.0270000 \
116
118
  --vz-file vz/result_prim_2.0270000 \
117
119
  --cube-shape 512 768 256 \
118
- --voxel-dx 0.192 \
119
- --voxel-dy 0.192 \
120
- --voxel-dz 0.064 \
121
- --vel-res 5.0 \
122
- --vel-lim 300.0 \
120
+ --voxel-dx "0.192 Mm" \
121
+ --voxel-dy "0.192 Mm" \
122
+ --voxel-dz "0.064 Mm" \
123
+ --vel-res "5.0 km/s" \
124
+ --vel-lim "300.0 km/s" \
123
125
  --integration-axis z \
124
- --crop-x -50 50 \
125
- --crop-y -50 50 \
126
- --crop-z 0 20 \
126
+ --crop-x "-50 Mm" "50 Mm" \
127
+ --crop-y "-50 Mm" "50 Mm" \
128
+ --crop-z "0 Mm" "20 Mm" \
127
129
  --downsample 1 \
128
130
  --precision float64 \
129
131
  --mean-mol-wt 1.29 \
@@ -150,11 +152,11 @@ synthesise-spectra --help
150
152
 
151
153
  **Grid Parameters:**
152
154
  - `--cube-shape`: Cube dimensions as three integers (default: `512 768 256`)
153
- - `--voxel-dx`, `--voxel-dy`, `--voxel-dz`: Voxel sizes in Mm (default: `0.192 0.192 0.064`)
155
+ - `--voxel-dx`, `--voxel-dy`, `--voxel-dz`: Voxel sizes with units (default: `"0.192 Mm"`, `"0.192 Mm"`, `"0.064 Mm"`)
154
156
 
155
157
  **Velocity Grid:**
156
- - `--vel-res`: Velocity resolution in km/s (default: `5.0`)
157
- - `--vel-lim`: Velocity limit ±km/s (default: `300.0`)
158
+ - `--vel-res`: Velocity resolution with units (default: `"5.0 km/s"`)
159
+ - `--vel-lim`: Velocity limit +-km/s with units (default: `"300.0 km/s"`)
158
160
 
159
161
  **Integration and Viewing:**
160
162
  - `--integration-axis`: Integration axis: `x`, `y`, or `z` (default: `z`)
@@ -162,10 +164,10 @@ synthesise-spectra --help
162
164
  - `x`: Side view from the left (integrates left-to-right)
163
165
  - `y`: Side view from the front (integrates front-to-back)
164
166
 
165
- **Spatial Cropping (Heliocentric coordinates in Mm):**
166
- - `--crop-x`: X-range to crop, e.g., `--crop-x -50 50` (optional)
167
- - `--crop-y`: Y-range to crop, e.g., `--crop-y -50 50` (optional)
168
- - `--crop-z`: Z-range to crop, e.g., `--crop-z 0 20` (optional)
167
+ **Spatial Cropping (Heliocentric coordinates with units):**
168
+ - `--crop-x`: X-range to crop with units, e.g., `--crop-x "-50 Mm" "50 Mm"` (optional)
169
+ - `--crop-y`: Y-range to crop with units, e.g., `--crop-y "-50 Mm" "50 Mm"` (optional)
170
+ - `--crop-z`: Z-range to crop with units, e.g., `--crop-z "0 Mm" "20 Mm"` (optional)
169
171
  - Omit any crop option to use the full range in that dimension
170
172
 
171
173
  **Processing Options:**
@@ -176,6 +178,40 @@ synthesise-spectra --help
176
178
  **Line Selection:**
177
179
  - `--limit-lines`: Limit to specific lines, e.g., `--limit-lines Fe12_195.1190 Fe12_195.1790`
178
180
 
181
+ #### Dynamic Mode (Time-varying Atmospheres)
182
+
183
+ For simulating raster scans over evolving atmospheres, use dynamic mode which combines MHD timesteps based on instrument scanning:
184
+
185
+ ```bash
186
+ synthesise-spectra \
187
+ --data-dir ./data/atmosphere \
188
+ --goft-file ./data/gofnt.sav \
189
+ --output-dir ./run/input \
190
+ --slit-rest-time "40 s" \
191
+ --slit-width "0.2 arcsec" \
192
+ --temp-dir temp \
193
+ --temp-filename eosT \
194
+ --rho-dir rho \
195
+ --rho-filename result_prim_0 \
196
+ --vz-dir vz \
197
+ --vz-filename result_prim_2 \
198
+ --time-dir time \
199
+ --time-filename tau_slice_0.100 \
200
+ --cube-shape 512 768 256 \
201
+ --voxel-dx "0.192 Mm" \
202
+ --voxel-dy "0.192 Mm" \
203
+ --voxel-dz "0.064 Mm" \
204
+ --vel-res "5.0 km/s" \
205
+ --vel-lim "300.0 km/s" \
206
+ --integration-axis z
207
+ ```
208
+
209
+ **Dynamic Mode Options:**
210
+ - `--slit-rest-time`: Slit rest time per position - enables dynamic mode
211
+ - `--slit-width`: Slit width
212
+ - `--temp-dir`, `--rho-dir`, `--vx-dir`, `--vy-dir`, `--vz-dir`, `--time-dir`: Directories containing timestep files
213
+ - `--temp-filename`, `--rho-filename`, `--vx-filename`, `--vy-filename`, `--vz-filename`, `--time-filename`: Filename prefix before timestep suffix
214
+
179
215
  #### Output
180
216
 
181
217
  The synthesis produces a pickle file containing:
@@ -187,7 +223,7 @@ The synthesis produces a pickle file containing:
187
223
 
188
224
  - Use `--downsample 2` or `--downsample 4` for initial testing
189
225
  - Use `--precision float32` to reduce memory usage (may affect accuracy)
190
- - Use `--limit-lines` to synthesize only specific lines for development
226
+ - Use `--limit-lines` to synthesise only specific lines for development
191
227
  - Use spatial cropping to focus on regions of interest and reduce computation time
192
228
  - Monitor memory usage - full resolution synthesis can require 50+ GB RAM
193
229
  - Side views (`--integration-axis x` or `y`) may require different velocity files
@@ -285,7 +321,11 @@ ccd_temperature: -60 Celsius # Default (expected operating temperature)
285
321
  vis_sl: 0 photon / (s * pixel) # Default, (ideal case, no stray light)
286
322
  ```
287
323
 
288
- For guidence on recommended values, see McKevitt et al. (2025) (in prep.).
324
+ For guidance on recommended values, see McKevitt et al. (2025) (in prep.).
325
+
326
+ If you synthesised data in dynamic mode, your configuration must specify:
327
+ - Exactly one slit width matching the synthesis slit width
328
+ - Exactly one exposure time matching the synthesis exposure time
289
329
 
290
330
  #### Running Simulations
291
331
 
@@ -4,7 +4,7 @@ ECLIPSE: Emission Calculation and Line Intensity 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.5.0"
7
+ __version__ = "0.6.0"
8
8
  __author__ = "James McKevitt"
9
9
  __email__ = "jm2@mssl.ucl.ac.uk"
10
10
 
@@ -561,6 +561,9 @@ def create_sunpy_maps_from_combo(
561
561
 
562
562
  maps['velocity_std'] = sunpy.map.Map(analysis["v_std"].value.T, wcs_2d)
563
563
  maps['velocity_std'].meta['bunit'] = str(analysis["v_std"].unit)
564
+
565
+ maps['velocity_true'] = sunpy.map.Map(analysis["v_true"].value.T, wcs_2d)
566
+ maps['velocity_true'].meta['bunit'] = str(analysis["v_true"].unit)
564
567
 
565
568
  # Velocity error (truth - mean)
566
569
  maps['velocity_err'] = sunpy.map.Map(analysis["v_err"].value.T, wcs_2d)
@@ -16,7 +16,7 @@ from tqdm import tqdm
16
16
  from .utils import tqdm_joblib, distance_to_angle
17
17
 
18
18
 
19
- def load_atmosphere(pkl_file: str, metadata_line: str = None) -> NDCube:
19
+ def load_atmosphere(pkl_file: str, metadata_line: str = None) -> tuple:
20
20
  """
21
21
  Load synthetic atmosphere cube from pickle file.
22
22
 
@@ -34,9 +34,10 @@ def load_atmosphere(pkl_file: str, metadata_line: str = None) -> NDCube:
34
34
 
35
35
  Returns
36
36
  -------
37
- NDCube
38
- Summed cube of all line intensities with proper WCS and metadata.
39
- Uses the wavelength grid from the metadata_line.
37
+ tuple
38
+ (summed_cube, dynamic_mode_info) where:
39
+ - summed_cube: NDCube with summed line intensities
40
+ - dynamic_mode_info: dict with dynamic mode metadata (or None if static)
40
41
  """
41
42
  with open(pkl_file, "rb") as f:
42
43
  tmp = dill.load(f)
@@ -49,6 +50,9 @@ def load_atmosphere(pkl_file: str, metadata_line: str = None) -> NDCube:
49
50
  if not line_cubes:
50
51
  raise ValueError("No line cubes found in synthesis results")
51
52
 
53
+ # Get dynamic mode info if present
54
+ dynamic_mode_info = tmp.get("dynamic_mode", {"enabled": False})
55
+
52
56
  # Get the line names
53
57
  line_names = list(line_cubes.keys())
54
58
 
@@ -96,7 +100,8 @@ def load_atmosphere(pkl_file: str, metadata_line: str = None) -> NDCube:
96
100
  "combined_lines": line_names,
97
101
  "n_lines": len(line_names),
98
102
  "metadata_source": metadata_line,
99
- "summed_intensity": True
103
+ "summed_intensity": True,
104
+ "dynamic_mode": dynamic_mode_info,
100
105
  })
101
106
 
102
107
  # Create the summed cube using the reference cube's WCS
@@ -107,7 +112,7 @@ def load_atmosphere(pkl_file: str, metadata_line: str = None) -> NDCube:
107
112
  meta=combined_meta
108
113
  )
109
114
 
110
- return summed_cube
115
+ return summed_cube, dynamic_mode_info
111
116
 
112
117
 
113
118
  def resample_ndcube_spectral_axis(ndcube, spectral_axis, output_resolution, ncpu=-1):
@@ -248,7 +253,7 @@ def rebin_atmosphere(cube_sim, det, sim, use_dask=False):
248
253
  sim : Simulation
249
254
  Simulation configuration
250
255
  use_dask : bool, optional
251
- Whether to use Dask for automatic parallelization (default: True)
256
+ Whether to use Dask for automatic parallelization (default: False)
252
257
 
253
258
  Returns
254
259
  -------
@@ -226,7 +226,51 @@ def main() -> None:
226
226
  # Load synthetic atmosphere cube
227
227
  print("Loading atmosphere...")
228
228
  print(f"Using '{reference_line}' as reference line for wavelength grid and metadata...")
229
- cube_sim = load_atmosphere(synthesis_file, reference_line)
229
+ cube_sim, dynamic_mode_info = load_atmosphere(synthesis_file, reference_line)
230
+
231
+ # Check for dynamic mode and validate parameters
232
+ is_dynamic_mode = dynamic_mode_info.get("enabled", False)
233
+ if is_dynamic_mode:
234
+ print("Synthesis was done in DYNAMIC MODE (time-varying atmosphere)")
235
+ print(f" Slit width: {dynamic_mode_info['slit_width']}")
236
+ print(f" Slit rest time: {dynamic_mode_info['slit_rest_time']}")
237
+ print(f" Timesteps used: {len(dynamic_mode_info['available_timesteps'])}")
238
+
239
+ # Check that exactly one slit width is provided for dynamic mode
240
+ synth_slit_width = dynamic_mode_info["slit_width"]
241
+ if len(slit_widths) != 1:
242
+ raise ValueError(
243
+ f"Dynamic mode synthesis requires exactly one slit width. "
244
+ f"Config specifies {len(slit_widths)} slit widths: {slit_widths}. "
245
+ f"Please provide only the synthesis slit width: {synth_slit_width}"
246
+ )
247
+
248
+ # Validate that the single slit width matches synthesis
249
+ if not np.isclose(slit_widths[0].to_value(u.arcsec), synth_slit_width.to_value(u.arcsec), rtol=1e-6):
250
+ raise ValueError(
251
+ f"Slit width mismatch: synthesis was done with {synth_slit_width}, "
252
+ f"but config specifies {slit_widths[0]}. "
253
+ f"For dynamic mode synthesis, the slit width must match exactly: {synth_slit_width}"
254
+ )
255
+
256
+ # Check that exactly one exposure time is provided for dynamic mode
257
+ synth_rest_time = dynamic_mode_info["slit_rest_time"]
258
+ if len(exposures) != 1:
259
+ raise ValueError(
260
+ f"Dynamic mode synthesis requires exactly one exposure time. "
261
+ f"Config specifies {len(exposures)} exposure times: {exposures}. "
262
+ f"Please provide only the synthesis slit rest time: {synth_rest_time}"
263
+ )
264
+
265
+ # Validate that the single exposure time matches synthesis
266
+ if not np.isclose(exposures[0].to_value(u.s), synth_rest_time.to_value(u.s), rtol=1e-6):
267
+ raise ValueError(
268
+ f"Exposure time mismatch: synthesis was done with {synth_rest_time}, "
269
+ f"but config specifies {exposures[0]}. "
270
+ f"For dynamic mode synthesis, the exposure time must match exactly: {synth_rest_time}"
271
+ )
272
+
273
+ print(" Dynamic mode parameters validated successfully!")
230
274
 
231
275
  # Set up base detector configuration (doesn't change with parameters)
232
276
  if instrument == "SWC":
@@ -243,8 +243,12 @@ def to_electrons(photon_counts: NDCube, t_exp: u.Quantity, det) -> NDCube:
243
243
 
244
244
  e = electron_counts * (u.electron / u.pixel)
245
245
 
246
- # Add dark current and read noise (these still depend on exposure time)
247
- e += det.dark_current * t_exp # dark current
246
+ # Add dark current with Poisson noise
247
+ dark_current_mean = (det.dark_current * t_exp).to(u.electron / u.pixel).value
248
+ dark_current_poisson = np.random.poisson(dark_current_mean) * (u.electron / u.pixel)
249
+ e += dark_current_poisson
250
+
251
+ # Add read noise
248
252
  e += np.random.normal(0, det.read_noise_rms.value,
249
253
  photon_counts.data.shape) * (u.electron / u.pixel) # read noise
250
254
 
@@ -279,7 +283,7 @@ def to_dn(electrons: NDCube, det) -> NDCube:
279
283
  dn_q = (electrons.data * electrons.unit) / det.gain_e_per_dn # Quantity
280
284
  dn_q = dn_q.to(det.max_dn.unit)
281
285
 
282
- dn_val = dn_q.value
286
+ dn_val = np.round(dn_q.value) # round to nearest whole number
283
287
  dn_val[dn_val > det.max_dn.value] = det.max_dn.value # clip
284
288
 
285
289
  return NDCube(