stellar-spice 0.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.

Potentially problematic release.


This version of stellar-spice might be problematic. Click here for more details.

Files changed (36) hide show
  1. stellar_spice-0.2/PKG-INFO +32 -0
  2. stellar_spice-0.2/README.md +15 -0
  3. stellar_spice-0.2/pyproject.toml +33 -0
  4. stellar_spice-0.2/setup.cfg +4 -0
  5. stellar_spice-0.2/src/spice/__init__.py +0 -0
  6. stellar_spice-0.2/src/spice/geometry/__init__.py +2 -0
  7. stellar_spice-0.2/src/spice/geometry/sutherland_hodgman.py +142 -0
  8. stellar_spice-0.2/src/spice/geometry/utils.py +167 -0
  9. stellar_spice-0.2/src/spice/models/__init__.py +5 -0
  10. stellar_spice-0.2/src/spice/models/binary.py +123 -0
  11. stellar_spice-0.2/src/spice/models/mesh_generation.py +214 -0
  12. stellar_spice-0.2/src/spice/models/mesh_model.py +133 -0
  13. stellar_spice-0.2/src/spice/models/mesh_transform.py +93 -0
  14. stellar_spice-0.2/src/spice/models/mesh_view.py +187 -0
  15. stellar_spice-0.2/src/spice/models/orbit_utils.py +90 -0
  16. stellar_spice-0.2/src/spice/models/phoebe_utils.py +81 -0
  17. stellar_spice-0.2/src/spice/models/spots.py +87 -0
  18. stellar_spice-0.2/src/spice/models/utils.py +125 -0
  19. stellar_spice-0.2/src/spice/phoebe_model/DI-Her-meshes.py +135 -0
  20. stellar_spice-0.2/src/spice/plots/__init__.py +1 -0
  21. stellar_spice-0.2/src/spice/plots/plot_mesh.py +323 -0
  22. stellar_spice-0.2/src/spice/spectrum/__init__.py +2 -0
  23. stellar_spice-0.2/src/spice/spectrum/filter.py +180 -0
  24. stellar_spice-0.2/src/spice/spectrum/filter_data/__init__.py +0 -0
  25. stellar_spice-0.2/src/spice/spectrum/planck_law.py +57 -0
  26. stellar_spice-0.2/src/spice/spectrum/spectrum.py +265 -0
  27. stellar_spice-0.2/src/spice/spectrum/spectrum_korg.py +201 -0
  28. stellar_spice-0.2/src/spice/spectrum/spectrum_transformer.py +148 -0
  29. stellar_spice-0.2/src/spice/spectrum/utils.py +38 -0
  30. stellar_spice-0.2/src/spice/utils/__init__.py +1 -0
  31. stellar_spice-0.2/src/spice/utils/warnings.py +6 -0
  32. stellar_spice-0.2/src/stellar_spice.egg-info/PKG-INFO +32 -0
  33. stellar_spice-0.2/src/stellar_spice.egg-info/SOURCES.txt +34 -0
  34. stellar_spice-0.2/src/stellar_spice.egg-info/dependency_links.txt +1 -0
  35. stellar_spice-0.2/src/stellar_spice.egg-info/requires.txt +14 -0
  36. stellar_spice-0.2/src/stellar_spice.egg-info/top_level.txt +1 -0
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.1
2
+ Name: stellar-spice
3
+ Version: 0.2
4
+ Description-Content-Type: text/markdown
5
+ Requires-Dist: jax~=0.4.24
6
+ Requires-Dist: jaxlib~=0.4.24
7
+ Requires-Dist: numpy~=1.26.4
8
+ Requires-Dist: flax~=0.8.1
9
+ Requires-Dist: joblib~=1.3.2
10
+ Requires-Dist: transformer-payne~=0.5
11
+ Provides-Extra: huggingface
12
+ Requires-Dist: huggingface-hub~=0.21.3; extra == "huggingface"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest~=8.0.0; extra == "dev"
15
+ Requires-Dist: pytest-datadir~=1.5.0; extra == "dev"
16
+ Requires-Dist: coverage~=7.4.3; extra == "dev"
17
+
18
+ ![spice logo](docs/img/spice.svg)
19
+
20
+ A stellar spectra integration library.
21
+
22
+ I am going to publish the package on PyPI very soon, for the time being it can be installed from GitHub:
23
+
24
+ ```bash
25
+ pip install git+https://github.com/maja-jablonska/spice
26
+ ```
27
+
28
+ Paper is currently in prep.
29
+
30
+ ## Dev log
31
+
32
+ Notebooks are being updated one by one.
@@ -0,0 +1,15 @@
1
+ ![spice logo](docs/img/spice.svg)
2
+
3
+ A stellar spectra integration library.
4
+
5
+ I am going to publish the package on PyPI very soon, for the time being it can be installed from GitHub:
6
+
7
+ ```bash
8
+ pip install git+https://github.com/maja-jablonska/spice
9
+ ```
10
+
11
+ Paper is currently in prep.
12
+
13
+ ## Dev log
14
+
15
+ Notebooks are being updated one by one.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "stellar-spice"
7
+ version = "0.2"
8
+ readme = "README.md"
9
+ dependencies = [
10
+ "jax~=0.4.24",
11
+ "jaxlib~=0.4.24",
12
+ "numpy~=1.26.4",
13
+ "flax~=0.8.1",
14
+ "joblib~=1.3.2",
15
+ "transformer-payne~=0.5"
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ huggingface = ["huggingface-hub~=0.21.3"]
20
+ dev = ["pytest~=8.0.0", "pytest-datadir~=1.5.0", "coverage~=7.4.3"]
21
+
22
+ [tool.setuptools.dynamic]
23
+ dependencies = {file = ["requirements.txt"]}
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [tool.pytest.ini_options]
29
+ python_classes = ["Test", "Describe"]
30
+ python_functions = ["test_", "it_", "and_", "but_", "they_"]
31
+ python_files = ["test_*.py",]
32
+ testpaths = ["tests",]
33
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,2 @@
1
+ from .utils import polygon_area
2
+ from .sutherland_hodgman import clip
@@ -0,0 +1,142 @@
1
+ import jax
2
+ import jax.numpy as jnp
3
+ from jax.typing import ArrayLike
4
+ from spice.geometry.utils import inside, last_non_nan, append_to_last_nan, repeat_last, sort_xy
5
+
6
+
7
+ @jax.jit
8
+ def x_y_first_line_vertical(p1, p2, q1, q2):
9
+ x = p1[0]
10
+ m2 = (q2[1]-q1[1])/(q2[0]-q1[0])
11
+ b2 = q1[1]-m2*q1[0]
12
+ return x, m2*x+b2
13
+
14
+ @jax.jit
15
+ def x_y_second_line_vertical(p1, p2, q1, q2):
16
+ x = q1[0]
17
+ m1 = (p2[1]-p1[1])/(p2[0]-p1[0])
18
+ b1 = p1[1]-m1*p1[0]
19
+ return x, m1*x+b1
20
+
21
+ @jax.jit
22
+ def x_y_no_vertical(p1, p2, q1, q2):
23
+ m1 = (p2[1]-p1[1])/(p2[0]-p1[0])
24
+ b1 = p1[1]-m1*p1[0]
25
+
26
+ m2 = (q2[1]-q1[1])/(q2[0]-q1[0])
27
+ b2 = q1[1]-m2*q1[0]
28
+
29
+ x = (b2-b1)/(m1-m2)
30
+ return x, m1*x+b1
31
+
32
+
33
+ @jax.jit
34
+ def compute_intersection(p1: ArrayLike, p2: ArrayLike, q1: ArrayLike, q2: ArrayLike) -> ArrayLike:
35
+ """Calculate the intersection of two lines
36
+
37
+ Args:
38
+ p1 (ArrayLike): point 1 of line 1
39
+ p2 (ArrayLike): point 2 of line 1
40
+ q1 (ArrayLike): point 1 of line 2
41
+ q2 (ArrayLike): point 2 of line 2
42
+
43
+ Returns:
44
+ ArrayLike: coordinates of the intersection of two lines
45
+ """
46
+ x, y = jax.lax.cond(jnp.isclose(p1[0]-p2[0], 0),
47
+ x_y_first_line_vertical,
48
+ lambda a, b, c, d: jax.lax.cond(
49
+ jnp.isclose(c[0]-d[0], 0),
50
+ x_y_second_line_vertical,
51
+ x_y_no_vertical,
52
+ a, b, c, d),
53
+ p1, p2, q1, q2)
54
+
55
+ return jnp.array([[x, y]])
56
+
57
+ def s_only_edge_start_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
58
+ intersection = compute_intersection(s_edge_start,
59
+ s_edge_end,
60
+ c_edge_start,
61
+ c_edge_end)
62
+ intersection = jax.lax.cond(jnp.any(jnp.isnan(intersection)),
63
+ lambda: jnp.nan*intersection,
64
+ lambda: intersection)
65
+ return append_to_last_nan(final_polygon, intersection)
66
+
67
+ def s_only_edge_end_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
68
+ intersection = compute_intersection(s_edge_start,
69
+ s_edge_end,
70
+ c_edge_start,
71
+ c_edge_end)
72
+ intersection = jax.lax.cond(jnp.any(jnp.isnan(intersection)),
73
+ lambda: jnp.nan*intersection,
74
+ lambda: intersection)
75
+ polygon_with_intersection = append_to_last_nan(final_polygon, intersection)
76
+ return append_to_last_nan(polygon_with_intersection, s_edge_end)
77
+
78
+ def s_both_start_and_end_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
79
+ return append_to_last_nan(final_polygon, s_edge_end)
80
+
81
+
82
+ @jax.jit
83
+ def clip(subject_polygon: ArrayLike, clipping_polygon: ArrayLike) -> ArrayLike:
84
+ """Calculate the intersection of the triangles subject_polygon and clipping_polygon
85
+
86
+ Args:
87
+ subject_polygon (ArrayLike): x and y coordinates (3, 2)
88
+ clipping_polygon (ArrayLike): x and y coordinates (3, 2)
89
+
90
+ Returns:
91
+ ArrayLike: intersection between two triangles (6, 2)
92
+ """
93
+ subject_polygon = sort_xy(subject_polygon)
94
+ clipping_polygon = sort_xy(clipping_polygon)
95
+ final_polygon = repeat_last(jnp.concatenate([jnp.copy(subject_polygon), jnp.nan*jnp.ones((6-subject_polygon.shape[0], 2))], axis=0))
96
+
97
+ def outer_iteration(i, final_polygon):
98
+ next_polygon = jnp.copy(final_polygon)
99
+
100
+ final_polygon = jnp.nan*jnp.ones((6, 2))
101
+
102
+ #c_edge_start = clipping_polygon[((i-1)%(clipping_polygon.shape[0])).astype(int)]
103
+ c_edge_start = jax.lax.cond(i==0,
104
+ lambda: last_non_nan(clipping_polygon),
105
+ lambda: clipping_polygon[i-1])
106
+ c_edge_end = clipping_polygon[i]
107
+
108
+ def inner_iteration(j, final_polygon):
109
+ s_edge_start = jax.lax.cond(j==0,
110
+ lambda: last_non_nan(next_polygon),
111
+ lambda: next_polygon[j-1])
112
+ #s_edge_start = next_polygon[((j-1)%(next_polygon.shape[0])).astype(int)]
113
+ s_edge_end = next_polygon[j]
114
+
115
+ return jax.lax.cond(
116
+ jnp.any(inside(c_edge_start, c_edge_end, s_edge_end)),
117
+ lambda: jax.lax.cond(
118
+ jnp.any(inside(c_edge_start, c_edge_end, s_edge_start)),
119
+ lambda: s_both_start_and_end_inside(
120
+ final_polygon,
121
+ c_edge_start, c_edge_end,
122
+ s_edge_start, s_edge_end
123
+ ),
124
+ lambda: s_only_edge_end_inside(
125
+ final_polygon,
126
+ c_edge_start, c_edge_end,
127
+ s_edge_start, s_edge_end
128
+ ),
129
+ ),
130
+ lambda: jax.lax.cond(
131
+ jnp.any(inside(c_edge_start, c_edge_end, s_edge_start)),
132
+ lambda: s_only_edge_start_inside(
133
+ final_polygon,
134
+ c_edge_start, c_edge_end,
135
+ s_edge_start, s_edge_end
136
+ ),
137
+ lambda: final_polygon
138
+ )
139
+ )
140
+
141
+ return jax.lax.fori_loop(0, next_polygon.shape[0], inner_iteration, final_polygon)
142
+ return repeat_last(jax.lax.fori_loop(0, clipping_polygon.shape[0], outer_iteration, final_polygon))
@@ -0,0 +1,167 @@
1
+ import jax
2
+ import jax.numpy as jnp
3
+ from jax.typing import ArrayLike
4
+
5
+
6
+ def sort_xy(points: ArrayLike) -> ArrayLike:
7
+ """Sort points clockwise
8
+
9
+ Args:
10
+ points (ArrayLike): x, y coordinates (n, 2)
11
+
12
+ Returns:
13
+ ArrayLike: points with x, y coordinates sorted clockwise
14
+ """
15
+ x, y = points[:, 0], points[:, 1]
16
+ x0 = jnp.mean(x)
17
+ y0 = jnp.mean(y)
18
+
19
+ r = jnp.sqrt((x-x0)**2 + (y-y0)**2)
20
+
21
+ angles = jnp.where((y-y0) > 0, jnp.arccos((x-x0)/r), 2*jnp.pi-jnp.arccos((x-x0)/r))
22
+
23
+ mask = jnp.argsort(angles)
24
+
25
+ return points[mask, :]
26
+
27
+
28
+
29
+ def inside(p1: ArrayLike, p2: ArrayLike, q: ArrayLike) -> bool:
30
+ """Check if the point is inside the polygon edge
31
+
32
+ Args:
33
+ p1 (ArrayLike): Polygon edge point 1
34
+ p2 (ArrayLike): Polygon edge point 2
35
+ q (ArrayLike): Point to be checked
36
+
37
+ Returns:
38
+ bool: Point is inside polygon
39
+ """
40
+ return (p2[0]-p1[0])*(q[1]-p1[1])-(p2[1]-p1[1])*(q[0]-p1[0])>=0
41
+
42
+
43
+ def last_non_nan_arg(arr: ArrayLike) -> ArrayLike:
44
+ """Return the last non-nan index of the array
45
+
46
+ Args:
47
+ arr (ArrayLike): Array
48
+
49
+ Returns:
50
+ ArrayLike: Last non-nan index of the array
51
+ """
52
+ return jnp.max(jnp.argwhere(jnp.all(~jnp.isnan(arr), axis=1), size=arr.shape[0], fill_value=0)).astype(int)
53
+
54
+
55
+ def last_non_nan(arr: ArrayLike, axis=1) -> ArrayLike:
56
+ """Return the last non-nan value of the array
57
+
58
+ Args:
59
+ arr (ArrayLike): Array
60
+
61
+ Returns:
62
+ ArrayLike: Last non-nan row of the array
63
+ """
64
+ return arr[jnp.max(jnp.argwhere(jnp.all(~jnp.isnan(arr), axis=axis), size=arr.shape[0], fill_value=0))]
65
+
66
+
67
+ def append_to_last_nan(arr: ArrayLike, to_add: ArrayLike) -> ArrayLike:
68
+ """Append the row to the first nan row
69
+
70
+ Args:
71
+ arr (ArrayLike): Array to add the row to
72
+ to_add (ArrayLike): The row to be added
73
+
74
+ Returns:
75
+ ArrayLike: Array with an appended row
76
+ """
77
+ next_ind = jnp.min(jnp.argwhere(jnp.all(jnp.isnan(arr), axis=1), size=arr.shape[0], fill_value=arr.shape[0]-1))
78
+ return arr.at[next_ind].set(to_add.flatten())
79
+
80
+
81
+ def append_value_to_last_nan(arr: ArrayLike, to_add: ArrayLike) -> ArrayLike:
82
+ """Append a single value to the first nan in array
83
+
84
+ Args:
85
+ arr (ArrayLike): Array to add the value to
86
+ to_add (ArrayLike): The value to be appended
87
+
88
+ Returns:
89
+ ArrayLike: Array with an appended value
90
+ """
91
+ next_ind = jnp.min(jnp.argwhere(jnp.isnan(arr), size=arr.shape[0], fill_value=arr.shape[0]-1))
92
+ return arr.at[next_ind].set(to_add)
93
+
94
+
95
+ def repeat_last(arr: ArrayLike) -> ArrayLike:
96
+ """Repeat the last non-nan row to fill the whole array
97
+
98
+ Args:
99
+ arr (ArrayLike):
100
+
101
+ Returns:
102
+ ArrayLike: Array with nan values filled with last non-nan values
103
+ """
104
+ return jax.lax.cond(jnp.any(jnp.all(jnp.isnan(arr), axis=1)),
105
+ lambda: jax.lax.fori_loop(0, jnp.sum(jnp.all(jnp.isnan(arr), axis=1)),
106
+ lambda i, _arr: append_to_last_nan(_arr, last_non_nan(_arr)),
107
+ arr),
108
+ lambda: arr)
109
+
110
+
111
+ def wrap(arr: ArrayLike) -> ArrayLike:
112
+ """Copy the first row and concatenate to the first array
113
+
114
+ Args:
115
+ arr (ArrayLike):
116
+
117
+ Returns:
118
+ ArrayLike: Array with the copy of the first row appended
119
+ """
120
+ return jnp.concatenate([arr, arr[0].reshape((-1, arr.shape[1]))])
121
+
122
+
123
+ def polygon_area(x: ArrayLike, y: ArrayLike) -> ArrayLike:
124
+ """Calculate a polygon's surface area from its x and y coordinates
125
+
126
+ Args:
127
+ x (ArrayLike): x coordinates of the polygon
128
+ y (ArrayLike): y coordinates of the polygon
129
+
130
+ Returns:
131
+ ArrayLike: polygon's surface area
132
+ """
133
+ return 0.5*jnp.abs(jnp.dot(x,jnp.roll(y,1))-jnp.dot(y,jnp.roll(x,1)))
134
+
135
+
136
+ polygon_areas = jax.jit(jax.vmap(polygon_area, in_axes=(0, 0)))
137
+
138
+ def cast_indexes(cast_vertices: ArrayLike) -> ArrayLike:
139
+ """When casting 3D vectors to 2D, get the indices of non-reduced dimensions
140
+
141
+ Args:
142
+ cast_vertices (ArrayLike): vertices after 3D to 2D casting
143
+
144
+ Returns:
145
+ ArrayLike: non-reduced (non-zero) dimensions
146
+ """
147
+ zeroth_ind = jnp.argmax(jnp.all(jnp.isclose(cast_vertices, 0), axis=1))
148
+
149
+ return jax.lax.cond(zeroth_ind == 0,
150
+ lambda: jnp.array([1, 2]),
151
+ lambda: jax.lax.cond(zeroth_ind == 1,
152
+ lambda: jnp.array([0, 2]),
153
+ lambda: jnp.array([0, 1])))
154
+
155
+
156
+ def get_cast_areas(face_vertices: ArrayLike) -> ArrayLike:
157
+ """Get visible, 2D-casted polygon areas
158
+
159
+ Args:
160
+ face_vertices (ArrayLike): polygons' vertices after 3D to 2D casting
161
+
162
+ Returns:
163
+ ArrayLike: areas of 2D-casted polygons
164
+ """
165
+ cast_ind = cast_indexes(face_vertices)
166
+ return polygon_areas(face_vertices[:, :, cast_ind[0]],
167
+ face_vertices[:, :, cast_ind[1]])
@@ -0,0 +1,5 @@
1
+ from .mesh_generation import icosphere
2
+ from .mesh_model import IcosphereModel, MeshModel
3
+ from .spots import add_spot, add_spots
4
+ from .binary import Binary
5
+ from .orbit_utils import get_orbit_jax
@@ -0,0 +1,123 @@
1
+ import jax.numpy as jnp
2
+ from functools import partial
3
+ import jax
4
+ from jax.typing import ArrayLike
5
+ from typing import NamedTuple, Tuple
6
+ from .mesh_model import MeshModel
7
+ from .mesh_transform import transform, evaluate_body_orbit
8
+ import astropy.units as u
9
+ from .orbit_utils import get_orbit_jax
10
+ from .mesh_view import resolve_occlusion, Grid
11
+
12
+
13
+ YEAR_TO_SECONDS = (u.year).to(u.s)
14
+
15
+
16
+ class Binary(NamedTuple):
17
+ body1: MeshModel
18
+ body2: MeshModel
19
+
20
+ # Orbital elements
21
+ P: float
22
+ ecc: float
23
+ T: float
24
+ i: float
25
+ omega: float
26
+ Omega: float
27
+
28
+ evaluated_times: ArrayLike
29
+
30
+ body1_centers: ArrayLike
31
+ body2_centers: ArrayLike
32
+ body1_velocities: ArrayLike
33
+ body2_velocities: ArrayLike
34
+
35
+ @classmethod
36
+ def from_bodies(cls, body1: MeshModel, body2: MeshModel) -> "Binary":
37
+ """Construct a Binary object from two mesh models.
38
+
39
+ Args:
40
+ body1 (MeshModel):
41
+ body2 (MeshModel):
42
+
43
+ Returns:
44
+ Binary: a binary consisting of body1 and body2
45
+ """
46
+ return cls(body1, body2, 1., 0., 0., 0., 0., 0., 0., jnp.zeros_like(body1.centers), jnp.zeros_like(body2.centers), jnp.zeros_like(body1.velocities), jnp.zeros_like(body2.velocities))
47
+
48
+
49
+ @partial(jax.jit, static_argnums=(7,))
50
+ def add_orbit(binary: Binary, P: float, ecc: float,
51
+ T: float, i: float, omega: float, Omega: float, orbit_resolution_points: int) -> Binary:
52
+ """Add orbit information to the binary object
53
+
54
+ Args:
55
+ binary (Binary):
56
+ P (float): orbit period [years]
57
+ ecc (float): orbit eccentrity
58
+ T (float): [defined time units]
59
+ i (float): inclination [rad]
60
+ omega (float): []
61
+ Omega (float): []
62
+ orbit_resolution_points (int): number of times to resolve the orbit at
63
+
64
+ Returns:
65
+ Binary: object with calculated orbit property values
66
+ """
67
+
68
+ orbit_resolution_times = jnp.linspace(0, P, orbit_resolution_points)
69
+ orbit = get_orbit_jax(orbit_resolution_times, binary.body1.mass, binary.body2.mass, P,
70
+ ecc, T, i, omega, Omega)
71
+ return binary._replace(P=P, ecc=ecc, T=T, i=i, omega=omega, Omega=Omega,
72
+ evaluated_times=orbit_resolution_times,
73
+ body1_centers=orbit[2, :, :], body2_centers=orbit[4, :, :],
74
+ body1_velocities=orbit[3, :, :], body2_velocities=orbit[5, :, :])
75
+
76
+
77
+ @partial(jax.jit, static_argnums=(2,))
78
+ def _evaluate_orbit(binary: Binary, time: ArrayLike, grid: Grid) -> Tuple[MeshModel, MeshModel]:
79
+ interpolate_orbit = jax.jit(jax.vmap(lambda x: jnp.interp(time, binary.evaluated_times, x, period=binary.P), in_axes=(0,)))
80
+ body1_center = interpolate_orbit(binary.body1_centers)
81
+ body2_center = interpolate_orbit(binary.body2_centers)
82
+ body1_velocity = interpolate_orbit(binary.body1_velocities)
83
+ body2_velocity = interpolate_orbit(binary.body2_velocities)
84
+
85
+ body1 = evaluate_body_orbit(transform(binary.body1, binary.body1.center+body1_center), body1_velocity)
86
+ body2 = evaluate_body_orbit(transform(binary.body2, binary.body2.center+body2_center), body2_velocity)
87
+
88
+ return jax.lax.cond(jnp.mean(body1.los_z)>jnp.mean(body2.los_z),
89
+ lambda: (body1, resolve_occlusion(body2, body1, grid)),
90
+ lambda: (resolve_occlusion(body1, body2, grid), body2))
91
+
92
+
93
+ v_evaluate_orbit = jax.jit(jax.vmap(_evaluate_orbit, in_axes=(None, 0, None)), static_argnums=(2,))
94
+
95
+
96
+ def evaluate_orbit(binary: Binary, time: ArrayLike, n_cells: int = 20) -> Tuple[MeshModel, MeshModel]:
97
+ """_summary_
98
+
99
+ Args:
100
+ binary (Binary): a binary object
101
+ time (ArrayLike): time to resolve the orbit at
102
+ n_cells (int, optional): number of grid cells to divide the calculation of occlusion to. Defaults to 20.
103
+
104
+ Returns:
105
+ Tuple[MeshModel, MeshModel]: (body1, body2)
106
+ """
107
+ grid = Grid.construct(binary.body1, binary.body2, n_cells)
108
+ return _evaluate_orbit(binary, time, grid)
109
+
110
+
111
+ def evaluate_orbit_at_times(binary: Binary, times: ArrayLike, n_cells: int = 20) -> Tuple[MeshModel, MeshModel]:
112
+ """_summary_
113
+
114
+ Args:
115
+ binary (Binary): a binary object
116
+ times (ArrayLike): times to resolve the orbit at
117
+ n_cells (int, optional): number of grid cells to divide the calculation of occlusion to. Defaults to 20.
118
+
119
+ Returns:
120
+ Tuple[MeshModel, MeshModel]: (body1, body2)
121
+ """
122
+ grid = Grid.construct(binary.body1, binary.body2, n_cells)
123
+ return v_evaluate_orbit(binary, times, grid)