#import "@preview/ctheorems:1.1.3": *
#import "@preview/cetz:0.4.0"
#import "@preview/lovelace:0.3.0": *
#set par(justify: true)
#set math.equation(numbering: "(1)")
#show: thmrules
#let lemma = thmbox("lemma", "Lemma")
#let def = thmbox("lemma", "Definition")
#let proof = thmproof("proof", "Proof")
#let remark = thmbox("lemma", "Remark")
#let inexact(term) = {
block(inset: 16pt,
block(
inset: 16pt,
stroke: 1pt + gray,
radius: 12pt,
text(
size: 10pt,
[Note for inexact arithmetic: #term]
)
)
)
}
// TODO: figure out how to get rid of the ":" in the caption
#let invariant(term) = {
figure(
block(inset: 16pt,
text(
size: 10pt,
[#term]
)
),
kind: "invariant",
supplement: "invariant",
caption: ""
)
}
#let fl = text([fl])
We'll be talking about sweep line algorithms, where the sweep line is horizontal and increasing in $y$.
Therefore, every line segment "starts" at the coordinate with smaller $y$ and "ends" at the coordinate
with larger $y$ (we'll assume for now that there are no horizontal segments). We'll parametrize each
line segment as a function of $y$. So if $alpha: [y_0 (alpha), y_1 (alpha)] arrow bb(R)$ is a line segment
then $alpha(y)$ is the $x$ coordinate at $y$-coordinate $y in [y_0 (alpha), y_1 (alpha)]$.
We write $theta_alpha$ for the angle that $alpha$ makes with the positive horizontal axis.
Let's have a picture. (In the discussion, it won't matter whether positive $y$ points up or down, but in the
pictures we'll adopt the graphics convention of having positive $y$ point down.)
#figure(
cetz.canvas({
import cetz.draw: *
line((1, 3), (0, 0), name: "a")
line((-4, 3), (4, 3), stroke: (dash: "dotted"))
line((-4, 0), (4, 0), stroke: (dash: "dotted"))
content((-4, 3), $y_0(alpha)$, anchor: "east")
content((-4, 0), $y_1(alpha)$, anchor: "east")
content((0.6, 1.5), $alpha$, anchor: "west")
cetz.angle.angle("a.start", "a.end", (4, 3), label: $theta_alpha$, label-radius: 0.8)
}),
caption: "A segment and its angle"
)
We'll be dealing with inexact arithmetic, so let's define some "error bars" on our line segments.
For an error parameter $epsilon > 0$, offsetting from $alpha$ by $plus.minus epsilon$ in the perpendicular-to-$alpha$ direction
is the same as offsetting by $alpha plus.minus epsilon / (|sin theta_alpha|)$ in the horizontal direction.
Roughly speaking, the "error bars" on $alpha$ amount to adding this horizontal error. But we'll be slightly
more accurate around the corners, by truncating these error bars to the horizontal extents of $alpha$. Precisely, we define
$
alpha_(+,epsilon)(y) = min(alpha(y) + epsilon / (|sin theta_alpha|), max(alpha(y_0), alpha(y_1)) + epsilon) \
alpha_(-,epsilon)(y) = max(alpha(y) - epsilon / (|sin theta_alpha|), min(alpha(y_0), alpha(y_1)) - epsilon) \
$
In pictures, the gray shaded region is the region between $alpha_(-,epsilon)$ and $alpha_(+,epsilon)$:
The point of the scaling by $|sin theta_alpha|$ is to make this an approximation of an $epsilon$-neighborhood (in the
perpendicular direction) of the line segment. The truncation near the corners ensures that if $x$ is between
$alpha_(-,epsilon)(y)$ and $alpha_(+,epsilon)(y)$ then it is within $sqrt(2) epsilon$ of $alpha$.
#figure(
cetz.canvas({
import cetz.draw: *
line((0.5, 3), (-0.3, 0.6), (-0.3, 0), (0.5, 0), (1.3, 2.4), (1.3, 3), close: true, fill: gray, stroke: 0pt)
line((0.5, 3), (-0.3, 0.6), (-0.3, 0), stroke: ( dash: "dashed" ))
line((0.5, 0), (1.3, 2.4), (1.3, 3), stroke: ( dash: "dashed" ))
line((1, 3), (0, 0), name: "a")
line((-4, 3), (4, 3), stroke: (dash: "dotted"))
line((-4, 0), (4, 0), stroke: (dash: "dotted"))
content((-4, 3), $y_0(alpha)$, anchor: "east")
content((-4, 0), $y_1(alpha)$, anchor: "east")
content((0.6, 1.5), $alpha$, anchor: "west")
content((0.8, 0.5), $alpha_(+,epsilon)$, anchor: "west")
content((0.2, 2.4), $alpha_(-,epsilon)$, anchor: "east")
}),
caption: "A segment and its error bars"
)<fig-error-bars>
Define a relation $prec_(y,epsilon)$ on line segments whose domain contains $y$, where
$alpha prec_(y,epsilon) beta$ if $alpha_(+,epsilon)(y) < beta_(-,epsilon)(y)$.
Intuitively, $alpha prec_(y,epsilon) beta$ if $alpha$ is definitely to the left of $beta$
at height $y$: $alpha$ is far enough to the left that their error bars don't overlap.
Clearly, for a given $y$ and $epsilon$ there are three mutually exclusive possibilities: either
$alpha prec_(y,epsilon) beta$ or $beta prec_(y,epsilon) alpha$ or neither of the two holds. We'll denote
this third possibility by $alpha approx_(y,epsilon) beta$, and we write
$alpha prec.tilde_(y,epsilon) beta$ for "$alpha prec_(y,epsilon) beta$ or $alpha approx_(y,epsilon) beta$."
Here are a few basic properties of our definitions:
#lemma[
1. For any $y$ and any $epsilon > 0$, $prec_(y,epsilon)$ is transitive:
if $alpha prec_(y,epsilon) beta$
and $beta prec_(y,epsilon) gamma$ then $alpha prec_(y,epsilon) gamma$. (However, $prec.tilde_(y,epsilon)$ is not transitive.)
2. For any $y$ and any $epsilon > 0$,
if $alpha prec_(y,epsilon) beta$
and $beta prec.tilde_(y,epsilon) gamma$ then $alpha prec.tilde_(y,epsilon) gamma$.
3. For any $y$ and any $epsilon > 0$,
if $alpha prec.tilde_(y,epsilon) beta$
and $beta prec_(y,epsilon) gamma$ then $alpha prec.tilde_(y,epsilon) gamma$.
4. For any $y$, the relation $prec_(y,epsilon)$ is monotone in $epsilon$, in that if $alpha prec_(y,epsilon) beta$ then $alpha prec_(y,eta) beta$ for
any $eta in (0, epsilon)$.
]<lem-basic-order-properties>
Since $epsilon$ for us will usually be fixed, we will often drop it from the notation, and write $alpha_-$ and $alpha_+$ instead
of $alpha_(-,epsilon)$ and $alpha_(+,epsilon)$.
#def[
Suppose $alpha$ and $beta$ are two segments whose domain contains $y$. We say that *$(alpha, beta)$
$epsilon$-cross by $y$* if $y$ belongs to both domains and $alpha succ_(y,epsilon) beta$.
We say that *$(alpha, beta)$ $epsilon$-cross* if they $epsilon$-cross by $min(y_1 (alpha), y_1 (beta))$.
When $epsilon$ is zero, we leave it out: we say that $(alpha, beta)$ cross by $y$ if they $0$-cross by $y$.
]
Note that the definition of $epsilon$-crossing is not symmetric: $(alpha, beta)$ $epsilon$-crossing is
not the same as $(beta, alpha)$ $epsilon$-crossing. We will usually talk about $(alpha, beta)$ $epsilon$-crossing
in the context that $alpha$ starts off to the left of $beta$, and in this context "$(alpha, beta)$ $epsilon$-cross" means
that at some height before the end of $alpha$ and $beta$, $alpha$ has definitely crossed to the right of $beta$.
== Partially ordered sweep-lines
At our first pass, we won't try to detect intersections at all. Instead, we'll produce
a continuum of sweep-lines (constant except at a finite number of points) that *approximately*
track the horizontal order of the segments.
#def[
The ordered collection $(alpha^1, ..., alpha^m)$ of line segments is #emph[$epsilon$-ordered at $y$]
if each $alpha^i$ has $y$ in its domain and $alpha^i prec.tilde_(y,epsilon) alpha^j$ for all $1 <= i < j <= m$.
]
Our algorithm will produce a family of sweep-lines that are $epsilon$-ordered at every $y$
(and also the sweep-lines will be #emph[complete] in the sense that the sweep-line at $y$ will contain all line segments whose
domain contains $y$). This seems weaker than finding all the intersections (for example, because if you
find all intersections you can use them to produce a completely ordered family of sweep-lines), but
in fact they're more-or-less equivalent: given weakly-ordered sweep-lines, you can perturb the lines so
that your weak order becomes the real order of the perturbed lines.
#lemma[
If $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered at $y$ then there exist $x^1 <= ... <= x^m$ such that
$alpha_(-,epsilon)^i (y) <= x^i <= alpha_(+,epsilon)^i (y)$ for all $i$.
]<lem-horizontal-realization>
#proof[
Define $x^i = max_(j <= i) alpha_(-,epsilon)^j (y)$.
Since we've included $j = i$, this clearly satisfies $x^i >= alpha_(-,epsilon)^j (y)$.
For the other inequality, the ordering condition implies that $alpha^j_(-,epsilon) (y) <= alpha^i_(+,epsilon) (y)$
for every $j <= i$. Therefore this inequality still holds for the maximum over these $j$.
]
One consequence of our approximate approach is that we need to do a little extra bookkeeping to maintain
the continuity of the input paths: when one segment exits and its path-neighbor enters, we need to remember
that they are connected because the approximate sweep-line might not keep them together. We'll ignore this
bookkeeping for now;
the goal here is to get into detail about the sweep-line invariants, and prove that we can maintain them.
== The sweep-line invariants
We're going to have a sweep-line that depends on $y$. When we need to emphasize this, we'll use the
cumbersome but explicit notation
$(alpha_y^1, ..., alpha_y^(m_y))$.
In addition to the sweep-line, we'll maintain a queue of "events." The events are ordered by $y$-position
and they are similar to the classical Bentley-Ottmann events so we'll skimp on the details here. There
is an "enter" event, an "exit" event, and an "intersection" event.
Our sweep-line will maintain two invariants:
+ At every $y$, the sweep-line is $epsilon$-ordered at $y$. (We'll call this the "order" invariant.)
+ For every $y$ and every $1 <= i < j <= m_y$, if $alpha_y^i$ and $alpha_y^j$ $epsilon$-cross
then the event queue contains an event between $i$ and $j$,
and at least one of these events occurs before the $epsilon$-crossing height, or at $y$.
(We'll call this the "crossing" invariant.)
(When we say the event queue contains an event between $i$ and $j$, we mean that either there's
an exit event for some $alpha^k$ with $i < k < j$ or there's an intersection event for some $alpha^k$ and $alpha^ell$
with $i <= k < ell <= j$.)
Hopefully the first invariant is already well-motivated, so let's discuss the second.
To naively ensure that we find
all intersections, we could adopt a stronger crossing invariant that requires all pairs of intersecting segments
in the sweep-line to immediately put an intersection event in the queue. This would be inefficient to maintain because
it would require testing all pairs of segments. The main Bentley-Ottmann observation is that it's enough to have intersection
events for all sweep-line-adjacent pairs, because any pair of lines will have to become sweep-line adjacent strictly
before they intersect. We can't rely only on sweep-line adjacency because of the $epsilon$ fudge, but our "crossing"
event essentially encodes the same property. Note that if $j = i+1$ and $y$ is just before the $epsilon$-crossing height and
there are no other segments nearby, then the mysterious $j'$ must be either $i$ or $j$ (because there is nothing in between)
and the mysterious queue event must be the crossing of $i$ and $j$ (it couldn't be an exit event, because we assumed they
cross and they must cross before they exit). In other cases, the crossing event ensures that even if we haven't
recorded the upcoming crossing of $alpha^i$ and $alpha^j$, something will happen in between them that will give us
a chance to test their crossing.
== Sweeping the sweep line
The first observation is that the sweep line invariants are maintained as we increase $y$ continuously up
to the next event:
+ For the order invariant, first note that there is an event whenever $y$ leaves the domain of a segment, so $y$ remains in all domains until the next event.
Moreover, if at any $y' > y$ the ordering breaks, two line segments must have $epsilon$-crossed one another by $y'$.
The third invariant guarantees that there's an event before this happens, so by the contra-positive until an event happens the ordering
constraint is maintained.
+ The crossing invariant is maintained because the set of things to check (i.e. the set of line segments that $epsilon$-cross
one another after $y$) only shrinks as $y$ increases.
== Interaction with adjacent segments
In vanilla Bentley-Ottmann, each segment gets compared to its two sweep-line neighbors; they can either intersect or not intersect.
When numerical errors are taken into account, we may need to compare to
more segments.
#figure(
cetz.canvas({
import cetz.draw: *
let calc_eps(x0, x1) = 0.5 * calc.sqrt(1 + calc.pow(calc.abs(x1 - x0) / 2, 2))
let mkline(x0, x1) = {
let eps = calc_eps(x0, x1)
line((x0, 1), (x1, -1))
line((x0 + eps, 1), (x1 + eps, -1), stroke: ( thickness: 0.2pt, dash: "dashed" ))
line((x0 - eps, 1), (x1 - eps, -1), stroke: ( thickness: 0.2pt, dash: "dashed" ))
}
let a0 = -8
let a1 = 8
let b0 = -2
let b1 = 2
let c0 = 0.5
let c1 = 0.5
content((a0, 1), $alpha^1$, anchor: "south", padding: 5pt)
content((b0, 1), $alpha^2$, anchor: "south", padding: 5pt)
content((c0, 1), $alpha^3$, anchor: "south", padding: 5pt)
mkline(a0, a1)
mkline(b0, b1)
mkline(c0, c1)
let a_eps = calc_eps(a0, a1)
let b_eps = calc_eps(b0, b1)
let c_eps = calc_eps(c0, c1)
let cross12 = 1 - 2*(b0 + b_eps - a0 + a_eps) / (b0 - a0 - (b1 - a1))
let cross13 = 1 - 2*(c0 + c_eps - a0 + a_eps) / (c0 - a0 - (c1 - a1))
line((-10, cross13), (10, cross13), stroke: ( thickness: 0.2pt ))
line((-10, cross12), (10, cross12), stroke: ( thickness: 0.2pt ))
}),
caption: [Three segments all mixed up. The upper horizontal line is the height at which $(alpha^1, alpha^3)$ $epsilon$-cross,
and the lower horizontal line is the height at which $(alpha^1, alpha^2)$ $epsilon$-cross.]
)<mixed-up-segments>
For example, in @mixed-up-segments imagine that $alpha^1$ and $alpha^2$
are present in the sweep line, and we just added $alpha^3$. If we compare
$alpha^3$ to $alpha^2$, we'll see that the $epsilon$-cross and so we'll add an
intersection event between them sometime betfore they $epsilon$-cross. But if
that's all we do then we're in trouble, because by the time we come around to
looking at that intersection event $alpha^1$ may have already $epsilon$-crossed
$alpha^3$, breaking the ordering invariant. Note that $alpha^2$ doesn't
$epsilon$-cross $alpha^3$ at all in @mixed-up-segments, so there's no guarantee of
an intersection event between those two.
Fix $y$ and $epsilon$, and
suppose we have a collection of lines $(alpha^i, ..., alpha^n)$ that satisfy the ordering invariant
at $y$, and suppose also that $(alpha^(i+1), ..., alpha^n)$
satisfy the crossing invariant at $y$.
To make the whole collection satisfy both invariants, we run the following algorithm.
We call this an *intersection scan to the right*.
#figure(
pseudocode-list([
+ *for* $j = i+1$ up to $n$
+ #line-label(<w-def>) let $w^j$ be the smallest height of any event between $i$ and $j$
+ #line-label(<crossing-test>) *if* $(alpha^i, alpha^j_(+,epsilon))$ cross by $w^j$
+ choose $z$ before the crossing, such that $alpha^i approx_(z,epsilon) alpha^j$
+ insert an intersection event for $(alpha^i, alpha^j)$ at $z$
+ #line-label(<protect>) *if* $alpha^i (z) <= alpha^j_-(z)$ for all $z in [y, w^j]$
+ *break*
]),
caption: "Intersection scan to the right"
)
#inexact[
The test at @protect can be seen as an early-stopping optimization, and is not necessary for correctness.
In particular, an approximation with no false positives
is also fine.
]
#lemma[
Suppose that $(alpha^i, ..., alpha^n)$ satisfy the ordering invariant
at $y$, and suppose also that $(alpha^(i+1), ..., alpha^n)$
satisfy the crossing invariant at $y$.
After running an intersection scan to the right,
$(alpha^i, ..., alpha^n)$ satisfy the crossing invariant at $y$.
In fact, $(alpha^i, ..., alpha^n)$ satisfy a slightly stronger crossing invariant at $y$: for every $j > i$,
if $(alpha^i, alpha^j_(+,epsilon))$ cross then the event queue contains an event between $i$ and $j$, and before
the crossing height.
]<lem-intersection-scan>
(The special thing about the stronger crossing invariant is that it asks whether
$(alpha^i, alpha^j_(+,epsilon))$ cross, where the usual crossing invariant asks
whether
$(alpha^i_(-,epsilon), alpha^j_(+,epsilon))$ cross.)
#proof[
It suffices to check the stronger crossing invariant.
So take some $k > i$
that $(alpha^i, alpha^k_(+,epsilon))$ cross. We consider two cases: whether or not the loop terminated
before reaching $k$.
- Suppose the loop terminated at some $j < k$. If $(alpha^i, alpha^k_(+,epsilon))$ cross
after $w^j$, then the definition of $w^j$ ensures that there is an event between $i$ and $j$ (and therefore between
$i$ and $k$) before the crossing. On the other hand, the termination condition ensures that
$alpha^i (z) <= alpha^j_-(z)$ until $w^j$, and so if $(alpha^i, alpha^k_(+,epsilon))$ cross before $w^j$ then
also $(alpha^j, alpha^k)$ cross before that. In this case, the crossing invariant for $(alpha^(i+1), ..., alpha^n)$
implies the existence of an event between $j$ and $k$ (and therefore between $i$ and $k$) before the crossing.
- If the loop included the case $j = k$, we break into two more cases:
- If $(alpha^i, alpha^k_(+,epsilon))$ cross by $w^j$, then the algorithm inserted a witnessing event between $i$ and $j$.
- Otherwise, the definition of $w^j$ ensures that there is an event between $i$ and $j$ before the crossing.
]
#remark[
We can tweak the algorithm a little to try and reduce the number of comparisons.
For a start, if we add an intersection event between $i$ and $j$ then we can
set $w^j$ to $z$, the height of that new event (because $z$ is the new smallest
height of any event between $i$ and $j$).
Therefore, if we broaden the test @crossing-test to check whether $(alpha^i,
alpha^j_-)$ cross, and we choose $z$ to be before the crossing of $(alpha^i,
alpha^j_-)$ then we can skip the test at @protect and terminate straight away.
Thus, there is no loop at all: we only need to consider $j = i + 1$.
One potential issue with this is finding a height $z$ with $alpha^i approx_(z,epsilon) alpha^j$
that's before $(alpha^i, alpha^j_-)$ cross; in other words, we want a height after $(alpha^i_+, alpha^j_-)$
cross but before $(alpha^i, alpha^j_-)$ cross. If $alpha^j$ is almost horizontal, this window might be very
small (or maybe even not representable with our restricted precision).
It may still be worthwhile to have a fast path with a single comparison, and a slow path with a loop.
]
As you might have already guessed, we can also intersection scan to the left; it's pretty much a reflection
of the other one.
#figure(
pseudocode-list([
+ *for* $j = i$ down to $1$
+ let $w^j$ be the smallest height of any event between $j$ and $i$
+ *if* $(alpha^j_(-,epsilon), alpha^i)$ cross by $w^j$
+ choose $z$ before the crossing, such that $alpha^j approx_(z,epsilon) alpha^i$
+ insert an intersection event for $(alpha^j, alpha^i)$ at $z$
+ *if* $alpha^j_+ (z) <= alpha^i (z)$ for all $z in [y, w^j]$
+ *break*
]),
caption: "Intersection scan to the left"
)
We'll skip the proof of the following lemma, because it's basically the same as the last one.
#lemma[
Suppose that $(alpha^1, ..., alpha^i)$ satisfy the ordering invariant
at $y$, and suppose also that $(alpha^1, ..., alpha^(i+1))$
satisfy the crossing invariant at $y$.
After running an intersection scan to the left,
$(alpha^1, ..., alpha^(i+1))$ satisfy a slightly stronger crossing invariant at $y$: for every $j <= i$,
if $(alpha^j_(-,epsilon), alpha^(i+1))$ cross then the event queue contains an event between $j$ and $i+1$, and before
the crossing height.
]<lem-intersection-scan-left>
The purpose of the stronger crossing invariant comes in the next lemma, which deals with scanning in both directions
and allows the insertion of a segment in the middle of a sweep-line.
#lemma[
Suppose that $(alpha^1, ..., alpha^n)$ satisfy the ordering invariant at $y$, and suppose that
$(alpha^1, ..., alpha^i)$ and $(alpha^(i+1), ..., alpha^n)$ satisfy the crossing invariant at $y$.
Let $beta$ be another segment and assume that $(alpha^1, ..., alpha^i, beta, alpha^(i+1), ... alpha^n)$
satisfy the ordering invariant at $y$. After running an intersection scan to the left and an intersection
scan to the right from $beta$,
$(alpha^1, ..., alpha^i, beta, alpha^(i+1), ... alpha^n)$ satisfies the crossing invariant at $y$.
]<lem-intersection-scan-bidirectional>
#proof[
@lem-intersection-scan implies that $(beta, alpha^(i+1), dots, alpha^n)$ satisfies the crossing invariant,
and
@lem-intersection-scan-left implies that $(alpha^1, ..., alpha^i, beta)$ does also. It only remains
to consider interactions between a segment before $beta$ and one after. So fix $j <= i < k$ and suppose
that $(alpha^j, alpha^k)$ $epsilon$-cross. If they $epsilon$-cross after $y_1(beta)$ then $beta$ exit
event witnesses the crossing invariant, so assume that $(alpha^j, alpha^k)$ $epsilon$-cross by $y_1(beta)$.
Then $(alpha^j_-, alpha^k_+)$ cross by $y_1(beta)$, and so one of them crosses $beta$ before $(alpha^j, alpha^k)$ $epsilon$-cross.
If $(alpha^j_-, beta)$ cross then @lem-intersection-scan-left implies that there is an event between $alpha^j$ and $beta$ (and
therefore between $alpha^j$) and $alpha^k$ before the crossing height; otherwise, $(beta, alpha^k_+)$ cross
and so @lem-intersection-scan provides the required event.
]
One last observation in this section, that follows trivially from the algorithm:
#lemma[
If an intersection scan inserts an intersection event for $(alpha, beta)$
then the intersection event's height $z$ satisfies $alpha approx_(z,epsilon) beta$.
]<lem-valid-intersection-events>
== An "enter" event
When inserting a new segment into the current sweep-line, we first choose its sweep-line position using
a binary search on its horizontal coordinate. Let's write $(alpha^1, dots, alpha^m)$ for the sweep-line
before inserting the new segment, and let's call the new segment $beta$. First, we observe that
there is a place to insert the new segment while preserving the ordering invariant.
#lemma[
Suppose $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered at $y$, and let $i$ be the largest $j$ for which
$alpha^j prec_(y,epsilon) beta$. Then
$(alpha^1, ..., alpha^i, beta, alpha^(i+1), ..., alpha^m)$ is $epsilon$-ordered at $y$.
(Here, we can allow the corner cases $i = 0$ and $i = m$ by declaring that
"$alpha^0$" is a vertical line at $x = -infinity$ and "$alpha^(m+1)$" is a vertical line at $x = infinity$).
]<lem-insert-preserving-order>
#proof[
Since $(alpha^1, ..., alpha^m)$ was $epsilon$-ordered at $y$, it suffices to compare beta with all $alpha^j$.
For $i + 1 <= j <= m$, our choice of $i$ immediately implies that $alpha^j succ.tilde_(y,epsilon) beta$.
So consider $1 <= j <= i$. Since $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered, $alpha^j prec.tilde_(y,epsilon) alpha^i$.
Since $alpha^i prec_(y,epsilon) beta$, @lem-basic-order-properties implies that $alpha^j prec.tilde_(y,epsilon) beta$.
]
#inexact[
@lem-insert-preserving-order guarantees the existence of an insertion point, but it doesn't say how to
find it efficiently.
But consider a predicate $f(alpha^j)$ that returns true whenever $alpha^j prec_(y,epsilon) beta$, and
false whenever $alpha^j_+(y) > beta_+(y)$. Running a binary search with this predicate will find some $i$
for which $f(alpha^i)$ is true and $f(alpha^(i+1))$ is false. By scanning to the right from there, we can
find the largest such $i$.
This $i$ is at least as large as the $i$ in @lem-insert-preserving-order, so
to check that it's a valid insertion point we only need to check that it isn't too large. So if $1 <= j <= i$ then
$alpha^j prec.tilde_(y,epsilon) alpha^i$ and so $alpha^j_-(y) <= alpha^i_+(y)$. On the other hand, $f(alpha^i)$
was true and so $alpha^i_+ <= beta_+(y)$. Putting these together shows that $alpha^j prec.tilde_(y,epsilon) beta$.
This algorithm can be implemented with approximate arithmetic, and its running time is logarithmic in the
total length of the sweep line, plus linear in the number of elements that are very close to $beta$.
]
@lem-insert-preserving-order implies that we can insert a new segment while preserving the ordering invariant. By
@lem-intersection-scan-bidirectional, running an intersection scan restores the crossing invariant.
Thus, we can insert a new segment while preserving the sweep-line invariants.
== An "exit" event
When a segments exits the sweep-line, the ordering invariant clearly doesn't break.
Regarding the crossing invariant, it can only break because of $epsilon$-crossing pairs whose
crossing invariant was witnessed by the exit event that was just processed.
To restore the crossing invariant, we need to enqueue some new intersection events.
Let $(alpha^1, ..., alpha^m)$ be the sweep-line after removing the just-exited segment
which, we assume, used to live between $alpha^i$ and $alpha^(i+1)$. Note that both
$(alpha^1, ..., alpha^i)$ and $(alpha^(i+1), ..., alpha^n)$ satisfy the crossing invariant.
By @lem-intersection-scan-bidirectional, running an intersection scan from $alpha^i$ in both
directions restores the crossing invariant. (Technically, this isn't covered by the statement
of @lem-intersection-scan-bidirectional -- which involves a new segment $beta$ -- but the proof
is basically the same.)
== An "intersection" event
Suppose our sweep-line is $(alpha^1, ..., alpha^m)$ and we've just encountered an intersection event for $(alpha^i, alpha^j)$
at height $y$.
If $i > j$ then they've already been swapped in our sweep-line, so we don't need to swap them again. If $i < j$, we need to
swap them. According to @lem-valid-intersection-events, $alpha^j prec.tilde_(y,epsilon) alpha^i$. It seems reasonable, therefore,
to reorder the sweep line by putting $alpha^i$ after $alpha^j$, like
$
alpha^1, ..., alpha^(i-1), alpha^(i+1), ..., alpha^j, alpha^i, alpha^(j+1), ... alpha^n.
$
The issue with this is that $prec.tilde_(y,epsilon)$ is that it's changed the order of pairs other than $alpha^i$ and $alpha^j$:
for every $i < k < j$, the ordering between $alpha^i$ and $alpha^k$ has been swapped. If $prec.tilde$ were transitive, this would
be fine. But it isn't.
To fix this issue, we allow $alpha^i$ to "push" some of the intermediate segments along with it. It's a bit tedious to write (and read)
this precisely, so hopefully this description is enough: for each $k$ between $i$ and $j$, if $alpha^i prec_(y,epsilon) alpha^k$ then
we also move $alpha^k$ after $alpha^j$. Also, we preserve the relative order of all segments moved in this way.
To see that this "pushing" maintains the ordering invariants, note that by definition it preserves the ordering for all comparisons
with $alpha^i$: if some $alpha^ell$ was pushed along with $alpha^i$ then their relative orders haven't changed; and if $alpha^ell$
wasn't pushed then $alpha^ell prec.tilde_(y,epsilon) alpha^i$ and the new order is fine.
What about other pairs? If $alpha^k$ and $alpha^ell$
changed orders then one of them ($alpha^ell$, say) was pushed and the other wasn't. Then $alpha^i prec_(y,epsilon) alpha^ell$
by our choice of the pushed segments, and $alpha^k prec.tilde_(y,epsilon) alpha^i$ by the previous paragraph. Putting these
together, @lem-basic-order-properties implies that $alpha^k prec.tilde_(y,epsilon) alpha^ell$ and so the new order is ok.
#inexact[
We might not be able to tell exactly which segments $alpha^k$ between $alpha^i$ and $alpha^j$ satisfy
$alpha^i prec_(y,epsilon) alpha^k$. Fortunately, we can push a few too many segments while still maintaining
correctness: it suffices to
include all segments $alpha^k succ_(y,epsilon) alpha^i$, while also ensuring that we only include $alpha^k$ for which
$alpha^k_+(y) >= alpha^i_+(y)$.
]
Finally, we need to maintain the crossing invariant. We can probably be more efficient about this, but one
way to be correct is to treat the various swappings as a bunch of deletions from the sweep-line followed by
a bunch of insertions into the sweep-line. We have already shown that running an intersection scan
after each insertion and deletion correctly maintains the crossing invariant, so we can just do that.
= Correctness of the output
We've described how to turn a bunch of segments into a continuum of sweep-lines, but we probably actually wanted
to find the segments' intersection points. Let's define exactly what that means and how to get there.
We'll assume that we start with one or more polylines that may or may not be closed. Equivalently, each segment
comes with an orientation and (optionally) a "following" segment whose starting point is the ending point of the
current segment.
Our goal is to subdivide all segments at all intersection points, meaning that for each segment we'll produce
a polyline; we'll call the segments in each such polyline "output segments," to distinguish them from
the original segments. We require three properties:
- "approximation": for each input segment, every vertex in its output polyline must be within $epsilon$ of the original segment.
Also the first and last vertices in the output polyline must be within $epsilon$ of the start and end of the segment, respectively.
- "continuity": if a segment has a following segment, then the last vertex in its output polyline must coincide
with the first vertex of the following segment's output polyline.
- "completeness": output segments intersect only at their endpoints, unless they are identical.
In the solution we describe, we'll achieve both continuity and the second half
of approximation by insisting that output polylines have exactly the same start-
and endpoints as their input segment. This stronger condition makes the algorithm
simpler, but it also subjectively can hurt output "quality" by requiring more segments.
== The "dense" version
Suppose we've already found a weakly-ordered sweep line at every height. We say
a height is "important" if the sweep line changed at that height, either because
some segments entered or exited, or because the order changed. The naive version
of our subdivision algorithm will subdivide every segment at every important
height. Note that because the sweep line changes at a discrete set of heights,
at every important height $y$ we actually have two weakly-ordered sweep line:
the "old" one from infinitesmally before $y$ height and the "new" one from $y$
onwards. Because everything is continuous and the weak ordering conditions are
closed, both the old and new sweep lines satisfy the weak ordering condi0.2tions
at $y$.
The algorithm goes like this: at every important height we use
@lem-horizontal-realization to assign horizontal positions to segments in the
old sweep line; we subdivide each segment at the resulting coordinate. Then we
use @lem-horizontal-realization again to assign horizontal positions to segments
in the new sweep line. If a segment gets assigned two different horizontal positions
by the old and new sweep lines, we add a horizontal segment between them. Also,
if a segment starts or ends at the current height and its assigned position is not the
same as its starting or ending position, we fix that up with another horizontal segment.
Finally, we subdivide all horizontal segments as necessary to ensure that they only
intersect at endpoints.
I think it's pretty clear that this algorithm is correct. The approximation property
holds (with $sqrt 2 epsilon$ instead of $epsilon$, but that doesn't matter) because
@lem-horizontal-realization always puts a new point within its segment's upper and lower bounds,
and the bounds are chosen to be within $sqrt 2 epsilon$ of the segment; see @fig-error-bars.
As mentioned above, the "continuity" property and the other half of "approximation" follow
because we insisted on including the segment endpoints exactly.
For the "completeness" property, let's focus on the bit between the important heights
(because everything gets subdivided at the important heights, and so maintaining the
completeness property there is just a matter of splitting into enough horizontal segments).
Between two important heights, the sweep line is constant, so the new sweep line
at the old height is the same as the old sweep line at the new height. Thus, all output
subsegments have the same order when leaving the old sweep line as they do when entering
the new sweep line. Therefore any two of them that intersect between the two important
heights must be identical.
== The "sparse" version
We'd like to avoid subdividing every segment at every important height. This basically
involves detecting which segments need to be divided and ignoring the rest. It's implemented
but not yet written up (TODO).
== A more detailed insertion algorithm
It took me a few tries to get the approximate insertion algorithm working, so
I figured it was worth writing up a few more details. First of all, a slight
tweak of @lem-insert-preserving-order shows that there is a position $i$ such
that $alpha^j_- (y) <= beta(y)$ for all $j <= i$ and $alpha^j_+ (y) >= beta(y)$
for all $j > i$. We will look for this $i$, but with some slight slack in our
arithmetic. The slack will be small enough to guarantee we find a position that
satisfies the weaker properties of @lem-insert-preserving-order.
First, we consider a predicate $p(j)$ that returns `false` whenever $alpha^j_+
(y) > beta(y)$. We put no conditions on the values of $j$ for which $p(j)$
returns `true`; a tighter predicate will give a more efficient algorithm, but
doesn't affect correctness. Let $i_-$ be such that $p(i_-) = #true$ and $p(i_-
+ 1) = #false$ (where we allow $i_- = -1$, and handle the boundary cases by
declaring that $p(-1) = #true$ and $p(m+1) = #false)$. Note that $i_-$ can be
found with a binary search.
The definition of $p$ ensures that $alpha^(i_-)_+(y) <= beta(y)$. Under the
assumption that $(alpha^1, ..., alpha^m)$ is weakly ordered, it follows that
for every $j <= i_-$, $alpha^j_-(y) <= beta(y)$. That is, the $i$ that we are
looking for is larger than or equal to $i_-$.
The next step is a linear scan up from $i_-$. Let $q(j)$ be a predicate
that returns `true` whenever $alpha^j_- (y) < beta^j (y)$ and `false` whenever
$alpha^j_- (y) > beta^j_+ (y)$. Take $i >= i_-$ to be the index just before $q$
first returns false; we will insert $beta$ after $alpha^i$.
Let's check that this insertion position is valid: for every $j <= i$ we have
either $j <= i_-$ or $i_- < j <= i$. In the first case, we already observed
that $alpha^j_-(y) <= beta(y)$; in the second case, $q(j)$ returned `true` and
so $alpha^j_-(y) <= beta^j^+ (y)$. In either case, $alpha^j prec.tilde beta$.
On the other hand, $q(i+i)$ being `false` implies that $alpha^(i+1)_- (y) >=
beta(y)$. This implies that for all $j >= i+1$, $alpha^j_+ (y) >= beta(y)$ and
so $alpha^j succ.tilde_y beta$.
= Accuracy analysis
We're going to implement some algorithms in floating point. Suppose $p$ is the number of mantissa bits
of the floating-point type, and let $fl(t)$ denote the rounding function. Let $epsilon_0 = 2^(-1-p)$;
this is the relative error of the rounding function for all normal numbers: for a real number $x$,
as long as $fl(x)$ is finite and non-subnormal,
$
fl(x) = x plus.minus epsilon_0 |x|.
$
Now, if the result of floating point addition or subtraction is subnormal then it is exact; therefore,
for any floating-point $x$ and $y$, if $x - y$ and $x + y$ don't overflow then after rounding they
have relative error at most $epsilon_0$.
#lemma[
If $y_0 <= y <= y_1$ are floating-point numbers and $epsilon_0 <= 1/2$ then computing $(y - y_0) / (y_1 - y_0)$
the naive way gives an absolute error of
at most $3 epsilon_0 + 4 epsilon_0^2$.
]<lem-ratio-error>
#proof[
The assumption $y_0 <= y <= y_1$ ensures that $y - y_0$ and $y_1 - y_0$ do not overflow. Therefore
$
fl(y - y_0) &= (y - y_0) (1 plus.minus epsilon_0) \
fl(y + y_0) &= (y + y_0) (1 plus.minus epsilon_0)
$
and so
$
fl(y - y_0) /
fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) times
(1 plus.minus epsilon_0) / (1 plus.minus epsilon_0).
$
For the upper bound,
$
(1 + epsilon_0) / (1 - epsilon_0) = (1 + epsilon_0) (1 + epsilon_0 + epsilon_0^2 / (1 - epsilon_0))
= 1 + 2 epsilon_0 + 2 / (1 - epsilon_0) epsilon_0^2 <= 1 + 2 epsilon_0 + 4 epsilon_0^2.
$
For the lower bound,
$
(1 - epsilon_0) / (1 + epsilon_0) = 1 - epsilon_0 - (epsilon_0 (1-epsilon_0)) / (1+epsilon_0) >= 1 - 2 epsilon_0.
$
Putting these together,
$
fl(y - y_0) /
fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) (1 plus.minus (2 epsilon_0 + 4 epsilon_0^2)).
$
Since $0 <= (y - y_0) / (y_1 - y_0) <= 1$, this relative error is also an absolute error:
$
fl(y - y_0) /
fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) plus.minus (2 epsilon_0 + 4 epsilon_0^2).
$
The final rounding step adds another absolute error of at most $epsilon_0$.
]
#lemma[
Suppose that $x_0$, $x_1$, and $M$ are floating-point numbers with $|x_0|, |x_1| <= M/2$.
Let $0 <= t <= 1$ be a real number, and let $0 <= hat(t) <= 1$ be a floating point number
with $|t - hat(t)| <= epsilon$. Then computing $x_0 + t (x_1 - x_0)$ the obvious way gives an
absolute error of at most $(3 epsilon_0 + epsilon) M$.
]<lem-convex-combination-error>
#proof[
Let's first consider the error introduced by approximating $t (x_1 - x_0)$ with $hat(t) fl(x_1 - x_0)$:
$
hat(t) fl(x_1 - x_0) = hat(t) (x_1 - x_0)(1 plus.minus epsilon_0) = hat(t) (x_1 - x_0) plus.minus epsilon_0 M,
$
where the second approximation follows because $0 <= hat(t) <= 1$ and $|x_1 - x_0| <= M$.
Since $hat(t) = t plus.minus epsilon$, we continue with
$
hat(t) fl(x_1 - x_0)
= (t plus.minus epsilon) (x_1 - x_0) plus.minus epsilon_0 M,
= t (x_1 - x_0) plus.minus (epsilon_0 + epsilon) M.
$
Rounding the left hand side introduces a relative error of at most $epsilon_0$, which is an absolute error of at most $M epsilon_0$. Therefore,
$
fl(hat(t) fl(x_1 - x_0))
= t (x_1 - x_0) plus.minus (2 epsilon_0 + epsilon) M.
$
Finally, we subtract both sides from $x_0$ and round one more time. This last rounding introduces a relative error of at most $epsilon_0$,
which again leads to an absolute error of at most $2 epsilon_0 M$ (and I think maybe the 2 is unnecessary, but let's not worry).
]
Next, let's analyze the computation of the $y$ intersection point.
#lemma[
Suppose we have two segments $alpha$ and $beta$. Set $y_0 = y_0(alpha) or y_0(beta)$ and $y_1 = y_1(alpha) and y_1(beta)$
(these can be computed exactly). Suppose that we can compute $alpha(y)$ and $beta(y)$ to additive accuracy $epsilon$,
and assume that $beta(y_0) - alpha(y_0) >= 0$ and
$
alpha(y_1) - beta(y_1) > 16/3 (epsilon + 2 epsilon_0 M).
$
Define $t = (alpha(y_1) - beta(y_1)) / (alpha(y_1) - beta(y_1) + beta(y_0) - alpha(y_0)$ and set $y = y_0 + t(y_1 - y_0)$. The
natural way to compute $y$ produces an approximation $hat(y)$ satisfying
$
|alpha(hat(y)) - beta(hat(y))| <= 4(epsilon + 2 epsilon_0 M) (1 + max(1/m_alpha, 1/m_beta)),
$
where $m_alpha$ and $m_beta$ are the slopes of $alpha$ and $beta$.
]<lem-intersection-height-error>
Basically, this says we can find a good approximate crossing height as long as the two segments cross by at least $16/3 (epsilon + 2 epsilon_0 M)$.
#proof[
First, note that at least one of $alpha(y_0)$ or $beta(y_0)$ is calculated exactly; similarly for $alpha(y_1)$ and $beta(y_1)$.
Therefore the absolute error in calculating $alpha(y_1) - beta(y_1)$ is at most $epsilon + epsilon_0 M$ (where the $epsilon_0 M$ comes
from rounding after the summation) and the absolute error in calculating
$(alpha(y_1) - beta(y_1)) + (beta(y_0) - alpha(y_0))$ is at most $2 epsilon + 3 epsilon_0 M$.
These errors aren't independent, though, since both terms involve a computation of $alpha(y_1) - beta(y_1)$.
Specifically, let $c = alpha(y_1) - beta(y_1)$ and let $d = (alpha(y_1) - beta(y_1)) + (beta(y_0) - alpha(y_0))$; suppose
our calculation of $c$ has error $epsilon_c$ and our calculation of $d$ has error $epsilon_d$. Then
$
|epsilon_c - epsilon_d| <= epsilon + 2 epsilon_0 M,
$ <eq-error-cancellation>
because this is the *additional* error introduced into $d$ by computing $beta(y_0) - alpha(y_0)$ and adding it to the other term.
Now we consider the quotient
$
(c + epsilon_c) / (d + epsilon_d)
&= c / (d (1 + epsilon_d/d)) + epsilon_c / (d (1 + epsilon_d/d)) \
&= c / d (1 - epsilon_d/d + (epsilon_d/d)^2 / (1 + epsilon_d/d)) + epsilon_c / d (1 - (epsilon_d/d)/(1 + epsilon_d / d)).
$
We've assumed that $1 + epsilon_d/d >= 1/2$, and so the error
$
abs((c + epsilon_c) / (d + epsilon_d) - c/d)
$
is at most
$
& abs(c / d ( - epsilon_d/d + (epsilon_d/d)^2 / (1 + epsilon_d/d)) + epsilon_c / d (1 - (epsilon_d/d)/(1 + epsilon_d / d))) \
& <= abs(- c / d epsilon_d/d + epsilon_c / d ) + 2 c/d (epsilon_d/d)^2 + 2 abs(epsilon_c epsilon_d) / d^2 \
& <= c/d abs(epsilon_c/d - epsilon_d/d) + (1 - c/d) abs(epsilon_c/d) + 2 (epsilon_d/d)^2 + 2 abs(epsilon_c epsilon_d) / d^2,
$
where in the last line we recall that $0 <= c/d <= 1$. Now, recall from @eq-error-cancellation and the paragraph before it
that both $abs(epsilon_c)$ and $abs(epsilon_c - epsilon_d)$ are bounded by $epsilon + 2 epsilon_0 M$, and that
$abs(epsilon_d)$ is bounded by $2 epsilon + 3 epsilon_0 M$. Therefore, our computation of $c/d$ (which, recall, we called $t$ above) has absolute error of at most
$
(epsilon + 2 epsilon_0 M)/d + 4 (2 epsilon + 3 epsilon_0 M)^2 / d^2.
$
Let's call this expression $epsilon_t$.
By @lem-convex-combination-error, the computation of $y = y_0 + t (y_1 - y_0)$ has absolute error at most $(2 epsilon_0 + epsilon_t) |y_1 - y_0| + epsilon_0 M$.
Let's see what this means for the horizontal accuracy of $alpha(y)$ and $beta(y)$. We can write
$
alpha(y) = alpha(y_0) + (y - y_0) / (y_1 - y_0) (alpha(y_1) - alpha(y_0)),
$
and similarly for $beta(y)$. Combining these and recalling our definitions of $c$ and $d$,
$
alpha(y) - beta(y) = -c + (y - y_0) / (y_1 - y_0) d.
$
For the true value of $y$, this is zero of course. With our error bound of $(2 epsilon_0 + epsilon_t) |y_1 - y_0| + epsilon_0 M$ on our
approximation of $y$ (let's call it $hat(y)$),
$
abs(alpha(hat(y)) - beta(hat(y))) <= (2 epsilon_0 + epsilon_t) d + (epsilon_0 M) / (y_1 - y_0) d
<= epsilon_t d + 3 epsilon_0 (M d) / (y_1 - y_0).
$
If we assume that $16 (epsilon + 2 epsilon_0 M) / d <= 3$ then $epsilon_t d <= 4 epsilon + 8 epsilon_0 M$.
Since $d/(y_1 - y_1)$ is the difference of the inverse-slopes of $alpha$ and $beta$, it's bounded by twice
the maximum of these inverse-slopes.
]
Putting all this together (and handling the "chamfers" yet):
#lemma[
Assume all line segments are inside the square $[-M/2, M/2]^2$,
and take $delta = 64 epsilon_0 M$. If $epsilon_0 <= 1/4$ then
- We can compute $x$ coordinates of line segments to additive accuracy $delta/8$.
- If $alpha$ $(3 delta)/4$-crosses $beta$, we can compute a crossing height $hat(y)$ such
that
$
|alpha(hat(y)) - beta(hat(y))| <= (9 delta) /16 (1 + max(1/m_alpha, 1/m_beta)).
$
]
#proof[
We compute $x$ coordinates by first computing $t = (y - y_0) / (y_1 - y_0)$ (with error at most $3 epsilon_0 + 4 epsilon_0^2$ by
@lem-ratio-error). Then we apply @lem-convex-combination-error with $epsilon = 3 epsilon_0 + 4 epsilon_0^2$ to see that we
can compute the horizontal coordinate with error at most $(6 epsilon_0 + 4 epsilon_0^2)M$. If $epsilon_0 <= 1/4$ as we assumed,
this is at most $7 epsilon_0 M <= delta/8$. This proves the first claim.
For the second claim, we apply @lem-intersection-height-error with $epsilon = (6 epsilon_0 + 4 epsilon_0^2) M$.
With this $epsilon$, $epsilon + 2 epsilon_0 M) <= 9 epsilon_0 M$ and so $16/3 (epsilon + 2 epsilon_0 M) <= 48 epsilon_0 M = (3 delta)/4$.
Thus, if $alpha$ $(3 delta)/4$-crosses $beta$ then the assumptions of @lem-intersection-height-error are satisfied
(and in the conclusion, $4 (epsilon + 2 epsilon_0) M <= 36 epsilon_0 M = (9 delta) /16 $.
]
= Curves etc.
Let's move on from lines and talk about curves. Each curve is of the form $alpha: [0, 1] -> bb(R)^2$, which we'll sometimes
in components as $alpha_0: [0, 1] -> bb(R)$ and $alpha_1: [0, 1] -> bb(R)$. We'll assume that each $alpha_0$ is a strictly
increasing function. Write $S = [-1, 1] times [-1, 1]$ for the unit square in $bb(R)^2$. For a curve $alpha$ and a set $B subset bb(R)^2$, we write
$alpha + B$ for the set
$
alpha + B = {(x, y) in bb(R)^2 : exists t in [0, 1] "with" (x, y) - alpha(t) in B}
$
In other words, $alpha + B$ is the Minkowski sum between $B$ and the locus of $alpha$. For any set $B subset bb(R)^2$,
let
$
B_y = {x in bb(R) : (x, y) in B}.
$
For example, here is a picture of $(alpha + epsilon B)_y$: (TODO)
== The curve orders
When moving to curves, it seems useful to work with more abstract orders. For fixed $epsilon$ and $y$, we'll define
three of them: the "weak pre-order," the "strong order," and the "weak order."
#let weakprelt = sym.lt.tri
#let weakpreleq = sym.lt.eq.tri
#let notweakprelt = sym.lt.tri.not
#let weakpreeq = sym.eq.dots
#let weaklt = sym.lt
#let stronglt = sym.lt.double
#let weaklt = sym.lt
#let weakeq = sym.tilde
We begin with the weak pre-order.
#def[
For each $epsilon >= 0$ and $y in bb(R)$, let $weakprelt_(y,epsilon)$ be a relation satisfying:
- if $alpha weakprelt_(y,epsilon) beta$ then $(alpha + epsilon/2 S)_y <= (beta + epsilon/2 S)_y$,
- if $(alpha + epsilon S)_y <= (beta + epsilon S)_y$ then $alpha weakprelt_(y,epsilon) beta$,
- for any $k >= 1$, there are no $alpha_1, ..., alpha_k$ with $alpha_1 weakprelt_(y,epsilon) alpha_2 weakprelt_(y,epsilon) dots.h.c weakprelt_(y,epsilon) alpha_k weakprelt_(y,epsilon) alpha_1$.
We call this last condition the "no cycles" condition.
]
Note that we *do not* assume transitivity: $alpha weakprelt_(y,epsilon) beta
weakprelt_(y,epsilon) gamma$ does not imply that $alpha weakprelt_(y,epsilon)
gamma$. We also do not assume anti-symmetry: it's possible to have neither
$alpha weakprelt_(y,epsilon) beta$ nor $beta weakprelt_(y,epsilon) alpha$. In
fact, if $(alpha + epsilon/2)_y$ and $(beta + epsilon/2 S)_y$ overlap, it is
guaranteed that neither of these orderings hold.
#def[
If neither $alpha weakprelt_(y,epsilon) beta$ nor $beta weakprelt_(y,epsilon) alpha$, we say that
$
alpha weakpreeq_(y,epsilon) beta.
$
If either $alpha weakprelt_(y,epsilon) beta$ or
$alpha weakpreeq_(y,epsilon) beta$, we write
$alpha weakpreleq_(y,epsilon) beta$. Equivalently,
$alpha weakpreleq_(y,epsilon) beta$ is the negation of
$beta weakprelt_(y,epsilon) alpha$.
]
TODO: draw a picture.
The strong order is similar to the weak pre-order, but with bigger epsilons.
#def[
For each $epsilon >= 0$ and $y in bb(R)$, let $stronglt_(y,epsilon)$ be a relation satisfying:
- if $alpha stronglt_(y,epsilon) beta$ then $(alpha + 2 epsilon S)_y <= (beta + 2 epsilon S)_y$,
- if $(alpha + 3 epsilon S)_y <= (beta + 3 epsilon S)_y$ then $alpha stronglt_(y,epsilon) beta$,
- for any $k >= 1$, there are no $alpha_1, ..., alpha_k$ with $alpha_1 stronglt_(y,epsilon) alpha_2 stronglt_(y,epsilon) dots.h.c stronglt_(y,epsilon) alpha_k stronglt_(y,epsilon) alpha_1$.
]
The definition of the weak order is a little strange at first. The initial idea
is that we want to ensure that our sweep-line is ordered by the weak pre-order:
we are going to try to ensure that if our sweep-line at $y$ is $alpha^1, ..., alpha^n$
then for all $i < j$, $alpha^i weakpreleq_y alpha^j$. Or in other words, we want to
ensure that whenever $alpha^j$ is definitely to the right of $alpha^i$, it comes after $alpha^i$
in the sweep-line.
For sweep-line algorithms to be efficient, we need to avoid too many segment comparisons.
In the classical Bentley-Ottmann algorithm with exact arithmetic this is easy: whenever
we look for intersections against segment $alpha^i$, we compare it with its sweep-line
neighbors to the left and right (so, $alpha^(i-1)$ and $alpha^(i+1)$ in our notation).
Inexact arithmetic will make this a little trickier; we will typically need to look further
than just the immediate neighbors of $alpha^i$. However, we will need *some* mechanism
for early stopping, some way to say that after we have compared $alpha^i$ to, say, $alpha^(i-k)$
through $alpha^(i-1)$, we can be sure that no other $alpha^j$ (for $j < i - k$) needs
to be checked. The intuitive idea is that if $alpha^(i-k)$ is very far to the left of $alpha^i$
then we can stop, because then $alpha^(i-k)$ will "shield" $alpha^i$ from segments further
left, in much the same way that its immediate neighbors "shield" $alpha^i$ in the classical
sweep-line algorithm.
We would like to use the strong order $stronglt_y$ to check whether $alpha^(i-k)$ is "very far
to the left" of $alpha^i$, but we run into trouble with examples like this:
TODO: picture.
Here, we clearly see that $alpha$ is to the left of $beta$; in situations like this, if we're
examining $beta$ for potential intersections and we see $alpha$ to its left, we'd really like
to stop looking further left. But $gamma$ is a problem: it's almost horizontal, and the
sweep-line at $y$ is at an ambiguous height, being more than $epsilon / 2$ but less than $epsilon$ below $gamma$.
According to the definition of our weak pre-order,
$alpha weakprelt_y gamma$ and $alpha weakpreeq_y gamma$ are
both allowed, as are both
$beta weakprelt_y gamma$ and $beta weakpreeq_y gamma$. So imagine that $alpha weakpreeq_y gamma$ and $beta weakprelt_y gamma$.
Before adding $beta$, the sweep-line could have been $(gamma, alpha)$. Then we could have tried to add $beta$ after $alpha$
(because clearly $beta$ is to the right of $alpha$), leading to the sweep-line $(gamma, alpha, beta)$.
Scanning for any intersections with the newly-added $beta$ won't find any because we'll look to the left, see
$alpha$, and stop. The result of all this is the illegal sweep-line $(gamma, alpha, beta)$, in which
$beta weakprelt_y gamma$ but $gamma$ comes first in the sweep-line.
Basically, it seems hard to have both early stopping (which we want for efficiency) and the
ordering invariants that we need for correctness. Our "fix" is to change the definition of
our ordering (while still keeping it tight enough for the correctness guarantees that we need).
It looks almost like cheating, because we're going to redefine it precisely so that our early
stopping will work.
#def[
Let $cal(S)_y$ be the set of all segments whose vertical range contains $y$. Say that $alpha weaklt_y beta$
if $alpha weakprelt_y beta$ and there do not exist $alpha', beta' in cal(S)_y$ with $beta weakpreleq_y beta' stronglt alpha' weakpreleq_y alpha$.
]<def-weak-order>
In other words, we define $weaklt$ to be the same as $weakprelt$, but if there's ever a situation where the (not-yet-specified)
early stopping algorithm could miss a wrongly ordered pair $alpha weakprelt beta$ by witnessing a gap between $beta'$ and $alpha'$,
then we weaken the $weakprelt$ order and declare instead that $alpha weakeq beta$ instead of $alpha weaklt beta$.
To see how this solves our tricky example above, note that when instantiating @def-weak-order with $alpha = beta$, $beta = gamma$,
$beta' = alpha$, and $alpha' = beta$, we see that there *do* exist $alpha', beta' in cal(S)_y$ with the required properties,
and so in fact $beta$ and $gamma$ are not strictly ordered in the weak order $weaklt$ even though they were strictly ordered
in the weak pre-order $weakprelt$. In particular, the sweep-line $(gamma, alpha, beta)$ is legal with respect to the weak order $weaklt$.
You might object to @def-weak-order because of how hilariously inefficient it will be to implement. The
clever part, though, is that we won't need to implement it. Our algorithm will make its decisions based
only on the weak pre-order $weakprelt$ and the strong order $stronglt$. The inefficient weak order $weaklt$
will only be used in the correctness proof.