abstract-math 0.0.0.17__py3-none-any.whl → 0.0.0.18__py3-none-any.whl

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.

Potentially problematic release.


This version of abstract-math might be problematic. Click here for more details.

@@ -0,0 +1,11 @@
1
+ from abstract_flask import *
2
+ from abstract_utilities import write_to_file
3
+ directory='/home/computron/Documents/pythonTools/modules/abstract_math/src/abstract_math/solar_math'
4
+ output_file='/home/computron/Documents/pythonTools/modules/abstract_math/src/abstract_math/flask_scripts/flask_utils.py'
5
+ output = generate_from_files(
6
+ directory=directory,
7
+ bp_name='math_data_bp',
8
+ url_prefix='api'
9
+ )
10
+ write_to_file(contents=output,file_path=output_file)
11
+
@@ -0,0 +1,263 @@
1
+ # adapt_units_api.py (or wherever you glue this in)
2
+ from typing import *
3
+ from .src.constants.distance_constants import convert as dconvert, _factor as dfactor
4
+ from .src.constants.time_constants import seconds_per
5
+ from .src.constants.planet_constants import planet_radius, get_body, g_at_radius
6
+ from .src.utils.geometry_utils import visible_area_flat, visible_surface_angle
7
+ from .src.imports import math, mul, div, add # your abstract_math ops
8
+ def normalized_velocity_conversioin(starting_velocity, input_time, input_units):
9
+ sec_per_time = seconds_per(input_time) # sec / timeunit
10
+ v_units_per_sec = div(starting_velocity, sec_per_time) # <input_units> / s
11
+ v0_mps = dconvert(v_units_per_sec, input_units, "meters") # m / s
12
+ return v0_mps
13
+
14
+ # --- Analyzer (prints a scan; no blocking input) ---
15
+ def analyze_visible_surface(
16
+ altitude_step: float = 200.0,
17
+ max_steps: int = 20,
18
+ fov_range: tuple[int, int] = (20, 160),
19
+ fov_interval: int = 10,
20
+ input_units: str = 'meters', # how to interpret altitude numbers
21
+ display_units: str = 'meters', # how to print areas/radii
22
+ planet: str = 'earth',
23
+ printit: bool = False
24
+ ):
25
+ """
26
+ Scan altitudes/FOVs. Altitudes are interpreted in `input_units`.
27
+ Results are printed in `display_units`.
28
+ """
29
+ # Planet radius and full area (compute in meters, convert for display)
30
+ r_m = planet_radius(planet, 'meters')
31
+ full_area_m2 = 4.0 * math.pi * (r_m ** 2)
32
+ k_disp = dfactor('meters', display_units) # linear meter->display factor
33
+ full_area_disp = full_area_m2 * (k_disp ** 2) # convert area to display units^2
34
+
35
+ all_stats = {"output": "", "input_units": input_units,
36
+ "display_units": display_units, "vars": []}
37
+
38
+ for i in range(1, max_steps + 1):
39
+ all_stats["vars"].append({})
40
+ altitude_in = altitude_step * i # input_units
41
+ altitude_m = dconvert(altitude_in, input_units, 'meters')
42
+
43
+ all_stats["vars"][-1]['altitude_input'] = altitude_in
44
+ all_stats["vars"][-1]['input_units'] = input_units
45
+ all_stats["vars"][-1]['fov'] = []
46
+
47
+ alt_pretty = dconvert(altitude_in, input_units, display_units)
48
+ all_stats["output"] += (
49
+ f"\n=== Altitude: {altitude_in} {input_units} (≈ {alt_pretty:.3f} {display_units}) ===\n"
50
+ )
51
+
52
+ for fov in range(fov_range[0], fov_range[1] + 1, fov_interval):
53
+ # Compute visible area/radius in meters, convert to display units
54
+ area_m2, vis_radius_m = visible_area_flat(fov, altitude_m, 'meters')
55
+ area_disp = area_m2 * (k_disp ** 2)
56
+ vis_radius_disp = dconvert(vis_radius_m, 'meters', display_units)
57
+
58
+ # Span uses geometry only; r_m is in meters
59
+ _, angle_deg = visible_surface_angle(vis_radius_m, r_m)
60
+
61
+ coverage_pct = 100.0 * (area_disp / full_area_disp)
62
+
63
+ fov_output = (
64
+ f"FOV: {fov:>3}° | "
65
+ f"Area: {area_disp:>12.2f} {display_units}² | "
66
+ f"Span: {angle_deg:>7.2f}° | "
67
+ f"Flat-visible: {coverage_pct:>6.3f}% | "
68
+ f"visR≈{vis_radius_disp:.3f} {display_units}"
69
+ )
70
+ all_stats["output"] += fov_output + "\n"
71
+
72
+ all_stats["vars"][-1]['fov'].append({
73
+ "FOV": fov,
74
+ "area": area_disp,
75
+ "angle_deg": angle_deg,
76
+ "coverage_pct": coverage_pct,
77
+ "visible_radius": vis_radius_disp,
78
+ "output": fov_output
79
+ })
80
+
81
+ if printit:
82
+ print(all_stats.get('output'))
83
+ return all_stats
84
+
85
+
86
+ def simulate_radial_flight(
87
+ planet: str,
88
+ start_altitude: float,
89
+ start_units: str,
90
+ v0_mps: float,
91
+ dt_s: float = 1.0,
92
+ max_steps: int = 5_000_000,
93
+ target_altitude_m: Optional[float] = None
94
+ ) -> dict:
95
+ """
96
+ Forward-Euler radial integrator (toy model).
97
+ Returns a dict with SI (meters, seconds) internal results.
98
+ """
99
+ if not (isinstance(start_altitude, (int, float)) and start_altitude >= 0):
100
+ return {"ok": False, "error": "Invalid start_altitude", "steps": 0}
101
+ if not (isinstance(v0_mps, (int, float)) and math.isfinite(v0_mps)):
102
+ return {"ok": False, "error": "Invalid starting_velocity (after unit conversion)", "steps": 0}
103
+ if not (dt_s > 0):
104
+ return {"ok": False, "error": "dt_s must be > 0", "steps": 0}
105
+
106
+ body = get_body(planet)
107
+ mu = body["mu"]
108
+ R = body["radius"]
109
+
110
+ r0 = add(R, dconvert(start_altitude, start_units, "meters"))
111
+ r = r0
112
+ v = v0_mps # outward positive, m/s
113
+ t = 0.0
114
+
115
+ has_target = target_altitude_m is not None and target_altitude_m > 0
116
+ r_target = add(R, target_altitude_m if has_target else float("inf"))
117
+
118
+ hit_surface = False
119
+ hit_target = False
120
+ turned_back_below_start = False
121
+
122
+ steps = 0
123
+ for _ in range(max_steps):
124
+ if r <= R:
125
+ hit_surface = True
126
+ break
127
+ if has_target and r >= r_target:
128
+ hit_target = True
129
+ break
130
+
131
+ a = - div(mu, mul(r, r)) # inward
132
+ v = add(v, mul(a, dt_s))
133
+ r = add(r, mul(v, dt_s))
134
+ t = add(t, dt_s)
135
+ steps += 1
136
+
137
+ if (not has_target) and (v < 0) and (r < r0):
138
+ turned_back_below_start = True
139
+ break
140
+
141
+ altitude_m = max(0.0, r - R)
142
+ g_end = g_at_radius(mu, r)
143
+ g_surface = g_at_radius(mu, R)
144
+ traveled_m = max(0.0, altitude_m - (r0 - R))
145
+
146
+ return {
147
+ "ok": True,
148
+ "planet": planet,
149
+ "rFromCenter_m": r,
150
+ "altitude_m": altitude_m,
151
+ "vEnd_mps": v,
152
+ "time_s": t,
153
+ "gAtEnd_mps2": g_end,
154
+ "gRatioSurface": div(g_end, g_surface),
155
+ "steps": steps,
156
+ "hitSurface": hit_surface,
157
+ "hitTarget": hit_target,
158
+ "turnedBackBelowStart": turned_back_below_start,
159
+ "traveled_m": traveled_m,
160
+ "vEsc_end_mps": math.sqrt(mul(2.0, div(mu, r))),
161
+ }
162
+
163
+
164
+ def radial_travel(
165
+ start_distance: float,
166
+ starting_velocity: float,
167
+ input_units: str = "meters", # distance part of starting_velocity & start_distance
168
+ input_time: str = "s", # time part of starting_velocity
169
+ output_units: str = "meters",
170
+ output_time: str = "s",
171
+ planet: str = "earth",
172
+ *,
173
+ dt_s: float = 1.0,
174
+ max_steps: int = 5_000_000,
175
+ target_distance: Optional[float] = None # in input_units above surface
176
+ ) -> dict:
177
+ """
178
+ Single-call wrapper:
179
+
180
+ - start_distance: altitude above surface (input_units)
181
+ - starting_velocity: <input_units>/<input_time>
182
+ """
183
+
184
+ v0_mps = normalized_velocity_conversioin(starting_velocity, input_time, input_units)
185
+
186
+ # Optional target altitude (to meters)
187
+ target_alt_m = None
188
+ if target_distance is not None:
189
+ target_alt_m = dconvert(target_distance, input_units, "meters")
190
+
191
+ # Integrate in SI
192
+ sim = simulate_radial_flight(
193
+ planet=planet,
194
+ start_altitude=start_distance,
195
+ start_units=input_units,
196
+ v0_mps=v0_mps,
197
+ dt_s=dt_s,
198
+ max_steps=max_steps,
199
+ target_altitude_m=target_alt_m
200
+ )
201
+
202
+ if not sim.get("ok", False):
203
+ return sim
204
+
205
+ # Distances for output
206
+ alt_out = dconvert(sim["altitude_m"], "meters", output_units)
207
+ r_out = dconvert(sim["rFromCenter_m"], "meters", output_units)
208
+ trav_out = dconvert(sim["traveled_m"], "meters", output_units)
209
+
210
+ # Velocity output: (output_units)/(output_time)
211
+ sec_per_out = seconds_per(output_time) # sec / out_timeunit
212
+ v_units_per_sec_out = dconvert(sim["vEnd_mps"], "meters", output_units)
213
+ v_out = mul(v_units_per_sec_out, sec_per_out)
214
+
215
+ # Escape velocity at end
216
+ vesc_units_per_sec_out = dconvert(sim["vEsc_end_mps"], "meters", output_units)
217
+ vesc_end_out = mul(vesc_units_per_sec_out, sec_per_out)
218
+
219
+ # Escape velocity at destination (if provided)
220
+ body = get_body(planet)
221
+ mu = body["mu"]; R = body["radius"]
222
+ destination_radius_m = None
223
+ vesc_dest_out = None
224
+ if target_alt_m is not None:
225
+ destination_radius_m = add(R, target_alt_m)
226
+ vesc_dest_mps = math.sqrt(mul(2.0, div(mu, destination_radius_m)))
227
+ vesc_dest_units_per_sec = dconvert(vesc_dest_mps, "meters", output_units)
228
+ vesc_dest_out = mul(vesc_dest_units_per_sec, sec_per_out)
229
+
230
+ # Time output in requested unit
231
+ t_out = div(sim["time_s"], sec_per_out)
232
+ t_label = output_time if output_time in ("s", "sec", "seconds", "m", "min", "minutes", "h", "hr", "hours") else "s"
233
+
234
+ return {
235
+ "ok": True,
236
+ "planet": planet,
237
+ "inputs": {
238
+ "start_distance": start_distance,
239
+ "starting_velocity": starting_velocity,
240
+ "input_units": input_units,
241
+ "input_time": input_time,
242
+ "output_units": output_units,
243
+ "output_time": output_time,
244
+ "target_distance": target_distance,
245
+ },
246
+ "altitude": alt_out, # in output_units
247
+ "radius_from_center": r_out, # in output_units
248
+ "distance_traveled": trav_out, # in output_units
249
+ "velocity": v_out, # in output_units / output_time
250
+ "velocity_escape_end": vesc_end_out, # same units/time as velocity
251
+ "velocity_escape_destination": vesc_dest_out,
252
+ "destination_radius": (
253
+ dconvert(destination_radius_m, "meters", output_units) if destination_radius_m is not None else None
254
+ ),
255
+ "time": t_out, # in output_time units
256
+ "time_unit": t_label,
257
+ "g_end_mps2": sim["gAtEnd_mps2"], # keep SI precise for g
258
+ "g_ratio_surface": sim["gRatioSurface"],
259
+ "steps": sim["steps"],
260
+ "hit_surface": sim["hitSurface"],
261
+ "hit_target": sim["hitTarget"],
262
+ "turned_back_below_start": sim["turnedBackBelowStart"],
263
+ }
@@ -0,0 +1,4 @@
1
+ from .imports import *
2
+ from .constants import *
3
+ from .utils import *
4
+
@@ -0,0 +1,19 @@
1
+ # src/constants/__init__.py
2
+ from .distance_constants import (
3
+ DISTANCE_CONVERSIONS, ALL_DISTANCE_UNITS,
4
+ normalize_distance_unit, get_distance_unit_conversions,
5
+ _factor, convert,
6
+ )
7
+ from .time_constants import (
8
+ TIME_CONVERSIONS, ALL_TIME_UNITS,
9
+ normalize_time_unit, get_time_unit_conversions,
10
+ time_factor, convert_time, seconds_per,
11
+ )
12
+ from .planet_constants import (
13
+ PLANETS, G, g0, MU_MOON,
14
+ get_planet_vars, planet_radius, planet_diameter,
15
+ full_planet_surface_area, planet_volume, planet_circumference,
16
+ planet_mass, planet_surface_g, escape_velocity,
17
+ earth_radius, earth_diameter, full_earth_surface_area,
18
+ earth_volume, earth_circumference,
19
+ )
@@ -0,0 +1,45 @@
1
+ #src/constants/distaance_constants.py
2
+ from ..imports import *
3
+ # -------------------------
4
+ # distance Unit schema
5
+ # -------------------------
6
+ MILES = {"strings": ['mi', 'mile', 'miles'],
7
+ "conv": {"miles": 1.0, "meters": 1609.34, "kilometers": 1.60934, 'feet': 5280.0}}
8
+ METER = {"strings": ['m', 'meter', 'meters'],
9
+ "conv": {"miles": 0.00621371, "meters": 1.0, "kilometers": 0.001, 'feet': 3.28084}}
10
+ KILOMETER = {"strings": ['km', 'kilo', 'kilometer', 'kilometers'],
11
+ "conv": {"miles": 0.621371, "meters": 1000.0, "kilometers": 1.0, 'feet': 3280.84}}
12
+ FEET = {"strings": ['ft', 'foot', 'feet', 'foots', 'feets'],
13
+ "conv": {"miles": 0.000189394, "meters": 0.3048, "kilometers": 0.0003048, 'feet': 1.0}}
14
+
15
+ DISTANCE_CONVERSIONS: Dict[str, Dict[str, Dict[str, float]]] = {
16
+ "miles": MILES,
17
+ "meters": METER,
18
+ "kilometers": KILOMETER,
19
+ "feet": FEET
20
+ }
21
+ ALL_DISTANCE_UNITS = ("meters", "kilometers", "miles", "feet")
22
+ # -------------------------
23
+ # Unit helpers
24
+ # -------------------------
25
+ def normalize_distance_unit(unit: str) -> str:
26
+ u = unit.strip().lower()
27
+ if u in DISTANCE_CONVERSIONS:
28
+ return u
29
+ for key, values in DISTANCE_CONVERSIONS.items():
30
+ if u in values.get("strings", []):
31
+ return key
32
+ # was: CONVERSIONS
33
+ raise ValueError(f"Unknown unit '{unit}'. Supported: {list(DISTANCE_CONVERSIONS.keys())}")
34
+
35
+ def get_distance_unit_conversions(unit: str) -> Dict[str, Dict[str, float]]:
36
+ return DISTANCE_CONVERSIONS[normalize_distance_unit(unit)]
37
+
38
+ def _factor(unit_from: str, unit_to: str) -> float:
39
+ """Multiplicative factor s.t. value_in_to = value_in_from * factor."""
40
+ uf = get_distance_unit_conversions(unit_from)["conv"]["meters"] # meters per 1 from-unit
41
+ ut = get_distance_unit_conversions(unit_to)["conv"]["meters"] # meters per 1 to-unit
42
+ return div(uf, ut)
43
+
44
+ def convert(value: float, unit_from: str, unit_to: str) -> float:
45
+ return mul(value, _factor(unit_from, unit_to))
@@ -0,0 +1,156 @@
1
+ # src/constants/planet_constants.py
2
+ from ..imports import * # <-- add this line
3
+ from .distance_constants import *
4
+ from .time_constants import *
5
+ # -------------------------
6
+ # Physical constants (SI)
7
+ # -------------------------
8
+ G = 6.67430e-11 # m^3 kg^-1 s^-2
9
+ g0 = 9.80665 # m/s^2
10
+
11
+ try:
12
+ MU_MOON
13
+ except NameError:
14
+ MU_MOON = 4.9048695e12 # m^3/s^2
15
+
16
+ # -------------------------
17
+ # Bodies (your data)
18
+ # -------------------------
19
+ PLANETS = [
20
+ { "name":'Sun',"m0_deg":0,"mu":1.32712440018e20,"a":0,"e":0,"radiusPx":20,"color":'yellow',"radius":696000000,"escapeVel":61770,"n":0,"peri_lon_deg":0 },
21
+ { "name":'Mercury',"m0_deg":252.25032350,"mu":2.20320e13,"a":5.7909927e10,"e":0.20563593,"radiusPx":4,"color":'#a6a6a6',"radius":2440000,"escapeVel":4300,"n":1,"peri_lon_deg":77.45779628 },
22
+ { "name":'Venus',"m0_deg":181.97909950,"mu":3.24859e14,"a":1.0820948e11,"e":0.00677672,"radiusPx":7,"color":'#e0c16b',"radius":6052000,"escapeVel":10400,"n":2,"peri_lon_deg":131.60246718 },
23
+ { "name":'Earth',"m0_deg":100.46457166,"mu":3.98600e14,"a":1.49598261e11,"e":0.01671123,"radiusPx":8,"color":'#4e6ef2',"radius":6371000,"escapeVel":11200,"n":3,"peri_lon_deg":102.93768193 },
24
+ { "name":'Mars',"m0_deg":-4.55343205,"mu":4.28284e13,"a":2.2793664e11,"e":0.09339410,"radiusPx":6,"color":'#c1440e',"radius":3390000,"escapeVel":5030,"n":4,"peri_lon_deg":-23.94362959 },
25
+ { "name":'Jupiter',"m0_deg":34.39644051,"mu":1.26687e17,"a":7.7841200e11,"e":0.04838624,"radiusPx":14,"color":'#d2a679',"radius":71492000,"escapeVel":59500,"n":5,"peri_lon_deg":14.72847983 },
26
+ { "name":'Saturn',"m0_deg":49.95424423,"mu":3.79312e16,"a":1.4267254e12,"e":0.05386179,"radiusPx":12,"color":'#e3c168',"radius":60268000,"escapeVel":35500,"n":6,"peri_lon_deg":92.59887831 },
27
+ { "name":'Uranus',"m0_deg":313.23810451,"mu":5.79394e15,"a":2.8706582e12,"e":0.04725744,"radiusPx":10,"color":'#7fbde8',"radius":25559000,"escapeVel":21300,"n":7,"peri_lon_deg":170.95427630 },
28
+ { "name":'Neptune',"m0_deg":-55.12002969,"mu":6.83653e15,"a":4.4983964e12,"e":0.00859048,"radiusPx":10,"color":'#4363d8',"radius":24764000,"escapeVel":23500,"n":8,"peri_lon_deg":44.96476227 },
29
+ { "name":'Moon',"m0_deg":0,"mu":MU_MOON,"a":3.844e8,"e":0.0549,"radiusPx":5,"color":'lightgray',"radius":1.737e6,"escapeVel":2380,"n":9}
30
+ ]
31
+
32
+ # -------------------------
33
+ # Body enrichment + lookup
34
+ # -------------------------
35
+ def _enrich_body(b: Dict[str, Any]) -> Dict[str, Any]:
36
+ """Add derived diameter, mass, surface g."""
37
+ mu = b["mu"]
38
+ r = b["radius"]
39
+ if "diameter" not in b or not b["diameter"]:
40
+ b["diameter"] = mul(2.0, r)
41
+ b["mass"] = div(mu, G)
42
+ surf_g = div(mu, mul(r, r))
43
+ b["surface_g"] = surf_g
44
+ b["surface_g_g0"] = div(surf_g, g0)
45
+ return b
46
+
47
+ _NAME_ALIASES = {"sol": "sun", "terra": "earth", "luna": "moon"}
48
+ def _normalize_name(name: str) -> str:
49
+ n = name.strip().lower()
50
+ return _NAME_ALIASES.get(n, n)
51
+
52
+ _BODY_BY_NAME: Dict[str, Dict[str, Any]] = {}
53
+ for entry in PLANETS:
54
+ _BODY_BY_NAME[entry["name"].lower()] = _enrich_body(dict(entry))
55
+
56
+ # -------------------------
57
+ # Public API
58
+ # -------------------------
59
+ def get_planet_vars(name: str, units: str = "meters") -> Dict[str, Any]:
60
+ """
61
+ Return body properties with radius/diameter in `units`.
62
+ Mass in kg; mu in m^3/s^2; surface_g in m/s^2.
63
+ """
64
+ key = _normalize_name(name)
65
+ body = _BODY_BY_NAME.get(key)
66
+ if body is None:
67
+ raise KeyError(f"Unknown body '{name}'. Available: {sorted(_BODY_BY_NAME.keys())}")
68
+
69
+ units_norm = normalize_distance_unit(units)
70
+ r_m = body["radius"]
71
+ d_m = body["diameter"]
72
+
73
+ out = dict(body)
74
+ out["radius"] = convert(r_m, "meters", units_norm)
75
+ out["diameter"] = convert(d_m, "meters", units_norm)
76
+ out["radius_units"] = units_norm
77
+ out["diameter_units"] = units_norm
78
+ return out
79
+
80
+ def planet_radius(name: str = "earth", units: str = "meters") -> float:
81
+ return get_planet_vars(name, units)["radius"]
82
+
83
+ def planet_diameter(name: str = "earth", units: str = "meters") -> float:
84
+ return get_planet_vars(name, units)["diameter"]
85
+
86
+ def full_planet_surface_area(name: str = "earth", units: str = 'meters') -> float:
87
+ r = planet_radius(name,units)
88
+ return mul(4 * pi(), exp(r, 2))
89
+
90
+ def planet_volume(name: str = "earth", units: str = 'meters') -> float:
91
+ r = planet_radius(name,units)
92
+ return mul((4.0/3.0) * pi(), exp(r, 3))
93
+
94
+ def planet_circumference(name: str = "earth", units: str = 'meters') -> float:
95
+ r = planet_radius(name,units)
96
+ return mul(2 * pi(), r)
97
+
98
+ def planet_mass(name: str = "earth") -> float:
99
+ return get_planet_vars(name)["mass"]
100
+
101
+ def planet_surface_g(name: str = "earth", as_g0: bool = False) -> float:
102
+ v = get_planet_vars(name)["surface_g"]
103
+ return div(v, g0) if as_g0 else v
104
+
105
+ def escape_velocity(name: str = "earth", altitude: float = 0.0, units: str = "meters") -> float:
106
+ """
107
+ Escape velocity (m/s) from altitude above surface.
108
+ """
109
+ mu = _BODY_BY_NAME[_normalize_name(name)]["mu"]
110
+ r = _BODY_BY_NAME[_normalize_name(name)]["radius"] # meters
111
+ h_m = convert(altitude, units, "meters")
112
+ R = add(r, h_m)
113
+ return math.sqrt(mul(2.0, div(mu, R)))
114
+
115
+ # -------------------------
116
+ # Earth-centric geometry utils (unit-consistent)
117
+ # -------------------------
118
+ def pi() -> float:
119
+ return math.pi
120
+
121
+ def earth_radius(units: str = 'meters') -> float:
122
+ return planet_radius('earth', units)
123
+
124
+ def earth_diameter(units: str = 'meters') -> float:
125
+ return planet_diameter('earth', units)
126
+
127
+ def full_earth_surface_area(units: str = 'meters') -> float:
128
+ r = earth_radius(units)
129
+ return mul(4 * pi(), exp(r, 2))
130
+
131
+ def earth_volume(units: str = 'meters') -> float:
132
+ r = earth_radius(units)
133
+ return mul((4.0/3.0) * pi(), exp(r, 3))
134
+
135
+ def earth_circumference(units: str = 'meters') -> float:
136
+ r = earth_radius(units)
137
+ return mul(2 * pi(), r)
138
+
139
+ # =========================
140
+ # Radial toy + single-call wrapper
141
+ # =========================
142
+ def distance_per_sec_to_mps(v_per_sec: float, units: str) -> float:
143
+ """Convert a speed given in `units`/s into m/s using your convert()."""
144
+ # v [units/s] * (meters per 1 units) => [m/s]
145
+ return mul(v_per_sec, get_distance_unit_conversions(_normalize_unit(units))["conv"]["meters"])
146
+
147
+
148
+ def get_body(planet: str) -> Dict[str, Any]:
149
+ key = _normalize_name(planet)
150
+ body = _BODY_BY_NAME.get(key)
151
+ if not body:
152
+ raise KeyError(f"Unknown body '{planet}'. Available: {sorted(_BODY_BY_NAME.keys())}")
153
+ return body
154
+
155
+ def g_at_radius(mu: float, r_m: float) -> float:
156
+ return div(mu, mul(r_m, r_m))
@@ -0,0 +1,56 @@
1
+ #src/constants/time_constants.py
2
+ # --- Time unit schema (seconds-per-unit) ---
3
+ second = 1.0
4
+ minute = 60.0 * second
5
+ hour = 60.0 * minute
6
+ day = 24.0 * hour
7
+
8
+ SECONDS = {"strings": ['s', 'sec', 'secs', 'second', 'seconds'],
9
+ "conv": {"seconds": 1.0}}
10
+ MINUTES = {"strings": ['min', 'mins', 'minute', 'minutes'],
11
+ "conv": {"seconds": minute}}
12
+ HOURS = {"strings": ['h', 'hr', 'hrs', 'hour', 'hours'],
13
+ "conv": {"seconds": hour}}
14
+ DAYS = {"strings": ['d', 'day', 'days'],
15
+ "conv": {"seconds": day}}
16
+
17
+ TIME_CONVERSIONS = {
18
+ "seconds": SECONDS,
19
+ "minutes": MINUTES,
20
+ "hours": HOURS,
21
+ "days": DAYS,
22
+ }
23
+ ALL_TIME_UNITS = ("seconds", "minutes", "hours", "days")
24
+
25
+ def normalize_time_unit(unit: str) -> str:
26
+ u = unit.strip().lower()
27
+ if u in TIME_CONVERSIONS:
28
+ return u
29
+ for key, values in TIME_CONVERSIONS.items():
30
+ if u in values.get("strings", []):
31
+ return key
32
+ raise ValueError(f"Unknown time unit '{unit}'. Supported: {list(TIME_CONVERSIONS.keys())}")
33
+
34
+ def get_time_unit_conversions(unit: str) -> dict:
35
+ return TIME_CONVERSIONS[normalize_time_unit(unit)]
36
+
37
+ def time_factor(unit_from: str, unit_to: str) -> float:
38
+ """
39
+ multiplicative factor s.t.
40
+ value_in_to = value_in_from * _time_factor(unit_from, unit_to)
41
+
42
+ seconds per 1 unit_from / seconds per 1 unit_to
43
+ """
44
+ sf = get_time_unit_conversions(unit_from)["conv"]["seconds"] # sec / unit_from
45
+ st = get_time_unit_conversions(unit_to)["conv"]["seconds"] # sec / unit_to
46
+ return sf / st
47
+
48
+ def convert_time(value: float, unit_from: str, unit_to: str) -> float:
49
+ return value * time_factor(unit_from, unit_to)
50
+
51
+ def seconds_per(unit: str) -> float:
52
+ """Return seconds in one <unit> (unit aliases supported)."""
53
+ return get_time_unit_conversions(normalize_time_unit(unit))["conv"]["seconds"]
54
+
55
+
56
+
@@ -0,0 +1,5 @@
1
+ import math
2
+ from typing import Dict, Callable, Optional, Any
3
+ from abstract_math import (
4
+ multiply_it as mul, divide_it as div, add_it as add, subtract_it as sub, exp_it as exp
5
+ )
@@ -0,0 +1,3 @@
1
+ from .geometry_utils import *
2
+ from .velocity_utils import *
3
+ from .escape_velocity import *
@@ -0,0 +1,79 @@
1
+ # src/utils/escape_velocity.py
2
+
3
+ from ..imports import math, mul, div, add
4
+ from ..constants.planet_constants import get_body
5
+ from ..constants.distance_constants import convert as dconvert
6
+ from ..constants.time_constants import get_time_unit_conversions, normalize_time_unit
7
+
8
+ def _seconds_per(unit: str) -> float:
9
+ """
10
+ Map a time unit (alias-friendly) to seconds per that unit, using your time schema.
11
+ """
12
+ return get_time_unit_conversions(normalize_time_unit(unit))["conv"]["seconds"]
13
+
14
+ def escape_velocity_at(
15
+ planet: str = "earth",
16
+ distance: float = 0.0,
17
+ *,
18
+ input_units: str = "meters", # how to interpret `distance`
19
+ output_units: str = "meters", # distance unit for the *speed*
20
+ output_time: str = "s", # time unit for the *speed*
21
+ as_radius: bool = False # False => `distance` is altitude above surface; True => radius from center
22
+ ) -> dict:
23
+ """
24
+ Compute v_escape at a given location around `planet`.
25
+
26
+ Args:
27
+ planet: body name (must exist in PLANETS)
28
+ distance: if as_radius=False => altitude above surface; if as_radius=True => radius from center
29
+ input_units: units of `distance`
30
+ output_units: distance unit of the returned speed
31
+ output_time: time unit of the returned speed ('s'|'min'|'h' etc.)
32
+ as_radius: interpret `distance` as radius-from-center when True
33
+
34
+ Returns:
35
+ {
36
+ "ok": True,
37
+ "planet": str,
38
+ "radius_from_center": <float in output_units>,
39
+ "v_escape": <float in output_units/output_time>,
40
+ "v_escape_mps": <float in m/s>,
41
+ "units": {"distance": output_units, "time": output_time}
42
+ }
43
+ """
44
+ if not (isinstance(distance, (int, float)) and math.isfinite(distance) and distance >= 0):
45
+ return {"ok": False, "error": "distance must be a non-negative number"}
46
+
47
+ body = get_body(planet)
48
+ mu = body["mu"] # m^3/s^2
49
+ R = body["radius"] # m
50
+
51
+ # Determine radius from center in meters
52
+ if as_radius:
53
+ r_m = dconvert(distance, input_units, "meters")
54
+ else:
55
+ alt_m = dconvert(distance, input_units, "meters")
56
+ r_m = add(R, alt_m)
57
+
58
+ if r_m <= 0:
59
+ return {"ok": False, "error": "computed radius is non-positive"}
60
+
61
+ # v_esc (m/s)
62
+ vesc_mps = math.sqrt(mul(2.0, div(mu, r_m)))
63
+
64
+ # Convert speed to <output_units>/<output_time>
65
+ vesc_units_per_sec = dconvert(vesc_mps, "meters", output_units)
66
+ sec_per = _seconds_per(output_time) # seconds per 1 output_time
67
+ vesc_out = mul(vesc_units_per_sec, sec_per) # <output_units>/<output_time>
68
+
69
+ # Also return the radius in output_units for convenience
70
+ r_out = dconvert(r_m, "meters", output_units)
71
+
72
+ return {
73
+ "ok": True,
74
+ "planet": planet,
75
+ "radius_from_center": r_out,
76
+ "v_escape": vesc_out,
77
+ "v_escape_mps": vesc_mps,
78
+ "units": {"distance": output_units, "time": output_time}
79
+ }