88
99The settings are process-wide (file-scope globals in orbit_fit.cpp), so each
1010test saves and restores the prior value to avoid leaking state into other
11- tests in the same process.
11+ tests in the same process. The one fit-based test,
12+ ``test_fit_is_mode_independent``, is guarded by ``requires_ephem``.
1213"""
1314
1415from __future__ import annotations
1516
1617import contextlib
1718
19+ import numpy as np
1820import pytest
1921
22+ from _bk_guards import requires_ephem
23+ from layup .orbitfit import orbitfit
2024from layup .routines import (
2125 get_ias15_adaptive_mode ,
2226 get_ias15_min_dt ,
2327 set_ias15_adaptive_mode ,
2428 set_ias15_min_dt ,
2529)
30+ from layup .utilities .data_processing_utilities import parse_fit_result
31+ from layup .utilities .data_utilities_for_tests import get_test_filepath
32+ from layup .utilities .file_io .CSVReader import CSVDataReader
2633
2734
2835@contextlib .contextmanager
@@ -65,9 +72,11 @@ def test_min_dt_reset_to_zero():
6572 assert get_ias15_min_dt () == 0.0
6673
6774
68- def test_adaptive_mode_default_is_unset ():
69- """By default adaptive_mode is -1, the sentinel meaning 'do not override'."""
70- assert get_ias15_adaptive_mode () == - 1
75+ def test_adaptive_mode_default_is_two ():
76+ """The default adaptive_mode is 2, the modern (Pham, Rein & Spiegel 2024)
77+ IAS15 step controller, engaged on every freshly-attached sim so close-Earth
78+ encounters do not drive the timestep to zero and hang a fit."""
79+ assert get_ias15_adaptive_mode () == 2
7180
7281
7382@pytest .mark .parametrize ("mode" , [0 , 1 , 2 , 3 ])
@@ -85,3 +94,29 @@ def test_adaptive_mode_reset_to_sentinel():
8594 assert get_ias15_adaptive_mode () == 2
8695 set_ias15_adaptive_mode (- 1 )
8796 assert get_ias15_adaptive_mode () == - 1
97+
98+
99+ @requires_ephem
100+ def test_fit_is_mode_independent ():
101+ """The IAS15 adaptive controller changes only whether/how fast a fit
102+ completes, never the fitted orbit. A well-behaved object fit under the
103+ legacy controller (mode 1) and the modern controller (mode 2, the default)
104+ returns the same state (bit-for-bit for a well-conditioned arc)."""
105+ input_data = CSVDataReader (
106+ get_test_filepath ("1_random_mpc_ADES_provIDs_no_sats_micro.csv" ),
107+ "csv" ,
108+ primary_id_column_name = "provID" ,
109+ ).read_rows ()
110+
111+ def _fit_state (mode ):
112+ with _restore_adaptive_mode ():
113+ set_ias15_adaptive_mode (mode )
114+ rows = orbitfit (input_data , cache_dir = None )
115+ for row in rows :
116+ if row ["flag" ] == 0 :
117+ return np .array (parse_fit_result (row ).state , dtype = float )
118+ raise AssertionError ("fit did not converge (flag != 0)" )
119+
120+ legacy = _fit_state (1 )
121+ modern = _fit_state (2 )
122+ np .testing .assert_allclose (modern , legacy , rtol = 1e-10 , atol = 1e-12 )
0 commit comments