11package at .ac .oeaw .imba .gerlich .gerlib .geometry
22
3- import scala .math .{pow , sqrt }
43import scala .util .NotGiven
54import scala .util .chaining .* // for pipe
65import cats .*
@@ -9,91 +8,6 @@ import cats.syntax.all.*
98
109import at .ac .oeaw .imba .gerlich .gerlib .numeric .*
1110
12- /** Something that can compare two {@code A} values w.r.t. threshold value of type {@code T}
13- */
14- trait ProximityComparable [A ]:
15- /** Are the two {@code A} values within threshold {@code T} of each other? */
16- def proximal : (A , A ) => Boolean
17- end ProximityComparable
18-
19- /** Helpers for working with proximity comparisons */
20- object ProximityComparable :
21- extension [A ](a1 : A )(using ev : ProximityComparable [A ])
22- infix def proximal (a2 : A ): Boolean = ev.proximal(a1, a2)
23-
24- given contravariantForProximityComparable : Contravariant [ProximityComparable ] =
25- new Contravariant [ProximityComparable ]:
26- override def contramap [A , B ](fa : ProximityComparable [A ])(f : B => A ) =
27- new ProximityComparable [B ]:
28- override def proximal = (b1, b2) => fa.proximal(f(b1), f(b2))
29- end ProximityComparable
30-
31- /** A threshold on distances, which should be nonnegative, to be semantically contextualised by the
32- * subtype
33- */
34- sealed trait DistanceThreshold :
35- def get : NonnegativeReal
36-
37- /** Helpers for working with distance thresholds */
38- object DistanceThreshold :
39- given showForDistanceThreshold : Show [DistanceThreshold ] = Show .show { (t : DistanceThreshold ) =>
40- val typeName = t match
41- case _ : EuclideanDistance .Threshold => " Euclidean"
42- case _ : PiecewiseDistance .ConjunctiveThreshold => " Conjunctive"
43- s " ${typeName}Threshold( ${t.get}) "
44- }
45-
46- /** Define a proximity comparison for 3D points values.
47- *
48- * @tparam C
49- * The type of raw value wrapped in a coordinate for each 3D point
50- * @param threshold
51- * The distance beneath which to consider a given pair of points as proximal
52- * @return
53- * An instance with which to check pairs of points for proximity, according to the given
54- * threshold value ('think': decision boundary)
55- * @see
56- * [[at.ac.oeaw.imba.gerlich.gerlib.geometry.Point3D ]]
57- */
58- def defineProximityPointwise [C : Numeric ](
59- threshold : DistanceThreshold
60- ): ProximityComparable [Point3D [C ]] = threshold match
61- case t : EuclideanDistance .Threshold =>
62- new ProximityComparable [Point3D [C ]]:
63- override def proximal = (a, b) =>
64- val d = EuclideanDistance .between(a, b)
65- if d.isInfinite then
66- throw new EuclideanDistance .OverflowException (
67- s " Cannot compute finite distance between $a and $b"
68- )
69- d `lessThan` t
70- case t : PiecewiseDistance .ConjunctiveThreshold =>
71- new ProximityComparable [Point3D [C ]]:
72- override def proximal = PiecewiseDistance .within(t)
73-
74- /** Define a proximity comparison for values of arbitrary type, according to given threshold and
75- * how to extract a 3D point value.
76- *
77- * @tparam A
78- * The type of value from which a 3D point will be extracted for purpose of proximity check /
79- * comparison
80- * @tparam C
81- * The type of raw value wrapped in a coordinate for each 3D point
82- * @param threshold
83- * The distance beneath which to consider a given pair of points as proximal
84- * @return
85- * An instance with which to check pairs of values for proximity, according to the given
86- * threshold value ('think': decision boundary), and how to get a 3D point from a value of type
87- * `A`
88- * @see
89- * [[at.ac.oeaw.imba.gerlich.gerlib.geometry.Point3D ]]
90- */
91- def defineProximityPointwise [A , C : Numeric ](
92- threshold : DistanceThreshold
93- ): (A => Point3D [C ]) => ProximityComparable [A ] =
94- defineProximityPointwise(threshold).contramap
95- end DistanceThreshold
96-
9711/** Piecewise / by-component distance, as absolute differences
9812 *
9913 * @param x
@@ -118,7 +32,7 @@ object PiecewiseDistance:
11832 /** Distance threshold in which predicate comparing values to this threshold operates
11933 * conjunctively over components
12034 */
121- final case class ConjunctiveThreshold (get : NonnegativeReal ) extends DistanceThreshold
35+ final case class Conjunctive (get : NonnegativeReal )
12236
12337 /** Compute the piecewise / component-wise distance between the given points.
12438 *
@@ -147,7 +61,7 @@ object PiecewiseDistance:
14761
14862 /** Are points closer than given threshold along each axis? */
14963 def within [C : Numeric ](
150- threshold : ConjunctiveThreshold
64+ threshold : Conjunctive
15165 )(a : Point3D [C ], b : Point3D [C ]): Boolean =
15266 val d = between(a, b)
15367 d.getX < threshold.get && d.getY < threshold.get && d.getZ < threshold.get
@@ -161,44 +75,17 @@ end PiecewiseDistance
16175
16276/** Semantic wrapper to denote that a nonnegative real number represents a Euclidean distance
16377 */
164- final case class EuclideanDistance private (get : NonnegativeReal ):
165- final def lessThan (t : EuclideanDistance .Threshold ): Boolean = get < t.get
166- final def greaterThan = ! lessThan(_ : EuclideanDistance .Threshold )
167- final def equalTo (t : EuclideanDistance .Threshold ) =
168- ! lessThan(t) && ! greaterThan(t)
169- final def lteq (t : EuclideanDistance .Threshold ) = lessThan(t) || equalTo(t)
170- final def gteq (t : EuclideanDistance .Threshold ) = greaterThan(t) || equalTo(t)
171- final def isFinite = get.isFinite
78+ final case class EuclideanDistance private (get : Distance ):
79+ final def isFinite = get.value.isFinite
17280 final def isInfinite = ! isFinite
17381end EuclideanDistance
17482
17583/** Helpers for working with Euclidean distances */
17684object EuclideanDistance :
177- import at .ac .oeaw .imba .gerlich .gerlib .numeric .instances .nonnegativeReal .given // for Order
178-
17985 /** Order distance by the wrapped value. */
180- given Order [EuclideanDistance ] = Order .by(_.get)
86+ given Order [EuclideanDistance ] =
87+ Order .by(_.get.value) // use the Double backing the squants.space.Length.
18188
18289 /** When something goes wrong with a distance computation or comparison */
18390 final case class OverflowException (message : String ) extends Exception (message)
184-
185- /** Comparison basis for Euclidean distance between points */
186- final case class Threshold (get : NonnegativeReal ) extends DistanceThreshold
187-
188- // TODO: account for infinity/null-numeric cases.
189- def between [C : Numeric ](a : Point3D [C ], b : Point3D [C ]): EuclideanDistance =
190- import scala .math .Numeric .Implicits .infixNumericOps
191- (a, b) match
192- case (Point3D (x1, y1, z1), Point3D (x2, y2, z2)) =>
193- List (x1 -> x2, y1 -> y2, z1 -> z2)
194- .foldLeft(0.0 ) { case (acc, (a, b)) => acc + pow((a.value - b.value).toDouble, 2 ) }
195- .pipe(sqrt)
196- .pipe(NonnegativeReal .unsafe)
197- .pipe(EuclideanDistance .apply)
198-
199- /** Use a lens of a 3D point from arbitrary type {@code A} to compute distance between {@code A}
200- * values.
201- */
202- def between [A , C : Numeric ](p : A => Point3D [C ])(a1 : A , a2 : A ): EuclideanDistance =
203- between(p(a1), p(a2))
20491end EuclideanDistance
0 commit comments