geometry¶
ソースコード¶
from titan_pylib.geometry.geometry import GeometryUtil
from titan_pylib.geometry.geometry import Point
from titan_pylib.geometry.geometry import Line
from titan_pylib.geometry.geometry import Polygon
from titan_pylib.geometry.geometry import ConvexPolygon
from titan_pylib.geometry.geometry import Circle
from titan_pylib.geometry.geometry import Segment
from titan_pylib.geometry.geometry import Geometry
展開済みコード¶
1# from titan_pylib.geometry.geometry import Geometry
2import math
3from typing import Optional, Union, Final
4from decimal import Decimal, getcontext
5# from titan_pylib.algorithm.sort.merge_sort import merge_sort
6# from titan_pylib.my_class.supports_less_than import SupportsLessThan
7from typing import Protocol
8
9
10class SupportsLessThan(Protocol):
11
12 def __lt__(self, other) -> bool: ...
13from typing import Iterable, TypeVar, Callable
14
15T = TypeVar("T", bound=SupportsLessThan)
16
17
18def merge_sort(
19 a: Iterable[T], key: Callable[[T, T], bool] = lambda s, t: s < t
20) -> list[T]:
21 """マージソートです。安定なはずです。
22 最悪 :math:`O(n\\log{n})` 時間です。
23
24 Args:
25 a (Iterable[T]): ソートする列です。
26 key (Callable[[T, T], bool], optional): 比較関数 `key` にしたがって比較演算をします。
27 (第1引数)<=(第2引数) のとき、 ``True`` を返すようにしてください。
28 """
29
30 def _sort(l: int, r: int) -> None:
31 if r - l <= 1:
32 return
33 if r - l == 2:
34 if not key(a[l], a[l + 1]):
35 a[l], a[l + 1] = a[l + 1], a[l]
36 return
37 mid = (l + r) // 2
38 _sort(l, mid)
39 _sort(mid, r)
40 i, j = l, mid
41 indx = 0
42 while i < mid and j < r:
43 if key(a[i], a[j]):
44 buff[indx] = a[i]
45 i += 1
46 else:
47 buff[indx] = a[j]
48 j += 1
49 indx += 1
50 for k in range(i, mid):
51 a[l + indx + k - i] = a[k]
52 for k in range(l, l + indx):
53 a[k] = buff[k - l]
54
55 a = list(a)
56 buff = [0] * len(a)
57 _sort(0, len(a))
58 return a
59# from titan_pylib.math.decimal_util import (
60# decimal_pi,
61# decimal_sin,
62# decimal_cos,
63# decimal_acos,
64# )
65from decimal import Decimal, getcontext
66import math
67
68
69def decimal_pi() -> Decimal:
70 getcontext().prec += 2
71 three = Decimal(3)
72 lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24
73 while s != lasts:
74 lasts = s
75 n, na = n + na, na + 8
76 d, da = d + da, da + 32
77 t = (t * n) / d
78 s += t
79 getcontext().prec -= 2
80 return +s
81
82
83def decimal_exp(x: Decimal) -> Decimal:
84 getcontext().prec += 2
85 i, lasts, s, fact, num = 0, 0, 1, 1, 1
86 while s != lasts:
87 lasts = s
88 i += 1
89 fact *= i
90 num *= x
91 s += num / fact
92 getcontext().prec -= 2
93 return +s
94
95
96def decimal_cos(x: Decimal) -> Decimal:
97 getcontext().prec += 2
98 i, lasts, s, fact, num, sign = 0, 0, 1, 1, 1, 1
99 while s != lasts:
100 lasts = s
101 i += 2
102 fact *= i * (i - 1)
103 num *= x * x
104 sign *= -1
105 s += num / fact * sign
106 getcontext().prec -= 2
107 return +s
108
109
110def decimal_sin(x: Decimal) -> Decimal:
111 getcontext().prec += 2
112 i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
113 while s != lasts:
114 lasts = s
115 i += 2
116 fact *= i * (i - 1)
117 num *= x * x
118 sign *= -1
119 s += num / fact * sign
120 getcontext().prec -= 2
121 return +s
122
123
124def decimal_asin(x: Decimal) -> Decimal:
125 assert isinstance(x, Decimal)
126 if x < -1 or x > 1:
127 raise ValueError("math domain error")
128 if x == 1:
129 return decimal_pi() / 2
130 if x == -1:
131 return -decimal_pi() / 2
132 getcontext().prec += 2
133 eps = Decimal("1e-" + str(getcontext().prec))
134 sum_acos = x
135 term = Decimal(1)
136 power = x
137 n = 0
138 while abs(term * power) > eps:
139 n += 1
140 term *= (2 * n - 1) * (2 * n) * (2 * (n - 1) + 1)
141 term /= 4 * n * n * (2 * n + 1)
142 power *= x * x
143 sum_acos += term * power
144 result = sum_acos
145 getcontext().prec -= 2
146 return +result
147
148
149def decimal_acos(x: Decimal) -> Decimal:
150 assert isinstance(x, Decimal)
151 if x < -1 or x > 1:
152 raise ValueError("math domain error")
153 if x == 1:
154 return Decimal(0)
155 if x == -1:
156 return decimal_pi()
157 getcontext().prec += 2
158 eps = Decimal("1e-" + str(getcontext().prec))
159 sum_acos = x
160 term = Decimal(1)
161 power = x
162 n = 0
163 while abs(term * power) > eps:
164 n += 1
165 term *= (2 * n - 1) * (2 * n) * (2 * (n - 1) + 1)
166 term /= 4 * n * n * (2 * n + 1)
167 power *= x * x
168 sum_acos += term * power
169 result = decimal_pi() / 2 - sum_acos
170 getcontext().prec -= 2
171 return +result
172
173
174class GeometryUtil:
175
176 getcontext().prec = 10
177 USE_DECIMAL: Final[bool] = False
178
179 EPS: Final[Union[Decimal, float]] = (
180 Decimal("1e-" + str(getcontext().prec // 2)) if USE_DECIMAL else 1e-10
181 )
182 GEO_INF: Final[Union[Decimal, float]] = (
183 Decimal("INF") if USE_DECIMAL else float("inf")
184 )
185 sin = decimal_sin if USE_DECIMAL else math.sin
186 cos = decimal_cos if USE_DECIMAL else math.cos
187 acos = decimal_acos if USE_DECIMAL else math.acos
188 pi = decimal_pi() if USE_DECIMAL else math.pi
189
190 @classmethod
191 def sqrt(cls, v: Union[Decimal, float]) -> None:
192 return math.sqrt(v) if not cls.USE_DECIMAL else v.sqrt()
193
194 @classmethod
195 def eq(cls, a: float, b: float) -> bool:
196 return abs(a - b) < cls.EPS
197
198 @classmethod
199 def lt(cls, u: float, v: float) -> bool:
200 return u < v and u < max(v, v - cls.EPS)
201
202 @classmethod
203 def gt(cls, u: float, v: float) -> bool:
204 return u > v and u > min(v, v - cls.EPS)
205
206 @classmethod
207 def le(cls, u: float, v: float) -> bool:
208 return cls.eq(u, v) or cls.lt(u, v)
209
210 @classmethod
211 def ge(cls, u: float, v: float) -> bool:
212 return cls.eq(u, v) or cls.gt(u, v)
213
214
215class Point:
216
217 __slots__ = "x", "y"
218
219 def __init__(self, x: float, y: float) -> None:
220 self.x = x
221 self.y = y
222
223 def __str__(self) -> str:
224 return f"Point({self.x}, {self.y})"
225
226 __repr__ = __str__
227
228 def show(self, precision: int = 16) -> None:
229 format_string = f"{{:.{precision}f}}"
230 print(f"{format_string.format(self.x)} {format_string.format(self.y)}")
231
232 def __add__(self, other: "Point") -> "Point":
233 if isinstance(other, Point):
234 return Point(self.x + other.x, self.y + other.y)
235 else:
236 return Point(self.x + other, self.y + other)
237
238 def __sub__(self, other: Union["Point", float]) -> "Point":
239 if isinstance(other, Point):
240 return Point(self.x - other.x, self.y - other.y)
241 else:
242 return Point(self.x - other, self.y - other)
243
244 def __mul__(self, other: Union["Point", float]) -> "Point":
245 if isinstance(other, Point):
246 return Point(self.x * other.x, self.y * other.y)
247 else:
248 return Point(self.x * other, self.y * other)
249
250 __rmul__ = __mul__
251
252 def __truediv__(self, other: float) -> "Point":
253 return Point(self.x / other, self.y / other)
254
255 def __le__(self, other: "Point") -> bool:
256 if self == other:
257 return True
258 if GeometryUtil.lt(self.x, other.x):
259 return True
260 if GeometryUtil.lt(other.x, self.x):
261 return False
262 return GeometryUtil.lt(self.y, other.y)
263
264 def __lt__(self, other: "Point") -> bool:
265 if self == other:
266 return False
267 if GeometryUtil.lt(self.x, other.x):
268 return True
269 if GeometryUtil.lt(other.x, self.x):
270 return False
271 return GeometryUtil.lt(self.y, other.y)
272
273 def __ge__(self, other: "Point") -> bool:
274 return not (self < other)
275
276 def __gt__(self, other: "Point") -> bool:
277 return not (self <= other)
278
279 def __eq__(self, other: "Point") -> bool:
280 return (
281 abs(self.x - other.x) < GeometryUtil.EPS
282 and abs(self.y - other.y) < GeometryUtil.EPS
283 )
284
285 def __neg__(self) -> "Point":
286 return Point(-self.x, -self.y)
287
288 def __abs__(self) -> float:
289 return GeometryUtil.sqrt(self.x * self.x + self.y * self.y)
290
291 def norm(self) -> float:
292 """norm"""
293 return abs(self)
294
295 def norm2(self) -> float:
296 """(x**2 + y**2)を返す(ルートの計算はしない)"""
297 return self.x * self.x + self.y * self.y
298
299 def unit(self) -> "Point":
300 return self / abs(self)
301
302 def rotate(self, theta: float) -> "Point":
303 return Point(
304 GeometryUtil.cos(theta) * self.x - GeometryUtil.sin(theta) * self.y,
305 GeometryUtil.sin(theta) * self.x + GeometryUtil.cos(theta) * self.y,
306 )
307
308 def copy(self) -> "Point":
309 return Point(self.x, self.y)
310
311
312class Line:
313 """ax + by + c = 0"""
314
315 __slots__ = "a", "b", "c", "p1", "p2"
316
317 def __init__(self, a: int, b: int, c: int, p1: Point, p2: Point) -> None:
318 self.a = a
319 self.b = b
320 self.c = c
321 self.p1 = p1
322 self.p2 = p2
323
324 @classmethod
325 def from_points(cls, p1: Point, p2: Point) -> "Line":
326 a = p2.y - p1.y
327 b = p1.x - p2.x
328 c = -p1.y * b - p1.x * a
329 if isinstance(a, int) and isinstance(b, int) and isinstance(c, int):
330 g = math.gcd(a, b)
331 g = math.gcd(g, c)
332 a //= g
333 b //= g
334 c //= g
335 a, b, c = max((a, b, c), (-a, -b, -c))
336 return cls(a, b, c, p1, p2)
337
338 @classmethod
339 def from_abc(cls, a: int, b: int, c: int) -> "Line":
340 raise NotImplementedError
341
342 def get_y(self, x: float) -> Optional[float]:
343 return None if GeometryUtil.eq(self.b, 0) else -(self.c + self.a * x) / self.b
344
345 def get_x(self, y: float) -> Optional[float]:
346 return None if GeometryUtil.eq(self.a, 0) else -(self.c + self.b * y) / self.a
347
348
349class Polygon:
350 """多角形クラス"""
351
352 def __init__(self, ps: list[Point]) -> None:
353 self.n = len(ps)
354 self.ps = ps
355
356 def area(self) -> float:
357 """面積を求める / :math:`O(n)`"""
358 res = 0
359 p = self.ps
360 for i in range(self.n - 1):
361 res += Geometry.cross(p[i], p[i + 1])
362 if p:
363 res += Geometry.cross(p[-1], p[0])
364 return res / 2
365
366 def is_convex(self) -> bool:
367 """凸多角形かどうか / :math:`O(n)`"""
368 ps = self.ps
369 for i in range(self.n):
370 pre = (i - 1 + self.n) % self.n
371 now = i
372 nxt = (i + 1) % self.n
373 if Geometry.ccw(ps[pre], ps[now], ps[nxt]) == -1:
374 return False
375 return True
376
377 def get_degree(self, radian):
378 return radian * (180 / GeometryUtil.pi)
379
380 def contains(self, p: Point) -> int:
381 """点の包含関係を返す / O(n)
382
383 Returns:
384 int: `2`: `p` を含む
385 `1`: `p` が辺上にある
386 `0`: それ以外
387 """
388 ps = self.ps
389 deg = 0
390 for i in range(self.n):
391 if ps[i] == ps[(i + 1) % self.n]:
392 continue
393 if GeometryUtil.eq(
394 Geometry.dist_p_to_segmemt(p, Segment(ps[i], ps[(i + 1) % self.n])), 0
395 ):
396 return 1
397 ax = ps[i].x - p.x
398 ay = ps[i].y - p.y
399 bx = ps[(i + 1) % self.n].x - p.x
400 by = ps[(i + 1) % self.n].y - p.y
401 cos = (ax * bx + ay * by) / (
402 GeometryUtil.sqrt((ax * ax + ay * ay) * (bx * bx + by * by))
403 )
404 cos = min(1, max(-1, cos))
405 deg += self.get_degree(GeometryUtil.acos(cos))
406 return 2 if GeometryUtil.eq(deg, 360) else 0
407
408
409class ConvexPolygon(Polygon):
410
411 def __init__(self, ps: list[Point], line_contains: bool = False) -> None:
412 self.ps = Geometry.convex_hull(ps, line_contains)
413 self.n = len(self.ps)
414
415 def diameter(self) -> tuple[float, int, int]:
416 ps = self.ps
417 n = len(ps)
418 if n == 0:
419 return -1, -1, -1
420 if n == 1:
421 return 0, ps[0], ps[0]
422 if n == 2:
423 return abs(ps[0] - ps[1]), ps[0], ps[1]
424 u, v = 0, 0
425 up, vp = None, None
426 for i in range(n):
427 if ps[u] > ps[i]:
428 u = i
429 up = ps[i]
430 if ps[v] < ps[i]:
431 v = i
432 vp = ps[i]
433 d = 0
434 su, sv = u, v
435 loop = False
436 while (u != su or v != sv) or (not loop):
437 loop = True
438 if GeometryUtil.lt(d, abs(ps[u] - ps[v])):
439 d = abs(ps[u] - ps[v])
440 up = ps[u]
441 vp = ps[v]
442 if GeometryUtil.lt(
443 Geometry.cross(ps[(u + 1) % n] - ps[u], ps[(v + 1) % n] - ps[v]), 0
444 ):
445 u = (u + 1) % n
446 else:
447 v = (v + 1) % n
448 return d, up, vp
449
450 def contains(self, p: Point) -> int:
451 """点の包含関係を返す / :math:`O(\\log{n})`
452
453 Returns:
454 int: `2`: `p` を含む
455 `1`: `p` が辺上にある
456 `0`: それ以外
457 """
458 ps = self.ps
459 n = len(ps)
460 b1 = Geometry.cross(ps[1] - ps[0], p - ps[0])
461 b2 = Geometry.cross(ps[-1] - ps[0], p - ps[0])
462 if GeometryUtil.lt(b1, 0) or GeometryUtil.gt(b2, 0):
463 return 0
464 l, r = 1, n - 1
465 while r - l > 1:
466 mid = (l + r) >> 1
467 c = Geometry.cross(p - ps[0], ps[mid] - ps[0])
468 if GeometryUtil.eq(c, 0) or GeometryUtil.gt(c, 0):
469 r = mid
470 else:
471 l = mid
472 b3 = Geometry.cross(ps[l] - p, ps[r] - p)
473 if GeometryUtil.eq(b3, 0):
474 return 1
475 if GeometryUtil.gt(b3, 0):
476 if GeometryUtil.eq(b1, 0) or GeometryUtil.eq(b2, 0):
477 return 1
478 else:
479 return 2
480 return 0
481
482 def convex_cut(self, l: Line) -> "ConvexPolygon":
483 """直線 ``l`` で切断したときの左側の凸多角形を返す"""
484 ret = []
485 for i in range(self.n):
486 now = self.ps[i]
487 nxt = self.ps[(i + 1) % self.n]
488 if Geometry.ccw(l.p1, l.p2, now) != -1:
489 ret.append(now.copy())
490 if Geometry.ccw(l.p1, l.p2, now) * Geometry.ccw(l.p1, l.p2, nxt) < 0:
491 p = Geometry.cross_point_line(Line.from_points(now, nxt), l)
492 assert p is not None
493 ret.append(p.copy())
494 return ConvexPolygon(ret)
495
496
497class Circle:
498
499 __slots__ = "p", "r"
500
501 def __init__(self, p: Point, r: float) -> None:
502 """円
503
504 Args:
505 p (Point): 直径を表す `Point`
506 r (float): 半径
507 """
508 self.p = p
509 self.r = r
510
511 def area(self) -> float:
512 """面積を返す"""
513 return GeometryUtil.pi * self.r * self.r
514
515
516class Segment:
517
518 def __init__(self, p1: Point, p2: Point) -> None:
519 assert p1 != p2, f"{p1=}, {p2=}"
520 if p1 > p2:
521 p1, p2 = p2, p1
522 self.p1 = p1
523 self.p2 = p2
524
525
526class Geometry:
527 """ref:
528 - https://hcpc-hokudai.github.io/arpsive/geometry_004.pdf
529 - https://bakamono1357.hatenablog.com/entry/2020/04/29/025320
530 - https://tjkendev.github.io/procon-library/python/geometry/circles_associated_with_triangle.html
531 - https://atcoder.jp/contests/abc296/editorial/6109
532 """
533
534 @classmethod
535 def dot(cls, p1: Point, p2: Point) -> float:
536 return p1.x * p2.x + p1.y * p2.y
537
538 @classmethod
539 def cross(cls, p1: Point, p2: Point) -> float:
540 return p1.x * p2.y - p1.y * p2.x
541
542 @classmethod
543 def ccw(cls, u: Point, v: Point, p: Point) -> int:
544 """u->vに対し、v->pの位置関係を求める
545
546 Returns:
547 int: `+1`: a->bに対し、b->cが半時計回りに進む
548 `-1`: a->bに対し、b->cが時計回りに進む
549 `+2`: a->bに対し、b->cが逆方向に進む
550 `-2`: a->bに対し、b->cが同一方向に進む
551 ` 0`: cが線分a->b上にある
552 """
553 if cls.cross(v - u, p - u) > GeometryUtil.EPS:
554 return 1
555 if cls.cross(v - u, p - u) < -GeometryUtil.EPS:
556 return -1
557 if cls.dot(v - u, p - u) < -GeometryUtil.EPS:
558 return 2
559 if GeometryUtil.lt(abs(v - u), abs(p - u)):
560 # if abs(v - u) + GeometryUtil.EPS < abs(p - u):
561 return -2
562 return 0
563
564 @classmethod
565 def projection_point(cls, p: Point, l: Line) -> Point:
566 """直線 `l` に、点 `p` からおろした垂線の足の `Point` を返す"""
567 t = cls.dot(p - l.p1, l.p1 - l.p2) / (l.p1 - l.p2).norm2()
568 return l.p1 + (l.p1 - l.p2) * t
569
570 @classmethod
571 def reflection_point(cls, p: Point, l: Line) -> Point:
572 """直線 `l` を対象軸として点 `p` と線対称の点を返す"""
573 return p + 2 * (cls.projection_point(p, l) - p)
574
575 @classmethod
576 def is_orthogonal(cls, l1: Line, l2: Line) -> bool:
577 """直線 `l1, l2` が直行しているかどうか"""
578 return GeometryUtil.eq(cls.dot(l1.p2 - l1.p1, l2.p2 - l2.p1), 0)
579
580 @classmethod
581 def is_parallel(cls, l1: Line, l2: Line) -> bool:
582 """直線 `l1, l2` が平行かどうか"""
583 return GeometryUtil.eq(cls.cross(l1.p2 - l1.p1, l2.p2 - l2.p1), 0)
584
585 @classmethod
586 def is_intersect_linesegment(cls, s1: Segment, s2: Segment) -> bool:
587 """線分 `s1` と `s2` が交差しているかどうか判定する"""
588 return (
589 cls.ccw(s1.p1, s1.p2, s2.p1) * cls.ccw(s1.p1, s1.p2, s2.p2) <= 0
590 and cls.ccw(s2.p1, s2.p2, s1.p1) * cls.ccw(s2.p1, s2.p2, s1.p2) <= 0
591 )
592
593 @classmethod
594 def is_intersect_circle(cls, c1: Circle, c2: Circle) -> int:
595 """2円の位置関係
596
597 Returns:
598 int: 共通接線の数
599 `0`: 内包している
600 `1`: 内接している
601 `2`: 2点で交わっている
602 `3`: 外接している
603 `4`: 離れている
604 """
605 d = abs(c1.p - c2.p)
606 if GeometryUtil.gt(d, c1.r + c2.r):
607 # if d > c1.r + c2.r + GeometryUtil.EPS:
608 return 4
609 if GeometryUtil.eq(d, c1.r + c2.r):
610 return 3
611 if GeometryUtil.eq(d, abs(c1.r - c2.r)):
612 return 1
613 if GeometryUtil.lt(d, abs(c1.r - c2.r)):
614 # if d < abs(c1.r - c2.r) - GeometryUtil.EPS:
615 return 0
616 return 2
617
618 @classmethod
619 def cross_point_line(cls, l1: Line, l2: Line) -> Optional[Point]:
620 assert isinstance(l1, Line)
621 assert isinstance(l2, Line)
622 d1 = cls.cross(l1.p2 - l1.p1, l2.p2 - l2.p1)
623 d2 = cls.cross(l1.p2 - l1.p1, l1.p2 - l2.p1)
624 if GeometryUtil.eq(abs(d1), 0) and GeometryUtil.eq(abs(d2), 0):
625 return l2.p1
626 if GeometryUtil.eq(abs(d1), 0):
627 return None
628 return l2.p1 + (l2.p2 - l2.p1) * (d2 / d1)
629
630 @classmethod
631 def cross_point_line_segment(cls, l: Line, s: Segment) -> Optional[Point]:
632 raise NotImplementedError
633 assert isinstance(l, Line)
634 assert isinstance(s, Segment)
635 assert s.p1.x <= s.p2.x
636 if (
637 (not GeometryUtil.eq(s.p1.x, s.p2.x))
638 and l.get_y(s.p1.x) is not None
639 and l.get_y(s.p2.x) is not None
640 ):
641 sl = Segment(
642 Point(s.p1.x, l.get_y(s.p1.x)),
643 Point(s.p2.x, l.get_y(s.p2.x)),
644 )
645 return cls.cross_point_segment(sl, s)
646 p1, p2 = s.p1, s.p2
647 if p1.y > p2.y:
648 p1, p2 = p2, p1
649 if (
650 (not GeometryUtil.eq(p1.y, p2.y))
651 and l.get_x(p1.y) is not None
652 and l.get_x(p2.y) is not None
653 ):
654 sl = Segment(
655 Point(l.get_x(p1.y), p1.y),
656 Point(l.get_x(p2.y), p2.y),
657 )
658 return cls.cross_point_segment(sl, s)
659 return False
660
661 @classmethod
662 def cross_point_segment(cls, s1: Segment, s2: Segment) -> Optional[Point]:
663 assert isinstance(s1, Segment)
664 assert isinstance(s2, Segment)
665 d1 = cls.cross(s1.p2 - s1.p1, s2.p2 - s2.p1)
666 d2 = cls.cross(s1.p2 - s1.p1, s1.p2 - s2.p1)
667 if GeometryUtil.eq(abs(d1), 0) and GeometryUtil.eq(abs(d2), 0):
668 return s2.p1
669 if GeometryUtil.eq(abs(d1), 0):
670 return None
671 res = s2.p1 + (s2.p2 - s2.p1) * (d2 / d1)
672 mn_x = max(s1.p1.x, s2.p1.x)
673 mx_x = min(s1.p2.x, s2.p2.x)
674 if not (GeometryUtil.le(mn_x, res.x) and GeometryUtil.le(res.x, mx_x)):
675 return None
676 return None
677 s1_p1, s1_p2 = s1.p1, s1.p2
678 if s1_p1.y > s1_p2.y:
679 s1_p1, s1_p2 = s1_p2, s1_p1
680 s2_p1, s2_p2 = s2.p1, s2.p2
681 if s2_p1.y > s2_p2.y:
682 s2_p1, s2_p2 = s2_p2, s2_p1
683 mn_y = max(s1_p1.y, s2_p1.y)
684 mx_y = min(s1_p2.y, s2_p2.y)
685 if not (GeometryUtil.le(mn_y, res.y) and GeometryUtil.le(res.y, mx_y)):
686 return None
687 return res
688
689 @classmethod
690 def cross_point_circle(cls, c1: Circle, c2: Circle) -> tuple[Point, ...]:
691 r = cls.is_intersect_circle(c1, c2)
692 if r == 4 or r == 0:
693 return ()
694 if r == 3: # 外接
695 t = c1.r / (c1.r + c2.r)
696 return (c1.p + (c2.p - c1.p) * t,)
697 d = abs(c1.p - c2.p)
698 if r == 1: # 内接
699 if GeometryUtil.lt(c2.r, c1.r):
700 # if c2.r < c1.r - GeometryUtil.EPS:
701 return (c1.p + (c2.p - c1.p) * (c1.r / d),)
702 else:
703 return (c2.p + (c1.p - c2.p) * (c2.r / d),)
704 # 交点が2つ
705 rcos = (c1.r * c1.r + d * d - c2.r * c2.r) / (2 * d)
706 rsin = GeometryUtil.sqrt(c1.r * c1.r - rcos * rcos)
707 if GeometryUtil.lt(c1.r - abs(rcos), 0):
708 # if c1.r - abs(rcos) < GeometryUtil.EPS:
709 rsin = 0
710 e12 = (c2.p - c1.p) / abs(c2.p - c1.p)
711 return tuple(
712 sorted(
713 [
714 c1.p + rcos * e12 + rsin * Point(-e12.y, e12.x),
715 c1.p + rcos * e12 + rsin * Point(e12.y, -e12.x),
716 ]
717 )
718 )
719
720 @classmethod
721 def cross_point_circle_line(cls, c: Circle, l: Line) -> list[Point]:
722 res = []
723 d = cls.dist_p_to_line(c.p, l)
724 if d > c.r + GeometryUtil.EPS:
725 return res
726 h = cls.projection_point(c.p, l)
727 if GeometryUtil.eq(d, c.r):
728 res.append(h)
729 return res
730 e = (l.p2 - l.p1).unit()
731 ph = GeometryUtil.sqrt(c.r * c.r - d * d)
732 res.append(h - e * ph)
733 res.append(h + e * ph)
734 res.sort()
735 return res
736
737 @classmethod
738 def dist_p_to_line(cls, p: Point, l: Line) -> float:
739 return abs(cls.cross(l.p2 - l.p1, p - l.p1)) / abs(l.p2 - l.p1)
740
741 @classmethod
742 def dist_p_to_p(cls, p1: Point, p2: Point) -> float:
743 return ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
744
745 @classmethod
746 def dist_p_to_segmemt(cls, p: Point, s: Segment) -> float:
747 if cls.dot(s.p2 - s.p1, p - s.p1) < GeometryUtil.EPS:
748 return abs(p - s.p1)
749 if cls.dot(s.p1 - s.p2, p - s.p2) < GeometryUtil.EPS:
750 return abs(p - s.p2)
751 q = cls.projection_point(p, Line.from_points(s.p1, s.p2))
752 if (min(s.p1.x, s.p2.x) <= q.x <= max(s.p1.x, s.p2.x)) or (
753 min(s.p1.y, s.p2.y) <= q.y <= max(s.p1.y, s.p2.y)
754 ):
755 return abs(cls.cross(s.p2 - s.p1, p - s.p1)) / abs(s.p2 - s.p1)
756 return min(cls.dist_p_to_p(p, s.p1), cls.dist_p_to_p(p, s.p2))
757
758 @classmethod
759 def dist_segment_to_segment(cls, s1: Segment, s2: Segment) -> float:
760 if cls.is_intersect_linesegment(s1, s2):
761 return 0.0
762 ans = GeometryUtil.GEO_INF
763 ans = min(ans, cls.dist_p_to_segmemt(s1.p1, s2))
764 ans = min(ans, cls.dist_p_to_segmemt(s1.p2, s2))
765 ans = min(ans, cls.dist_p_to_segmemt(s2.p1, s1))
766 ans = min(ans, cls.dist_p_to_segmemt(s2.p2, s1))
767 return ans
768
769 @classmethod
770 def triangle_incircle(cls, p1: Point, p2: Point, p3: Point) -> Circle:
771 a, b, c = abs(p2 - p3), abs(p1 - p3), abs(p1 - p2)
772 p = Point(a * p1.x + b * p2.x + c * p3.x, a * p1.y + b * p2.y + c * p3.y)
773 p /= a + b + c
774 d = cls.dist_p_to_segmemt(p, Line.from_points(p1, p2))
775 return Circle(p, d)
776
777 @classmethod
778 def triangle_circumcircle(cls, p1: Point, p2: Point, p3: Point) -> Circle:
779 a = 2 * (p1.x - p2.x)
780 b = 2 * (p1.y - p2.y)
781 p = p1.x**2 - p2.x**2 + p1.y**2 - p2.y**2
782 c = 2 * (p1.x - p3.x)
783 d = 2 * (p1.y - p3.y)
784 q = p1.x**2 - p3.x**2 + p1.y**2 - p3.y**2
785 u = a * d - b * c
786 x = d * p - b * q
787 y = a * q - c * p
788 if GeometryUtil.lt(u, 0):
789 x = -x
790 y = -y
791 u = -u
792 x /= u
793 y /= u
794 r = GeometryUtil.sqrt((x - p1.x) ** 2 + (y - p1.y) ** 2)
795 return Circle(Point(x, y), r)
796
797 @classmethod
798 def triangle_excircle(
799 cls, p1: Point, p2: Point, p3: Point
800 ) -> tuple[Circle, Circle, Circle]:
801 dx1 = p2.x - p1.x
802 dy1 = p2.y - p1.y
803 dx2 = p3.x - p1.x
804 dy2 = p3.y - p1.y
805 d1 = GeometryUtil.sqrt((p3.x - p2.x) ** 2 + (p3.y - p2.y) ** 2)
806 d3 = GeometryUtil.sqrt(dx1**2 + dy1**2)
807 d2 = GeometryUtil.sqrt(dx2**2 + dy2**2)
808 S2 = abs(dx1 * dy2 - dx2 * dy1)
809 dsum1 = -d1 + d2 + d3
810 r1 = S2 / dsum1
811 ex1 = (-p1.x * d1 + p2.x * d2 + p3.x * d3) / dsum1
812 ey1 = (-p1.y * d1 + p2.y * d2 + p3.y * d3) / dsum1
813
814 dsum2 = d1 - d2 + d3
815 r2 = S2 / dsum2
816 ex2 = (p1.x * d1 - p2.x * d2 + p3.x * d3) / dsum2
817 ey2 = (p1.y * d1 - p2.y * d2 + p3.y * d3) / dsum2
818
819 dsum3 = d1 + d2 - d3
820 r3 = S2 / dsum3
821 ex3 = (p1.x * d1 + p2.x * d2 - p3.x * d3) / dsum3
822 ey3 = (p1.y * d1 + p2.y * d2 - p3.y * d3) / dsum3
823
824 return (
825 Circle(Point(ex1, ey1), r1),
826 Circle(Point(ex2, ey2), r2),
827 Circle(Point(ex3, ey3), r3),
828 )
829
830 @classmethod
831 def circle_tangent(cls, p: Point, c: Circle) -> tuple[Point, Point]:
832 return cls.cross_point_circle(
833 c, Circle(p, GeometryUtil.sqrt((c.p - p).norm2() - c.r * c.r))
834 )
835
836 @classmethod
837 def circle_common_tangent(cls, c1: Circle, c2: Circle) -> list[Line]:
838 ret = []
839 d = abs(c1.p - c2.p)
840 if GeometryUtil.eq(d, 0):
841 return ret
842 u = (c2.p - c1.p).unit()
843 v = u.rotate(GeometryUtil.pi / 2)
844 for s in [-1, 1]:
845 h = (c1.r + c2.r * s) / d
846 if GeometryUtil.eq(h * h, 1):
847 ret.append(
848 Line.from_points(
849 c1.p + (u if h > 0 else -u) * c1.r,
850 c1.p + (u if h > 0 else -u) * c1.r + v,
851 )
852 )
853 elif 1 - h * h > 0:
854 U = u * h
855 V = v * GeometryUtil.sqrt(1 - h * h)
856 ret.append(
857 Line.from_points(c1.p + (U + V) * c1.r, c2.p - (U + V) * (c2.r * s))
858 )
859 ret.append(
860 Line.from_points(c1.p + (U - V) * c1.r, c2.p - (U - V) * (c2.r * s))
861 )
862 return ret
863
864 @classmethod
865 def convex_hull(cls, ps: list[Point], line_contains: bool = False) -> list[Point]:
866 """凸包を求める / `O(n)`
867
868 Args:
869 line_contains (bool, optional): 一直線上の点を含める場合、 `True` .
870
871 Returns:
872 list[Point]: 凸包
873 """
874 ps = cls.sort_by_y(ps)
875 if not line_contains:
876 ps = cls.unique(ps)
877 if len(ps) <= 1:
878 return ps
879
880 def cross(a, p):
881 if line_contains:
882 return cls.cross(a[-1] - a[-2], p - a[-1]) < -GeometryUtil.EPS
883 else:
884 return cls.cross(a[-1] - a[-2], p - a[-1]) < GeometryUtil.EPS
885
886 lower = []
887 for p in ps:
888 while len(lower) > 1 and cross(lower, p):
889 lower.pop()
890 lower.append(p)
891
892 upper = []
893 for p in reversed(ps):
894 while len(upper) > 1 and cross(upper, p):
895 upper.pop()
896 upper.append(p)
897
898 return lower[:-1] + upper[:-1]
899
900 @classmethod
901 def unique(cls, a: list[Point]) -> list[Point]:
902 """同一要素の削除
903
904 Note:
905 `a` は事前にソートされていなければいけません。
906 """
907 if not a:
908 return []
909 res = [a[0]]
910 for i in range(1, len(a)):
911 if a[i] == res[-1]:
912 continue
913 res.append(a[i])
914 return res
915
916 @classmethod
917 def argumant_sort(cls, a: list[Point]) -> list[Point]:
918 upper = []
919 lower = []
920 zeros = []
921 for p in a:
922 if GeometryUtil.lt(p.y, 0) or (
923 GeometryUtil.eq(p.y, 0) and GeometryUtil.lt(p.x, 0)
924 ):
925 lower.append(p)
926 elif GeometryUtil.eq(p.x, 0) and GeometryUtil.eq(p.y, 0):
927 zeros.append(p)
928 else:
929 upper.append(p)
930
931 def cmp(s: Point, t: Point) -> bool:
932 return GeometryUtil.lt(s.y * t.x, s.x * t.y) or GeometryUtil.eq(
933 s.y * t.x, s.x * t.y
934 )
935
936 upper = merge_sort(upper, key=cmp)
937 lower = merge_sort(lower, key=cmp)
938 return lower + zeros + upper
939
940 @classmethod
941 def sort_by_x(cls, a: list[Point]) -> list[Point]:
942 return merge_sort(a)
943
944 @classmethod
945 def _cmp_y(cls, u: Point, v: Point):
946 if GeometryUtil.eq(u, v):
947 return True
948 if GeometryUtil.lt(u.y, v.y):
949 return True
950 if GeometryUtil.lt(v.y, u.y):
951 return False
952 return GeometryUtil.lt(u.x, v.x)
953
954 @classmethod
955 def sort_by_y(cls, a: list[Point]) -> list[Point]:
956 return merge_sort(a, cls._cmp_y)
957
958 @classmethod
959 def furthest_pair(cls, a: list[Point]) -> tuple[float, int, int]:
960 p = ConvexPolygon(a)
961 d, up, vp = p.diameter()
962 ui, vi = -1, -1
963 for i, point in enumerate(a):
964 if point == up:
965 ui = i
966 for i, point in enumerate(a):
967 if i != ui and point == vp:
968 vi = i
969 assert ui != -1 and vi != -1
970 return d, ui, vi
971
972 @classmethod
973 def closest_pair(cls, a: list[Point]) -> tuple[float, int, int]:
974 """最近点対を求める / `O(nlogn)`
975
976 Args:
977 a (list[Point]): 距離配列
978
979 Returns:
980 tuple[float, int, int]: 距離、最近点対
981 """
982
983 buff = [None] * len(a)
984 d, p, q = GeometryUtil.GEO_INF, -1, -1
985
986 def f(l: int, r: int) -> None:
987 nonlocal d, p, q
988 mid = (l + r) // 2
989 x = b[mid][0].x
990 if mid - l > 1:
991 f(l, mid)
992 if r - mid > 1:
993 f(mid, r)
994 i, j = l, mid
995 indx = 0
996 while i < mid and j < r:
997 if cls._cmp_y(b[i][0], b[j][0]):
998 buff[indx] = b[i]
999 i += 1
1000 else:
1001 buff[indx] = b[j]
1002 j += 1
1003 indx += 1
1004 for k in range(i, mid):
1005 b[l + indx + k - i] = b[k]
1006 for k in range(l, l + indx):
1007 b[k] = buff[k - l]
1008
1009 near_line = []
1010 for i in range(l, r):
1011 e_p, e_i = b[i]
1012 v = abs(e_p.x - x)
1013 if cls._gt(v, d) or GeometryUtil.eq(v, d):
1014 continue
1015 for j in range(len(near_line) - 1, -1, -1):
1016 ne_p, ne_i = near_line[j]
1017 dx = e_p.x - ne_p.x
1018 dy = e_p.y - ne_p.y
1019 if cls._gt(dy, d) or GeometryUtil.eq(dy, d):
1020 break
1021 cand = GeometryUtil.sqrt(dx * dx + dy * dy)
1022 if GeometryUtil.lt(cand, d):
1023 d, p, q = cand, ne_i, e_i
1024 near_line.append(b[i])
1025
1026 b = merge_sort((e, i) for i, e in enumerate(a))
1027 f(0, len(b))
1028 return d, p, q
1029
1030 @classmethod
1031 def count_segment_intersection(cls, a: list[Segment]) -> int:
1032 """線分の交点の個数"""
1033 # from titan_pylib.data_structures.fenwick_tree.fenwick_tree import FenwickTree
1034 segs = []
1035 events = []
1036 Y = []
1037 for i, seg in enumerate(a):
1038 p1 = seg.p1
1039 p2 = seg.p2
1040 if p1.x == p2.x:
1041 if p1.y > p2.y:
1042 p1, p2 = p2, p1
1043 segs.append((p1.y, p2.y))
1044 events.append((p1.x, 1, i))
1045 Y.append(p1.y)
1046 Y.append(p2.y)
1047 else:
1048 if p1.x > p2.x:
1049 p1, p2 = p2, p1
1050 segs.append((p1.y, p2.y))
1051 events.append((p1.x, 0, i))
1052 events.append((p2.x, 2, i))
1053 Y.append(p1.y)
1054
1055 to_origin = sorted(set(Y))
1056 to_zaatsu = {a: i for i, a in enumerate(to_origin)}
1057
1058 bt = FenwickTree(len(to_origin))
1059
1060 ans = 0
1061 events.sort()
1062 for _, q, i in events:
1063 if q == 0:
1064 y1, y2 = segs[i]
1065 bt.add(to_zaatsu[y1], 1)
1066 elif q == 1:
1067 y1, y2 = segs[i]
1068 ans += bt.sum(to_zaatsu[y1], to_zaatsu[y2] + 1)
1069 else:
1070 y1, y2 = segs[i]
1071 bt.add(to_zaatsu[y1], -1)
1072 return ans
仕様¶
- class ConvexPolygon(ps: list[Point], line_contains: bool = False)[source]¶
Bases:
Polygon
- contains(p: Point) int [source]¶
点の包含関係を返す / \(O(\log{n})\)
- Returns:
- 2: p を含む
1: p が辺上にある 0: それ以外
- Return type:
int
- convex_cut(l: Line) ConvexPolygon [source]¶
直線
l
で切断したときの左側の凸多角形を返す
- class Geometry[source]¶
Bases:
object
ref: - https://hcpc-hokudai.github.io/arpsive/geometry_004.pdf - https://bakamono1357.hatenablog.com/entry/2020/04/29/025320 - https://tjkendev.github.io/procon-library/python/geometry/circles_associated_with_triangle.html - https://atcoder.jp/contests/abc296/editorial/6109
- classmethod ccw(u: Point, v: Point, p: Point) int [source]¶
u->vに対し、v->pの位置関係を求める
- Returns:
- +1: a->bに対し、b->cが半時計回りに進む
-1: a->bに対し、b->cが時計回りに進む +2: a->bに対し、b->cが逆方向に進む -2: a->bに対し、b->cが同一方向に進む ` 0`: cが線分a->b上にある
- Return type:
int
- classmethod closest_pair(a: list[Point]) tuple[float, int, int] [source]¶
最近点対を求める / O(nlogn)
- Parameters:
a (list[Point]) – 距離配列
- Returns:
距離、最近点対
- Return type:
tuple[float, int, int]
- classmethod convex_hull(ps: list[Point], line_contains: bool = False) list[Point] [source]¶
凸包を求める / O(n)
- Parameters:
line_contains (bool, optional) – 一直線上の点を含める場合、 True .
- Returns:
凸包
- Return type:
list[Point]
- classmethod is_intersect_circle(c1: Circle, c2: Circle) int [source]¶
2円の位置関係
- Returns:
- 共通接線の数
0: 内包している 1: 内接している 2: 2点で交わっている 3: 外接している 4: 離れている
- Return type:
int
- classmethod is_intersect_linesegment(s1: Segment, s2: Segment) bool [source]¶
線分 s1 と s2 が交差しているかどうか判定する
- class GeometryUtil[source]¶
Bases:
object
- EPS: Final[Decimal | float] = 1e-10¶
- GEO_INF: Final[Decimal | float] = inf¶
- USE_DECIMAL: Final[bool] = False¶
- acos(x, /)¶
Return the arc cosine (measured in radians) of x.
The result is between 0 and pi.
- cos(x, /)¶
Return the cosine of x (measured in radians).
- pi = 3.141592653589793¶
- sin(x, /)¶
Return the sine of x (measured in radians).
- class Line(a: int, b: int, c: int, p1: Point, p2: Point)[source]¶
Bases:
object
ax + by + c = 0
- a¶
- b¶
- c¶
- p1¶
- p2¶