Skip to content

Commit 1bea9e4

Browse files
committed
Added esis.optics.CentralObscuration class.
1 parent 5c6c94d commit 1bea9e4

6 files changed

Lines changed: 207 additions & 0 deletions

File tree

.github/workflows/tests.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
name: tests
3+
4+
on:
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
pytest:
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-latest, windows-latest, macOS-latest]
17+
python-version: ["3.10", "3.12"]
18+
name: ${{ matrix.os }}, Python ${{ matrix.python-version }} tests
19+
steps:
20+
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Python ${{ matrix.python-version }}
25+
uses: actions/setup-python@v2
26+
with:
27+
python-version: ${{ matrix.python-version }}
28+
29+
- name: Install package
30+
run: |
31+
python -m pip install --upgrade pip
32+
pip install setuptools wheel
33+
pip install -e .
34+
35+
- name: Test with pytest
36+
run: |
37+
pip install pytest pytest-cov
38+
pytest --cov=. --cov-report=xml --cov-report=html
39+
40+
- name: Upload coverage to Codecov
41+
uses: codecov/codecov-action@v5
42+
with:
43+
token: ${{ secrets.CODECOV_TOKEN }}
44+
files: coverage.xml
45+
flags: unittests
46+
env_vars: OS,PYTHON
47+
name: codecov-umbrella
48+
fail_ci_if_error: true

esis/optics/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Model the ESIS optical system.
3+
"""
4+
5+
from . import abc
6+
from ._central_obscurations import CentralObscuration
7+
8+
__all__ = [
9+
"abc",
10+
"CentralObscuration",
11+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ._central_obscurations import (
2+
AbstractCentralObscuration,
3+
CentralObscuration,
4+
)
5+
6+
__all__ = [
7+
"AbstractCentralObscuration",
8+
"CentralObscuration",
9+
]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import abc
2+
import dataclasses
3+
import numpy as np
4+
import astropy.units as u
5+
import named_arrays as na
6+
import optika
7+
8+
__all__ = [
9+
"CentralObscuration",
10+
]
11+
12+
13+
@dataclasses.dataclass(eq=False, repr=False)
14+
class AbstractCentralObscuration(
15+
optika.mixins.Printable,
16+
optika.mixins.Translatable,
17+
):
18+
@property
19+
@abc.abstractmethod
20+
def num_folds(self) -> int:
21+
"""the order of the rotational symmetry of the optical system"""
22+
23+
@property
24+
@abc.abstractmethod
25+
def halfwidth(self) -> u.Quantity | na.AbstractScalar:
26+
"""distance from the center to the edge of the obscuration"""
27+
28+
@property
29+
def radius(self) -> u.Quantity | na.AbstractScalar:
30+
"""
31+
distance from the center to a vertex of the obscuration
32+
"""
33+
return self.halfwidth / np.cos(360 * u.deg / self.num_folds / 2)
34+
35+
@property
36+
@abc.abstractmethod
37+
def remove_last_vertex(self) -> bool:
38+
"""flag controlling whether the last vertex should be removed"""
39+
40+
@property
41+
def surface(self) -> optika.surfaces.Surface:
42+
num_folds = self.num_folds
43+
radius = self.radius
44+
offset_angle = 360 * u.deg / num_folds
45+
angle = na.linspace(
46+
start=0 * u.deg,
47+
stop=360 * u.deg,
48+
num=num_folds,
49+
axis="vertex",
50+
endpoint=False,
51+
)
52+
if self.remove_last_vertex:
53+
angle = angle[dict(vertex=slice(None, ~0))]
54+
angle = angle - offset_angle
55+
return optika.surfaces.Surface(
56+
name="obscuration",
57+
aperture=optika.apertures.PolygonalAperture(
58+
vertices=na.Cartesian3dVectorArray(
59+
x=radius * np.cos(angle),
60+
y=radius * np.sin(angle),
61+
z=0 * u.mm,
62+
),
63+
inverted=True,
64+
),
65+
transformation=self.transformation,
66+
)
67+
68+
69+
@dataclasses.dataclass(eq=False, repr=False)
70+
class CentralObscuration(
71+
AbstractCentralObscuration,
72+
):
73+
num_folds: int = 0
74+
halfwidth: u.Quantity | na.AbstractScalar = 0 * u.mm
75+
remove_last_vertex: bool = False
76+
translation: u.Quantity | na.AbstractCartesian3dVectorArray = 0 * u.mm
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pytest
2+
import numpy as np
3+
import astropy.units as u
4+
import named_arrays as na
5+
import optika
6+
from optika._tests import test_mixins
7+
import esis
8+
9+
10+
class AbstractTestAbstractCentralObscuration(
11+
test_mixins.AbstractTestPrintable,
12+
test_mixins.AbstractTestTranslatable,
13+
):
14+
def test_num_folds(self, a: esis.optics.abc.AbstractCentralObscuration):
15+
result = a.num_folds
16+
assert isinstance(result, int)
17+
assert result >= 0
18+
19+
def test_halfwidth(self, a: esis.optics.abc.AbstractCentralObscuration):
20+
result = a.halfwidth
21+
assert na.unit_normalized(result).is_equivalent(u.mm)
22+
assert np.all(result >= 0)
23+
24+
def test_radiush(self, a: esis.optics.abc.AbstractCentralObscuration):
25+
result = a.radius
26+
assert na.unit_normalized(result).is_equivalent(u.mm)
27+
assert np.all(result >= 0)
28+
29+
def test_remove_last_vertex(self, a: esis.optics.abc.AbstractCentralObscuration):
30+
result = a.remove_last_vertex
31+
assert isinstance(result, bool)
32+
33+
def test_surface(self, a: esis.optics.abc.AbstractCentralObscuration):
34+
result = a.surface
35+
assert isinstance(result, optika.surfaces.AbstractSurface)
36+
assert isinstance(result.aperture, optika.apertures.AbstractAperture)
37+
assert np.all(result.aperture.inverted)
38+
39+
40+
@pytest.mark.parametrize(
41+
argnames="a",
42+
argvalues=[
43+
esis.optics.CentralObscuration(
44+
num_folds=8,
45+
halfwidth=50 * u.mm,
46+
remove_last_vertex=True,
47+
translation=na.Cartesian3dVectorArray(z=100) * u.mm,
48+
)
49+
],
50+
)
51+
class TestCentralObscuration(
52+
AbstractTestAbstractCentralObscuration,
53+
):
54+
pass

esis/optics/abc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
Abstract classes used in this module.
3+
"""
4+
5+
from ._central_obscurations import AbstractCentralObscuration
6+
7+
__all__ = [
8+
"AbstractCentralObscuration",
9+
]

0 commit comments

Comments
 (0)