Skip to content

Commit e687350

Browse files
authored
Merge pull request #12 from quant-sci/add-ci-pipeline
chore: add ci pipleine and format check
2 parents 3494d0a + 2bb1da3 commit e687350

28 files changed

Lines changed: 1086 additions & 463 deletions

.github/workflows/ci.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
concurrency:
9+
group: ci-${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
quality:
14+
name: Format, lint & test
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
python-version: ["3.12"]
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Set up uv
25+
uses: astral-sh/setup-uv@v4
26+
27+
- name: Install Python ${{ matrix.python-version }}
28+
run: uv python install ${{ matrix.python-version }}
29+
30+
- name: Install dependencies
31+
run: uv sync --dev
32+
33+
- name: Check formatting (ruff)
34+
run: uv run ruff format --check .
35+
36+
- name: Lint (ruff)
37+
run: uv run ruff check --output-format=github .
38+
39+
- name: Test (pytest)
40+
run: uv run pytest

.github/workflows/python-package.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ jobs:
2525
run: uv python install ${{ matrix.python-version }}
2626
- name: Install dependencies
2727
run: uv sync --dev
28-
- name: Lint with flake8
29-
run: |
30-
# stop the build if there are Python syntax errors or undefined names
31-
uv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=.venv
32-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
33-
uv run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=.venv
28+
- name: Check formatting with ruff
29+
run: uv run ruff format --check .
30+
- name: Lint with ruff
31+
run: uv run ruff check .
3432
- name: Test with pytest
3533
run: uv run pytest
3634

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ __pycache__/
55
dist/
66
build/
77
.venv/
8+
9+
docs/_build/

cgeom/algorithms/__init__.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
1-
2-
3-
from ._mincircle import (
4-
MinimumCircle
5-
)
6-
71
from ._convexhull import (
82
ConvexHull,
93
)
10-
11-
from ._triangulation import (
12-
PolygonTriangulation,
13-
)
14-
15-
from ._voronoi import (
16-
VoronoiDiagram,
17-
)
18-
194
from ._delaunay import (
205
DelaunayTriangulation,
216
)
22-
237
from ._intersection import (
248
SegmentIntersection,
259
)
10+
from ._mincircle import MinimumCircle
11+
from ._triangulation import (
12+
PolygonTriangulation,
13+
)
14+
from ._voronoi import (
15+
VoronoiDiagram,
16+
)
2617

2718
__all__ = [
28-
'MinimumCircle',
29-
'ConvexHull',
30-
'PolygonTriangulation',
31-
'VoronoiDiagram',
32-
'DelaunayTriangulation',
33-
'SegmentIntersection',
34-
]
19+
"MinimumCircle",
20+
"ConvexHull",
21+
"PolygonTriangulation",
22+
"VoronoiDiagram",
23+
"DelaunayTriangulation",
24+
"SegmentIntersection",
25+
]

cgeom/algorithms/_convexhull.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import numpy as np
21
import math
32

3+
import numpy as np
4+
5+
46
class ConvexHull:
57
"""Class to find the convex hull of a set of points using the Gift Wrapping algorithm""
68
@@ -11,6 +13,7 @@ class ConvexHull:
1113
Attributes:
1214
points (np.array): Array of points
1315
"""
16+
1417
def __init__(self, points):
1518
"""Initialize the convex hull with a set of 2D points.
1619
@@ -23,6 +26,7 @@ def __init__(self, points):
2326
non-numeric, or wrong shape.
2427
"""
2528
from cgeom.elements.models import ConvexHullInput
29+
2630
validated = ConvexHullInput(points=points)
2731
self.points = np.array(validated.points)
2832

@@ -62,7 +66,14 @@ def low_angle(self, b, c):
6266
u = [u[0] / mag_u, u[1] / mag_u]
6367
v = [v[0] / mag_v, v[1] / mag_v]
6468
angle = math.acos(u[0] * v[0] + u[1] * v[1])
65-
orient = b[0] * c[1] + b[1] * a[0] + c[0] * a[1] - a[0] * c[1] - a[1] * b[0] - c[0] * b[1]
69+
orient = (
70+
b[0] * c[1]
71+
+ b[1] * a[0]
72+
+ c[0] * a[1]
73+
- a[0] * c[1]
74+
- a[1] * b[0]
75+
- c[0] * b[1]
76+
)
6677
if orient < 0:
6778
angle = 2 * math.pi - angle
6879
if angle < low_angle:
@@ -86,16 +97,18 @@ def convex_hull(self):
8697
b = self.low_angle(a, ch[-2])
8798
return ch
8899

89-
def plot(self, title='Convex Hull for a set of points'):
100+
def plot(self, title="Convex Hull for a set of points"):
90101
"""Deprecated: use cgeom.visualization.plot_convex_hull() instead."""
91102
import warnings
103+
92104
warnings.warn(
93105
"ConvexHull.plot() is deprecated. "
94106
"Use cgeom.visualization.plot_convex_hull(hull_obj, title) instead.",
95107
DeprecationWarning,
96108
stacklevel=2,
97109
)
98110
from cgeom.visualization import plot_convex_hull
111+
99112
plot_convex_hull(self, title)
100113

101114
def get_indexes(self):
@@ -110,4 +123,4 @@ def get_indexes(self):
110123
while element[0] != self.points[j][0] or element[1] != self.points[j][1]:
111124
j += 1
112125
indexes.append(j)
113-
return(indexes)
126+
return indexes

cgeom/algorithms/_delaunay.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(self, points):
2020
non-numeric, or wrong shape.
2121
"""
2222
from cgeom.elements.models import DelaunayTriangulationInput
23+
2324
validated = DelaunayTriangulationInput(points=points)
2425
self.points = np.array(validated.points)
2526
self._triangles = None
@@ -50,11 +51,13 @@ def triangulate(self):
5051
margin = 20.0
5152

5253
# Super-triangle vertices stored at indices n, n+1, n+2
53-
super_pts = np.array([
54-
[mid_x - margin * delta, mid_y - delta],
55-
[mid_x + margin * delta, mid_y - delta],
56-
[mid_x, mid_y + margin * delta],
57-
])
54+
super_pts = np.array(
55+
[
56+
[mid_x - margin * delta, mid_y - delta],
57+
[mid_x + margin * delta, mid_y - delta],
58+
[mid_x, mid_y + margin * delta],
59+
]
60+
)
5861
all_pts = np.vstack([pts, super_pts])
5962

6063
# Each triangle is a frozenset of 3 indices
@@ -65,7 +68,9 @@ def triangulate(self):
6568
bad = set()
6669
for tri in triangles:
6770
a, b, c = tri
68-
if self._in_circumcircle(all_pts[i], all_pts[a], all_pts[b], all_pts[c]):
71+
if self._in_circumcircle(
72+
all_pts[i], all_pts[a], all_pts[b], all_pts[c]
73+
):
6974
bad.add(tri)
7075

7176
# Find boundary polygon: edges belonging to exactly one bad triangle
@@ -118,8 +123,9 @@ def get_edges(self):
118123
key = (min(edge), max(edge))
119124
if key not in seen:
120125
seen.add(key)
121-
edges.append([self.points[key[0]].tolist(),
122-
self.points[key[1]].tolist()])
126+
edges.append(
127+
[self.points[key[0]].tolist(), self.points[key[1]].tolist()]
128+
)
123129
return edges
124130

125131
def get_circumcircles(self):
@@ -140,13 +146,15 @@ def get_circumcircles(self):
140146
def plot(self, title="Delaunay Triangulation"):
141147
"""Deprecated: use cgeom.visualization.plot_delaunay() instead."""
142148
import warnings
149+
143150
warnings.warn(
144151
"DelaunayTriangulation.plot() is deprecated. "
145152
"Use cgeom.visualization.plot_delaunay(dt_obj, title) instead.",
146153
DeprecationWarning,
147154
stacklevel=2,
148155
)
149156
from cgeom.visualization import plot_delaunay
157+
150158
plot_delaunay(self, title)
151159

152160
@staticmethod
@@ -161,12 +169,16 @@ def _circumcircle(a, b, c):
161169
cx, cy = float(c[0]), float(c[1])
162170

163171
D = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
164-
ux = ((ax * ax + ay * ay) * (by - cy)
165-
+ (bx * bx + by * by) * (cy - ay)
166-
+ (cx * cx + cy * cy) * (ay - by)) / D
167-
uy = ((ax * ax + ay * ay) * (cx - bx)
168-
+ (bx * bx + by * by) * (ax - cx)
169-
+ (cx * cx + cy * cy) * (bx - ax)) / D
172+
ux = (
173+
(ax * ax + ay * ay) * (by - cy)
174+
+ (bx * bx + by * by) * (cy - ay)
175+
+ (cx * cx + cy * cy) * (ay - by)
176+
) / D
177+
uy = (
178+
(ax * ax + ay * ay) * (cx - bx)
179+
+ (bx * bx + by * by) * (ax - cx)
180+
+ (cx * cx + cy * cy) * (bx - ax)
181+
) / D
170182
r = np.sqrt((ax - ux) ** 2 + (ay - uy) ** 2)
171183
return ux, uy, r
172184

@@ -181,14 +193,17 @@ def _in_circumcircle(p, a, b, c):
181193
bx, by = float(b[0]) - float(p[0]), float(b[1]) - float(p[1])
182194
cx, cy = float(c[0]) - float(p[0]), float(c[1]) - float(p[1])
183195

184-
det = (ax * ax + ay * ay) * (bx * cy - cx * by) \
185-
- (bx * bx + by * by) * (ax * cy - cx * ay) \
196+
det = (
197+
(ax * ax + ay * ay) * (bx * cy - cx * by)
198+
- (bx * bx + by * by) * (ax * cy - cx * ay)
186199
+ (cx * cx + cy * cy) * (ax * by - bx * ay)
200+
)
187201

188202
# The sign of det depends on triangle orientation (CCW vs CW).
189203
# Compute orientation and flip if clockwise.
190-
orient = (float(b[0]) - float(a[0])) * (float(c[1]) - float(a[1])) \
191-
- (float(b[1]) - float(a[1])) * (float(c[0]) - float(a[0]))
204+
orient = (float(b[0]) - float(a[0])) * (float(c[1]) - float(a[1])) - (
205+
float(b[1]) - float(a[1])
206+
) * (float(c[0]) - float(a[0]))
192207
if orient < 0:
193208
det = -det
194209

cgeom/algorithms/_intersection.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import numpy as np
77

8-
98
# ---------------------------------------------------------------------------
109
# Event types — ordering matters for the heap (left < intersection < right)
1110
# ---------------------------------------------------------------------------
1211

12+
1313
class _EventType(IntEnum):
1414
LEFT = 0
1515
INTERSECTION = 1
@@ -97,6 +97,7 @@ def _y_at_x(seg, x):
9797
# Sweep-line status structure
9898
# ---------------------------------------------------------------------------
9999

100+
100101
class _SweepLineStatus:
101102
"""Maintains sorted list of active segments ordered by y at current sweep x."""
102103

@@ -159,6 +160,7 @@ def neighbors(self, seg_idx):
159160
# Main class
160161
# ---------------------------------------------------------------------------
161162

163+
162164
class SegmentIntersection:
163165
"""Find intersection points among a set of 2D line segments.
164166
@@ -181,6 +183,7 @@ def __init__(self, segments):
181183
segment, non-numeric, or wrong shape.
182184
"""
183185
from cgeom.elements.models import SegmentIntersectionInput
186+
184187
validated = SegmentIntersectionInput(segments=segments)
185188
self.segments = np.array(validated.segments)
186189

@@ -222,9 +225,9 @@ def _check_and_add(seg_a, seg_b):
222225
if rkey not in found_points:
223226
found_points[rkey] = pt
224227
found_pairs.add(pair)
225-
heapq.heappush(events, (
226-
pt[0], _EventType.INTERSECTION, pt[1], seg_a, seg_b
227-
))
228+
heapq.heappush(
229+
events, (pt[0], _EventType.INTERSECTION, pt[1], seg_a, seg_b)
230+
)
228231

229232
while events:
230233
x, etype, y, s1, s2 = heapq.heappop(events)
@@ -303,13 +306,15 @@ def get_segments(self):
303306
def plot(self, title="Segment Intersections"):
304307
"""Deprecated: use cgeom.visualization.plot_intersections() instead."""
305308
import warnings
309+
306310
warnings.warn(
307311
"SegmentIntersection.plot() is deprecated. "
308312
"Use cgeom.visualization.plot_intersections(si_obj, title) instead.",
309313
DeprecationWarning,
310314
stacklevel=2,
311315
)
312316
from cgeom.visualization import plot_intersections
317+
313318
plot_intersections(self, title)
314319

315320
def _normalize_segments(self):

0 commit comments

Comments
 (0)