-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bringing over lots of distance-related code from tracing project, and…
… writing typeclasses to assist in information/member hiding
- Loading branch information
Showing
16 changed files
with
310 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.geometry | ||
|
||
import scala.math.pow | ||
import cats.* | ||
import cats.data.Validated | ||
import cats.syntax.all.* | ||
|
||
import at.ac.oeaw.imba.gerlich.gerlib.numeric.* | ||
|
||
/** Something that can compare two {@code A} values w.r.t. threshold value of | ||
* type {@code T} | ||
*/ | ||
trait ProximityComparable[A]: | ||
/** Are the two {@code A} values within threshold {@code T} of each other? */ | ||
def proximal: (A, A) => Boolean | ||
end ProximityComparable | ||
|
||
/** Helpers for working with proximity comparisons */ | ||
object ProximityComparable: | ||
extension [A](a1: A)(using ev: ProximityComparable[A]) | ||
infix def proximal(a2: A): Boolean = ev.proximal(a1, a2) | ||
|
||
given contravariantForProximityComparable | ||
: Contravariant[ProximityComparable] = | ||
new Contravariant[ProximityComparable] { | ||
override def contramap[A, B](fa: ProximityComparable[A])(f: B => A) = | ||
new ProximityComparable[B] { | ||
override def proximal = (b1, b2) => fa.proximal(f(b1), f(b2)) | ||
} | ||
} | ||
end ProximityComparable | ||
|
||
/** A threshold on distances, which should be nonnegative, to be semantically | ||
* contextualised by the subtype | ||
*/ | ||
sealed trait DistanceThreshold { def get: NonnegativeReal } | ||
|
||
/** Helpers for working with distance thresholds */ | ||
object DistanceThreshold: | ||
given showForDistanceThreshold: Show[DistanceThreshold] = Show.show { | ||
(t: DistanceThreshold) => | ||
val typeName = t match { | ||
case _: EuclideanDistance.Threshold => "Euclidean" | ||
case _: PiecewiseDistance.ConjunctiveThreshold => "Conjunctive" | ||
} | ||
s"${typeName}Threshold(${t.get})" | ||
} | ||
|
||
/** Define a proximity comparison for 3D points values. | ||
* | ||
* @param threshold | ||
* The distance beneath which to consider a given pair of points as | ||
* proximal | ||
* @return | ||
* An instance with which to check pairs of points for proximity, according | ||
* to the given threshold value ('think': decision boundary) | ||
* @see | ||
* [[at.ac.oeaw.imba.gerlich.gerlib.geometry.Point3D]] | ||
*/ | ||
def defineProximityPointwise( | ||
threshold: DistanceThreshold | ||
): ProximityComparable[Point3D[Double]] = threshold match { | ||
case t: EuclideanDistance.Threshold => | ||
new ProximityComparable[Point3D[Double]] { | ||
override def proximal = (a, b) => | ||
val d = EuclideanDistance.between(a, b) | ||
if (d.isInfinite) { | ||
throw new EuclideanDistance.OverflowException( | ||
s"Cannot compute finite distance between $a and $b" | ||
) | ||
} | ||
d `lessThan` t | ||
} | ||
case t: PiecewiseDistance.ConjunctiveThreshold => | ||
new ProximityComparable[Point3D[Double]] { | ||
override def proximal = PiecewiseDistance.within(t) | ||
} | ||
} | ||
|
||
/** Define a proximity comparison for values of arbitrary type, according to | ||
* given threshold and how to extract a 3D point value. | ||
* | ||
* @tparam A | ||
* The type of value from which a 3D point will be extracted for purpose of | ||
* proximity check / comparison | ||
* @param threshold | ||
* The distance beneath which to consider a given pair of points as | ||
* proximal | ||
* @return | ||
* An instance with which to check pairs of values for proximity, according | ||
* to the given threshold value ('think': decision boundary), and how to | ||
* get a 3D point from a value of type `A` | ||
* @see | ||
* [[at.ac.oeaw.imba.gerlich.gerlib.geometry.Point3D]] | ||
*/ | ||
def defineProximityPointwise[A]( | ||
threshold: DistanceThreshold | ||
): (A => Point3D[Double]) => ProximityComparable[A] = | ||
defineProximityPointwise(threshold).contramap | ||
end DistanceThreshold | ||
|
||
/** Piecewise / by-component distance, as absolute differences | ||
* | ||
* @param x | ||
* The x-component of the absolute difference between two points' | ||
* coordinatess | ||
* @param y | ||
* The y-component of the absolute difference between two points' coordinates | ||
* @param z | ||
* The z-component of the absolute difference between two points' coordinates | ||
*/ | ||
final class PiecewiseDistance private ( | ||
x: NonnegativeReal, | ||
y: NonnegativeReal, | ||
z: NonnegativeReal | ||
): | ||
def getX: NonnegativeReal = x | ||
def getY: NonnegativeReal = y | ||
def getZ: NonnegativeReal = z | ||
|
||
/** Helpers for working with distances in by-component / piecewise fashion */ | ||
object PiecewiseDistance: | ||
|
||
/** Distance threshold in which predicate comparing values to this threshold | ||
* operates conjunctively over components | ||
*/ | ||
final case class ConjunctiveThreshold(get: NonnegativeReal) | ||
extends DistanceThreshold | ||
|
||
/** Compute the piecewise / component-wise distance between the given points. | ||
* | ||
* @param a | ||
* One point | ||
* @param b | ||
* The other point | ||
* @return | ||
* A wrapper with access to the (absolute) difference between each | ||
* component / dimension of the two given points' coordinates | ||
* @throws java.lang.ArithmeticException | ||
* if taking any absolute difference fails to refine as nonnegative | ||
*/ | ||
def between(a: Point3D[Double], b: Point3D[Double]): PiecewiseDistance = | ||
val xNel = | ||
NonnegativeReal.either((a.x.value - b.x.value).abs).toValidatedNel | ||
val yNel = | ||
NonnegativeReal.either((a.y.value - b.y.value).abs).toValidatedNel | ||
val zNel = | ||
NonnegativeReal.either((a.z.value - b.z.value).abs).toValidatedNel | ||
(xNel, yNel, zNel).tupled match { | ||
case Validated.Valid((delX, delY, delZ)) => | ||
PiecewiseDistance(x = delX, y = delY, z = delZ) | ||
case Validated.Invalid(es) => | ||
throw new ArithmeticException { | ||
s"Computing distance between point $a and point $b yielded ${es.length} error(s): ${es.mkString_("; ")}" | ||
} | ||
} | ||
|
||
/** Are points closer than given threshold along each axis? */ | ||
def within( | ||
threshold: ConjunctiveThreshold | ||
)(a: Point3D[Double], b: Point3D[Double]): Boolean = | ||
val d = between(a, b) | ||
d.getX < threshold.get && d.getY < threshold.get && d.getZ < threshold.get | ||
end PiecewiseDistance | ||
|
||
/** Semantic wrapper to denote that a nonnegative real number represents a | ||
* Euclidean distance | ||
*/ | ||
final case class EuclideanDistance private (get: NonnegativeReal): | ||
final def lessThan(t: EuclideanDistance.Threshold): Boolean = get < t.get | ||
final def greaterThan = !lessThan(_: EuclideanDistance.Threshold) | ||
final def equalTo(t: EuclideanDistance.Threshold) = | ||
!lessThan(t) && !greaterThan(t) | ||
final def lteq(t: EuclideanDistance.Threshold) = lessThan(t) || equalTo(t) | ||
final def gteq(t: EuclideanDistance.Threshold) = greaterThan(t) || equalTo(t) | ||
final def isFinite = get.isFinite | ||
final def isInfinite = !isFinite | ||
end EuclideanDistance | ||
|
||
/** Helpers for working with Euclidean distances */ | ||
object EuclideanDistance: | ||
import at.ac.oeaw.imba.gerlich.gerlib.numeric.instances.nonnegativeReal.given // for Order | ||
|
||
/** Order distance by the wrapped value. */ | ||
given Order[EuclideanDistance] = Order.by(_.get) | ||
|
||
/** When something goes wrong with a distance computation or comparison */ | ||
final case class OverflowException(message: String) extends Exception(message) | ||
|
||
/** Comparison basis for Euclidean distance between points */ | ||
final case class Threshold(get: NonnegativeReal) extends DistanceThreshold | ||
|
||
// TODO: account for infinity/null-numeric cases. | ||
def between(a: Point3D[Double], b: Point3D[Double]): EuclideanDistance = | ||
(a, b) match { | ||
case (Point3D(x1, y1, z1), Point3D(x2, y2, z2)) => | ||
val d = NonnegativeReal.unsafe( | ||
math.sqrt { | ||
pow(x1.value - x2.value, 2) + pow(y1.value - y2.value, 2) + pow( | ||
z1.value - z2.value, | ||
2 | ||
) | ||
} | ||
) | ||
new EuclideanDistance(d) | ||
} | ||
|
||
/** Use a lens of a 3D point from arbitrary type {@code A} to compute distance | ||
* between {@code A} values. | ||
*/ | ||
def between[A](p: A => Point3D[Double])(a1: A, a2: A): EuclideanDistance = | ||
between(p(a1), p(a2)) | ||
end EuclideanDistance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.geometry | ||
|
||
import cats.Monoid | ||
import cats.data.NonEmptyList | ||
import cats.syntax.all.* | ||
|
||
/** Syntax enrichment on values of data types related to geometry */ | ||
package object syntax: | ||
extension [C](points: NonEmptyList[Point3D[C]]) | ||
/** Take the centroid simply as the arithmetic mean of the points. */ | ||
def centroid(using Monoid[Point3D[C]], Fractional[C]): Point3D[C] = | ||
import scala.math.Fractional.Implicits.infixFractionalOps | ||
val total = points.combineAll | ||
val n = summon[Fractional[C]].fromInt(points.length) | ||
Point3D( | ||
XCoordinate(total.x.value / n), | ||
YCoordinate(total.y.value / n), | ||
ZCoordinate(total.z.value / n) | ||
) |
3 changes: 1 addition & 2 deletions
3
modules/json/JsonObjectWriter.scala → ...son/src/main/scala/JsonObjectWriter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
modules/json/src/main/scala/instances/JsonInstancesForGeometry.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.json | ||
package instances | ||
|
||
import scala.util.NotGiven | ||
import at.ac.oeaw.imba.gerlich.gerlib.geometry.Coordinate | ||
|
||
trait JsonInstancesForGeometry: | ||
def getPlainJsonValueWriter[A, C <: Coordinate[ | ||
A | ||
], O <: ujson.Value: [C] =>> NotGiven[C =:= Coordinate[A]]](using | ||
writeRaw: JsonValueWriter[A, O] | ||
): JsonValueWriter[C, O] = new: | ||
override def apply(c: C): O = writeRaw(c.value) | ||
end JsonInstancesForGeometry |
23 changes: 23 additions & 0 deletions
23
modules/json/src/main/scala/instances/JsonInstancesForNumeric.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.json | ||
package instances | ||
|
||
import at.ac.oeaw.imba.gerlich.gerlib.numeric.* | ||
|
||
/** Typeclass instances related to JSON for numeric data types */ | ||
trait JsonInstancesForNumeric: | ||
given JsonValueWriter[NonnegativeInt, ujson.Num] = JsonValueWriter(identity) | ||
|
||
given JsonValueWriter[NonnegativeReal, ujson.Num] = JsonValueWriter(identity) | ||
|
||
given JsonValueWriter[Boolean, ujson.Bool] = | ||
JsonValueWriter.instance(ujson.Bool.apply) | ||
|
||
given JsonValueWriter[Double, ujson.Num] = | ||
JsonValueWriter.instance(ujson.Num.apply) | ||
|
||
given JsonValueWriter[Int, ujson.Num] = | ||
JsonValueWriter.instance(z => ujson.Num.apply(z.toDouble)) | ||
|
||
given JsonValueWriter[String, ujson.Str] = | ||
JsonValueWriter.instance(ujson.Str.apply) | ||
end JsonInstancesForNumeric |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.json | ||
package instances | ||
|
||
package object geometry extends JsonInstancesForGeometry |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.json | ||
package instances | ||
|
||
package object numeric extends JsonInstancesForNumeric |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package at.ac.oeaw.imba.gerlich.gerlib.json | ||
|
||
package object instances: | ||
object all extends AllJsonInstances | ||
|
||
trait AllJsonInstances | ||
extends JsonInstancesForGeometry, | ||
JsonInstancesForNumeric |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.