exosim 2.2.0__tar.gz → 2.2.2__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 (182) hide show
  1. exosim-2.2.2/CHANGELOG.rst +288 -0
  2. {exosim-2.2.0 → exosim-2.2.2}/CITATION.cff +2 -2
  3. {exosim-2.2.0 → exosim-2.2.2}/PKG-INFO +12 -10
  4. {exosim-2.2.0 → exosim-2.2.2}/README.md +10 -0
  5. {exosim-2.2.0 → exosim-2.2.2}/RELEASING.md +6 -6
  6. {exosim-2.2.0 → exosim-2.2.2}/codemeta.json +2 -2
  7. {exosim-2.2.0 → exosim-2.2.2}/pyproject.toml +5 -9
  8. exosim-2.2.2/scripts/changelog_extract.py +74 -0
  9. {exosim-2.2.0 → exosim-2.2.2}/scripts/release.py +40 -11
  10. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/__about__.py +4 -1
  11. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/models/channel.py +8 -2
  12. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/models/signal.py +5 -5
  13. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/focal_plane_plotter.py +1 -1
  14. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/create_focal_plane.py +19 -11
  15. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/radiometric_model.py +11 -5
  16. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/astrosignal/estimate_planetary_signal.py +4 -1
  17. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/accumulate_sub_exposures.py +7 -3
  18. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_cosmic_rays.py +1 -0
  19. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_gain_drift.py +5 -0
  20. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/analog_to_digital.py +4 -2
  21. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/apply_pixels_non_linearity.py +3 -1
  22. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/merge_groups.py +11 -0
  23. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/foregrounds/estimate_zodi.py +2 -2
  24. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/compute_sources_pointing_offset.py +33 -10
  25. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/create_focal_plane_array.py +5 -11
  26. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_constant_dark_current_noise.py +5 -5
  27. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_constant_read_noise.py +6 -6
  28. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/download_sed.py +16 -3
  29. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/instantaneous_read_out.py +1 -1
  30. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/prepare_instantaneous_read_out.py +1 -44
  31. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/dark_current_map.py +9 -7
  32. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/pixels_non_linearity_from_correction.py +18 -18
  33. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/focal_plane_locations.py +17 -7
  34. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/PKG-INFO +12 -10
  35. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/requires.txt +1 -9
  36. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/scm_file_list.json +20 -11
  37. exosim-2.2.2/src/exosim.egg-info/scm_version.json +8 -0
  38. exosim-2.2.0/CHANGELOG.rst +0 -135
  39. exosim-2.2.0/scripts/changelog_extract.py +0 -51
  40. exosim-2.2.0/src/exosim.egg-info/scm_version.json +0 -8
  41. {exosim-2.2.0 → exosim-2.2.2}/.gitignore +0 -0
  42. {exosim-2.2.0 → exosim-2.2.2}/CODE_OF_CONDUCT.md +0 -0
  43. {exosim-2.2.0 → exosim-2.2.2}/LICENSE +0 -0
  44. {exosim-2.2.0 → exosim-2.2.2}/MANIFEST.in +0 -0
  45. {exosim-2.2.0 → exosim-2.2.2}/setup.cfg +0 -0
  46. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/__init__.py +0 -0
  47. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/data/Zodi_map.hdf5 +0 -0
  48. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/exosim.py +0 -0
  49. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/log/__init__.py +0 -0
  50. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/log/logger.py +0 -0
  51. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/log/structlog_config.py +0 -0
  52. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/models/__init__.py +0 -0
  53. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/models/utils/__init__.py +0 -0
  54. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/models/utils/cached_data.py +0 -0
  55. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/__init__.py +0 -0
  56. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/hdf5/__init__.py +0 -0
  57. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/hdf5/hdf5.py +0 -0
  58. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/hdf5/utils.py +0 -0
  59. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/output.py +0 -0
  60. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/set_output.py +0 -0
  61. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/output/utils.py +0 -0
  62. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/__init__.py +0 -0
  63. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/ndrs_plotter.py +0 -0
  64. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/plotter.py +0 -0
  65. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/radiometric_plotter.py +0 -0
  66. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/sub_exposures_plotter.py +0 -0
  67. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/plots/utils.py +0 -0
  68. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/py.typed +0 -0
  69. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/__init__.py +0 -0
  70. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/create_ndrs.py +0 -0
  71. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/create_sub_exposures.py +0 -0
  72. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/recipes/simulate_observation.py +0 -0
  73. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/__init__.py +0 -0
  74. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/astrosignal/__init__.py +0 -0
  75. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/astrosignal/apply_astronomical_signal.py +0 -0
  76. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/astrosignal/estimate_astronomical_signal.py +0 -0
  77. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/astrosignal/find_astronomical_signals.py +0 -0
  78. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/chain_task.py +0 -0
  79. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/__init__.py +0 -0
  80. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_constant_dark_current.py +0 -0
  81. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_dark_current_map_numpy.py +0 -0
  82. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_ktc.py +0 -0
  83. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_read_noise.py +0 -0
  84. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_read_noise_map_numpy.py +0 -0
  85. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/add_shot_noise.py +0 -0
  86. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/apply_dead_pixel_map.py +0 -0
  87. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/apply_dead_pixel_map_numpy.py +0 -0
  88. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/apply_simple_saturation.py +0 -0
  89. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/load_pixels_non_linearity_map.py +0 -0
  90. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/detector/load_pixels_non_linearity_map_numpy.py +0 -0
  91. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/foregrounds/__init__.py +0 -0
  92. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/__init__.py +0 -0
  93. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/apply_intra_pixel_response_function.py +0 -0
  94. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/compute_saturation.py +0 -0
  95. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/compute_solid_angle.py +0 -0
  96. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/create_focal_plane.py +0 -0
  97. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/create_intrapixel_response_function.py +0 -0
  98. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/create_oversampled_intrapixel_response_function.py +0 -0
  99. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/foregrounds_to_focal_plane.py +0 -0
  100. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/load_psf.py +0 -0
  101. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/load_psf_paos.py +0 -0
  102. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/load_psf_paos_time_interp.py +0 -0
  103. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/load_responsivity.py +0 -0
  104. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/load_wavelength_solution.py +0 -0
  105. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/populate_focal_plane.py +0 -0
  106. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/propagate_foregrounds.py +0 -0
  107. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/instrument/propagate_sources.py +0 -0
  108. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/load/__init__.py +0 -0
  109. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/load/load_optical_element.py +0 -0
  110. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/load/load_optical_element_hdf5.py +0 -0
  111. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/load/load_options.py +0 -0
  112. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/load/load_source_list.py +0 -0
  113. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/parse/__init__.py +0 -0
  114. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/parse/parse_optical_element.py +0 -0
  115. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/parse/parse_path.py +0 -0
  116. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/parse/parse_source.py +0 -0
  117. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/parse/parse_zodi.py +0 -0
  118. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/__init__.py +0 -0
  119. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/aperture_photometry.py +0 -0
  120. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_custom_noise.py +0 -0
  121. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_integration_time.py +0 -0
  122. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_observation_efficiency.py +0 -0
  123. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_observation_efficiency_from_dead_time.py +0 -0
  124. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_photon_noise.py +0 -0
  125. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_signals_channel.py +0 -0
  126. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_sub_frg_signals_channel.py +0 -0
  127. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/compute_total_noise.py +0 -0
  128. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/estimate_apertures.py +0 -0
  129. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/estimate_spectral_binning.py +0 -0
  130. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/load_apertures.py +0 -0
  131. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/multiaccum.py +0 -0
  132. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/saturation_channel.py +0 -0
  133. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/__init__.py +0 -0
  134. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/compute_multiaccum.py +0 -0
  135. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/compute_noise_column.py +0 -0
  136. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/compute_photon_noise.py +0 -0
  137. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/compute_saturation.py +0 -0
  138. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/create_table.py +0 -0
  139. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/radiometric/utils/update_total_noise.py +0 -0
  140. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/__init__.py +0 -0
  141. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/create_custom_source.py +0 -0
  142. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/create_planck_star.py +0 -0
  143. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/load_custom.py +0 -0
  144. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/load_phoenix.py +0 -0
  145. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/sed/prepare_sed.py +0 -0
  146. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/__init__.py +0 -0
  147. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/add_foregrounds.py +0 -0
  148. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/apply_qe_map.py +0 -0
  149. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/compute_reading_scheme.py +0 -0
  150. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/estimate_ch_jitter.py +0 -0
  151. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/estimate_pointing_jitter.py +0 -0
  152. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/load_ils.py +0 -0
  153. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/load_qe_map.py +0 -0
  154. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/subexposures/load_qe_map_numpy.py +0 -0
  155. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tasks/task.py +0 -0
  156. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/__init__.py +0 -0
  157. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/adc_gain_estimator.py +0 -0
  158. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/dead_pixels_map.py +0 -0
  159. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/exosim_tool.py +0 -0
  160. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/pixels_non_linearity.py +0 -0
  161. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/quantum_efficiency_map.py +0 -0
  162. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/tools/readout_scheme_calculator.py +0 -0
  163. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/__init__.py +0 -0
  164. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/aperture.py +0 -0
  165. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/ascii_arts.py +0 -0
  166. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/binning.py +0 -0
  167. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/checks.py +0 -0
  168. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/convolution.py +0 -0
  169. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/grids.py +0 -0
  170. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/iterators.py +0 -0
  171. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/klass_factory.py +0 -0
  172. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/operations.py +0 -0
  173. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/output_cleaners.py +0 -0
  174. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/prepare_recipes.py +0 -0
  175. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/psf.py +0 -0
  176. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/run_config.py +0 -0
  177. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/timed_class.py +0 -0
  178. {exosim-2.2.0 → exosim-2.2.2}/src/exosim/utils/types.py +0 -0
  179. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/SOURCES.txt +0 -0
  180. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/dependency_links.txt +0 -0
  181. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/entry_points.txt +0 -0
  182. {exosim-2.2.0 → exosim-2.2.2}/src/exosim.egg-info/top_level.txt +0 -0
@@ -0,0 +1,288 @@
1
+ ===========
2
+ Changelog
3
+ ===========
4
+
5
+ All notable changes to this project will be documented in this file.
6
+
7
+ The format is based on Keep a Changelog (keepachangelog_), and this project adheres
8
+ to Semantic Versioning (semver_).
9
+
10
+ Unreleased changes live as fragments in ``changelog.d/`` and are merged here by
11
+ ``nox -s release``. See ``docs/source/contributing/releasing.rst``.
12
+
13
+ .. scriv-insert-here
14
+
15
+ .. _changelog-2.2.2:
16
+
17
+ [2.2.2_] - Fix ACES solar-metallicity SED download
18
+ ==================================================
19
+
20
+ Changed
21
+ -------
22
+
23
+ - the documentation home page now points readers who only need signal and
24
+ noise budgets to ExoRad 2, the radiometric model ExoSim 2 builds on
25
+
26
+ Fixed
27
+ -----
28
+
29
+ - ``DownloadSed`` with ``model_name="phoenix-aces"`` built a 404 URL for a
30
+ solar-metallicity star: the Goettingen server names the directory and files
31
+ ``Z-0.0`` / ``lte...-0.0...``, but the URL builder formatted ``0.0`` as
32
+ ``+0.0``. Metallicity formatting is now routed through ``_feh_str``.
33
+
34
+ .. _changelog-2.2.1:
35
+
36
+ [2.2.1_] - Pipeline hardening
37
+ =============================
38
+
39
+ Changed
40
+ -------
41
+
42
+ - user guide and developer guide reworked for clarity: tighter prose, corrected
43
+ grammar, consistent sentence-case headings, and each mechanism now explains
44
+ the reason behind it, not just the behaviour
45
+ - home page, user-guide and developer-guide landing pages now share the same
46
+ card-based navigation with consistent line icons, rendered from
47
+ reStructuredText instead of hand-written HTML and clip-art
48
+
49
+ Removed
50
+ -------
51
+
52
+ - unused documentation dependencies from the ``docs`` extra
53
+ (``sphinx-rtd-theme``, ``sphinx-panels``, ``sphinxcontrib-napoleon``,
54
+ ``nbsphinx``, ``myst-parser`` and a few transitive-only pins)
55
+
56
+ - dead duplicate ``PrepareInstantaneousReadOut.force_power_conservation`` method
57
+ (the live implementation is in ``InstantaneousReadOut``)
58
+
59
+ Fixed
60
+ -----
61
+
62
+ - ``nox -s docs`` and ``nox -s docs-live`` now install the ``docs`` extra, so the
63
+ documentation builds on a clean checkout (Sphinx and the theme were missing)
64
+ - two broken links on the user-guide landing page
65
+ - several ``note`` and ``warning`` admonitions that were not rendering because
66
+ their content was not indented
67
+ - broken cross-references to the installation instructions from the quick-start
68
+ and FAQ pages
69
+ - malformed XML in the custom-task example (``unit`` attributes now quoted) and a
70
+ duplicate documentation label for pixel non-linearity
71
+ - stale continuous-integration details in the contributing guide (workflow name
72
+ and supported Python versions)
73
+ - title overline/underline mismatch on the sky-sources page that newer Sphinx
74
+ rejects
75
+ - corrupted LaTeX in several task docstrings (``\frac``, ``\times`` and similar
76
+ were being read as escape characters), so the rendered formulae for read
77
+ noise, dark current noise and the dark-current map are now correct
78
+ - the ExoSim logo no longer overflows and clips the page header
79
+
80
+ - ``FocalPlanePlotter`` failed with ``KeyError: 'channels'`` on a normal
81
+ focal-plane output: it looked for ``channels/channels`` instead of
82
+ ``channels``
83
+
84
+ - ``exosim.__branch__`` and ``exosim.__commit__`` were always ``None``: the git
85
+ metadata reader looked for ``.git`` one directory too shallow
86
+
87
+ - ``InstantaneousReadOut`` in memory-saving mode (``slicing=True``) crashed with
88
+ ``AttributeError: 'InstantaneousReadOut' object has no attribute
89
+ 'getOversampleFactors'`` whenever the pointing jitter needed sub-pixel
90
+ magnification; the call now uses the renamed ``get_oversample_factors``
91
+
92
+ - the target-list mode of ``RadiometricModel`` (a ``targetlist_filepath`` under
93
+ ``sky/source``) crashed with ``KeyError: component not found``: it rebinned
94
+ the channel efficiencies from the file root instead of from the per-target
95
+ ``targets/<name>/channels`` group. The recipe now runs to completion and
96
+ writes a radiometric table for every target in the list.
97
+
98
+ - ``RadiometricModel`` and ``CreateFocalPlane`` failed on a payload with a
99
+ single, unwrapped ``channel`` entry (``KeyError: component not found`` /
100
+ ``KeyError: 'Photometer'``): the efficiency-rebinning and the
101
+ foreground/source signal helpers iterated the channel dictionary as if it
102
+ were the multi-channel mapping. Single-channel configurations now run through
103
+ the whole radiometric pipeline.
104
+
105
+ - ``Signal.get_slice`` / ``Signal.set_slice`` now accept a plain ``float``
106
+ start/end time as documented (assumed to be hours); before they raised
107
+ ``AttributeError``
108
+ - ``AddGainDrift`` raises a clear ``KeyError`` when neither
109
+ ``gain_drift_amplitude`` nor the amplitude range is configured, instead of an
110
+ ``UnboundLocalError``
111
+ - ``EstimatePlanetarySignal`` raises a clear ``ValueError`` when the planet
112
+ ``rp`` is neither a file path nor a number, instead of an
113
+ ``UnboundLocalError``
114
+ - ``Channel.target_source`` raises an informative ``KeyError`` when several
115
+ sources are defined but none (or more than one) is flagged as the science
116
+ target, instead of an ``IndexError``
117
+ - ``RadiometricModel`` now passes the per-channel configuration to the
118
+ sub-foreground signal task, so the ``<contribution>_total_signal`` columns are
119
+ computed for every channel type
120
+ - ``CreateFocalPlane`` infers the wavelength grid from the channel passbands
121
+ (``channel``/``wl_min``/``wl_max``) when ``wl_grid`` is absent from the
122
+ configuration; the previous fallback looked up a non-existent ``channels`` key
123
+ and always used the 1-2 micron default
124
+ - the spectrometer focal-plane builder no longer relies on a fragile
125
+ buffer-level ``array == zeros`` comparison to detect a flat wavelength axis
126
+ (in ``CreateFocalPlaneArray._wav_osr`` and the PSF window locator); it now
127
+ checks the values explicitly
128
+ - ``locate_wavelength_windows`` raises a clear error for an unsupported PSF
129
+ dimensionality or channel type instead of returning ``None`` (which made the
130
+ caller fail with an opaque unpacking ``TypeError``)
131
+ - ``EstimateZodi`` no longer loops forever when the bundled ``data`` directory
132
+ cannot be found by walking up from the module; the search is bounded and
133
+ raises ``OSError`` if it fails
134
+ - ``AccumulateSubExposures`` corrupted the ramp of a single-exposure
135
+ acquisition: the first chunk read ``state_machine[-1]`` / ``dataset[-1]`` (the
136
+ wrapped-around last frame) as its carry-in offset. The first chunk now starts
137
+ from zero
138
+ - ``AnalogToDigital`` clips the converted signal to the ADC full scale; a bright
139
+ pixel above full scale with a fixed ADC gain used to wrap around on the
140
+ integer cast (e.g. 200000 counts became 3392 on a 16-bit ADC)
141
+ - ``AddCosmicRays`` no longer fails with an ``UnboundLocalError`` when the
142
+ configured ``interaction_shapes`` probabilities sum to less than one; the
143
+ event shape falls back to the last configured shape
144
+ - ``MergeGroups`` raises a clear error when the number of sub-exposures is not
145
+ ``n_groups * n_ndrs`` instead of silently mis-averaging a truncated group or
146
+ raising ``IndexError``
147
+ - ``ComputeSourcesPointingOffset`` computes the source-to-pointing offset with
148
+ ``SkyCoord.spherical_offsets_to``, so it handles the RA 0/360 wrap and the
149
+ ``cos(dec)`` foreshortening (a plain RA/Dec difference was wrong away from the
150
+ equator and near RA = 0); the offset is now rounded to the nearest sub-pixel
151
+ rather than truncated
152
+
153
+ Security
154
+ --------
155
+
156
+ - refreshed the locked dependency versions to pull in security fixes for
157
+ ``pillow``, ``requests``, ``urllib3``, ``cryptography``, ``idna``,
158
+ ``jupyter-server``, ``jupyterlab``, ``notebook``, ``nbconvert``, ``mistune``,
159
+ ``tornado``, ``pygments`` and the ``safety`` toolchain (``authlib``,
160
+ ``nltk``); this clears the outstanding Dependabot alerts on ``uv.lock``. The
161
+ release still installs the highest compatible versions for end users, so this
162
+ only affects reproducible development and CI environments
163
+
164
+ .. _changelog-2.2.0:
165
+
166
+ [2.2.0_] - End-to-end simulations
167
+ =================================
168
+
169
+ Added
170
+ -----
171
+
172
+ - new recipe ``SimulateObservation`` to run the full simulation pipeline from an options file to the NDRs
173
+ - radiometric model now supports target-list mode
174
+ - new documentation section for the radiometric model with a detailed description of the pipeline and operating modes
175
+ - observation efficiency can now be specified in the options file
176
+ - ``slim_output`` option for recipes to reduce output file size by dropping intermediate data products
177
+ - SED download task to fetch stellar spectra from online databases (currently Phoenix models only)
178
+
179
+ Changed
180
+ -------
181
+
182
+ - ``fast_convolution`` now uses ``scipy.signal.fftconvolve`` for better performance and accuracy
183
+ - saturation time now includes the well-depth fraction; integration time is the minimum saturation time across the detector; frame time is integration time divided by observation efficiency
184
+ - photon noise is now computed on the observation-efficiency-corrected signal
185
+ - packaging and dependency management moved from Poetry to uv
186
+ - logging moved to ``structlog``
187
+ - ``wl_interpolate`` reworked to use argsort + inverse mapping, avoiding O(n^2) lookups and extra copies
188
+ - ExoSim can now run without any source (no input SED)
189
+ - versioning is now derived from git tags via setuptools-scm; releases are cut with ``nox -s release``
190
+
191
+ Fixed
192
+ -----
193
+
194
+ - normalization in ``Signal`` now handles custom units correctly
195
+ - foreground is now decimated before being added to the focal plane instead of summed
196
+ - inverted parameters in the wavelength-centering equation, to avoid a polynomial inversion
197
+ - wrong wavelength order for ``loadPsfPaos``
198
+
199
+ [2.1.1_] - CLI Fixed
200
+ ====================
201
+ Fixed
202
+ -----
203
+ - missing summary for cli.
204
+
205
+ [2.1.0_] - Hello Python 3.12
206
+ ============================
207
+ Added
208
+ -----
209
+ - added unit normalization in Signal
210
+
211
+ Changed
212
+ -------
213
+ - dropped support for Python 3.9 and 3.10. Now supports only Python >3.12
214
+ - test framework changed into pytest
215
+ - added default value for low frequency time grid
216
+
217
+
218
+
219
+ [2.0.1_] - stable release
220
+ =========================
221
+ Added
222
+ -----
223
+ - improved exosim CLI with new click commands
224
+ - option to load optical elements from .hdf5 files (LoadOpticalElementHDF5)
225
+ - option to parse options file replacing a custom keyword with a value (LoadOptions)
226
+ - added codemeta.json file for better metadata management
227
+ - warning for misuse of oversampling factor and intra-pixel response function
228
+ - added support for wavelength dependent limb darkening
229
+ Changed
230
+ -------
231
+ - updated citation information
232
+ - rp in astronomical signal is now binned with the same binning method as Signal
233
+ Fixed
234
+ -----
235
+ - Custom SED units and documentation (added missing angle in loadCustom.py)
236
+ - find keys is not case sensitive anymore
237
+ - cosmic rays now work for low rates
238
+ - fixed elliptical PSF from PAOS
239
+ - ADC offset implemented as trigger offset (no negative values)
240
+ - astronomical signal now is weighted by the stellar flux on the detector
241
+
242
+ [2.0.0-rc2_] - ADC automode fix and minor improvements
243
+ ======================================================
244
+ Added
245
+ -----
246
+ - boundaries for random amplitude in gain noise
247
+ - option to force non-time-dependent PSF for LoadPsfPaos (`time_dependence` parameter to `False`)
248
+ - option to slice jitter to save memory when the focal plane is sampled at high frequencies (`slicing` parameter to `True`)
249
+ Changed
250
+ -------
251
+ - dependencies versions updated to support `Poetry` for Python 3.10
252
+ Fixed
253
+ -----
254
+ - added offset and "auto" mode to ADC
255
+ - removed Numba in populateFocalPlane (better numerical accuracy)
256
+ - interp2d replaced with RectBivariateSpline in LoadPsfPaos to support Scipy 1.10+
257
+
258
+ [2.0.0-rc1_] - Scipy compatibility fix
259
+ ======================================
260
+ Added
261
+ -----
262
+ - .npy input support for pixel non-linearity coefficients (LoadPixelsNonLinearityMapNumpy)
263
+ - multiplicative noise simulator (AddGainNoise)
264
+
265
+ Fixed
266
+ -----
267
+ - replaced scipy.convolve with scipy.signal.convolve
268
+ - ADC now works with unsigned integers
269
+
270
+ [v2.0.0-rc0_] - Release Candidate
271
+ ================================
272
+ Cleaned repository
273
+
274
+ .. _v2.0.0-rc0: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.0.0-rc0
275
+ .. _2.0.0-rc1: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.0.0-rc1
276
+ .. _2.0.0-rc2: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.0.0-rc2
277
+ .. _2.0.1: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.0.1
278
+ .. _2.1.0: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.1.0
279
+ .. _2.1.1: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.1.1
280
+
281
+ .. _2.2.0: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.2.0
282
+
283
+ .. _2.2.1: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.2.1
284
+
285
+ .. _2.2.2: https://github.com/arielmission-space/ExoSim2.0/releases/tag/v2.2.2
286
+
287
+ .. _keepachangelog: https://keepachangelog.com/en/1.0.0/
288
+ .. _semver: https://semver.org/spec/v2.0.0.html
@@ -18,5 +18,5 @@ authors:
18
18
 
19
19
  title: "ExoSim 2: the new exoplanet observation simulator applied to the Ariel space mission"
20
20
  doi: 10.1007/s10686-024-09976-2
21
- version: 2.2.0
22
- date-released: 2026-08-27
21
+ version: 2.2.2
22
+ date-released: 2026-11-12
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exosim
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: ExoSim 2 is the next generation of the Exoplanet Observation Simulator (ExoSim) tailored for spectro-photometric observations of transiting exoplanets from space, ground, and sub-orbital platforms. This software is a complete rewrite implemented in Python 3, embracing object-oriented design principles.
5
5
  Author: E. Pascale, A. Bocchieri, A. Lorenzani, A. Papageorgiou
6
6
  Author-email: "L. V. Mugnai" <mugnail@cardiff.ac.uk>
@@ -58,18 +58,10 @@ Requires-Dist: safety>=3.2.11; extra == "dev"
58
58
  Requires-Dist: bandit>=1.8.0; extra == "dev"
59
59
  Provides-Extra: docs
60
60
  Requires-Dist: sphinx>=8.1.3; extra == "docs"
61
- Requires-Dist: sphinx-rtd-theme>=3.0.2; extra == "docs"
62
- Requires-Dist: sphinxcontrib-napoleon; extra == "docs"
63
- Requires-Dist: sphinxcontrib-jsmath; extra == "docs"
64
- Requires-Dist: nbsphinx; extra == "docs"
65
- Requires-Dist: sphinx-panels; extra == "docs"
61
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
66
62
  Requires-Dist: sphinx-autoapi; extra == "docs"
67
63
  Requires-Dist: sphinx-autobuild; extra == "docs"
68
64
  Requires-Dist: sphinx-design; extra == "docs"
69
- Requires-Dist: sphinxcontrib-serializinghtml; extra == "docs"
70
- Requires-Dist: docutils; extra == "docs"
71
- Requires-Dist: pydata-sphinx-theme; extra == "docs"
72
- Requires-Dist: myst-parser>=4.0.0; extra == "docs"
73
65
  Provides-Extra: jupyter
74
66
  Requires-Dist: pyzmq<27,>=26; extra == "jupyter"
75
67
  Requires-Dist: notebook; extra == "jupyter"
@@ -90,6 +82,8 @@ Dynamic: license-file
90
82
  [![DOI](https://img.shields.io/badge/doi-10.1007%2Fs10686--024--09976--2-blue?link=https%3A%2F%2Fdoi.org%2F10.1007%2Fs10686-024-09976-2)](https://doi.org/10.1007/s10686-024-09976-2)
91
83
  [![arXiv](https://img.shields.io/badge/arXiv-2501.12809-red?link=https%3A%2F%2Farxiv.org%2Fabs%2F2501.12809)](https://arxiv.org/abs/2501.12809)
92
84
  [![EMACS](https://img.shields.io/badge/EMAC-2504--003-blue)](https://emacs.gsfc.nasa.gov?cid=2504-003)
85
+ [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Ask-0a7bbb)](https://deepwiki.com/arielmission-space/ExoSim2-public)
86
+ [![GitDiagram](https://img.shields.io/badge/GitDiagram-architecture-4c1)](https://gitdiagram.com/arielmission-space/ExoSim2-public)
93
87
 
94
88
 
95
89
  ## Introduction <a name="introduction"></a>
@@ -145,6 +139,14 @@ If you have installed ExoSim from source code, to test your ExoSim 2 installatio
145
139
 
146
140
  ExoSim2 comes with an extensive documentation, which can be built using Sphinx.
147
141
  The documentation includes a tutorial, a user guide and a reference guide.
142
+ The published documentation is at <https://exosim2-public.readthedocs.io/>.
143
+
144
+ An AI-generated overview of the codebase is also available on
145
+ [DeepWiki](https://deepwiki.com/arielmission-space/ExoSim2-public), and an
146
+ interactive architecture diagram on
147
+ [GitDiagram](https://gitdiagram.com/arielmission-space/ExoSim2-public). Both are
148
+ generated automatically from the code and may be out of date or inaccurate: the
149
+ documentation is the reference.
148
150
 
149
151
  To build the documentation, install the needed packages first via `uv`:
150
152
 
@@ -10,6 +10,8 @@
10
10
  [![DOI](https://img.shields.io/badge/doi-10.1007%2Fs10686--024--09976--2-blue?link=https%3A%2F%2Fdoi.org%2F10.1007%2Fs10686-024-09976-2)](https://doi.org/10.1007/s10686-024-09976-2)
11
11
  [![arXiv](https://img.shields.io/badge/arXiv-2501.12809-red?link=https%3A%2F%2Farxiv.org%2Fabs%2F2501.12809)](https://arxiv.org/abs/2501.12809)
12
12
  [![EMACS](https://img.shields.io/badge/EMAC-2504--003-blue)](https://emacs.gsfc.nasa.gov?cid=2504-003)
13
+ [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Ask-0a7bbb)](https://deepwiki.com/arielmission-space/ExoSim2-public)
14
+ [![GitDiagram](https://img.shields.io/badge/GitDiagram-architecture-4c1)](https://gitdiagram.com/arielmission-space/ExoSim2-public)
13
15
 
14
16
 
15
17
  ## Introduction <a name="introduction"></a>
@@ -65,6 +67,14 @@ If you have installed ExoSim from source code, to test your ExoSim 2 installatio
65
67
 
66
68
  ExoSim2 comes with an extensive documentation, which can be built using Sphinx.
67
69
  The documentation includes a tutorial, a user guide and a reference guide.
70
+ The published documentation is at <https://exosim2-public.readthedocs.io/>.
71
+
72
+ An AI-generated overview of the codebase is also available on
73
+ [DeepWiki](https://deepwiki.com/arielmission-space/ExoSim2-public), and an
74
+ interactive architecture diagram on
75
+ [GitDiagram](https://gitdiagram.com/arielmission-space/ExoSim2-public). Both are
76
+ generated automatically from the code and may be out of date or inaccurate: the
77
+ documentation is the reference.
68
78
 
69
79
  To build the documentation, install the needed packages first via `uv`:
70
80
 
@@ -15,14 +15,14 @@ Full guide: `docs/source/contributing/releasing.rst`
15
15
  nox -s release
16
16
  ```
17
17
 
18
- It asks major/minor/patch, folds the changelog fragments in, updates
19
- `CITATION.cff` / `codemeta.json`, commits, fast-forwards `main`, and pushes the
20
- `vX.Y.Z` tag.
18
+ It asks major/minor/patch **and a short release name**, folds the changelog
19
+ fragments in, updates `CITATION.cff` / `codemeta.json`, commits, fast-forwards
20
+ `main`, and pushes the `vX.Y.Z` tag.
21
21
 
22
22
  - The tag triggers `.github/workflows/release.yml`:
23
- build → TestPyPI → verify install → PyPI → GitHub Release.
24
- Then `sync-to-public.yml` mirrors `main` to `ExoSim2-public` and copies the
25
- release there.
23
+ build → TestPyPI → verify install → PyPI → GitHub Release → clone of the
24
+ tag + release + wheels into `ExoSim2-public`.
25
+ `sync-to-public.yml` mirrors the `main` code on every push.
26
26
 
27
27
  ## One-time setup
28
28
 
@@ -40,12 +40,12 @@
40
40
  "license": "https://spdx.org/licenses/BSD-3-Clause",
41
41
  "name": "ExoSim2",
42
42
  "programmingLanguage": "Python 3",
43
- "version": "2.2.0",
43
+ "version": "2.2.2",
44
44
  "codemeta:contIntegration": {
45
45
  "id": "https://app.codecov.io/gh/arielmission-space/ExoSim2-public"
46
46
  },
47
47
  "continuousIntegration": "https://app.codecov.io/gh/arielmission-space/ExoSim2-public",
48
48
  "issueTracker": "https://github.com/arielmission-space/ExoSim2-public/issues",
49
49
  "referencePublication": "https://link.springer.com/article/10.1007/s10686-024-09976-2",
50
- "dateModified": "2026-08-27"
50
+ "dateModified": "2026-11-12"
51
51
  }
@@ -84,18 +84,10 @@ dev = [
84
84
 
85
85
  docs = [
86
86
  "sphinx>=8.1.3",
87
- "sphinx-rtd-theme>=3.0.2",
88
- "sphinxcontrib-napoleon",
89
- "sphinxcontrib-jsmath",
90
- "nbsphinx",
91
- "sphinx-panels",
87
+ "pydata-sphinx-theme",
92
88
  "sphinx-autoapi",
93
89
  "sphinx-autobuild",
94
90
  "sphinx-design",
95
- "sphinxcontrib-serializinghtml",
96
- "docutils",
97
- "pydata-sphinx-theme",
98
- "myst-parser>=4.0.0",
99
91
  ]
100
92
 
101
93
  jupyter = [
@@ -323,6 +315,10 @@ exclude_lines = [
323
315
  "if __name__ == \\.__main__\\.",
324
316
  "class .*\\bProtocol\\):",
325
317
  "@(abc\\.)?abstractmethod",
318
+ # numba-compiled kernels: coverage.py cannot trace into JIT-compiled code,
319
+ # so the decorated function bodies are excluded. They are still exercised
320
+ # behaviourally by the detector, sub-exposure and instrument test suites.
321
+ "^\\s*@(jit|njit)\\(",
326
322
  ]
327
323
  ignore_errors = true
328
324
  show_missing = true
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """Read the CHANGELOG.rst entry for a given version (used to build release notes).
3
+
4
+ Usage:
5
+ python scripts/changelog_extract.py 2.2.0 # print the entry body
6
+ python scripts/changelog_extract.py 2.2.0 --title # print just the title
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ CHANGELOG = Path(__file__).resolve().parent.parent / "CHANGELOG.rst"
15
+
16
+
17
+ def _find_heading(lines: list[str], version: str) -> int | None:
18
+ for i, line in enumerate(lines):
19
+ if line.startswith((f"[{version}]", f"[{version}_]")):
20
+ return i
21
+ return None
22
+
23
+
24
+ def extract_title(version: str) -> str:
25
+ """Return the human part of the heading, e.g. "CLI fixes" from
26
+ ``[2.1.1_] - CLI fixes``. Falls back to the bare version."""
27
+ lines = CHANGELOG.read_text().splitlines()
28
+ i = _find_heading(lines, version)
29
+ if i is None:
30
+ return version
31
+ heading = lines[i]
32
+ _, _, rest = heading.partition("] - ")
33
+ rest = rest.strip()
34
+ return rest or version
35
+
36
+
37
+ def extract_body(version: str) -> str:
38
+ lines = CHANGELOG.read_text().splitlines()
39
+ start = _find_heading(lines, version)
40
+ if start is None:
41
+ return f"Release {version}.\n"
42
+
43
+ body: list[str] = []
44
+ for line in lines[start + 2 :]: # skip heading + its underline
45
+ stripped = line.strip()
46
+ if stripped.startswith("[") and "]" in stripped:
47
+ break
48
+ if stripped.startswith((".. _", ".. scriv")):
49
+ break
50
+ body.append(line)
51
+
52
+ while body and not body[0].strip():
53
+ body.pop(0)
54
+ while body and not body[-1].strip():
55
+ body.pop()
56
+ return "\n".join(body) + "\n"
57
+
58
+
59
+ def main() -> int:
60
+ args = sys.argv[1:]
61
+ want_title = "--title" in args
62
+ args = [a for a in args if a != "--title"]
63
+ if len(args) != 1:
64
+ sys.stderr.write("usage: changelog_extract.py <version> [--title]\n")
65
+ return 2
66
+ version = args[0]
67
+ sys.stdout.write(extract_title(version) if want_title else extract_body(version))
68
+ if want_title:
69
+ sys.stdout.write("\n")
70
+ return 0
71
+
72
+
73
+ if __name__ == "__main__":
74
+ raise SystemExit(main())
@@ -140,6 +140,15 @@ def choose_part(cli_part: str | None) -> str:
140
140
  print(" please answer 'major', 'minor' or 'patch'")
141
141
 
142
142
 
143
+ def choose_title() -> str:
144
+ """Ask for a short human name for the release (e.g. 'CLI fixes')."""
145
+ while True:
146
+ title = input("Release name (short, e.g. 'CLI fixes'): ").strip()
147
+ if title:
148
+ return title
149
+ print(" a release name is required")
150
+
151
+
143
152
  # --------------------------------------------------------------------------- #
144
153
  # Quality gate
145
154
  # --------------------------------------------------------------------------- #
@@ -154,20 +163,33 @@ def quality_gate() -> None:
154
163
  # --------------------------------------------------------------------------- #
155
164
  # Changelog + metadata
156
165
  # --------------------------------------------------------------------------- #
157
- def collect_changelog(version: str) -> None:
158
- run("uv", "run", "scriv", "collect", "--version", version)
166
+ def collect_changelog(version: str, title: str) -> None:
167
+ heading = f"[{version}_] - {title}"
159
168
 
160
169
  changelog = REPO_ROOT / "CHANGELOG.rst"
170
+ if f"[{version}_]" in changelog.read_text():
171
+ raise ReleaseError(f"CHANGELOG.rst already has an entry for {version}")
172
+
173
+ # scriv >= 1.8 refuses --version together with --title. We want a named
174
+ # heading (not the date-based default from ``entry_title_template``), so we
175
+ # pass only --title and add the ``.. _changelog-<version>:`` cross-reference
176
+ # anchor ourselves -- scriv only emits that on the --version code path.
177
+ run("uv", "run", "scriv", "collect", "--title", heading)
178
+
161
179
  text = changelog.read_text()
162
180
 
181
+ anchor = f".. _changelog-{version}:"
182
+ if anchor not in text:
183
+ text = text.replace(f"{heading}\n", f"{anchor}\n\n{heading}\n", 1)
184
+
163
185
  link = f".. _{version}: {TAG_URL.format(version=version)}\n"
164
186
  if link not in text:
165
187
  marker = "\n.. _keepachangelog:"
166
188
  if marker not in text:
167
189
  raise ReleaseError("could not find the link block in CHANGELOG.rst")
168
190
  text = text.replace(marker, f"\n{link}{marker}", 1)
169
- changelog.write_text(text)
170
191
 
192
+ changelog.write_text(text)
171
193
  (REPO_ROOT / "docs" / "source" / "CHANGELOG.rst").write_text(text)
172
194
 
173
195
 
@@ -204,10 +226,10 @@ def update_codemeta(version: str, today: str) -> None:
204
226
  # --------------------------------------------------------------------------- #
205
227
  # Git release
206
228
  # --------------------------------------------------------------------------- #
207
- def commit_and_tag(version: str) -> None:
229
+ def commit_and_tag(version: str, title: str) -> None:
208
230
  tag = f"v{version}"
209
231
  git("add", "-A", capture=False)
210
- git("commit", "-m", f"docs: release {version}", capture=False)
232
+ git("commit", "-m", f"docs: release {version} — {title}", capture=False)
211
233
 
212
234
  print("\nChanges staged for the release commit:")
213
235
  print(git("show", "--stat", "HEAD"))
@@ -240,11 +262,17 @@ def commit_and_tag(version: str) -> None:
240
262
  f"git push {REMOTE} {tag}"
241
263
  )
242
264
 
243
- git("tag", "-a", tag, "-m", f"Release {version}", capture=False)
265
+ git("tag", "-a", tag, "-m", f"{version} — {title}", capture=False)
244
266
  git("push", REMOTE, tag, capture=False)
245
267
 
246
- print(f"\n✅ Tag {tag} pushed. Release pipeline: {ACTIONS_URL}")
247
- print(f" GitHub Release will appear at: {TAG_URL.format(version=version)}")
268
+ print(f"\n✅ Tag {tag} pushed. The release pipeline now runs:")
269
+ print(f" {ACTIONS_URL}")
270
+ print(" build → TestPyPI → verify → PyPI → GitHub Release →")
271
+ print(
272
+ " clone of the tag + release + wheels into arielmission-space/ExoSim2-public"
273
+ )
274
+ print(f"\n GitHub Release: {TAG_URL.format(version=version)}")
275
+ print(f" PyPI: https://pypi.org/project/exosim/{version}/")
248
276
 
249
277
 
250
278
  # --------------------------------------------------------------------------- #
@@ -271,8 +299,9 @@ def main(argv: list[str] | None = None) -> int:
271
299
  current = latest_stable()
272
300
  part = choose_part(args.part)
273
301
  version = next_version(current, part)
302
+ title = choose_title()
274
303
  cur_str = ".".join(str(x) for x in current)
275
- print(f"\nRelease: {cur_str} -> {version} ({part})")
304
+ print(f"\nRelease: {cur_str} -> {version} ({part}) — {title!r}")
276
305
  if not confirm("Proceed?"):
277
306
  raise ReleaseError("cancelled")
278
307
 
@@ -282,10 +311,10 @@ def main(argv: list[str] | None = None) -> int:
282
311
  quality_gate()
283
312
 
284
313
  today = dt.date.today().isoformat()
285
- collect_changelog(version)
314
+ collect_changelog(version, title)
286
315
  update_citation(version, today)
287
316
  update_codemeta(version, today)
288
- commit_and_tag(version)
317
+ commit_and_tag(version, title)
289
318
  except ReleaseError as exc:
290
319
  print(f"\n✗ {exc}", file=sys.stderr)
291
320
  return 1
@@ -165,7 +165,10 @@ def _get_git_info() -> tuple[str | None, str | None]:
165
165
  tuple: (branch_name, commit_hash) or (None, None) if not in git repo
166
166
  """
167
167
  try:
168
- base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
168
+ # __file__ is <root>/src/exosim/__about__.py -> walk up to <root>
169
+ base_dir = os.path.dirname(
170
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
171
+ )
169
172
  except NameError:
170
173
  return None, None
171
174
 
@@ -295,13 +295,19 @@ class Channel(log.Logger):
295
295
  target = [
296
296
  source
297
297
  for source, param in self.sources.items()
298
- if "source_target" in param.metadata["parsed_parameters"]
299
- and param.metadata["parsed_parameters"]["source_target"]
298
+ if param.metadata.get("parsed_parameters", {}).get("source_target")
300
299
  ]
300
+ if not target:
301
+ self.error(
302
+ "Multiple sources are defined but none is flagged as the science "
303
+ "target. Set 'source_target' on one source in the input file."
304
+ )
305
+ raise KeyError("no source is flagged as 'source_target'")
301
306
  if len(target) > 1:
302
307
  self.error(
303
308
  "More than one target source found. Please check your input file."
304
309
  )
310
+ raise KeyError("more than one source is flagged as 'source_target'")
305
311
  self.debug(f"Target source is {target[0]}")
306
312
  return target[0]
307
313