Skip to content

Conversation

@jvshields
Copy link
Contributor

@jvshields jvshields commented Aug 6, 2025

This makes everywhere in tardis use the same sorting algorithm set as a global. Everywhere was using quicksort by default which we don't really want, but this enforces that everywhere so we can make another pr that changes everywhere to stable so we can see those differences.

The regression data in the PR below makes the v_inner_workflow tests pass for this PR (at least on mac) by generating regression data from this PR.
tardis-sn/tardis-regression-data#72

Copilot AI review requested due to automatic review settings August 6, 2025 19:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR systematically updates all sorting operations throughout the TARDIS codebase to use stable sorting algorithms. The change aims to resolve potential cross-platform differences between Mac and Linux by ensuring consistent ordering of elements with equal sort keys.

  • Replaced all instances of np.argsort(), np.sort(), and DataFrame.sort_*() methods with their stable equivalents
  • Added kind="stable" parameter to ensure deterministic sorting behavior across platforms
  • Also includes minor code formatting improvements (line breaks, string concatenation fixes)

Reviewed Changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tardis/workflows/util.py Updated numpy argsort to use stable sorting
tardis/visualization/widgets/line_info.py Updated pandas sort_values and numpy sort operations to stable, plus code formatting
tardis/visualization/widgets/custom_abundance.py Updated pandas sort_index calls to stable sorting
tardis/visualization/tools/sdec_plot.py Updated pandas sort_values and numpy sort to stable
tardis/transport/montecarlo/estimators/util.py Updated pandas sort_index to stable
tardis/plasma/properties/continuum_processes/rates.py Updated pandas sort_index to stable
tardis/plasma/properties/atomic.py Updated pandas sort_values to stable
tardis/plasma/equilibrium/rates/radiative_rates.py Updated pandas sort_index to stable
tardis/plasma/equilibrium/rates/collisional_rates.py Updated pandas sort_values and sort_index to stable
tardis/plasma/equilibrium/rates/collision_strengths.py Updated pandas sort_index to stable
tardis/opacities/macro_atom/transition_probabilities.py Updated pandas sort_index and sort_values to stable
tardis/opacities/macro_atom/macroatom_solver.py Updated pandas sort_values to stable
tardis/model/matter/decay.py Updated pandas sort_index to stable with code formatting
tardis/model/matter/composition.py Updated pandas sort_index to stable with code formatting
tardis/io/tests/test_atomic.py Updated pandas sort_index to stable
tardis/io/model/readers/arepo.py Updated numpy sort to stable
tardis/io/atom_data/base.py Updated pandas sort_values to stable
tardis/gui/widgets.py Updated list() conversion and code formatting
tardis/energy_input/tests/test_energy_source.py Updated numpy sort to stable
tardis/energy_input/samplers.py Updated numpy sort and argsort to stable with code formatting
tardis/energy_input/main_gamma_ray_loop.py Updated pandas sort_values to stable with code formatting
tardis/analysis.py Updated pandas sort_values and numpy sort to stable

"""
energy.sort()
sorted_indices = np.argsort(energy)
energy.sort(kind="stable")
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function modifies the input energy array in-place with energy.sort(), but then also calls np.argsort(energy) on the already sorted array. This is redundant and potentially incorrect - either sort in-place OR use argsort, not both. The original logic should be preserved.

Copilot uses AI. Check for mistakes.
@tardis-bot
Copy link
Contributor

tardis-bot commented Aug 6, 2025

*beep* *bop*
Hi human,
I ran ruff on the latest commit (283e9cc).
Here are the outputs produced.
Results can also be downloaded as artifacts here.
Summarised output:

Details
23	I001   	[*] unsorted-imports
9	RET505 	[*] superfluous-else-return
8	F405   	[ ] undefined-local-with-import-star-usage
8	UP008  	[ ] super-call-with-parameters
5	G004   	[ ] logging-f-string
4	E722   	[ ] bare-except
4	F401   	[*] unused-import
3	B011   	[ ] assert-false
3	C408   	[ ] unnecessary-collection-call
3	D202   	[*] blank-line-after-function
3	D209   	[*] new-line-after-last-paragraph
3	F821   	[ ] undefined-name
3	PT015  	[ ] pytest-assert-always-false
3	RUF022 	[*] unsorted-dunder-all
2	NPY201 	[ ] numpy2-deprecation
1	F403   	[ ] undefined-local-with-import-star
1	F811   	[ ] redefined-while-unused
1	FA100  	[ ] future-rewritable-type-annotation
1	FURB187	[ ] list-reverse-copy
1	G001   	[ ] logging-string-format
1	ICN001 	[ ] unconventional-import-alias
1	PIE790 	[*] unnecessary-placeholder
1	RET506 	[*] superfluous-else-raise
1	RUF021 	[*] parenthesize-chained-operators
1	UP024  	[*] os-error-alias
Found 94 errors.
[*] 49 fixable with the `--fix` option (18 hidden fixes can be enabled with the `--unsafe-fixes` option).

Complete output(might be large):

Details
tardis/analysis.py:97:9: E722 Do not use bare `except`
tardis/analysis.py:111:9: E722 Do not use bare `except`
tardis/analysis.py:122:9: E722 Do not use bare `except`
tardis/analysis.py:137:9: E722 Do not use bare `except`
tardis/analysis.py:375:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/analysis.py:397:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/analysis.py:422:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/energy_input/main_gamma_ray_loop.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/energy_input/main_gamma_ray_loop.py:31:23: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
tardis/energy_input/samplers.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/energy_input/samplers.py:155:49: NPY201 `np.trapz` will be removed in NumPy 2.0. Use `numpy.trapezoid` on NumPy 2.0, or ignore this warning on earlier versions.
tardis/energy_input/tests/test_energy_source.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/energy_input/tests/test_energy_source.py:36:5: PT015 Assertion always fails, replace with `pytest.fail()`
tardis/energy_input/tests/test_energy_source.py:36:12: B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
tardis/energy_input/tests/test_energy_source.py:42:5: PT015 Assertion always fails, replace with `pytest.fail()`
tardis/energy_input/tests/test_energy_source.py:42:12: B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
tardis/energy_input/tests/test_energy_source.py:48:5: PT015 Assertion always fails, replace with `pytest.fail()`
tardis/energy_input/tests/test_energy_source.py:48:12: B011 Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`
tardis/gui/widgets.py:7:5: I001 [*] Import block is un-sorted or un-formatted
tardis/gui/widgets.py:9:5: I001 [*] Import block is un-sorted or un-formatted
tardis/gui/widgets.py:17:1: I001 [*] Import block is un-sorted or un-formatted
tardis/gui/widgets.py:17:8: ICN001 `matplotlib` should be imported as `mpl`
tardis/gui/widgets.py:18:1: F403 `from matplotlib.figure import *` used; unable to detect undefined names
tardis/gui/widgets.py:25:32: F401 [*] `matplotlib.patches.Circle` imported but unused
tardis/gui/widgets.py:30:30: F401 [*] `tardis.util` imported but unused
tardis/gui/widgets.py:37:9: D202 [*] No blank lines allowed after function docstring (found 1)
tardis/gui/widgets.py:44:23: F405 `Figure` may be undefined, or defined from star imports
tardis/gui/widgets.py:55:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:56:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:59:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:165:27: C408 Unnecessary `dict()` call (rewrite as a literal)
tardis/gui/widgets.py:168:26: C408 Unnecessary `dict()` call (rewrite as a literal)
tardis/gui/widgets.py:169:23: C408 Unnecessary `dict()` call (rewrite as a literal)
tardis/gui/widgets.py:187:9: RET505 [*] Unnecessary `elif` after `return` statement
tardis/gui/widgets.py:199:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:227:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:230:22: F405 `yaml` may be undefined, or defined from star imports
tardis/gui/widgets.py:230:61: F405 `yaml` may be undefined, or defined from star imports
tardis/gui/widgets.py:354:24: F405 `TreeModel` may be undefined, or defined from star imports
tardis/gui/widgets.py:359:38: F405 `TreeDelegate` may be undefined, or defined from star imports
tardis/gui/widgets.py:430:35: UP024 [*] Replace aliased errors with `OSError`
tardis/gui/widgets.py:445:9: PIE790 [*] Unnecessary `pass` statement
tardis/gui/widgets.py:529:9: D202 [*] No blank lines allowed after function docstring (found 1)
tardis/gui/widgets.py:647:41: F405 `np` may be undefined, or defined from star imports
tardis/gui/widgets.py:656:9: D209 [*] Multi-line docstring closing quotes should be on a separate line
tardis/gui/widgets.py:659:41: F405 `np` may be undefined, or defined from star imports
tardis/gui/widgets.py:687:41: F405 `np` may be undefined, or defined from star imports
tardis/gui/widgets.py:814:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:850:9: D209 [*] Multi-line docstring closing quotes should be on a separate line
tardis/gui/widgets.py:926:9: D209 [*] Multi-line docstring closing quotes should be on a separate line
tardis/gui/widgets.py:928:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:1123:14: UP008 Use `super()` instead of `super(__class__, self)`
tardis/gui/widgets.py:1230:9: D202 [*] No blank lines allowed after function docstring (found 1)
tardis/io/atom_data/base.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/io/atom_data/base.py:198:34: G004 Logging statement uses f-string
tardis/io/atom_data/base.py:260:17: G004 Logging statement uses f-string
tardis/io/atom_data/base.py:264:21: G001 Logging statement uses `str.format`
tardis/io/atom_data/base.py:708:17: G004 Logging statement uses f-string
tardis/io/model/readers/arepo.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/model/matter/composition.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/model/matter/composition.py:178:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/model/matter/decay.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/model/matter/decay.py:103:21: G004 Logging statement uses f-string
tardis/opacities/macro_atom/macroatom_solver.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/opacities/macro_atom/transition_probabilities.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/opacities/macro_atom/transition_probabilities.py:16:11: RUF022 [*] `__all__` is not sorted
tardis/plasma/equilibrium/rates/collision_strengths.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/plasma/equilibrium/rates/collisional_rates.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/plasma/equilibrium/rates/radiative_rates.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/plasma/properties/atomic.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/plasma/properties/atomic.py:28:11: RUF022 [*] `__all__` is not sorted
tardis/plasma/properties/atomic.py:555:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/plasma/properties/atomic.py:586:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/plasma/properties/atomic.py:599:17: G004 Logging statement uses f-string
tardis/plasma/properties/atomic.py:640:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/plasma/properties/continuum_processes/rates.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/plasma/properties/continuum_processes/rates.py:23:11: RUF022 [*] `__all__` is not sorted
tardis/plasma/properties/continuum_processes/rates.py:157:13: FURB187 Use of assignment of `reversed` on list `idx_arrays`
tardis/transport/montecarlo/estimators/util.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/transport/montecarlo/estimators/util.py:66:32: NPY201 `np.trapz` will be removed in NumPy 2.0. Use `numpy.trapezoid` on NumPy 2.0, or ignore this warning on earlier versions.
tardis/visualization/tools/sdec_plot.py:8:1: I001 [*] Import block is un-sorted or un-formatted
tardis/visualization/widgets/custom_abundance.py:3:1: I001 [*] Import block is un-sorted or un-formatted
tardis/visualization/widgets/custom_abundance.py:741:16: RUF021 [*] Parenthesize `a and b` expressions when chaining `and` and `or` together, to make the precedence clear
tardis/visualization/widgets/custom_abundance.py:1131:9: RET505 [*] Unnecessary `else` after `return` statement
tardis/visualization/widgets/custom_abundance.py:1422:9: RET506 [*] Unnecessary `else` after `raise` statement
tardis/visualization/widgets/custom_abundance.py:1725:9: F811 Redefinition of unused `input_d_time_0_eventhandler` from line 1714
tardis/visualization/widgets/custom_abundance.py:1755:13: F821 Undefined name `display`
tardis/visualization/widgets/custom_abundance.py:1757:13: F821 Undefined name `display`
tardis/visualization/widgets/custom_abundance.py:1759:13: F821 Undefined name `display`
tardis/visualization/widgets/line_info.py:3:1: I001 [*] Import block is un-sorted or un-formatted
tardis/workflows/tests/test_workflows.py:1:1: I001 [*] Import block is un-sorted or un-formatted
tardis/workflows/tests/test_workflows.py:2:21: F401 [*] `pathlib.Path` imported but unused
tardis/workflows/tests/test_workflows.py:7:51: F401 [*] `tardis.io.configuration.config_reader.Configuration` imported but unused
tardis/workflows/util.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 94 errors.
[*] 49 fixable with the `--fix` option (18 hidden fixes can be enabled with the `--unsafe-fixes` option).

@tardis-bot
Copy link
Contributor

tardis-bot commented Aug 6, 2025

*beep* *bop*

Hi, human.

The docs workflow has succeeded ✔️

Click here to see your results.

@codecov
Copy link

codecov bot commented Aug 6, 2025

Codecov Report

❌ Patch coverage is 83.75000% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.78%. Comparing base (5d6a32e) to head (283e9cc).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
tardis/visualization/widgets/custom_abundance.py 54.54% 5 Missing ⚠️
tardis/energy_input/main_gamma_ray_loop.py 75.00% 2 Missing ⚠️
tardis/visualization/widgets/line_info.py 90.47% 2 Missing ⚠️
tardis/analysis.py 50.00% 1 Missing ⚠️
tardis/model/matter/decay.py 80.00% 1 Missing ⚠️
tardis/visualization/tools/sdec_plot.py 66.66% 1 Missing ⚠️
tardis/workflows/util.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3223      +/-   ##
==========================================
- Coverage   67.96%   66.78%   -1.18%     
==========================================
  Files         172      173       +1     
  Lines       13211    13230      +19     
==========================================
- Hits         8979     8836     -143     
- Misses       4232     4394     +162     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tardis-bot
Copy link
Contributor

tardis-bot commented Aug 6, 2025

*beep* *bop*
Hi human,
I ran benchmarks as you asked comparing master (5d6a32e) and the latest commit (283e9cc).
Here are the logs produced by ASV.
Results can also be downloaded as artifacts here.

Significantly changed benchmarks:

Details

All benchmarks:

Details
Benchmarks that have stayed the same:

| Change   | Before [5d6a32e3] <master>   | After [283e9cc5]    | Ratio   | Benchmark (Parameter)                                                                                                               |
|----------|------------------------------|---------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------|
|          | 551±200ns                    | 812±100ns           | ~1.47   | opacities_opacity.BenchmarkMontecarloMontecarloNumbaOpacities.time_pair_creation_opacity_calculation                                |
|          | 41.4±30μs                    | 58.3±20μs           | ~1.41   | transport_montecarlo_interaction.BenchmarkTransportMontecarloInteraction.time_line_emission                                         |
|          | 45.5±20μs                    | 57.1±20μs           | ~1.26   | transport_montecarlo_interaction.BenchmarkTransportMontecarloInteraction.time_line_scatter                                          |
|          | 1.28±0.4μs                   | 1.44±0.2μs          | ~1.12   | transport_geometry_calculate_distances.BenchmarkTransportGeometryCalculateDistances.time_calculate_distance_line                    |
|          | 6.55±3μs                     | 7.22±3μs            | ~1.10   | transport_montecarlo_vpacket.BenchmarkMontecarloMontecarloNumbaVpacket.time_trace_vpacket_volley                                    |
|          | 29.2±8μs                     | 26.2±7μs            | ~0.90   | transport_montecarlo_packet_trackers.BenchmarkTransportMontecarloPacketTrackers.time_generate_rpacket_last_interaction_tracker_list |
|          | 642±200ns                    | 571±100ns           | ~0.89   | opacities_opacity.BenchmarkMontecarloMontecarloNumbaOpacities.time_photoabsorption_opacity_calculation                              |
|          | 3.42±0.4μs                   | 2.91±0.3μs          | ~0.85   | transport_montecarlo_vpacket.BenchmarkMontecarloMontecarloNumbaVpacket.time_trace_bad_vpacket                                       |
|          | 58.2±0.03s                   | 1.07±0m             | 1.10    | run_tardis.BenchmarkRunTardis.time_run_tardis_rpacket_tracking                                                                      |
|          | 5.97±0.5μs                   | 6.48±0.9μs          | 1.09    | transport_montecarlo_vpacket.BenchmarkMontecarloMontecarloNumbaVpacket.time_trace_vpacket                                           |
|          | 4.65±0.04ms                  | 4.98±0.03ms         | 1.07    | opacities_opacity_state.BenchmarkOpacitiesOpacityState.time_opacity_state_initialize('macroatom')                                   |
|          | 2.82±0.5μs                   | 3.03±0.3μs          | 1.07    | transport_montecarlo_vpacket.BenchmarkMontecarloMontecarloNumbaVpacket.time_trace_vpacket_within_shell                              |
|          | 58.2±0.1ms                   | 60.3±0.2ms          | 1.04    | transport_montecarlo_packet_trackers.BenchmarkTransportMontecarloPacketTrackers.time_rpacket_trackers_to_dataframe                  |
|          | 1.16±0.01μs                  | 1.19±0μs            | 1.03    | transport_geometry_calculate_distances.BenchmarkTransportGeometryCalculateDistances.time_calculate_distance_boundary                |
|          | 36.5±0.1s                    | 37.2±0.04s          | 1.02    | run_tardis.BenchmarkRunTardis.time_run_tardis                                                                                       |
|          | 657±0.2ns                    | 667±1ns             | 1.02    | transport_montecarlo_interaction.BenchmarkTransportMontecarloInteraction.time_thomson_scatter                                       |
|          | 2.72±0.3ms                   | 2.76±0.3ms          | 1.02    | transport_montecarlo_single_packet_loop.BenchmarkTransportMontecarloSinglePacketLoop.time_single_packet_loop                        |
|          | 1.05±0m                      | 1.05±0m             | 1.00    | spectrum_formal_integral.BenchmarkTransportMontecarloFormalIntegral.time_FormalIntegrator_functions                                 |
|          | 1.14±0μs                     | 1.14±0.02μs         | 1.00    | spectrum_formal_integral.BenchmarkTransportMontecarloFormalIntegral.time_intensity_black_body                                       |
|          | 2.53±0ms                     | 2.44±0.01ms         | 0.96    | transport_montecarlo_main_loop.BenchmarkTransportMontecarloMontecarloMainLoop.time_montecarlo_main_loop                             |
|          | 40.1±0.09μs                  | 38.1±0.01μs         | 0.95    | transport_montecarlo_packet_trackers.BenchmarkTransportMontecarloPacketTrackers.time_generate_rpacket_tracker_list                  |
|          | 2.08±1μs                     | 1.95±0.9μs          | 0.94    | transport_montecarlo_estimators_radfield_estimator_calcs.BenchmarkMontecarloMontecarloNumbaPacket.time_update_line_estimators       |
|          | 541±100ns                    | 501±100ns           | 0.93    | opacities_opacity.BenchmarkMontecarloMontecarloNumbaOpacities.time_compton_opacity_calculation                                      |
|          | 3.35±0.03ms                  | 3.05±0.01ms         | 0.91    | opacities_opacity_state.BenchmarkOpacitiesOpacityState.time_opacity_state_initialize('scatter')                                     |

If you want to see the graph of the results, you can check it here

@jvshields
Copy link
Contributor Author

I think we expect this to change the regression data, but probably should be regenerated for this if we're happy with it for more consistency between platforms.

@tardis-bot
Copy link
Contributor

tardis-bot commented Aug 20, 2025

Regression Data Comparison Results

The comparison results have been generated and can be viewed here:

📊 View Comparison Plots

macOS (not continuum)

Spectrum Comparison
Same Name Differences
Key Differences

macOS (continuum)

Spectrum Comparison
Same Name Differences
Key Differences

Ubuntu (not continuum)

Spectrum Comparison
Same Name Differences
Key Differences

Ubuntu (continuum)

Spectrum Comparison
Same Name Differences
Key Differences

You can also download the artifacts directly from the Actions tab.

@jvshields jvshields added run-regression-comparison runs regression comparison workflow and removed run-regression-comparison runs regression comparison workflow labels Aug 28, 2025
index = plasma.atomic_data.lines.nu.index
freqs = plasma.atomic_data.lines.nu.values * u.Hz
order = np.argsort(freqs, kind="stable")
order = np.argsort(freqs, kind=SORTING_ALGORITHM)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one change that breaks the test, because it's the one place stable was applied.

@jvshields jvshields changed the title use stable sort Standardize sorting with a global Sep 2, 2025
@andrewfullard andrewfullard enabled auto-merge (squash) September 2, 2025 20:16
@andrewfullard andrewfullard merged commit 8a33616 into tardis-sn:master Sep 2, 2025
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build-docs run-regression-comparison runs regression comparison workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants