mirror of
https://github.com/TheAlgorithms/Python.git
synced 2026-09-28 21:45:27 +08:00
* Addition to the Geometry Folder with Triangle that calculates the different triangle centers * Addition to the Geometry Folder with Triangle that calculates the different triangle centers * Original triangle center w/fixes for automated testing * triangles, added more fixes for automated testing * trying to rename triangle.py * Fixed a documentation error * Removed a space I suspect is preventing the automated code reviewer from completing * Fixed a documentation error * updating DIRECTORY.md * Apply suggestion from @cclauss --------- Co-authored-by: AlexB <acbucha@clemson.edu> Co-authored-by: Christian Clauss <cclauss@me.com> Co-authored-by: cclauss <cclauss@users.noreply.github.com>
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
from dataclasses import dataclass, field
|
|
|
|
from numpy import array, linalg
|
|
|
|
# Define a Point on a 2D normalized orthogonal euclidean grid
|
|
# https://mathworld.wolfram.com/Circumcenter.html
|
|
# https://mathworld.wolfram.com/Incenter.html
|
|
# https://mathworld.wolfram.com/Orthocenter.html
|
|
|
|
|
|
@dataclass
|
|
class Point:
|
|
"""
|
|
A point defined by 2 floats representing a length on a normalized
|
|
orthogonal coordinate system
|
|
default coordinate is the origin
|
|
|
|
>>> Point(-1.0, 0.0)
|
|
Point(x=-1.0, y=0.0)
|
|
|
|
"""
|
|
|
|
x: float = 0.0
|
|
y: float = 0.0
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.x, (int, float)):
|
|
raise TypeError("x must be an int or float numeric value")
|
|
if not isinstance(self.y, (int, float)):
|
|
raise TypeError("y must be an int or float numeric value")
|
|
|
|
|
|
@dataclass
|
|
class Triangle:
|
|
"""
|
|
A 3 point Triangle on a 2D normalized orthogonal euclidean grid
|
|
|
|
>>> p1 = Point(-1.0,0.0)
|
|
>>> p2 = Point(1.0,0.0)
|
|
>>> p3 = Point(0.0,1.0)
|
|
>>> Triangle(p1, p2, p3) # doctest: +NORMALIZE_WHITESPACE
|
|
Triangle(v1=Point(x=-1.0, y=0.0), v2=Point(x=1.0, y=0.0), v3=Point(x=0.0, y=1.0),
|
|
circum=Point(x=0.0, y=0.0), incen=Point(x=0.0, y=0.0),
|
|
ortho=Point(x=0.0, y=0.0))
|
|
"""
|
|
|
|
v1: Point = field(default_factory=Point)
|
|
v2: Point = field(default_factory=Point)
|
|
v3: Point = field(default_factory=Point)
|
|
circum: Point = field(default_factory=Point)
|
|
incen: Point = field(default_factory=Point)
|
|
ortho: Point = field(default_factory=Point)
|
|
|
|
def __post_init__(self) -> None:
|
|
# Check for valid arguments
|
|
if (
|
|
not isinstance(self.v1, Point)
|
|
or not isinstance(self.v2, Point)
|
|
or not isinstance(self.v3, Point)
|
|
):
|
|
raise TypeError("All 3 arguments should be Point Objects")
|
|
# Check for 3 unique points
|
|
if self.v1 in (self.v2, self.v3):
|
|
raise TypeError("All 3 arguments should be unique")
|
|
# Check for linearity
|
|
if self.v1.x == self.v2.x and self.v3.y == self.v2.y:
|
|
raise TypeError("One or more arguments are redundant")
|
|
m = (self.v1.y - self.v2.y) / (self.v1.x - self.v2.x)
|
|
yb = self.v1.y - m * self.v1.x
|
|
if self.v3.y == m * self.v3.x + yb:
|
|
raise TypeError("One or more arguments are redundant")
|
|
|
|
# Circumcenter
|
|
@property
|
|
def circumcenter(self) -> Point:
|
|
m_0 = array(
|
|
[
|
|
[self.v1.x, self.v1.y, 1],
|
|
[self.v2.x, self.v2.y, 1],
|
|
[self.v3.x, self.v3.y, 1],
|
|
]
|
|
)
|
|
m_1 = array(
|
|
[
|
|
[self.v1.x**2 + self.v1.y**2, self.v1.y, 1],
|
|
[self.v2.x**2 + self.v2.y**2, self.v2.y, 1],
|
|
[self.v3.x**2 + self.v3.y**2, self.v3.y, 1],
|
|
]
|
|
)
|
|
m_2 = array(
|
|
[
|
|
[self.v1.x**2 + self.v1.y**2, self.v1.x, 1],
|
|
[self.v2.x**2 + self.v2.y**2, self.v2.x, 1],
|
|
[self.v3.x**2 + self.v3.y**2, self.v3.x, 1],
|
|
]
|
|
)
|
|
a = linalg.det(m_0)
|
|
bx = -linalg.det(m_1)
|
|
by = linalg.det(m_2)
|
|
self.circum.x = -bx / (2 * a)
|
|
self.circum.y = -by / (2 * a)
|
|
return self.circum
|
|
|
|
# Incenter
|
|
@property
|
|
def incenter(self) -> Point:
|
|
a = math.sqrt((self.v2.x - self.v3.x) ** 2 + (self.v2.y - self.v3.y) ** 2)
|
|
b = math.sqrt((self.v1.x - self.v3.x) ** 2 + (self.v1.y - self.v3.y) ** 2)
|
|
c = math.sqrt((self.v1.x - self.v2.x) ** 2 + (self.v1.y - self.v2.y) ** 2)
|
|
self.incen.x = (a * self.v1.x + b * self.v2.x + c * self.v3.x) / (a + b + c)
|
|
self.incen.y = (a * self.v1.y + b * self.v2.y + c * self.v3.y) / (a + b + c)
|
|
return self.incen
|
|
|
|
# Orthocenter
|
|
@property
|
|
def orthocenter(self) -> Point:
|
|
inv_m1 = -1 / ((self.v3.y - self.v1.y) / (self.v3.x - self.v1.x))
|
|
inv_m2 = -1 / ((self.v3.y - self.v2.y) / (self.v3.x - self.v2.x))
|
|
m = array([[inv_m2, -1], [inv_m1, -1]])
|
|
b = array([[inv_m2 * self.v1.x - self.v1.y], [inv_m1 * self.v2.x - self.v2.y]])
|
|
soln = linalg.solve(m, b)
|
|
self.ortho.x = soln[0][0]
|
|
self.ortho.y = soln[1][0]
|
|
return self.ortho
|
|
|
|
|
|
if __name__ == "__main__":
|
|
__import__("doctest").testmod()
|