Files
c9a815a41f Triangle Centers (#11201)
* 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>
2026-09-13 08:00:52 +02:00

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()