Skip to content

Commit a250618

Browse files
Merge pull request #1 from michael-petersen/main
Add basic test package, which nicely also resulted in some cleanup!
2 parents 0e91fb8 + 17ed19d commit a250618

8 files changed

Lines changed: 262 additions & 31 deletions

File tree

.github/workflows/test.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Tests
2+
3+
on:
4+
- push
5+
- pull_request
6+
7+
jobs:
8+
test:
9+
runs-on: ${{ matrix.os }}
10+
strategy:
11+
matrix:
12+
os: [ubuntu-latest, windows-latest, macos-latest]
13+
python-version: ['3.10', '3.11', '3.12']
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v4
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install -r test_requirements.txt
25+
git clone https://github.com/ObservationalExpansions/flex.git
26+
pip install -e ./flex
27+
- name: Test with pytest
28+
run: |
29+
coverage run -m pytest -v -s
30+
- name: Upload coverage to Coveralls
31+
run: coveralls --service=github
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
**Simple exponential disc generation for testing galaxy morphology routines.**
44

5-
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/michael-petersen/discmodel/blob/main/LICENSE)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ObservationalExpansions/discmodel/blob/main/LICENSE)
6+
[![Coverage Status](https://coveralls.io/repos/github/ObservationalExpansions/discmodel/badge.svg?branch=main)](https://coveralls.io/github/ObservationalExpansions/discmodel?branch=main)
67

78

89
## Installation
910

1011
Installation of `discmodel` currently proceeds from local builds after cloning this repository:
1112
```
12-
git clone https://github.com/michael-petersen/discmodel.git
13+
git clone https://github.com/ObservationalExpansions/discmodel.git
1314
```
1415

1516
```

src/discmodel/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
66
See README.md for details
77
"""
8+
9+
from .optional_imports import _check_lintsampler, _check_flex
10+
HAS_LINTSAMPLER = _check_lintsampler()
11+
HAS_FLEX = _check_flex()
12+
813
from .discmodel import DiscGalaxy
914
from importlib.metadata import version
1015

1116
__version__ = version("discmodel")
12-
__all__ = ["DiscGalaxy"]
17+
__all__ = ["DiscGalaxy"]
18+

src/discmodel/discmodel.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,16 @@
77
# for interpolation
88
from scipy import interpolate
99

10-
# check if lintsampler is available
11-
try:
10+
# check if depedendencies are available
11+
from .optional_imports import _check_lintsampler, _check_flex
12+
HAS_LINTSAMPLER = _check_lintsampler()
13+
HAS_FLEX = _check_flex()
14+
15+
if HAS_LINTSAMPLER:
1216
import lintsampler
13-
HAS_LINTSAMPLER = True
14-
except ImportError:
15-
HAS_LINTSAMPLER = False
1617

17-
# check if flex is available
18-
try:
18+
if HAS_FLEX:
1919
import flex
20-
HAS_FLEX = True
21-
except ImportError:
22-
HAS_FLEX = False
23-
24-
2520

2621

2722
class DiscGalaxy(object):
@@ -80,7 +75,7 @@ def menclosed(r,a=self.a):
8075
return x,y,z,u,v,w
8176

8277
@staticmethod
83-
def make_rotation_matrix(xrotation,yrotation,zrotation,euler):
78+
def make_rotation_matrix(xrotation,yrotation,zrotation,euler=False):
8479

8580
radfac = np.pi/180.
8681

@@ -115,7 +110,7 @@ def make_rotation_matrix(xrotation,yrotation,zrotation,euler):
115110

116111
def rotate_disc(self,xrotation=0.,yrotation=0.,zrotation=0.,euler=False):
117112
'''
118-
rotate_point_vector
113+
rotate_disc
119114
take a collection of 3d points and return the positions rotated by a specified set of angles
120115
121116
inputs
@@ -172,19 +167,6 @@ def rotate_disc(self,xrotation=0.,yrotation=0.,zrotation=0.,euler=False):
172167
self.v = vout
173168
self.w = wout
174169

175-
@staticmethod
176-
def _angle_from_faceon(xrotation,yrotation,zrotation):
177-
"""compute the total inclination, relative to face on.
178-
179-
we're doing it this way because inclination is degenerate with the two dimensions into the page,
180-
so we just want a rough idea of how to correct.
181-
"""
182-
x = np.array([0,0,1.0])
183-
Rmatrix = make_rotation_matrix(xrotation,yrotation,zrotation)
184-
y = rotate_point_vector([0,0,1],Rmatrix)
185-
print('Angle from faceon: ',(180./np.pi)*np.arccos(np.dot(x,y)/(np.linalg.norm(x)*np.linalg.norm(y))))
186-
187-
188170
def generate_image(self,rmax,nbins,noiselevel=-1.0):
189171

190172
x_range = (-rmax, rmax) # range for the x-axis
@@ -245,7 +227,7 @@ def make_expansion(self,mmax,nmax,rscl,xmax=10000.,noisy=False):
245227

246228
return laguerre
247229

248-
def make_particle_expansion(self,mmax,nmax,rscl,xmax=10000.,noisy=False):
230+
def make_particle_expansion(self,mmax,nmax,rscl):
249231

250232
if not HAS_FLEX:
251233
raise ImportError("flex is not available. Please install flex to use this method.")

src/discmodel/optional_imports.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
def _check_lintsampler():
3+
try:
4+
import importlib
5+
importlib.import_module("lintsampler")
6+
return True
7+
except ImportError:
8+
return False
9+
10+
def _check_flex():
11+
try:
12+
import importlib
13+
importlib.import_module("lintsampler")
14+
return True
15+
except ImportError:
16+
return False
17+

test_requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
numpy==1.26.4
2+
pytest==7.4.0
3+
coverage==6.5.0
4+
coveralls==3.3.1
5+
lintsampler
6+
-e .

tests/test_discmodel.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import numpy as np
2+
3+
import discmodel
4+
5+
def test_discmodel_initialization():
6+
"""Test initialization of DiscGalaxy class."""
7+
N = 1000
8+
a = 3.0
9+
M = 1.0
10+
vcirc = 200.0
11+
rmax = 30.0
12+
13+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
14+
15+
assert disc.N == N
16+
assert disc.a == a
17+
assert disc.M == M
18+
assert disc.vcirc == vcirc
19+
assert disc.rmax == rmax * a
20+
21+
assert len(disc.x) == N
22+
assert len(disc.y) == N
23+
assert len(disc.z) == N
24+
assert len(disc.u) == N
25+
assert len(disc.v) == N
26+
assert len(disc.w) == N
27+
28+
# Check that mass is correctly assigned
29+
expected_mass = M / N
30+
assert np.all(disc.mass == expected_mass)
31+
32+
def test_discmodel_rotation():
33+
"""Test the rotation angles in DiscModel."""
34+
N = 1000
35+
a = 3.0
36+
M = 1.0
37+
vcirc = 200.0
38+
rmax = 30.0
39+
40+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
41+
42+
R1 = disc.make_rotation_matrix(45.0,0.,30.,False)
43+
# check R1 is 3x3
44+
assert R1.shape == (3,3)
45+
46+
R2 = disc.make_rotation_matrix(45.0,0.,30.,True)
47+
# check R2 is 3x3
48+
assert R2.shape == (3,3)
49+
50+
# check R1 and R2 are different
51+
assert not np.allclose(R1,R2)
52+
53+
# apply the rotation
54+
disc.rotate_disc(45.0,0.,30.,False)
55+
# check lengths are unchanged
56+
assert len(disc.x) == N
57+
assert len(disc.y) == N
58+
assert len(disc.z) == N
59+
60+
disc.rotate_disc(45.0,0.,30.,True)
61+
# check lengths are unchanged
62+
assert len(disc.x) == N
63+
assert len(disc.y) == N
64+
assert len(disc.z) == N
65+
66+
# try special case of N=1
67+
N = 1
68+
69+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
70+
71+
# apply the rotation
72+
disc.rotate_disc(45.0,0.,30.,False)
73+
74+
75+
76+
def test_discmodel_phasespace_input():
77+
"""Test initialization of DiscGalaxy class with phasespace input."""
78+
N = 1000
79+
a = 3.0
80+
M = 1.0
81+
vcirc = 200.0
82+
rmax = 30.0
83+
84+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
85+
86+
newdisc = discmodel.DiscGalaxy(phasespace=(disc.x,disc.y,disc.z,disc.u,disc.v,disc.w))
87+
88+
89+
def test_discmodel_version():
90+
"""Test that the version string is correctly set."""
91+
import discmodel
92+
assert isinstance(discmodel.__version__, str)
93+
94+
def test_discmodel_image():
95+
N = 1000
96+
a = 3.0
97+
M = 1.0
98+
vcirc = 200.0
99+
rmax = 30.0
100+
noiselevel = -100.0
101+
nbins = 50
102+
103+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
104+
disc.generate_image(rmax,nbins,noiselevel=noiselevel)
105+
106+
# add some noise
107+
noiselevel = 0.1
108+
disc.generate_image(rmax,nbins,noiselevel=noiselevel)
109+
110+
# check that r and p are set
111+
assert hasattr(disc,'r')
112+
assert hasattr(disc,'p')
113+
114+
115+
def test_discmodel_expansion():
116+
N = 1000
117+
a = 3.0
118+
M = 1.0
119+
vcirc = 200.0
120+
rmax = 30.0
121+
noiselevel = -100.0
122+
nbins = 50
123+
124+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
125+
disc.generate_image(rmax,nbins,noiselevel=noiselevel)
126+
127+
# add some noise
128+
noiselevel = 0.1
129+
disc.generate_image(rmax,nbins,noiselevel=noiselevel)
130+
131+
132+
# compute the expansion
133+
E1 = disc.make_expansion(mmax=4,nmax=4,rscl=1.0,xmax=rmax,noisy=False)
134+
135+
# compute the expansion from the particles
136+
E2 = disc.make_particle_expansion(mmax=4,nmax=4,rscl=1.0)
137+
138+
# compute A1
139+
a1 = disc.compute_a1(E1)
140+
141+
# check it is a float
142+
assert isinstance(a1,float)
143+
144+
145+
def test_discmodel_resampling():
146+
N = 1000
147+
a = 3.0
148+
M = 1.0
149+
vcirc = 200.0
150+
rmax = 30.0
151+
noiselevel = -100.0
152+
nbins = 50
153+
154+
disc = discmodel.DiscGalaxy(N=N, a=a, M=M, vcirc=vcirc, rmax=rmax)
155+
disc.generate_image(rmax,nbins,noiselevel=noiselevel)
156+
157+
# compute the expansion
158+
E1 = disc.make_expansion(mmax=4,nmax=4,rscl=1.0,xmax=rmax,noisy=False)
159+
160+
newdisc = disc.resample_expansion(E1)
161+
162+
# check newdisc is Nx2 (sampled from 2d image only)
163+
assert newdisc.shape == (N, 2)

tests/test_optional_imports.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# tests/test_optional_imports.py
2+
import importlib
3+
import pytest
4+
import discmodel.discmodel as dm
5+
6+
def test_check_lintsampler_missing(monkeypatch):
7+
# make importlib.import_module raise for lintsampler
8+
def raise_for(name):
9+
if name == "lintsampler":
10+
raise ImportError
11+
return importlib.import_module(name)
12+
monkeypatch.setattr(importlib, "import_module", raise_for)
13+
assert dm._check_lintsampler() is False
14+
15+
def test_check_lintsampler_present(monkeypatch):
16+
# make importlib.import_module return a dummy module for lintsampler
17+
def stub(name):
18+
if name == "lintsampler":
19+
import types
20+
return types.ModuleType("lintsampler")
21+
return importlib.import_module(name)
22+
monkeypatch.setattr(importlib, "import_module", stub)
23+
assert dm._check_lintsampler() is True

0 commit comments

Comments
 (0)