orblet 0.1.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 (68) hide show
  1. orblet-0.1.0/LICENSE +21 -0
  2. orblet-0.1.0/PKG-INFO +140 -0
  3. orblet-0.1.0/README.md +100 -0
  4. orblet-0.1.0/pyproject.toml +70 -0
  5. orblet-0.1.0/setup.cfg +4 -0
  6. orblet-0.1.0/src/orblet/__init__.py +207 -0
  7. orblet-0.1.0/src/orblet/chain_stats.py +194 -0
  8. orblet-0.1.0/src/orblet/constants.py +307 -0
  9. orblet-0.1.0/src/orblet/design/__init__.py +53 -0
  10. orblet-0.1.0/src/orblet/design/builder.py +461 -0
  11. orblet-0.1.0/src/orblet/design/columns.py +453 -0
  12. orblet-0.1.0/src/orblet/elements.py +934 -0
  13. orblet-0.1.0/src/orblet/interpret/__init__.py +20 -0
  14. orblet-0.1.0/src/orblet/interpret/amrf.py +1178 -0
  15. orblet-0.1.0/src/orblet/interpret/astrometric_upper_limit.py +262 -0
  16. orblet-0.1.0/src/orblet/interpret/companion_mass.py +758 -0
  17. orblet-0.1.0/src/orblet/interpret/flux_ratio.py +724 -0
  18. orblet-0.1.0/src/orblet/kepler.py +222 -0
  19. orblet-0.1.0/src/orblet/likelihood.py +335 -0
  20. orblet-0.1.0/src/orblet/model.py +1082 -0
  21. orblet-0.1.0/src/orblet/parallax.py +258 -0
  22. orblet-0.1.0/src/orblet/periodogram.py +875 -0
  23. orblet-0.1.0/src/orblet/plotting.py +1315 -0
  24. orblet-0.1.0/src/orblet/prepare.py +370 -0
  25. orblet-0.1.0/src/orblet/priors.py +715 -0
  26. orblet-0.1.0/src/orblet/residuals.py +225 -0
  27. orblet-0.1.0/src/orblet/rv_chain.py +346 -0
  28. orblet-0.1.0/src/orblet/sampling.py +544 -0
  29. orblet-0.1.0/src/orblet/search.py +686 -0
  30. orblet-0.1.0/src/orblet/simulate/__init__.py +19 -0
  31. orblet-0.1.0/src/orblet/simulate/bundles.py +277 -0
  32. orblet-0.1.0/src/orblet/simulate/cadence.py +331 -0
  33. orblet-0.1.0/src/orblet/simulate/orbit.py +470 -0
  34. orblet-0.1.0/src/orblet/solve/__init__.py +24 -0
  35. orblet-0.1.0/src/orblet/solve/astrometry.py +1020 -0
  36. orblet-0.1.0/src/orblet/solve/rv.py +799 -0
  37. orblet-0.1.0/src/orblet.egg-info/PKG-INFO +140 -0
  38. orblet-0.1.0/src/orblet.egg-info/SOURCES.txt +66 -0
  39. orblet-0.1.0/src/orblet.egg-info/dependency_links.txt +1 -0
  40. orblet-0.1.0/src/orblet.egg-info/requires.txt +26 -0
  41. orblet-0.1.0/src/orblet.egg-info/top_level.txt +1 -0
  42. orblet-0.1.0/tests/test_a0_independent_orbit_oracle.py +720 -0
  43. orblet-0.1.0/tests/test_a0_joint_oracle.py +567 -0
  44. orblet-0.1.0/tests/test_a0_joint_twoamp_oracle.py +1034 -0
  45. orblet-0.1.0/tests/test_campbell_primary_frame_noop.py +214 -0
  46. orblet-0.1.0/tests/test_companion_mass_core.py +91 -0
  47. orblet-0.1.0/tests/test_design_builder_equivalence_rv.py +57 -0
  48. orblet-0.1.0/tests/test_design_builder_equivalence_ti.py +86 -0
  49. orblet-0.1.0/tests/test_design_builder_protocol.py +113 -0
  50. orblet-0.1.0/tests/test_design_builder_singlestar_block.py +66 -0
  51. orblet-0.1.0/tests/test_design_columns_baseline.py +147 -0
  52. orblet-0.1.0/tests/test_forward_model_baseline.py +157 -0
  53. orblet-0.1.0/tests/test_joint_epoch_contract.py +155 -0
  54. orblet-0.1.0/tests/test_kepler.py +301 -0
  55. orblet-0.1.0/tests/test_kepler_basis_tperi_contract.py +71 -0
  56. orblet-0.1.0/tests/test_likelihood_baseline.py +118 -0
  57. orblet-0.1.0/tests/test_manual_doc_anchors.py +206 -0
  58. orblet-0.1.0/tests/test_obs_time_j2010_invariant.py +43 -0
  59. orblet-0.1.0/tests/test_orbit_plotting.py +201 -0
  60. orblet-0.1.0/tests/test_orblet_optional_dependencies.py +336 -0
  61. orblet-0.1.0/tests/test_packaging.py +152 -0
  62. orblet-0.1.0/tests/test_pdc.py +301 -0
  63. orblet-0.1.0/tests/test_postprocess.py +493 -0
  64. orblet-0.1.0/tests/test_prior_spec_parser_contract.py +146 -0
  65. orblet-0.1.0/tests/test_priors.py +67 -0
  66. orblet-0.1.0/tests/test_public_composable_api.py +199 -0
  67. orblet-0.1.0/tests/test_rv_sign_convention.py +99 -0
  68. orblet-0.1.0/tests/test_simulator_python_roundtrip.py +541 -0
orblet-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sahar Shahaf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
orblet-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: orblet
3
+ Version: 0.1.0
4
+ Summary: Atoms for Keplerian orbit analysis: forward models, likelihoods, design matrices and linear solves, period search, element conversion. Arrays in, results out.
5
+ Author-email: Sahar Shahaf <sahar.shahaf@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/saharsh1/orblet
8
+ Project-URL: Issues, https://github.com/saharsh1/orblet/issues
9
+ Keywords: astronomy,binary-stars,orbits,astrometry,radial-velocity,kepler
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
16
+ Requires-Python: <3.13,>=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy<3,>=2
20
+ Requires-Dist: scipy<2,>=1.14
21
+ Requires-Dist: astropy<8,>=7
22
+ Requires-Dist: matplotlib<4,>=3.8
23
+ Provides-Extra: sampling
24
+ Requires-Dist: emcee<4,>=3.1; extra == "sampling"
25
+ Provides-Extra: plots
26
+ Requires-Dist: corner<3,>=2.2; extra == "plots"
27
+ Provides-Extra: tables
28
+ Requires-Dist: pandas<4,>=2; extra == "tables"
29
+ Provides-Extra: progress
30
+ Requires-Dist: tqdm>=4.60; extra == "progress"
31
+ Provides-Extra: all
32
+ Requires-Dist: emcee<4,>=3.1; extra == "all"
33
+ Requires-Dist: corner<3,>=2.2; extra == "all"
34
+ Requires-Dist: pandas<4,>=2; extra == "all"
35
+ Requires-Dist: tqdm>=4.60; extra == "all"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=7; extra == "dev"
38
+ Requires-Dist: build>=1.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # orblet <img src="https://raw.githubusercontent.com/saharsh1/orblet/main/docs/assets/orblet.png" alt="orblet" height="44" align="right">
42
+
43
+ Atoms for Keplerian orbit analysis. Forward models, likelihoods, design
44
+ matrices and linear solves, period search, element conversion, plotting —
45
+ each a function that takes arrays and returns a result, with its units,
46
+ frames and assumptions stated in its docstring.
47
+
48
+ orblet is **not a solver**. It does not know what a Gaia epoch is, does not
49
+ read files, and does not contact the network. You build the pipeline; orblet
50
+ supplies the pieces.
51
+
52
+ ```python
53
+ from orblet import ti_design_matrix, linear_solve_ti, ti_to_kepler
54
+
55
+ X = ti_design_matrix(t_mjd, psi_rad, parallax_factor_al, P_days, e, tau, epoch_ref_mjd)
56
+ fit = linear_solve_ti(along_scan_mas, sigma_mas, X)
57
+ elements = ti_to_kepler({"A": fit.beta[0], "B": fit.beta[1], "F": fit.beta[2], "G": fit.beta[3]})
58
+ ```
59
+
60
+ ## Install
61
+
62
+ orblet is not on PyPI yet. Install it from the repository:
63
+
64
+ ```
65
+ pip install "orblet[all] @ git+https://github.com/saharsh1/orblet.git"
66
+ ```
67
+
68
+ or from a local clone, which is the right choice while you are editing it:
69
+
70
+ ```
71
+ git clone https://github.com/saharsh1/orblet.git
72
+ pip install -e "./orblet[all]"
73
+ ```
74
+
75
+ Drop `[all]` for the four core dependencies only — numpy, scipy, astropy,
76
+ matplotlib — which is all a fresh install needs to run the atoms and the
77
+ quickstarts. The extras (`emcee`, `corner`, `pandas`, `tqdm`; also available
78
+ one at a time as `[sampling]`, `[plots]`, `[tables]`, `[progress]`) are each
79
+ imported inside the one function that uses them, so a missing extra fails
80
+ that call with a message naming it and leaves everything else working.
81
+
82
+ ## What is here
83
+
84
+ | | |
85
+ |---|---|
86
+ | `orblet.model` | forward models: RV curve, along-scan astrometry, Thiele-Innes and Campbell photocentre orbits |
87
+ | `orblet.likelihood` | Gaussian log-likelihoods with jitter, per channel |
88
+ | `orblet.kepler` | the Kepler-equation solver |
89
+ | `orblet.design` / `orblet.solve` | design-matrix builders and the generalised-least-squares linear solves for RV and astrometry |
90
+ | `orblet.search` / `orblet.periodogram` | the Thiele-Innes frequency scan; Lomb-Scargle and phase-distance-correlation periodograms |
91
+ | `orblet.elements` | Thiele-Innes → Campbell, NSS convention, element extraction from chains |
92
+ | `orblet.priors` | prior classes and the log-prior composer |
93
+ | `orblet.sampling` / `orblet.chain_stats` | emcee helpers; quantiles and circular summaries of chains |
94
+ | `orblet.interpret` | companion mass and the astrometric mass-ratio function |
95
+ | `orblet.simulate` | a synthetic-orbit simulator and a parallax-consistent cadence, for tests and tutorials |
96
+ | `orblet.parallax` | per-direction parallax factors from a DE432s ephemeris |
97
+ | `orblet.plotting` | orbit, residual, sky-overlay and corner plots |
98
+
99
+ The front door — `from orblet import <name>` — exposes 35 names and imports
100
+ nothing heavy: `import orblet` pulls in no scipy, no matplotlib, no astropy.
101
+ Each name resolves on first use.
102
+
103
+ ## Conventions
104
+
105
+ Every public function states them in its docstring. The ones that bite:
106
+
107
+ - radial velocity: positive is receding; `omega` is the **primary's** argument of periastron
108
+ - period in **days** at the public surface (Keplerian years only inside the Kepler solver)
109
+ - `tau` is the periastron phase in `[0, 1)`; `tp = tau * P + epoch_ref_mjd`
110
+ - astrometric amplitudes `A, B, F, G` are **photocentre** amplitudes in mas, positive-amplitude convention; the parallax term enters as `parallax_mas * parallax_factor_al`, additive
111
+ - Gaia's `pmra` is already `mu_alpha*` — never apply `cos(dec)` again
112
+ - **a seed is initialisation, never a prior**: a starting point for a sampler carries no evidence
113
+
114
+ ## Four laws, each a test
115
+
116
+ 1. A public name **is** the object it claims to be — no silent wrappers.
117
+ 2. The numerical core does not move: byte-identity baselines pin the forward
118
+ models, the likelihoods and the design columns.
119
+ 3. Conventions hold: RV sign, primary-frame ω, τ → tp, time scales.
120
+ 4. orblet imports nothing outside itself, the four dependencies and the
121
+ standard library.
122
+
123
+ ## Status
124
+
125
+ Grew inside the `gaia-orbits` repository until 2026-09-24; developed here
126
+ since, and consumed there as an installed dependency.
127
+
128
+ Three simulator presets ship. `OrbitSimulator.bh1_like()` is the one the
129
+ quickstarts use, through `load_simulated_inputs(scenario="bh1")`: an
130
+ illustrative orbit on an invented sky position, with a cadence whose parallax
131
+ factor is coupled to that position, so injecting and removing the parallax
132
+ signal use the same array and closure is exact. `toy_orbit()` is a compact
133
+ short-period orbit for quick smoke tests. `bh3_like()` carries orbital numbers
134
+ rounded from a published system and is useful for a realistic-looking truth;
135
+ pair it with `DemoParallaxConsistentCadence` for anything closure-grade. The
136
+ `scenario=` parameter and the `bh1`/`toy` naming are slated to be simplified.
137
+
138
+ ## Licence
139
+
140
+ MIT.
orblet-0.1.0/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # orblet <img src="https://raw.githubusercontent.com/saharsh1/orblet/main/docs/assets/orblet.png" alt="orblet" height="44" align="right">
2
+
3
+ Atoms for Keplerian orbit analysis. Forward models, likelihoods, design
4
+ matrices and linear solves, period search, element conversion, plotting —
5
+ each a function that takes arrays and returns a result, with its units,
6
+ frames and assumptions stated in its docstring.
7
+
8
+ orblet is **not a solver**. It does not know what a Gaia epoch is, does not
9
+ read files, and does not contact the network. You build the pipeline; orblet
10
+ supplies the pieces.
11
+
12
+ ```python
13
+ from orblet import ti_design_matrix, linear_solve_ti, ti_to_kepler
14
+
15
+ X = ti_design_matrix(t_mjd, psi_rad, parallax_factor_al, P_days, e, tau, epoch_ref_mjd)
16
+ fit = linear_solve_ti(along_scan_mas, sigma_mas, X)
17
+ elements = ti_to_kepler({"A": fit.beta[0], "B": fit.beta[1], "F": fit.beta[2], "G": fit.beta[3]})
18
+ ```
19
+
20
+ ## Install
21
+
22
+ orblet is not on PyPI yet. Install it from the repository:
23
+
24
+ ```
25
+ pip install "orblet[all] @ git+https://github.com/saharsh1/orblet.git"
26
+ ```
27
+
28
+ or from a local clone, which is the right choice while you are editing it:
29
+
30
+ ```
31
+ git clone https://github.com/saharsh1/orblet.git
32
+ pip install -e "./orblet[all]"
33
+ ```
34
+
35
+ Drop `[all]` for the four core dependencies only — numpy, scipy, astropy,
36
+ matplotlib — which is all a fresh install needs to run the atoms and the
37
+ quickstarts. The extras (`emcee`, `corner`, `pandas`, `tqdm`; also available
38
+ one at a time as `[sampling]`, `[plots]`, `[tables]`, `[progress]`) are each
39
+ imported inside the one function that uses them, so a missing extra fails
40
+ that call with a message naming it and leaves everything else working.
41
+
42
+ ## What is here
43
+
44
+ | | |
45
+ |---|---|
46
+ | `orblet.model` | forward models: RV curve, along-scan astrometry, Thiele-Innes and Campbell photocentre orbits |
47
+ | `orblet.likelihood` | Gaussian log-likelihoods with jitter, per channel |
48
+ | `orblet.kepler` | the Kepler-equation solver |
49
+ | `orblet.design` / `orblet.solve` | design-matrix builders and the generalised-least-squares linear solves for RV and astrometry |
50
+ | `orblet.search` / `orblet.periodogram` | the Thiele-Innes frequency scan; Lomb-Scargle and phase-distance-correlation periodograms |
51
+ | `orblet.elements` | Thiele-Innes → Campbell, NSS convention, element extraction from chains |
52
+ | `orblet.priors` | prior classes and the log-prior composer |
53
+ | `orblet.sampling` / `orblet.chain_stats` | emcee helpers; quantiles and circular summaries of chains |
54
+ | `orblet.interpret` | companion mass and the astrometric mass-ratio function |
55
+ | `orblet.simulate` | a synthetic-orbit simulator and a parallax-consistent cadence, for tests and tutorials |
56
+ | `orblet.parallax` | per-direction parallax factors from a DE432s ephemeris |
57
+ | `orblet.plotting` | orbit, residual, sky-overlay and corner plots |
58
+
59
+ The front door — `from orblet import <name>` — exposes 35 names and imports
60
+ nothing heavy: `import orblet` pulls in no scipy, no matplotlib, no astropy.
61
+ Each name resolves on first use.
62
+
63
+ ## Conventions
64
+
65
+ Every public function states them in its docstring. The ones that bite:
66
+
67
+ - radial velocity: positive is receding; `omega` is the **primary's** argument of periastron
68
+ - period in **days** at the public surface (Keplerian years only inside the Kepler solver)
69
+ - `tau` is the periastron phase in `[0, 1)`; `tp = tau * P + epoch_ref_mjd`
70
+ - astrometric amplitudes `A, B, F, G` are **photocentre** amplitudes in mas, positive-amplitude convention; the parallax term enters as `parallax_mas * parallax_factor_al`, additive
71
+ - Gaia's `pmra` is already `mu_alpha*` — never apply `cos(dec)` again
72
+ - **a seed is initialisation, never a prior**: a starting point for a sampler carries no evidence
73
+
74
+ ## Four laws, each a test
75
+
76
+ 1. A public name **is** the object it claims to be — no silent wrappers.
77
+ 2. The numerical core does not move: byte-identity baselines pin the forward
78
+ models, the likelihoods and the design columns.
79
+ 3. Conventions hold: RV sign, primary-frame ω, τ → tp, time scales.
80
+ 4. orblet imports nothing outside itself, the four dependencies and the
81
+ standard library.
82
+
83
+ ## Status
84
+
85
+ Grew inside the `gaia-orbits` repository until 2026-09-24; developed here
86
+ since, and consumed there as an installed dependency.
87
+
88
+ Three simulator presets ship. `OrbitSimulator.bh1_like()` is the one the
89
+ quickstarts use, through `load_simulated_inputs(scenario="bh1")`: an
90
+ illustrative orbit on an invented sky position, with a cadence whose parallax
91
+ factor is coupled to that position, so injecting and removing the parallax
92
+ signal use the same array and closure is exact. `toy_orbit()` is a compact
93
+ short-period orbit for quick smoke tests. `bh3_like()` carries orbital numbers
94
+ rounded from a published system and is useful for a realistic-looking truth;
95
+ pair it with `DemoParallaxConsistentCadence` for anything closure-grade. The
96
+ `scenario=` parameter and the `bh1`/`toy` naming are slated to be simplified.
97
+
98
+ ## Licence
99
+
100
+ MIT.
@@ -0,0 +1,70 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "orblet"
7
+ dynamic = ["version"] # read from orblet.__version__, the single source
8
+ description = "Atoms for Keplerian orbit analysis: forward models, likelihoods, design matrices and linear solves, period search, element conversion. Arrays in, results out."
9
+ authors = [{name = "Sahar Shahaf", email = "sahar.shahaf@gmail.com"}]
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ requires-python = ">=3.11,<3.13"
13
+ keywords = ["astronomy", "binary-stars", "orbits", "astrometry", "radial-velocity", "kepler"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Scientific/Engineering :: Astronomy",
21
+ ]
22
+
23
+ # The four. Everything in orblet imports at module scope from these and the
24
+ # standard library only; tests/test_orblet_optional_dependencies.py enforces it.
25
+ dependencies = [
26
+ "numpy>=2,<3",
27
+ "scipy>=1.14,<2",
28
+ "astropy>=7,<8",
29
+ "matplotlib>=3.8,<4",
30
+ ]
31
+
32
+ [project.urls]
33
+ Repository = "https://github.com/saharsh1/orblet"
34
+ Issues = "https://github.com/saharsh1/orblet/issues"
35
+
36
+ # Optional, each imported inside the one function that needs it, so the
37
+ # package imports and its core paths run without any of them installed.
38
+ [project.optional-dependencies]
39
+ sampling = ["emcee>=3.1,<4"] # orblet.sampling.run_emcee_chains
40
+ plots = ["corner>=2.2,<3"] # orblet.plotting.plot_orbit_corner
41
+ tables = ["pandas>=2,<4"] # orblet.chain_stats.chain_summary_table
42
+ progress = ["tqdm>=4.60"] # a progress bar over batch periodograms
43
+ all = [
44
+ "emcee>=3.1,<4",
45
+ "corner>=2.2,<3",
46
+ "pandas>=2,<4",
47
+ "tqdm>=4.60",
48
+ ]
49
+ dev = [
50
+ "pytest>=7",
51
+ "build>=1.0",
52
+ ]
53
+
54
+ [tool.setuptools.dynamic]
55
+ version = {attr = "orblet.__version__"}
56
+
57
+ [tool.setuptools.packages.find]
58
+ where = ["src"]
59
+ include = ["orblet*"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+ # Same opt-in contract as the parent project: plain `pytest` runs the fast
64
+ # synthetic suite; `pytest -m slow` and `pytest -m benchmark` are explicit.
65
+ addopts = "-m 'not slow and not benchmark'"
66
+ markers = [
67
+ "slow: synthetic MCMC and closure tests; opt-in",
68
+ "coverage: calibration gates over many noise seeds; a subset of slow",
69
+ "benchmark: synthetic-data closure tests; opt-in",
70
+ ]
orblet-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,207 @@
1
+ """orblet — a standalone orbit fitter: arrays in, orbital elements out.
2
+
3
+ No mission, no data files, no network. orblet is a CONSUMER of data and never
4
+ a producer: you hand it epochs, measurements and uncertainties, and it hands
5
+ back a model, a likelihood, a solution. Where those arrays came from, and
6
+ whether you are allowed to have them, is not orblet's business.
7
+
8
+ Work the open way
9
+ -----------------
10
+ orblet ships ATOMS, not a black box. A Kepler solver, forward models, design
11
+ matrices, the two linear cores, proposed likelihoods, default priors, a
12
+ frequency search, the element conversions, the plots. You assemble the
13
+ optimiser or sampler you actually want and plug your own target function into
14
+ it; the default recipes are written out in the manuals and the example
15
+ notebooks rather than hidden inside a wrapper.
16
+
17
+ That is a deliberate trade. A one-line `fit()` is quicker to call and harder
18
+ to trust: when it disagrees with your expectation you cannot see which step
19
+ disagreed. Here every step is a function you can call, print and plot on its
20
+ own.
21
+
22
+ The shape of the thing
23
+ ----------------------
24
+ model the forward models: elements and epochs -> observables
25
+ design the design matrices, one column per linear parameter
26
+ solve the linear cores — fix the geometry, solve the rest exactly
27
+ search the frequency scan and the periodograms
28
+ likelihood proposed likelihoods (Gaussian with jitter), not mandates
29
+ priors prior classes, defaults, and the spec parser
30
+ prepare epoch and time-scale handling
31
+ sampling MCMC atoms: chains, stacking, R-hat, ESS, dead chains
32
+ elements conversions between parameter conventions
33
+ residuals the 5-parameter residual routine
34
+ parallax observer position -> on-sky parallax factors
35
+ interpret what a fitted orbit implies: masses, limits, flux ratios
36
+ plotting the orbit, the corner, the sky plane
37
+ simulate synthetic data with known truth, for testing and teaching
38
+
39
+ Why the split matters: the geometry of a Keplerian orbit is non-linear, but
40
+ once it is fixed the amplitudes enter LINEARLY and have a closed-form best
41
+ fit. `solve` is that closed form. A sampler over a handful of non-linear
42
+ parameters, with `solve` as its inner step, beats one over all of them.
43
+
44
+ Import cost
45
+ -----------
46
+ Nothing heavy is imported when you `import orblet`. Every name below is
47
+ resolved on first attribute access (PEP 562), so scipy, emcee and matplotlib
48
+ arrive only if you touch something that needs them.
49
+
50
+ **This package is under construction.** It is being assembled in place inside
51
+ the gaia-orbits monorepo, one dependency layer at a time, before being
52
+ extracted to its own repository. The modules arrive by relocation, unchanged.
53
+
54
+ State of the move: `docs/plans/2026-09-23-orblet-b1-chunks.md`
55
+ Plan of record: `docs/plans/2026-09-23-orblet-extraction-plan.md`
56
+ Machine-checked: `tests/_orblet_move_map.py`
57
+ """
58
+
59
+ from __future__ import annotations
60
+
61
+ import importlib
62
+
63
+ # public name -> (module, attribute). One mechanism, one table, grouped by
64
+ # what the name is FOR rather than by which file it happens to live in.
65
+ #: The package version. The ONLY place it is written: the build reads it from
66
+ #: here (``[tool.setuptools.dynamic]`` in orblet's pyproject), and the packaging
67
+ #: test pins that the installed metadata agrees. A plain literal on purpose —
68
+ #: setuptools parses it from the source without importing the package.
69
+ __version__ = "0.1.0"
70
+
71
+ _LAZY_EXPORTS: dict[str, tuple[str, str]] = {
72
+ # ── Design matrices: one column per linear parameter ───────────────
73
+ "rv_design_matrix": ("orblet.design.columns", "rv_design_matrix"),
74
+ "ti_design_matrix": ("orblet.design.columns", "ti_design_matrix"),
75
+ "acceleration_columns": ("orblet.design.columns", "acceleration_columns"),
76
+ "astrometric_5param_design_matrix": (
77
+ "orblet.solve.astrometry",
78
+ "astrometric_5param_design_matrix",
79
+ ),
80
+
81
+ # ── Linear cores: geometry fixed, amplitudes solved exactly ────────
82
+ "linear_solve_rv": ("orblet.solve.rv", "linear_solve_rv"),
83
+ "linear_solve_ti": ("orblet.solve.astrometry", "linear_solve_ti"),
84
+ "best_linear_params_rv": ("orblet.solve.rv", "best_linear_params_rv"),
85
+ "best_linear_params_ti": (
86
+ "orblet.solve.astrometry",
87
+ "best_linear_params_ti",
88
+ ),
89
+ "fit_astrometric_5param": (
90
+ "orblet.solve.astrometry",
91
+ "fit_astrometric_5param",
92
+ ),
93
+ "recover_K": ("orblet.solve.rv", "recover_K"),
94
+ "recover_omega": ("orblet.solve.rv", "recover_omega"),
95
+ "semi_amplitude_kms": ("orblet.model", "semi_amplitude_kms"),
96
+
97
+ # ── Searching for the period ───────────────────────────────────────
98
+ "scan_ti_frequency": ("orblet.search", "scan_ti_frequency"),
99
+
100
+ # ── Seeds: turning one solution into a starting point for the next ─
101
+ "compose_rv_seed": ("orblet.solve.rv", "compose_rv_seed"),
102
+ "compose_ti_seed": ("orblet.search", "compose_ti_seed"),
103
+ "ti_amplitude_chains": ("orblet.solve.astrometry", "ti_amplitude_chains"),
104
+ "mirror_inclination": ("orblet.elements", "mirror_inclination"),
105
+
106
+ # ── Epochs and time scales ─────────────────────────────────────────
107
+ "prepare_rv_for_orbit": ("orblet.prepare", "prepare_rv_for_orbit"),
108
+ "resolve_epochs_mjd": ("orblet.prepare", "resolve_epochs_mjd"),
109
+
110
+ # ── Where the observer is, as on-sky parallax factors ──────────────
111
+ "per_direction_parallax_factors": (
112
+ "orblet.parallax",
113
+ "per_direction_parallax_factors",
114
+ ),
115
+ "OBSERVER_GEOCENTRE": ("orblet.parallax", "OBSERVER_GEOCENTRE"),
116
+ "OBSERVER_L2": ("orblet.parallax", "OBSERVER_L2"),
117
+ "L2_OFFSET_AU": ("orblet.parallax", "L2_OFFSET_AU"),
118
+
119
+ # ── Element conversions and derived curves ─────────────────────────
120
+ "extract_orbital_elements": (
121
+ "orblet.elements",
122
+ "extract_orbital_elements",
123
+ ),
124
+ "ti_to_kepler": ("orblet.elements", "ti_to_kepler"),
125
+ "to_nss_convention": ("orblet.elements", "to_nss_convention"),
126
+ "compute_rv_model_curve": ("orblet.elements", "compute_rv_model_curve"),
127
+ "compute_residuals": ("orblet.elements", "compute_residuals"),
128
+
129
+ # ── Carrying one channel's chain into the other's prior ────────────
130
+ "a1_sini_from_rv_chain": ("orblet.rv_chain", "a1_sini_from_rv_chain"),
131
+ "astrometric_priors_from_rv_chain": (
132
+ "orblet.rv_chain",
133
+ "astrometric_priors_from_rv_chain",
134
+ ),
135
+ "predicted_k_kms_from_astrometric_chain": (
136
+ "orblet.rv_chain",
137
+ "predicted_k_kms_from_astrometric_chain",
138
+ ),
139
+
140
+ # ── Plots (matplotlib arrives here, not before) ────────────────────
141
+ "plot_orbit_fit": ("orblet.plotting", "plot_orbit_fit"),
142
+ "plot_orbit_corner": ("orblet.plotting", "plot_orbit_corner"),
143
+ "plot_astrometric_orbit": ("orblet.plotting", "plot_astrometric_orbit"),
144
+ "plot_astrometric_sky_overlay": (
145
+ "orblet.plotting",
146
+ "plot_astrometric_sky_overlay",
147
+ ),
148
+ }
149
+
150
+
151
+ def __getattr__(name: str):
152
+ """Resolve a public name on first access (PEP 562).
153
+
154
+ Keeps ``import orblet`` cheap: nothing below the front door is executed
155
+ until something is actually asked for.
156
+ """
157
+ target = _LAZY_EXPORTS.get(name)
158
+ if target is None:
159
+ raise AttributeError(
160
+ f"module {__name__!r} has no attribute {name!r}"
161
+ )
162
+ module_name, attr = target
163
+ return getattr(importlib.import_module(module_name), attr)
164
+
165
+
166
+ def __dir__() -> list[str]:
167
+ return sorted(__all__)
168
+
169
+
170
+ # Kept as a literal, same names as the table above.
171
+ __all__ = [
172
+ "rv_design_matrix",
173
+ "ti_design_matrix",
174
+ "acceleration_columns",
175
+ "astrometric_5param_design_matrix",
176
+ "linear_solve_rv",
177
+ "linear_solve_ti",
178
+ "best_linear_params_rv",
179
+ "best_linear_params_ti",
180
+ "fit_astrometric_5param",
181
+ "recover_K",
182
+ "recover_omega",
183
+ "semi_amplitude_kms",
184
+ "scan_ti_frequency",
185
+ "compose_rv_seed",
186
+ "compose_ti_seed",
187
+ "ti_amplitude_chains",
188
+ "mirror_inclination",
189
+ "prepare_rv_for_orbit",
190
+ "resolve_epochs_mjd",
191
+ "per_direction_parallax_factors",
192
+ "OBSERVER_GEOCENTRE",
193
+ "OBSERVER_L2",
194
+ "L2_OFFSET_AU",
195
+ "extract_orbital_elements",
196
+ "ti_to_kepler",
197
+ "to_nss_convention",
198
+ "compute_rv_model_curve",
199
+ "compute_residuals",
200
+ "a1_sini_from_rv_chain",
201
+ "astrometric_priors_from_rv_chain",
202
+ "predicted_k_kms_from_astrometric_chain",
203
+ "plot_orbit_fit",
204
+ "plot_orbit_corner",
205
+ "plot_astrometric_orbit",
206
+ "plot_astrometric_sky_overlay",
207
+ ]