de.sciss.lucre.data.DetSkipOctree.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lucre-data_sjs1_2.13 Show documentation
Show all versions of lucre-data_sjs1_2.13 Show documentation
Extension of Scala-STM, adding optional durability layer, and providing API for confluent and reactive event layers
/*
* DetSkipOctree.scala
* (Lucre 4)
*
* Copyright (c) 2009-2022 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU Affero General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* [email protected]
*/
package de.sciss.lucre.data
import java.io.PrintStream
import de.sciss.lucre.geom.{DistanceMeasure, HyperCube, QueryShape, Space}
import de.sciss.lucre.{Disposable, Exec, Ident, Identified, Mutable, TOrdering, Var}
import de.sciss.serial.impl.ByteArrayOutputStream
import de.sciss.serial.{DataInput, DataOutput, TFormat, Writable, WritableFormat}
import scala.annotation.{elidable, switch, tailrec}
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.collection.mutable.{PriorityQueue => MPriorityQueue, Queue => MQueue}
/** A transactional deterministic skip octree as outlined in the paper by Eppstein et al.
* It is constructed from a given space (dimensions) and a skip-gap parameter
* which determines the kind of skip list which is used to govern the
* level decimation.
*
* The tree is a mutable data structure which supports lookup, insertion and removal
* in O(log n), as well as efficient range queries and nearest neighbour search.
*
* The current implementation, backed by `impl.SkipOctreeImpl`, uses the types of
* the `geom` package, assuming that coordinates are integers, with the maximum
* root hyper-cube given by a span from `0` to `0x7FFFFFFF` (e.g. in `Space.IntTwoDim`,
* this is `IntSquare( 0x40000000, 0x40000000, 0x40000000 )`.
*/
object DetSkipOctree {
private final val SER_VERSION = 79 // 'O'
private var stat_rounds = 0
private var stat_pq_add = 0
private var stat_pq_rem = 0
private val stat_print = false
@volatile private var sanitizing = false
@elidable(elidable.CONFIG) private def stat_reset(): Unit = {
stat_rounds = 0
stat_pq_add = 0
stat_pq_rem = 0
}
@elidable(elidable.CONFIG) private def stat_report(): Unit = ()
@elidable(elidable.CONFIG) private def stat_rounds1(obj: Any): Unit = {
stat_rounds += 1
if (stat_print) println(s" round max: $obj")
}
@elidable(elidable.CONFIG) private def stat_pq_add1(obj: Any): Unit = {
stat_pq_add += 1
if (stat_print) println(s" add pq: $obj")
}
@elidable(elidable.CONFIG) private def stat_pq_rem1(obj: Any): Unit = {
stat_pq_rem += 1
if (stat_print) println(s" remove pq: $obj")
}
def empty[T <: Exec[T], P, H <: HyperCube[P, H], A](hyperCube: H, skipGap: Int = 2)
(implicit pointView: (A, T) => P, tx: T, space: Space[P, H],
keyFormat: TFormat[T, A]): DetSkipOctree[T, P, H, A] =
new ImplNew[T, P, H, A](skipGap, tx.newId(), hyperCube, pointView, tx)
def read[T <: Exec[T], P, H <: HyperCube[P, H], A](in: DataInput)
(implicit tx: T, pointView: (A, T) => P,
space: Space[P, H],
keyFormat: TFormat[T, A]): DetSkipOctree[T, P, H, A] =
new ImplRead[T, P, H, A](pointView, in, tx)
private final class ImplRead[T <: Exec[T], P, H <: HyperCube[P, H], A](val pointView: (A, T) => P,
in: DataInput, tx0: T)
(implicit val space: Space[P, H],
val keyFormat: TFormat[T, A])
extends Impl[T, P, H, A] {
{
val version = in.readByte()
require(version == SER_VERSION,
s"Incompatible serialized version (found $version, required $SER_VERSION).")
}
val id: Ident[T] = tx0.readId(in)
val hyperCube: H = space.hyperCubeFormat.read(in) // (in, tx)
val skipList: HASkipList.Set[T, this.Leaf] = {
implicit val ord: TOrdering [T, this.Leaf] = LeafOrdering
implicit val r1 : TFormat [T, this.Leaf] = LeafFormat
HASkipList.Set.format[T, this.Leaf](KeyObserver).readT(in)(tx0)
}
val headTree: this.LeftTopBranch = LeftTopBranchFormat.readT(in)(tx0)
val lastTreeRef: Var[T, this.TopBranch] =
id.readVar[this.TopBranch](in)(TopBranchFormat)
}
implicit def format[T <: Exec[T], P,
H <: HyperCube[P, H], A](implicit pointView: (A, T) => P, space: Space[P, H],
keyFormat: TFormat[T, A]): TFormat[T, DetSkipOctree[T, P, H, A]] =
new OctreeFormat[T, P, H, A]
private final class OctreeFormat[T <: Exec[T], P,
H <: HyperCube[P, H], A](implicit pointView: (A, T) => P, space: Space[P, H],
keyFormat: TFormat[T, A])
extends WritableFormat[T, DetSkipOctree[T, P, H, A]] {
override def readT(in: DataInput)(implicit tx: T): DetSkipOctree[T, P, H, A] =
new ImplRead[T, P, H, A](pointView, in, tx)
override def toString = "DetSkipOctree.format"
}
private final class ImplNew[T <: Exec[T], P,
H <: HyperCube[P, H], A](skipGap: Int, val id: Ident[T], val hyperCube: H, val pointView: (A, T) => P, tx0: T)
(implicit val space: Space[P, H], val keyFormat: TFormat[T, A])
extends Impl[T, P, H, A] { octree =>
val skipList: HASkipList.Set[T, this.Leaf] =
HASkipList.Set.empty[T, this.Leaf](skipGap, KeyObserver)(tx0, LeafOrdering, LeafFormat)
val headTree: this.LeftTopBranch = {
val sz = numOrthants
val ch = new Array[Var[T, this.LeftChild]](sz)
val cid = tx0.newId()
implicit val tx: T = tx0
implicit val r1: TFormat[T, this.LeftChild] = LeftChildFormat
var i = 0
while (i < sz) {
ch(i) = cid.newVar[this.LeftChild](Empty)
i += 1
}
implicit val r2: TFormat[T, this.Next] = RightOptionReader
val headRight = cid.newVar[this.Next](Empty)
new LeftTopBranchImpl(octree, cid, children = ch, nextRef = headRight)
}
val lastTreeRef: Var[T, this.TopBranch] = {
implicit val r3: TFormat[T, this.TopBranch] = TopBranchFormat
implicit val tx: T = tx0
id.newVar[this.TopBranch](headTree)
}
}
sealed trait Child[+T, +P, +H, +A] extends Writable
sealed trait LeftNonEmpty[T <: Exec[T], P, H] extends Left with NonEmpty[T, P, H]
/** Utility trait which elements the rightward search `findPN`. */
sealed trait ChildBranch[T <: Exec[T], P, H, A]
extends Branch [T, P, H, A] /* Like */
with NonEmptyChild[T, P, H, A]
sealed trait Next[+T, +P, +H, +A] extends Child[T, P, H, A]
/** A node is an object that can be stored in a orthant of a branch. */
sealed trait NonEmpty[T <: Exec[T], P, H]
extends Identified[T] /* extends Down with Child */ {
protected def shortString: String
override def toString: String = s"$shortString$id"
// override def equals(that: Any): Boolean = that match {
// case n: Identifiable[_] => id == n.id // do _not_ match against n: NonEmpty because that's an inner class!!!
// case _ => super.equals(that)
// }
//
// override def hashCode: Int = id.hashCode()
/** Computes the greatest interesting hyper-cube within
* a given hyper-cube `mq` so that this (leaf's or node's)
* hyper-cube and the given point will be placed in
* separated orthants of this resulting hyper-cube.
*/
def union(mq: H, point: P)(implicit tx: T): H
/**
* Queries the orthant index for this (leaf's or node's) hyper-cube
* with respect to a given outer hyper-cube `iq`.
*/
def orthantIndexIn(iq: H)(implicit tx: T): Int
}
sealed trait Left
sealed trait LeftChild[+T, +P, +H, +A] extends Left with Child[T, P, H, A]
sealed trait Branch[T <: Exec[T], P, H, A] extends Child[T, P, H, A] with NonEmpty[T, P, H] {
/** Returns the hyper-cube covered by this node. */
def hyperCube: H
def nextOption(implicit tx: T): Option[Branch[T, P, H, A]]
/** Returns the corresponding interesting
* node in Qi+1, or `empty` if no such
* node exists.
*/
def next(implicit tx: T): Next[T, P, H, A]
/** Sets the corresponding interesting
* node in Qi+1.
*/
private[DetSkipOctree] def next_=(n: Next[T, P, H, A])(implicit tx: T): Unit
def prevOption: Option[Branch[T, P, H, A]]
/** Returns the child for a given orthant index. */
def child(idx: Int)(implicit tx: T): Child[T, P, H, A]
/** Assuming that the given `leaf` is a child of this node,
* removes the child from this node's children. This method
* will perform further clean-up such as merging this node
* with its parent if it becomes uninteresting as part of the
* removal.
*/
private[DetSkipOctree] def demoteLeaf(point: P, leaf: Leaf[T, P, H, A])(implicit tx: T): Unit
}
sealed trait Leaf[T <: Exec[T], P, H, A]
extends Child [T, P, H, A]
with LeftNonEmptyChild [T, P, H, A]
with RightNonEmptyChild[T, P, H, A]
with LeafOrEmpty [T, P, H, A] {
def value: A
private[DetSkipOctree] def parent_=(b: Branch[T, P, H, A])(implicit tx: T): Unit
private[DetSkipOctree] def remove()(implicit tx: T): Unit
}
/** A common trait used in pattern matching, comprised of `Leaf` and `LeftChildBranch`. */
sealed trait LeftNonEmptyChild[T <: Exec[T], P, H, A]
extends LeftNonEmpty [T, P, H]
with NonEmptyChild[T, P, H, A]
with LeftChild [T, P, H, A] with Writable {
private[DetSkipOctree] def updateParentLeft(p: LeftBranch[T, P, H, A])(implicit tx: T): Unit
}
sealed trait RightChild[+T, +P, +H, +A] extends Child[T, P, H, A]
/** An inner non empty tree element has a mutable parent node. */
sealed trait NonEmptyChild[T <: Exec[T], P, H, A] extends NonEmpty[T, P, H] with Child[T, P, H, A] {
def parent(implicit tx: T): Branch[T, P, H, A]
}
protected sealed trait LeafOrEmpty[+T, +P, +H, +A] extends LeftChild[T, P, H, A]
/** A common trait used in pattern matching, comprised of `Leaf` and `RightChildBranch`. */
sealed trait RightNonEmptyChild[T <: Exec[T], P, H, A]
extends RightChild [T, P, H, A]
with NonEmptyChild[T, P, H, A] with Writable {
private[DetSkipOctree] def updateParentRight(p: RightBranch[T, P, H, A])(implicit tx: T): Unit
}
sealed trait TopBranch[T <: Exec[T], P, H, A] extends Branch[T, P, H, A]
sealed trait LeftTopBranch[T <: Exec[T], P, H, A]
extends LeftBranch [T, P, H, A]
with TopBranch [T, P, H, A]
with Disposable [T]
sealed trait RightTopBranch[T <: Exec[T], P, H, A]
extends RightBranch [T, P, H, A]
with TopBranch [T, P, H, A]
case object Empty
extends LeftChild [Nothing, Nothing, Nothing, Nothing]
with RightChild [Nothing, Nothing, Nothing, Nothing]
with Next [Nothing, Nothing, Nothing, Nothing]
with LeafOrEmpty [Nothing, Nothing, Nothing, Nothing] {
def write(out: DataOutput): Unit = out.writeByte(0)
}
/** A left tree node implementation provides more specialized child nodes
* of type `LeftChild`. It furthermore defines a resolution method
* `findImmediateLeaf` which is typically called after arriving here
* from a `findP0` call.
*/
sealed trait LeftBranch[T <: Exec[T], P, H, A]
extends Branch[T, P, H, A] /* Like */
with LeftNonEmpty[T, P, H] {
def prevOption: Option[Branch[T, P, H, A]]
def child(idx: Int)(implicit tx: T): LeftChild[T, P, H, A]
private[DetSkipOctree] def insert(point: P, value: A)(implicit tx: T): Leaf[T, P, H, A]
private[DetSkipOctree] def updateChild(idx: Int, c: LeftChild[T, P, H, A])(implicit tx: T): Unit
/** Instantiates an appropriate
* leaf whose parent is this node, and which should be
* ordered according to its position in this node.
*
* @param qIdx the orthant index of the new leaf in this node
* @param value the value associated with the new leaf
* @return the new leaf which has already assigned this node as
* parent and is already stored in this node's children
* at index `qIdx`
*/
private[DetSkipOctree] def newLeaf(qIdx: Int, value: A)(implicit tx: T): Leaf[T, P, H, A]
}
/** A right tree node implementation provides more specialized child nodes
* of type `RightChild`. It furthermore defines the node in Qi-1 via the
* `prev` method.
*/
sealed trait RightBranch[T <: Exec[T], P, H, A] extends Next[T, P, H, A] with Branch[T, P, H, A] {
def prev: Branch[T, P, H, A]
private[DetSkipOctree] def updateChild(idx: Int, c: RightChild[T, P, H, A])(implicit tx: T): Unit
/** Promotes a leaf that exists in Qi-1 to this
* tree, by inserting it into this node which
* is its interesting node in Qi.
*
* If the result of insertion is a new child node
* below this node, this intermediate node will
* be connected to Qi by looking for the corresponding
* hyper-cube in the given search path that led here
* (i.e. that was constructed in `findPN`).
*
* This method also sets the parent of the leaf
* accordingly.
*/
private[DetSkipOctree] def insert(point: P, leaf: Leaf[T, P, H, A])(implicit tx: T): Unit
}
sealed trait LeftChildBranch[T <: Exec[T], P, H, A]
extends LeftBranch [T, P, H, A]
with ChildBranch [T, P, H, A]
with LeftNonEmptyChild [T, P, H, A] {
def parent(implicit tx: T): LeftBranch[T, P, H, A]
private[DetSkipOctree] def parent_=(node: LeftBranch[T, P, H, A])(implicit tx: T): Unit
}
sealed trait RightChildBranch[T <: Exec[T], P, H, A]
extends RightBranch [T, P, H, A]
with ChildBranch [T, P, H, A]
with RightNonEmptyChild[T, P, H, A] {
def parent(implicit tx: T): RightBranch[T, P, H, A]
private[DetSkipOctree] def parent_=(node: RightBranch[T, P, H, A])(implicit tx: T): Unit
}
/* Nodes are defined by a hyperCube area as well as a list of children,
* as well as a pointer `next` to the corresponding node in the
* next highest tree. A `Branch` also provides various search methods.
*/
private sealed trait BranchImpl[T <: Exec[T], P, H <: HyperCube[P, H], A] {
thisBranch: Branch[T, P, H, A] =>
// ---- abstract ----
protected def nextRef: Var[T, Next[T, P, H, A]]
/** Called when a leaf has been removed from the node.
* The node may need to cleanup after this, e.g. promote
* an under-full node upwards.
*/
protected def leafRemoved()(implicit tx: T): Unit
protected def nodeName: String
// ---- impl ----
final def next_=(node: Next[T, P, H, A])(implicit tx: T): Unit = nextRef() = node
final def next(implicit tx: T): Next[T, P, H, A] = nextRef()
final def nextOption(implicit tx: T): Option[Branch[T, P, H, A]] = thisBranch.next match {
case Empty => None
case b: Branch[T, P, H, A] => Some(b)
}
final def union(mq: H, point2: P)(implicit tx: T): H = { // scalac warning bug
val q = thisBranch.hyperCube
mq.greatestInterestingH(q, point2)
}
final def orthantIndexIn(iq: H)(implicit tx: T): Int = // scalac warning bug
iq.indexOfH(thisBranch.hyperCube)
protected final def shortString = s"$nodeName($thisBranch.hyperCube)"
}
private trait LeftBranchImpl[T <: Exec[T], P, H <: HyperCube[P, H], A]
extends BranchImpl[T, P, H, A] {
branch: LeftBranch[T, P, H, A] =>
// ---- abstract ----
protected val octree: Impl[T, P, H, A]
/** For a `LeftBranch`, all its children are more specific
* -- they are instances of `LeftChild` and thus support
* order intervals.
*/
protected def children: Array[Var[T, LeftChild[T, P, H, A]]]
// ---- impl ----
final def prevOption: Option[Branch[T, P, H, A]] = None
final def child(idx: Int)(implicit tx: T): LeftChild[T, P, H, A] = children(idx)()
final def updateChild(idx: Int, c: LeftChild[T, P, H, A])(implicit tx: T): Unit =
children(idx)() = c
final def demoteLeaf(point: P, leaf: Leaf[T, P, H, A])(implicit tx: T): Unit = {
val qIdx = branch.hyperCube.indexOfP(point)
val ok = child(qIdx) == leaf
if (ok) {
updateChild(qIdx, Empty)
leafRemoved()
leaf.remove() // dispose()
} else {
if (!DetSkipOctree.sanitizing)
assert(assertion = false, s"Internal error - expected $leaf not found in $this")
}
}
final def insert(point: P, value: A)(implicit tx: T): Leaf[T, P, H, A] = {
val qIdx = branch.hyperCube.indexOfP(point)
child(qIdx) match {
case Empty =>
newLeaf(qIdx, /* point, */ value) // (this adds it to the children!)
case old: LeftNonEmptyChild[T, P, H, A] =>
// define the greatest interesting square for the new node to insert
// in this node at qIdx:
val qn2 = old.union(branch.hyperCube.orthant(qIdx), point)
// create the new node (this adds it to the children!)
val n2 = newNode(qIdx, qn2)
val oIdx = old.orthantIndexIn(qn2)
n2.updateChild(oIdx, old)
val lIdx = qn2.indexOfP(point)
assert(oIdx != lIdx)
// This is a tricky bit! And a reason
// why should eventually try to do without
// parent pointers at all. Since `old`
// may be a leaf whose parent points
// to a higher level tree, we need to
// check first if the parent is `this`,
// and if so, adjust the parent to point
// to the new intermediate node `ne`!
if (old.parent == this) old.updateParentLeft(n2)
n2.newLeaf(lIdx, value)
}
}
/** Instantiates an appropriate
* leaf whose parent is this node, and which should be
* ordered according to its position in this node.
*
* @param qIdx the orthant index of the new leaf in this node
* @param value the value associated with the new leaf
* @return the new leaf which has already assigned this node as
* parent and is already stored in this node's children
* at index `qIdx`
*/
private[DetSkipOctree] def newLeaf(qIdx: Int, value: A)(implicit tx: T): Leaf[T, P, H, A] = {
val leafId = tx.newId()
val parentRef = leafId.newVar[Branch[T, P, H, A]](this)(tx, octree.BranchFormat)
val l = new LeafImpl[T, P, H, A](octree, leafId, value, parentRef)
updateChild(qIdx, l)
l
}
/*
* Instantiates an appropriate
* sub-node whose parent is this node, and which should be
* ordered according to its position in this node.
*
* @param qIdx the orthant index of the new node in this (parent) node
* @param iq the hyper-cube of the new node
* @return the new node which has already assigned this node as
* parent and is already stored in this node's children
* at index `qIdx`
*/
private[this] def newNode(qIdx: Int, iq: H)(implicit tx: T): LeftChildBranch[T, P, H, A] = {
val sz = children.length
val ch = new Array[Var[T, LeftChild[T, P, H, A]]](sz)
val cid = tx.newId()
var i = 0
while (i < sz) {
ch(i) = cid.newVar[LeftChild[T, P, H, A]](Empty)(tx, octree.LeftChildFormat)
i += 1
}
val parentRef = cid.newVar[LeftBranch[T, P, H, A]](this)(tx, octree.LeftBranchFormat)
val rightRef = cid.newVar[Next[T, P, H, A]](Empty)(tx, octree.RightOptionReader)
val n = new LeftChildBranchImpl[T, P, H, A](
octree, cid, parentRef, iq, children = ch, nextRef = rightRef
)
updateChild(qIdx, n)
n
}
}
/* A leaf in the octree, carrying a map entry
* in the form of a point and associated value.
* Note that a single instance of a leaf is used
* across the levels of the octree! That means
* that multiple child pointers may go to the
* same leaf, while the parent of a leaf always
* points into the highest level octree that
* the leaf resides in, according to the skiplist.
*/
private final class LeafImpl[T <: Exec[T], P,
H <: HyperCube[P, H], A](octree: Impl[T, P, H, A], val id: Ident[T], val value: A,
parentRef: Var[T, Branch[T, P, H, A]])
extends LeftNonEmptyChild [T, P, H, A]
with RightNonEmptyChild [T, P, H, A]
with LeafOrEmpty [T, P, H, A]
with Leaf [T, P, H, A] {
def updateParentLeft (p: LeftBranch [T, P, H, A])(implicit tx: T): Unit = parent_=(p)
def updateParentRight(p: RightBranch[T, P, H, A])(implicit tx: T): Unit = parent_=(p)
def parent(implicit tx: T): Branch[T, P, H, A] = parentRef()
def parent_=(p: Branch[T, P, H, A])(implicit tx: T): Unit = parentRef() = p
def dispose()(implicit tx: T): Unit = {
id.dispose()
parentRef.dispose()
}
def write(out: DataOutput): Unit = {
out.writeByte(1)
id.write(out)
octree.keyFormat.write(value, out)
parentRef.write(out)
}
def union(mq: H, point2: P)(implicit tx: T): H =
mq.greatestInterestingP(octree.pointView(value, tx), point2)
def orthantIndexIn(iq: H)(implicit tx: T): Int =
iq.indexOfP(octree.pointView(value, tx))
def shortString = s"Leaf($value)"
def remove()(implicit tx: T): Unit = dispose()
}
private final class LeftChildBranchImpl[T <: Exec[T], P,
H <: HyperCube[P, H], A](val octree: Impl[T, P, H, A], val id: Ident[T],
parentRef: Var[T, LeftBranch[T, P, H, A]], val hyperCube: H,
protected val children: Array[Var[T, LeftChild[T, P, H, A]]],
protected val nextRef: Var[T, Next[T, P, H, A]])
extends LeftBranchImpl[T, P, H, A]
with LeftChildBranch[T, P, H, A] {
thisBranch =>
protected def nodeName = "LeftInner"
def updateParentLeft(p: LeftBranch[T, P, H, A])(implicit tx: T): Unit = parent = p
def parent(implicit tx: T): LeftBranch[T, P, H, A] = parentRef()
def parent_=(node: LeftBranch[T, P, H, A])(implicit tx: T): Unit = parentRef() = node
def dispose()(implicit tx: T): Unit = {
id .dispose()
parentRef .dispose()
var i = 0
val sz = children.length
while (i < sz) {
children(i).dispose()
i += 1
}
nextRef.dispose()
}
def write(out: DataOutput): Unit = {
out.writeByte(3)
id.write(out)
parentRef.write(out)
octree.space.hyperCubeFormat.write(thisBranch.hyperCube, out)
var i = 0
val sz = children.length
while (i < sz) {
children(i).write(out)
i += 1
}
nextRef.write(out)
}
private[this] def remove()(implicit tx: T): Unit = dispose()
// make sure the node is not becoming uninteresting, in which case
// we need to merge upwards
protected def leafRemoved()(implicit tx: T): Unit = {
val sz = children.length
@tailrec def removeIfLonely(i: Int): Unit =
if (i < sz) child(i) match {
case lonely: LeftNonEmptyChild[T, P, H, A] =>
@tailrec def isLonely(j: Int): Boolean = {
j == sz || (child(j) match {
case _: LeftNonEmptyChild[T, P, H, A] => false
case _ => isLonely(j + 1)
})
}
if (isLonely(i + 1)) {
val p = parent
val myIdx = p.hyperCube.indexOfH(thisBranch.hyperCube)
p.updateChild(myIdx, lonely)
if (lonely.parent == this) lonely.updateParentLeft(p)
remove() // dispose() // removeAndDispose()
}
case _ => removeIfLonely(i + 1)
}
removeIfLonely(0)
}
}
private trait RightBranchImpl[T <: Exec[T], P, H <: HyperCube[P, H], A]
extends BranchImpl[T, P, H, A] {
branch: RightBranch[T, P, H, A] =>
// ---- abstract ----
protected val octree: Impl[T, P, H, A]
protected def children: Array[Var[T, RightChild[T, P, H, A]]]
// ---- impl ----
final def prevOption: Option[Branch[T, P, H, A]] = Some(prev: Branch[T, P, H, A])
final def child(idx: Int)(implicit tx: T): RightChild[T, P, H, A] = children(idx)()
final def updateChild(idx: Int, c: RightChild[T, P, H, A])(implicit tx: T): Unit =
children(idx)() = c
/** Promotes a leaf that exists in Qi-1 to this
* tree, by inserting it into this node which
* is its interesting node in Qi.
*
* If the result of insertion is a new child node
* below this node, this intermediate node will
* be connected to Qi by looking for the corresponding
* hyper-cube in the given search path that led here
* (i.e. that was constructed in `findPN`).
*
* This method also sets the parent of the leaf
* accordingly.
*/
final def insert(point: P, leaf: Leaf[T, P, H, A])(implicit tx: T): Unit = {
val qIdx = branch.hyperCube.indexOfP(point)
child(qIdx) match {
case Empty =>
updateChild(qIdx, leaf)
leaf.parent = this
case old: RightNonEmptyChild[T, P, H, A] =>
// determine the greatest interesting square for the new
// intermediate node to create
val qn2 = old.union(branch.hyperCube.orthant(qIdx), point)
// find the corresponding node in the lower tree
@tailrec def findInPrev(b: Branch[T, P, H, A]): Branch[T, P, H, A] = {
if (b.hyperCube == qn2) b
else {
val idx = b.hyperCube.indexOfP(point)
b.child(idx) match {
case _: LeafOrEmpty[T, P, H, A] => sys.error("Internal error - cannot find sub-cube in prev")
case cb: Branch[T, P, H, A] => findInPrev(cb)
}
}
}
val pPrev = findInPrev(prev)
val n2 = newNode(qIdx, pPrev, qn2)
val oIdx = old.orthantIndexIn(qn2)
n2.updateChild(oIdx, old)
// This is a tricky bit! And a reason
// why should eventually try to do without
// parent pointers at all. Since `old`
// may be a leaf whose parent points
// to a higher level tree, we need to
// check first if the parent is `this`,
// and if so, adjust the parent to point
// to the new intermediate node `ne`!
if (old.parent == this) old.updateParentRight(n2)
val lIdx = qn2.indexOfP(point)
n2.updateChild(lIdx, leaf)
leaf.parent = n2
}
}
/*
* Instantiates an appropriate
* sub-node whose parent is this node, and whose predecessor
* in the lower octree is given.
*
* @param qIdx the orthant index in this node where the node belongs
* @param prev the new node's prev field, i.e. its correspondent in
* Qi-1
* @param iq the hyper-cube for the new node
* @return the new node which has already been inserted into this node's
* children at index `qIdx`.
*/
@inline private[this] def newNode(qIdx: Int, prev: Branch[T, P, H, A],
iq: H)(implicit tx: T): RightChildBranch[T, P, H, A] = {
val sz = children.length
val ch = new Array[Var[T, RightChild[T, P, H, A]]](sz)
val cid = tx.newId()
var i = 0
implicit val format: TFormat[T, RightChild[T, P, H, A]] = octree.RightChildFormat
while (i < sz) {
ch(i) = cid.newVar[RightChild[T, P, H, A]](Empty)
i += 1
}
val parentRef = cid.newVar[RightBranch[T, P, H, A]](this)(tx, octree.RightBranchFormat)
val rightRef = cid.newVar[Next[T, P, H, A]](Empty)(tx, octree.RightOptionReader)
val n = new RightChildBranchImpl[T, P, H, A](octree, cid, parentRef, prev, iq, ch, rightRef)
prev.next = n
updateChild(qIdx, n)
n
}
final def demoteLeaf(point: P, leaf: Leaf[T, P, H, A])(implicit tx: T): Unit = {
val qIdx = branch.hyperCube.indexOfP(point)
assert(child(qIdx) == leaf)
updateChild(qIdx, Empty)
@tailrec def findParent(b: Branch[T, P, H, A], idx: Int): Branch[T, P, H, A] = b.child(idx) match {
case sl: Leaf [T, P, H, A] => assert(sl == leaf); b
case cb: Branch[T, P, H, A] => findParent(cb, cb.hyperCube.indexOfP(point))
case Empty => throw new IllegalStateException // should not happen by definition
}
val newParent = findParent(prev, qIdx)
leafRemoved()
leaf.parent = newParent
}
}
private final class RightChildBranchImpl[T <: Exec[T], P,
H <: HyperCube[P, H], A](val octree: Impl[T, P, H, A], val id: Ident[T],
parentRef: Var[T, RightBranch[T, P, H, A]], val prev: Branch[T, P, H, A],
val hyperCube: H,
protected val children: Array[Var[T, RightChild[T, P, H, A]]],
protected val nextRef: Var[T, Next[T, P, H, A]])
extends RightChildBranch[T, P, H, A] with RightBranchImpl[T, P, H, A] {
thisBranch =>
protected def nodeName = "RightInner"
def updateParentRight(p: RightBranch[T, P, H, A])(implicit tx: T): Unit =
parent = p
private[this] def remove()(implicit tx: T): Unit = {
// first unlink
prev.next = Empty
dispose()
}
def dispose()(implicit tx: T): Unit = {
id.dispose()
// // first unlink
// prev.next = Empty
// then dispose refs
parentRef.dispose()
var i = 0
val sz = children.length
while (i < sz) {
children(i).dispose()
i += 1
}
nextRef.dispose()
}
def write(out: DataOutput): Unit = {
out.writeByte(5)
id.write(out)
parentRef.write(out)
prev.write(out)
octree.space.hyperCubeFormat.write(thisBranch.hyperCube, out)
var i = 0
val sz = children.length
while (i < sz) {
children(i).write(out)
i += 1
}
nextRef.write(out)
}
def parent(implicit tx: T): RightBranch[T, P, H, A] = parentRef()
def parent_=(node: RightBranch[T, P, H, A])(implicit tx: T): Unit = parentRef() = node
// make sure the node is not becoming uninteresting, in which case
// we need to merge upwards
protected def leafRemoved()(implicit tx: T): Unit = {
val sz = children.length
@tailrec def removeIfLonely(i: Int): Unit =
if (i < sz) child(i) match {
case lonely: RightNonEmptyChild[T, P, H, A] =>
@tailrec def isLonely(j: Int): Boolean = {
j == sz || (child(j) match {
case _: RightNonEmptyChild[T, P, H, A] => false
case _ => isLonely(j + 1)
})
}
if (isLonely(i + 1)) {
val p = parent
val myIdx = p.hyperCube.indexOfH(thisBranch.hyperCube)
p.updateChild(myIdx, lonely)
if (lonely.parent == this) lonely.updateParentRight(p)
remove()
}
case _ => removeIfLonely(i + 1)
}
removeIfLonely(0)
}
}
private final class LeftTopBranchImpl[T <: Exec[T], P,
H <: HyperCube[P, H], A](val octree: Impl[T, P, H, A], val id: Ident[T],
protected val children: Array[Var[T, LeftChild[T, P, H, A]]],
protected val nextRef: Var[T, Next[T, P, H, A]])
extends LeftTopBranch[T, P, H, A]
with LeftBranchImpl[T, P, H, A]
with TopBranchImpl [T, P, H, A]
with Mutable[T] {
// that's alright, we don't need to do anything special here
protected def leafRemoved()(implicit tx: T): Unit = ()
def dispose()(implicit tx: T): Unit = {
id.dispose()
var i = 0
val sz = children.length
while (i < sz) {
children(i).dispose()
i += 1
}
nextRef.dispose()
}
def write(out: DataOutput): Unit = {
out.writeByte(2)
id.write(out)
// no need to write the hyperCube?
var i = 0
val sz = children.length
while (i < sz) {
children(i).write(out)
i += 1
}
nextRef.write(out)
}
protected def nodeName = "LeftTop"
}
private final class RightTopBranchImpl[T <: Exec[T], P,
H <: HyperCube[P, H], A](val octree: Impl[T, P, H, A], val id: Ident[T], val prev: TopBranch[T, P, H, A],
protected val children: Array[Var[T, RightChild[T, P, H, A]]],
protected val nextRef: Var[T, Next[T, P, H, A]])
extends RightTopBranch[T, P, H, A]
with RightBranchImpl[T, P, H, A]
with TopBranchImpl [T, P, H, A] {
protected def nodeName = "RightTop"
private[this] def remove()(implicit tx: T): Unit = {
// first unlink
assert(octree.lastTree == this)
octree.lastTree = prev
prev.next = Empty
dispose()
}
def dispose()(implicit tx: T): Unit = {
id.dispose()
// // first unlink
// assert( lastTreeImpl == this )
// lastTreeImpl= prev
// prev.next = Empty
// then dispose refs
var i = 0
val sz = children.length
while (i < sz) {
children(i).dispose()
i += 1
}
nextRef.dispose()
}
def write(out: DataOutput): Unit = {
out.writeByte(4)
id.write(out)
// no need to write the hypercube!
prev.write(out)
var i = 0
val sz = children.length
while (i < sz) {
children(i).write(out)
i += 1
}
nextRef.write(out)
}
// remove this node if it empty now and right-node tree
protected def leafRemoved()(implicit tx: T): Unit = {
if (next != Empty) return
val sz = children.length
var i = 0
while (i < sz) {
val c = child(i)
if (c != Empty) return // node not empty, abort the check
i += 1
}
// ok, we are the right most tree and the node is empty...
remove()
}
}
private sealed trait TopBranchImpl[T <: Exec[T], P, H <: HyperCube[P, H], A] {
protected val octree: Impl[T, P, H, A]
final def hyperCube: H = octree.hyperCube
}
// cf. https://github.com/lampepfl/dotty/issues/9844
private abstract class Impl[T <: Exec[T], P, H <: HyperCube[P, H], A]
extends DetSkipOctree[T, P, H, A] {
octree =>
final type Child = DetSkipOctree.Child [T, P, H, A]
final type Branch = DetSkipOctree.Branch [T, P, H, A]
final type Leaf = DetSkipOctree.Leaf [T, P, H, A]
final type LeftBranch = DetSkipOctree.LeftBranch [T, P, H, A]
final type RightBranch = DetSkipOctree.RightBranch [T, P, H, A]
final type LeftChild = DetSkipOctree.LeftChild [T, P, H, A]
final type RightChild = DetSkipOctree.RightChild [T, P, H, A]
final type Next = DetSkipOctree.Next [T, P, H, A]
final type ChildBranch = DetSkipOctree.ChildBranch [T, P, H, A]
final type LeftChildBranch = DetSkipOctree.LeftChildBranch [T, P, H, A]
final type RightChildBranch = DetSkipOctree.RightChildBranch [T, P, H, A]
final type NonEmptyChild = DetSkipOctree.NonEmptyChild [T, P, H, A]
final type LeafOrEmpty = DetSkipOctree.LeafOrEmpty [T, P, H, A]
final type LeftNonEmptyChild = DetSkipOctree.LeftNonEmptyChild [T, P, H, A]
final type RightNonEmptyChild = DetSkipOctree.RightNonEmptyChild[T, P, H, A]
final type TopBranch = DetSkipOctree.TopBranch [T, P, H, A]
final type LeftTopBranch = DetSkipOctree.LeftTopBranch [T, P, H, A]
final type RightTopBranch = DetSkipOctree.RightTopBranch [T, P, H, A]
// ---- abstract types and methods ----
implicit def space: Space[P, H]
implicit def keyFormat: TFormat[T, A]
protected def skipList: HASkipList.Set[T, Leaf]
protected def lastTreeRef: Var[T, TopBranch]
// ----
override def toString = s"Octree-${space.dim}d$id"
protected object LeafOrdering extends TOrdering[T, Leaf] {
/** Leaves are ordered by the tree's in-order traversal,
* where the quadrants I+II and III+IV can be thought
* of as dummy nodes to make the octree binary. That is
* to say, in a node, the child order corresponds to
* their quadrant indices (I < II < III < IV).
*/
def compare(a: Leaf, b: Leaf)(implicit tx: T): Int = {
val pa = pointView(a.value, tx)
val pb = pointView(b.value, tx)
space.lexicalOrder.compare(pa, pb)
}
}
implicit object RightBranchFormat extends WritableFormat[T, RightBranch] {
override def readT(in: DataInput)(implicit tx: T): RightBranch = {
val cookie = in.readByte()
val id = tx.readId(in)
(cookie: @switch) match {
case 4 => readRightTopBranch (in, id)
case 5 => readRightChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
implicit object BranchFormat extends WritableFormat[T, Branch] {
override def readT(in: DataInput)(implicit tx: T): Branch = {
val cookie = in.readByte()
val id = tx.readId(in)
(cookie: @switch) match {
case 2 => readLeftTopBranch (in, id)
case 3 => readLeftChildBranch (in, id)
case 4 => readRightTopBranch (in, id)
case 5 => readRightChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
protected object TopBranchFormat extends WritableFormat[T, TopBranch] {
override def readT(in: DataInput)(implicit tx: T): TopBranch = {
val cookie = in.readByte()
val id = tx.readId(in)
(cookie: @switch) match {
case 2 => readLeftTopBranch (in, id)
case 4 => readRightTopBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
object LeftChildFormat extends WritableFormat[T, LeftChild] {
override def readT(in: DataInput)(implicit tx: T): LeftChild = {
val cookie = in.readByte()
if (cookie == 0) return Empty
val id = tx.readId(in)
(cookie: @switch) match {
case 1 => readLeaf(in, id)
case 3 => readLeftChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
implicit object LeftBranchFormat extends WritableFormat[T, LeftBranch] {
override def readT(in: DataInput)(implicit tx: T): LeftBranch = {
val cookie = in.readByte()
val id = tx.readId(in)
(cookie: @switch) match {
case 2 => readLeftTopBranch (in, id)
case 3 => readLeftChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
implicit object RightChildFormat extends WritableFormat[T, RightChild] {
override def readT(in: DataInput)(implicit tx: T): RightChild = {
val cookie = in.readByte()
if (cookie == 0) return Empty
val id = tx.readId(in)
(cookie: @switch) match {
case 1 => readLeaf(in, id)
case 5 => readRightChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
implicit object LeftTopBranchFormat extends WritableFormat[T, LeftTopBranch] {
override def readT(in: DataInput)(implicit tx: T): LeftTopBranch = {
val cookie = in.readByte()
if (cookie != 2) sys.error(s"Unexpected cookie $cookie")
val id = tx.readId(in)
readLeftTopBranch(in, id)
}
}
object RightOptionReader extends WritableFormat[T, Next] {
override def readT(in: DataInput)(implicit tx: T): Next = {
val cookie = in.readByte()
if (cookie == 0) return Empty
val id = tx.readId(in)
(cookie: @switch) match {
case 4 => readRightTopBranch (in, id)
case 5 => readRightChildBranch(in, id)
case _ => sys.error(s"Unexpected cookie $cookie")
}
}
}
protected object LeafFormat extends WritableFormat[T, Leaf] {
override def readT(in: DataInput)(implicit tx: T): Leaf = {
val cookie = in.readByte()
if (cookie != 1) sys.error(s"Unexpected cookie $cookie")
val id = tx.readId(in)
readLeaf(in, id)
}
}
implicit protected object KeyObserver extends SkipList.KeyObserver[T, Leaf] {
def keyUp(l: Leaf)(implicit tx: T): Unit = {
//println( "up : " + l )
// "To insert x into Qi+1 we go from xi to pi(x) in Qi,
// then traverse upwards in Qi until we find the lowest
// ancestor q of x which is also interesting in Qi+1.
// (This is the reversed process of searching x in Qi
// with q = pi,start = pi+1,end so it takes at most 6
// steps by Lemma 5.) Then we go to the same square q
// in Qi+1 and insert x."
/* The reverse process of `findP0`: Finds the lowest
* common ancestor interesting node of this node
* which is also contained in Qi+1. Returns this node
* in Qi+1, or empty if no such node exists.
*/
@tailrec def findPN(b: Branch): Next = b match {
case tb: TopBranch => tb.next
case cb: ChildBranch => cb.next match {
case nb: Branch => nb
case Empty => findPN(cb.parent)
}
}
val pNext = findPN(l.parent) match {
case Empty => // create new level
val sz = numOrthants
// val ch = tx.newVarArray[RightChild](sz)
val ch = new Array[Var[T, RightChild]](sz)
val cid = tx.newId()
var i = 0
while (i < sz) {
ch(i) = cid.newVar[RightChild](Empty)
i += 1
}
val nextRef = cid.newVar[Next](Empty)(tx, RightOptionReader)
val prev = lastTree
val res = new RightTopBranchImpl(octree, cid, prev, ch, nextRef)
prev.next = res
lastTree = res
res
case r: RightBranch => r
}
pNext.insert(pointView(l.value, tx), l)
}
def keyDown(l: Leaf)(implicit tx: T): Unit = {
//println( "down : " + l )
// "To delete x from Qi we go from xi to the smallest interesting
// square pi(x) containing x in Qi following the pointers. Then
// the deletion given pi(x) is as described in Section 2.3."
l.parent.demoteLeaf(pointView(l.value, tx), l)
}
}
final def numOrthants: Int = 1 << space.dim // 4 for R2, 8 for R3, 16 for R4, etc.
final def write(out: DataOutput): Unit = {
out.writeByte(SER_VERSION)
id .write(out)
space.hyperCubeFormat.write(hyperCube, out)
skipList .write(out)
headTree .write(out)
lastTreeRef .write(out)
}
final def clear()(implicit tx: T): Unit = {
val sz = numOrthants
@tailrec def removeAllLeaves(b: Branch): Unit = {
@tailrec def stepB(down: Branch, i: Int): Child = {
if (i == sz) down
else b.child(i) match {
case l: Leaf =>
removeLeaf(pointView(l.value, tx), l)
lastTree
case _ => stepB(down, i + 1)
}
}
@tailrec def step(i: Int): Child = {
if (i == sz) Empty
else b.child(i) match {
case cb: Branch => stepB(cb, i + 1)
case l: Leaf =>
removeLeaf(pointView(l.value, tx), l)
lastTree
case _ => step(i + 1)
}
}
step(0) match {
case _: LeafOrEmpty =>
case next: Branch => removeAllLeaves(next)
}
}
removeAllLeaves(lastTree)
}
final def dispose()(implicit tx: T): Unit = {
id .dispose()
lastTreeRef .dispose()
headTree .dispose()
skipList .dispose()
}
final def lastTree(implicit tx: T): TopBranch = lastTreeRef()
final def lastTree_=(node: TopBranch)(implicit tx: T): Unit = lastTreeRef() = node
final def size(implicit tx: T): Int = skipList.size
final def add(elem: A)(implicit tx: T): Boolean =
insertLeaf(elem) match {
case Empty => true
case oldLeaf: Leaf => oldLeaf.value != elem
}
final def update(elem: A)(implicit tx: T): Option[A] =
insertLeaf(elem) match {
case Empty => None
case oldLeaf: Leaf => Some(oldLeaf.value)
}
final def remove(elem: A)(implicit tx: T): Boolean =
removeLeafAt(pointView(elem, tx)) != Empty
final def removeAt(point: P)(implicit tx: T): Option[A] =
removeLeafAt(point) match {
case Empty => None
case oldLeaf: Leaf => Some(oldLeaf.value)
}
final def contains(elem: A)(implicit tx: T): Boolean = {
val point = pointView(elem, tx)
if (!hyperCube.containsP(point)) return false
findAt(point) match {
case l: Leaf => l.value == elem
case _ => false
}
}
final def isDefinedAt(point: P)(implicit tx: T): Boolean = {
if (!hyperCube.containsP(point)) return false
findAt(point) != Empty
}
final def get(point: P)(implicit tx: T): Option[A] = {
if (!hyperCube.containsP(point)) return None
findAt(point) match {
case l: Leaf => Some(l.value)
case _ => None
}
}
final def nearestNeighbor[M](point: P, metric: DistanceMeasure[M, P, H])(implicit tx: T): A = {
val nn = new NN(point, metric).find()
stat_report()
nn match {
case Empty => throw new NoSuchElementException("nearestNeighbor on an empty tree")
case l: Leaf => l.value
}
}
final def nearestNeighborOption[M](point: P, metric: DistanceMeasure[M, P, H])(implicit tx: T): Option[A] = {
val nn = new NN(point, metric).find()
stat_report()
nn match {
case Empty => None
case l: Leaf => Some(l.value)
}
}
final def isEmpty(implicit tx: T): Boolean = {
val n = headTree
val sz = numOrthants
@tailrec def step(i: Int): Boolean = if (i == sz) true
else n.child(i) match {
case _: NonEmptyChild => false
case _ => step(i + 1)
}
step(0)
}
final def numLevels(implicit tx: T): Int = {
@tailrec def step(b: Branch, num: Int): Int = {
b.next match {
case Empty => num
case n: Branch => step(n, num + 1)
}
}
step(headTree, 1)
}
final def +=(elem: A)(implicit tx: T): this.type = {
insertLeaf(elem)
// match {
// case oldLeaf: Leaf => oldLeaf.dispose()
// case _ =>
// }
this
}
final def -=(elem: A)(implicit tx: T): this.type = {
removeLeafAt(pointView(elem, tx))
// match {
// case oldLeaf: Leaf => oldLeaf.dispose()
// case _ =>
// }
this
}
final def rangeQuery[Area](qs: QueryShape[Area, P, H])(implicit tx: T): Iterator[A] = {
val q = new RangeQuery(qs)
q.findNextValue()
q
}
final def toIndexedSeq(implicit tx: T): Vec[A] = iterator.toIndexedSeq
final def toList(implicit tx: T): List[A] = iterator.toList
// note that `iterator.toSeq` produces a `Stream` !!
final def toSeq(implicit tx: T): Seq[A] = iterator.toIndexedSeq
final def toSet(implicit tx: T): Set[A] = iterator.toSet
private[this] def findAt(point: P)(implicit tx: T): LeafOrEmpty = {
val p0 = findP0(point) // lastTreeImpl.findP0( point )
findLeafInP0(p0, point) // p0.findImmediateLeaf( point )
}
// OBSOLETE: the caller _must not call dispose_
//
// (( WARNING: if the returned oldLeaf is defined, the caller is
// responsible for disposing it (after extracting useful information such as its value) ))
private[this] def insertLeaf(elem: A)(implicit tx: T): LeafOrEmpty = {
val point = pointView(elem, tx)
if (!hyperCube.containsP(point)) sys.error(s"$point lies out of root hyper-cube $hyperCube")
val p0 = findP0(point) // lastTreeImpl.findP0( point )
val res = findLeafInP0(p0, point)
res match {
case Empty =>
val leaf = p0.insert(point, elem)
skipList.add(leaf)
case oldLeaf: Leaf =>
// remove previous leaf
removeLeaf(point, oldLeaf)
// search anew
val p0b = findP0(point) // lastTreeImpl.findP0( point )
assert(findLeafInP0(p0b, point) == Empty)
val leaf = p0b.insert(point, elem)
skipList.add(leaf)
}
res
}
// WARNING: if the returned oldLeaf is defined, the caller is
// responsible for disposing it (after extracting useful information such as its value)
private[this] def removeLeafAt(point: P)(implicit tx: T): LeafOrEmpty = {
if (!hyperCube.containsP(point)) return Empty
// "To insert or delete a point y into or from S, we first search the
// quadtree structure to locate y in each Qi ..."
val p0 = findP0(point) // lastTreeImpl.findP0( point )
// "... Then we insert or delete y
// in the binary Q0 and update our total order."
val res = findLeafInP0(p0, point) // p0.findImmediateLeaf( point )
res match {
case l: Leaf => removeLeaf(point, l)
case _ =>
}
res
}
def transformAt(point: P)(fun: Option[A] => Option[A])(implicit tx: T): Option[A] = {
require(hyperCube.containsP(point), s"$point lies out of root hyper-cube $hyperCube")
val p0 = findP0(point)
findLeafInP0(p0, point) match {
case Empty =>
val res = None
fun(res).foreach { elem =>
val leaf = p0.insert(point, elem)
skipList.add(leaf)
}
res
case oldLeaf: Leaf =>
// it's not possible currently to update a leaf's value...
// remove previous leaf
val res = Some(oldLeaf.value)
removeLeaf(point, oldLeaf)
fun(res).foreach {
elem =>
// search anew
val p0b = findP0(point)
assert(findLeafInP0(p0b, point) == Empty)
val leaf = p0b.insert(point, elem)
skipList.add(leaf)
}
res
}
}
/*
* After arriving at this node from a `findP0` call, this resolves
* the given point to an actual leaf.
*
* @return the `Leaf` child in this node associated with the given
* `point`, or `empty` if no such leaf exists.
*/
private[this] def findLeafInP0(b: LeftBranch, point: P)(implicit tx: T): LeafOrEmpty = {
val qIdx = b.hyperCube.indexOfP(point)
b.child(qIdx) match {
case l: Leaf if pointView(l.value, tx) == point => l
case _ => Empty
}
}
/*
* Finds to smallest interesting hyper-cube
* in Q0, containing a given point. This method
* traverses downwards into its children, or,
* if the "bottom" has been reached, tries to
* continue in Qi-1.
*
* @return the node defined by the given search `point`, or `empty`
* if no such node exists.
*/
private[this] def findP0(point: P)(implicit tx: T): LeftBranch = {
@tailrec def stepLeft(lb: LeftBranch): LeftBranch = {
val qIdx = lb.hyperCube.indexOfP(point)
lb.child(qIdx) match {
case _: LeafOrEmpty => lb
case cb: LeftBranch =>
if (!cb.hyperCube.containsP(point)) lb else stepLeft(cb)
}
}
@tailrec def step(b: Branch): LeftBranch = b match {
case lb: LeftBranch => stepLeft(lb)
case rb: RightBranch =>
val qIdx = rb.hyperCube.indexOfP(point)
val n = rb.child(qIdx) match {
case cb: Branch if cb.hyperCube.containsP(point) => cb
case _ => rb.prev
}
step(n)
}
step(lastTree)
}
private[this] def removeLeaf(point: P, l: Leaf)(implicit tx: T): Unit = {
// this will trigger removals from upper levels
val skipOk = skipList.remove(l)
assert(skipOk, s"Leaf $l with point $point was not found in skip list")
// now l is in P0. demote it once more (this will dispose the leaf)
l.parent.demoteLeaf(point /* pointView( l.value ) */ , l)
}
final def iterator(implicit tx: T): Iterator[A] = skipList.iterator.map(_.value)
private[this] final class NNIteration[M](val bestLeaf: LeafOrEmpty, val bestDist: M, val rMax: M)
private[this] final class NN[M](point: P, metric: DistanceMeasure[M, P, H])(implicit tx: T)
extends scala.math.Ordering[VisitedNode[M]] {
stat_reset()
// NOTE: `sz` must be protected and not private, otherwise
// scala's specialization blows up
protected val sz: Int = numOrthants
private[this] val acceptedChildren = new Array[Branch](sz)
// private val acceptedDistances = {
// implicit val mf = metric.manifest
// new Array[ M ]( sz )
// }
private[this] val acceptedMinDistances = metric.newArray(sz)
// private val acceptedMaxDistances = metric.newArray(sz)
/* @tailrec */ private[this] def findNNTailOLD(n0: /* Left */ Branch, pri: MPriorityQueue[VisitedNode[M]],
_bestLeaf: LeafOrEmpty, _bestDist: M, _rMax: M): NNIteration[M] = {
stat_rounds1(_rMax)
var numAccepted = 0
var acceptedQIdx = 0
var bestLeaf = _bestLeaf
var bestDist = _bestDist
var rMax = _rMax
var i = 0
while (i < sz) {
n0.child(i) match {
case l: Leaf =>
val lDist = metric.distance(point, pointView(l.value, tx))
if (metric.isMeasureGreater(bestDist, lDist)) { // found a point that is closer than previously known best result
bestDist = lDist
bestLeaf = l
if (metric.isMeasureGreater(rMax, bestDist)) { // update minimum required distance if necessary
rMax = bestDist // note: we'll re-check acceptedChildren at the end of the loop
}
}
case c: LeftBranch =>
val cq = c.hyperCube
val cMinDist = metric.minDistance(point, cq)
if (!metric.isMeasureGreater(cMinDist, rMax)) { // is less than or equal to minimum required distance
// (otherwise we're out already)
val cMaxDist = metric.maxDistance(point, cq)
if (metric.isMeasureGreater(rMax, cMaxDist)) {
rMax = cMaxDist // found a new minimum required distance
}
acceptedChildren(numAccepted) = c
acceptedMinDistances(numAccepted) = cMinDist
// acceptedMaxDistances(numAccepted) = cMaxDist
numAccepted += 1
acceptedQIdx = i // this will be used only if numAccepted == 1
}
case _ => // ignore empty orthants
}
i += 1
}
if (rMax != _rMax) {
// recheck
var j = 0
while (j < numAccepted) {
if (metric.isMeasureGreater(acceptedMinDistances(j), rMax)) {
// immediately kick it out
numAccepted -= 1
var k = j
while (k < numAccepted) {
val k1 = k + 1
acceptedChildren(k) = acceptedChildren(k1)
acceptedMinDistances(k) = acceptedMinDistances(k1)
// acceptedMaxDistances(k) = acceptedMaxDistances(k1)
k = k1
}
}
j += 1
}
}
// Unless exactly one child is accepted, round is over
// if (numAccepted != 1) {
/* var */ i = 0
while (i < numAccepted) { // ...and the children are added to the priority queue
val vn = new VisitedNode[M](acceptedChildren(i), acceptedMinDistances(i) /*, acceptedMaxDistances(i) */)
stat_pq_add1(vn)
pri += vn
i += 1
}
new NNIteration[M](bestLeaf, bestDist, rMax)
}
def find(): LeafOrEmpty = {
val pri = MPriorityQueue.empty[VisitedNode[M]](this)
@tailrec def step(p0: Branch, /*pMinDist: M,*/ bestLeaf: LeafOrEmpty, bestDist: M, rMax: M): LeafOrEmpty = {
val res = findNNTailOLD(p0, /* pMinDist, */ pri, bestLeaf, bestDist, rMax)
if (metric.isMeasureZero(res.bestDist)) {
res.bestLeaf // found a point exactly at the query position, so stop right away
} else {
if (pri.isEmpty) res.bestLeaf
else {
val vis = pri.dequeue()
stat_pq_rem1(vis.n.hyperCube)
// if (!metric.isMeasureGreater(vis.minDist, res.rMax)) vis.n else pop()
// because the queue is sorted by smallest minDist, if we find an element
// whose minimum distance is greater than the maximum distance allowed,
// we are done and do not need to process the remainder of the priority queue.
if (metric.isMeasureGreater(vis.minDist, res.rMax)) res.bestLeaf else {
val lb = vis.n
step(lb, /*vis.minDist,*/ res.bestLeaf, res.bestDist, res.rMax)
}
}
}
}
val mMax = metric.maxValue
val p = headTree // lastTree
// val pMinDist = metric.minDistance(point, octree.hyperCube) // XXX could have metric.zero
step(p, /*pMinDist,*/ Empty, mMax, mMax)
}
def compare(a: VisitedNode[M], b: VisitedNode[M]): Int = {
val min = metric.compareMeasure(b.minDist, a.minDist)
min // if (min != 0) min else metric.compareMeasure(b.maxDist, a.maxDist)
}
}
private[this] final class VisitedNode[M](val n: Branch, val minDist: M /*, val maxDist: M */) {
override def toString = s"($n, min = $minDist" // , max = $maxDist)"
}
// note: Iterator is not specialized, hence we can safe use the effort to specialize in A anyway
private[this] final class RangeQuery[Area](qs: QueryShape[Area, P, H])(implicit tx: T) extends Iterator[A] {
val sz: Int = numOrthants
val stabbing: MQueue[(Branch, Area)] = MQueue.empty
// Tuple2 is specialized for Long, too!
val in: MQueue[NonEmptyChild] = MQueue.empty
var current: A = _
// overwritten by initial run of `findNextValue`
var hasNextVar = true // eventually set to `false` by `findNextValue`
stabbing += headTree -> qs.overlapArea(headTree.hyperCube)
// findNextValue()
override def toString = s"$octree.rangeQuery($qs)"
def hasNext: Boolean = hasNextVar
// search downwards:
// "At each square q ∈ Qi we either go to a child square in Qi
// that covers the same area of R ∪ A as p does, if such a child
// square exists, or jump to the next level q ∈ Qi−1."
@tailrec private[this] def findEquidistantStabbingTail(node: Branch, area: Area): LeftBranch = {
var pi = node
var i = 0
while (i < sz) {
pi.child(i) match {
case pic: Branch =>
val a2 = qs.overlapArea(pic.hyperCube)
if (a2 == area) {
pi = pic
i = 0 // start over in child
} else {
i += 1
}
case _ => i += 1
}
}
// ... or jump to the next (previous) level
pi match {
case lb: LeftBranch => lb
case rb: RightBranch => findEquidistantStabbingTail(rb.prev, area)
}
}
// the movement from Q0 to Qj
// "assuming that p is not critical in Q0, we promote to Qj where Qj is the highest
// level in which p is not a critical square"
//
// definition of critical square:
// "a stabbing node of Qi whose child nodes are either not stabbing, or still
// stabbing but cover less volume of R than p does."
// ; or rather inverted: an uncritical node is one, in which exists at least one stabbing node
// with the same overlapping area!
//
// definition stabbing: 0 < overlap-area < area-of-p
@tailrec def findHighestUncritical(p0: Branch, area: Area): Branch = {
@tailrec def isCritical(b: Branch, i: Int): Boolean = {
i < sz && (b.child(i) match {
// if there is any child which has the same overlap area, it means the node is uncritical
case ci: Branch if qs.overlapArea(ci.hyperCube) == area => true
case _ => isCritical(b, i + 1)
})
}
p0.next match {
case Empty => p0
case pi: Branch => if (isCritical(pi, 0)) p0 else findHighestUncritical(pi, area)
}
}
def next(): A = {
if (!hasNextVar) throw new java.util.NoSuchElementException("next on empty iterator")
val res = current
findNextValue()
res
}
def findNextValue(): Unit = {
while (true) {
if (in.isEmpty) {
if (stabbing.isEmpty) {
hasNextVar = false
return
}
val tup = stabbing.dequeue()
val ns = tup._1 // stabbing node
val as = tup._2 // overlapping area with query shape
val hi = findHighestUncritical(ns, as) // find highest uncritical hyper-cube of the stabbing node
val nc = findEquidistantStabbingTail (hi, as) // now traverse towards Q0 to find the critical square
var i = 0
while (i < sz) {
nc.child(i) match {
case cl: Leaf =>
if (qs.containsP(pointView(cl.value, tx))) in += cl
case cn: ChildBranch =>
val q = cn.hyperCube
val ao = qs.overlapArea(q)
// test for stabbing or inclusion:
// inclusion: overlap-area == area-of-p
// stabbing: 0 < overlap-area < area-of-p
if (qs.isAreaNonEmpty(ao)) {
// q is _not_ out
if (qs.isAreaGreater(q, ao)) {
// q is stabbing
stabbing += cn -> ao
} else {
// q is in
in += cn
}
}
case _ =>
}
i += 1
}
} else {
in.dequeue() match {
case l: Leaf =>
current = l.value
return
case n: Branch =>
var i = 0
while (i < sz) {
n.child(i) match {
case cc: NonEmptyChild => in += cc // `enqueue` would create intermediate Seq because of varargs
case _ =>
}
i += 1
}
}
}
}
}
}
/*
* Serialization-id: 1
*/
private[this] def readLeaf(in: DataInput, id: Ident[T])(implicit tx: T): Leaf = {
val value = keyFormat.readT(in)
val parentRef = id.readVar[Branch](in)
new LeafImpl(octree, id, value, parentRef)
}
/*
* Serialization-id: 2
*/
private[this] def readLeftTopBranch(in: DataInput, id: Ident[T]): LeftTopBranch = {
val sz = numOrthants
val ch = new Array[Var[T, LeftChild]](sz)
var i = 0
while (i < sz) {
ch(i) = id.readVar[LeftChild](in)(LeftChildFormat)
i += 1
}
val nextRef = id.readVar[Next](in)(RightOptionReader)
new LeftTopBranchImpl(octree, id, children = ch, nextRef = nextRef)
}
/*
* Serialization-id: 3
*/
private[this] def readLeftChildBranch(in: DataInput, id: Ident[T]): LeftChildBranch = {
val parentRef = id.readVar[LeftBranch](in)
val hc = space.hyperCubeFormat.read(in)
val sz = numOrthants
val ch = new Array[Var[T, LeftChild]](sz)
var i = 0
while (i < sz) {
ch(i) = id.readVar[LeftChild](in)(LeftChildFormat)
i += 1
}
val nextRef = id.readVar[Next](in)(RightOptionReader)
new LeftChildBranchImpl(octree, id, parentRef, hc, children = ch, nextRef = nextRef)
}
/*
* Serialization-id: 4
*/
private[this] def readRightTopBranch(in: DataInput, id: Ident[T])
(implicit tx: T): RightTopBranch = {
val prev = TopBranchFormat.readT(in)
val sz = numOrthants
val ch = new Array[Var[T, RightChild]](sz)
var i = 0
while (i < sz) {
ch(i) = id.readVar[RightChild](in)
i += 1
}
val nextRef = id.readVar[Next](in)(RightOptionReader)
new RightTopBranchImpl(octree, id, prev, ch, nextRef)
}
/*
* Serialization-id: 5
*/
private[this] def readRightChildBranch(in: DataInput, id: Ident[T])
(implicit tx: T): RightChildBranch = {
val parentRef = id.readVar[RightBranch](in)
val prev = BranchFormat.readT(in)
val hc = space.hyperCubeFormat.read(in)
val sz = numOrthants
val ch = new Array[Var[T, RightChild]](sz)
var i = 0
while (i < sz) {
ch(i) = id.readVar[RightChild](in)
i += 1
}
val nextRef = id.readVar[Next](in)(RightOptionReader)
new RightChildBranchImpl(octree, id, parentRef, prev, hc, ch, nextRef)
}
def debugPrint()(implicit tx: T): String = {
val bs = new ByteArrayOutputStream()
val ps = new PrintStream(bs)
import ps._
println(s"Debug print for $this")
println("Skip list of leaves:")
println(skipList.debugPrint())
println("Octree structure:")
def dumpTree(b: Branch, indent: Int): Unit = {
val iStr = " " * indent
b match {
case lb: LeftBranch =>
println(s"${iStr}LeftBranch${lb.id} with ${b.hyperCube}")
case _ =>
println(s"${iStr}RightBranch${b.id} with ${b.hyperCube}")
}
for(i <- 0 until numOrthants) {
print(s"$iStr Child #${i+1} = ")
b.child(i) match {
case cb: Branch =>
println("Branch:")
dumpTree(cb, indent + 4)
case l: Leaf =>
println(s"Leaf${l.id} ${l.value}")
case empty => println(empty)
}
}
}
def dumpTrees(b: Branch, level: Int): Unit = {
println(s"\n---level $level----")
dumpTree(b, 0)
b.nextOption.foreach(b => dumpTrees(b, level + 1))
}
dumpTrees(headTree, 0)
ps.close()
new String(bs.toByteArray, "UTF-8")
}
/* A debugging facility to inspect an octree only (not the skip list) for internal structural consistency.
*
* @param tree the tree to inspect.
* @param reportOnly if `true`, merely reports anomalies but does not try to resolve them. If `false` attempts to
* fix corrupt entries.
* @return empty if there were no inconsistencies found, otherwise a list of textual descriptions
* of the problems found
*/
private def verifyOctreeConsistency(reportOnly: Boolean)(implicit tx: T): Vec[String] = {
val q = hyperCube
var level = numLevels
var h: Branch = lastTree
var currUnlinkedOcs = Set.empty[H]
var currPoints = Set.empty[Leaf]
var errors = Vec.empty[String]
val repair = !reportOnly
while ({
if (h.hyperCube != q) {
errors :+= s"Root level quad is ${h.hyperCube} while it should be $q in level $level"
}
val nextUnlinkedOcs = currUnlinkedOcs
val nextPoints = currPoints
currUnlinkedOcs = Set.empty
currPoints = Set.empty
def checkChildren(n: Branch, depth: Int): Unit = {
def assertInfo = s"in level $level / depth $depth"
var i = 0
while (i < numOrthants) {
n.child(i) match {
case cb: ChildBranch =>
if (cb.parent != n) {
errors :+= s"Child branch $cb has invalid parent ${cb.parent}, expected: $n $assertInfo"
if (repair) {
((n, cb): @unchecked) match {
case (pl: LeftBranch , cbl: LeftChildBranch ) => cbl.parent = pl
case (pr: RightBranch, cbr: RightChildBranch) => cbr.parent = pr
}
}
}
val nq = n.hyperCube.orthant(i)
val cq = cb.hyperCube
if (!nq.containsH(cq)) {
errors :+= s"Node has invalid hyper-cube ($cq), expected: $nq $assertInfo"
}
if (n.hyperCube.indexOfH(cq) != i) {
errors :+= s"Mismatch between index-of and used orthant ($i), with parent ${n.hyperCube} and $cq"
}
cb.nextOption match {
case Some(next) =>
if (!next.prevOption.contains(cb)) {
errors :+= s"Asymmetric next link $cq $assertInfo"
}
if (next.hyperCube != cq) {
errors :+= s"Next hyper-cube does not match ($cq vs. ${next.hyperCube}) $assertInfo"
}
case None =>
if (nextUnlinkedOcs.contains(cq)) {
errors :+= s"Double missing link for $cq $assertInfo"
}
}
cb.prevOption match {
case Some(prev) =>
if (!prev.nextOption.contains(cb)) {
errors :+= s"Asymmetric prev link $cq $assertInfo"
}
if (prev.hyperCube != cq) {
errors :+= s"Next hyper-cube do not match ($cq vs. ${prev.hyperCube}) $assertInfo"
}
case None => currUnlinkedOcs += cq
}
checkChildren(cb, depth + 1)
case l: Leaf =>
currPoints += l // .value
case _ =>
}
i += 1
}
}
checkChildren(h, 0)
val pointsOnlyInNext = nextPoints.diff(currPoints)
if (pointsOnlyInNext.nonEmpty) {
errors :+= s"Points in next which aren't in current (${pointsOnlyInNext.take(10).map(_.value)}); in level $level"
if (repair && level == 1) {
assert(h.prevOption.isEmpty)
def newNode(b: LeftBranch, qIdx: Int, iq: H): LeftChildBranch = {
val sz = numOrthants // b.children.length
// val ch = tx.newVarArray[LeftChild](sz)
val ch = new Array[Var[T, LeftChild]](sz)
val cid = tx.newId()
var i = 0
while (i < sz) {
ch(i) = cid.newVar[LeftChild](Empty)(tx, LeftChildFormat)
i += 1
}
val parentRef = cid.newVar[LeftBranch](b )(tx, LeftBranchFormat)
val rightRef = cid.newVar[Next ](Empty)(tx, RightOptionReader )
val n = new LeftChildBranchImpl(
octree, cid, parentRef, iq, children = ch, nextRef = rightRef
)
b.updateChild(qIdx, n)
n
}
def insert(b: LeftBranch, point: P, leaf: Leaf): Unit = {
val qIdx = b.hyperCube.indexOfP(point)
b.child(qIdx) match {
case Empty =>
b.updateChild(qIdx, leaf)
case old: LeftNonEmptyChild =>
// define the greatest interesting square for the new node to insert
// in this node at qIdx:
val qn2 = old.union(b.hyperCube.orthant(qIdx), point)
// create the new node (this adds it to the children!)
val n2 = newNode(b, qIdx, qn2)
val oIdx = old.orthantIndexIn(qn2)
n2.updateChild(oIdx, old)
val lIdx = qn2.indexOfP(point)
assert(oIdx != lIdx)
// This is a tricky bit! And a reason
// why should eventually try to do without
// parent pointers at all. Since `old`
// may be a leaf whose parent points
// to a higher level tree, we need to
// check first if the parent is `this`,
// and if so, adjust the parent to point
// to the new intermediate node `ne`!
if (old.parent == this) old.updateParentLeft(n2)
n2.updateChild(lIdx, leaf)
}
}
h match {
case lb: LeftBranch =>
pointsOnlyInNext.foreach { leaf =>
val point = pointView(leaf.value, tx)
@tailrec
def goDown(b: LeftBranch): Unit = {
val idx = b.hyperCube.indexOfP(point)
if (idx < 0) {
errors :+= s"Can't repair because $point is not in $lb"
} else {
b.child(idx) match {
case lb1: LeftBranch => goDown(lb1)
case _ =>
insert(b, point, leaf)
}
}
}
goDown(lb)
}
case _ =>
errors +:= "Can't repair because not in left branch !?"
}
}
}
h = h.prevOption.orNull
level -= 1
h != null
}) ()
errors
}
/** Checks the tree for correctness.
*
* @param reportOnly if `true` simply scans the tree, if `false` it will apply corrections if necessary
* @return empty if no problems were found, otherwise a list of strings describing the problems found
*/
def verifyConsistency(reportOnly: Boolean)(implicit tx: T): Vec[String] = {
var errors = Vec.empty[String]
var repair = !reportOnly
val treeOnlyErrors = verifyOctreeConsistency(reportOnly = reportOnly)
errors ++= treeOnlyErrors
if (treeOnlyErrors.nonEmpty) {
repair = false // stay on the safe side; require that consistency within tree is repaired first
}
// Take skip list as reference. Find if octree levels do not match skip list levels,
// or whether points in the skip list are not found in the octree.
val leafIt = skipList.iterator
while (leafIt.hasNext) {
val leaf = leafIt.next()
val pv = pointView(leaf.value, tx)
@tailrec def findLeaf(b: Branch = lastTree,
lvl: Int = numLevels, doPrint: Boolean = false): Option[(Branch, Int)] = {
if (doPrint) errors :+= s"...checking $b in level $lvl"
val idx = b.hyperCube.indexOfP(pv)
b.child(idx) match {
case `leaf` => Some(b -> lvl)
case cb: Branch if cb.hyperCube.containsP(pv) =>
findLeaf(cb, lvl = lvl, doPrint = doPrint)
case _ =>
b.prevOption match {
case Some(pb: Branch) =>
findLeaf(pb, lvl = lvl - 1, doPrint = doPrint)
case _ => None
}
}
}
findLeaf() match {
case None =>
val foundLevelSkip = HASkipList.debugFindLevel(skipList, leaf)
errors :+= s"Severe problem with $leaf - in skip list (level $foundLevelSkip) but octree does not find it"
if (repair && foundLevelSkip == 1) { // this one is fixable
try {
DetSkipOctree.sanitizing = true
skipList.remove(leaf)
} finally {
DetSkipOctree.sanitizing = false
return errors // hackish!!! skipList iterator possibly invalid, thus abort straight after removal
}
}
case Some((_ /* foundParent */, foundLevel)) =>
val foundLevelSkip = HASkipList.debugFindLevel(skipList, leaf)
if (foundLevel != foundLevelSkip) {
errors :+= s"Severe problem with $leaf - is in skip list level $foundLevelSkip versus octree level $foundLevel"
}
val parent = leaf.parent
val idx = parent.hyperCube.indexOfP(pv)
if (idx < 0) {
errors :+= s"Severe problem with $leaf - reported parent is $parent which doesn't contain the point $pv"
} else {
val saw = parent.child(idx)
if (saw != leaf) {
errors :+= s"$leaf with point $pv reported parent $parent but in orthant $idx we see $saw"
findLeaf(doPrint = true) match {
case Some((b, _)) =>
errors :+= s"...that is the correct parent!"
if (repair) {
leaf.parent = b
}
case None => errors :+= s"...this is bad. can't locate leaf!"
}
}
}
}
}
// Take octree as reference and see if it contains any points not in the skip list.
val inSkipList = skipList.toSet
def checkInTreeLevel(b: Branch, lvl: Int): Unit = {
val sz = numOrthants
var i = 0
while (i < sz) {
b.child(i) match {
case l: Leaf if !inSkipList(l) =>
errors :+= s"Only in octree level $lvl but not skip list: $l"
if (repair) {
println(s"\n============== BEFORE REMOVING $l ==============")
println(debugPrint())
b.demoteLeaf(pointView(l.value, tx), l)
println(s"\n============== AFTER REMOVING $l ==============")
println(debugPrint())
return // XXX dirty - but if there is more than one wrong leaf, continuing may reinstall a lonely parent
}
case cb: Branch =>
checkInTreeLevel(cb, lvl)
case _ =>
}
i += 1
}
}
def checkInTree(t: Branch, lvl: Int): Unit = {
checkInTreeLevel(t, lvl)
t.prevOption.foreach {
p => checkInTree(p, lvl - 1)
}
}
checkInTree(lastTree, numLevels)
errors
}
}
}
sealed trait DetSkipOctree[T <: Exec[T], P, H, A]
extends SkipOctree[T, P, H, A] {
def verifyConsistency(reportOnly: Boolean)(implicit tx: T): Vec[String]
def headTree: DetSkipOctree.LeftTopBranch[T, P, H, A]
def lastTree(implicit tx: T): DetSkipOctree.TopBranch [T, P, H, A]
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy