moonlight-planar-1.2.0.0: src-dcel/Moonlight/Planar/Curve/Measure.hs
-- | Certified Euclidean arc length of canonical curves, in the coordinates of
-- the submitted curve. A measured trail is an immutable observation of its
-- source, never an editable shadow curve: it retains the source subpath, its
-- accepted spans, and their cumulative rational length enclosures. The curve's
-- arc length is enclosed, not represented; no radical expression of the arc
-- itself is fabricated.
--
-- Enclosure kernel, one for all four shapes. For a span with Euclidean control
-- polygon @P0..Pn@, the chord @|Pn - P0|@ is a lower bound and the polygon
-- length an upper bound of its arc length. For polynomial shapes this is the
-- classical Bernstein argument. For a positive-weight rational quadratic with
-- controls @P0,P1,P2@ and weights @(1,a,b)@, one homogeneous de Casteljau step
-- at any @t@ in @[0,1]@ yields @p@ on segment @P0P1@, @q@ on @P1P2@ and @m@ on
-- @pq@: each is a convex combination with positive homogeneous weights, so its
-- projection lies on the segment between the projected endpoints. By the
-- triangle inequality the refined chain @P0,p,m,q,P2@ is no longer than
-- @P0,P1,P2@, and its chords are no shorter than @P0P2@. The children, after
-- division by a common positive weight, are again positive-weight rational
-- quadratics, so the argument repeats without any square root. Under repeated
-- subdivision the inscribed chord polygons converge to the arc, which bounds
-- it from below, and the control polygons converge from above; lower
-- semicontinuity of length makes the limit of the nonincreasing polygon
-- lengths an upper bound, and endpoint distance is the lower bound at every
-- stage. Validity is independent of convergence rate: acceptance is decided by
-- the actual outward gap, and exhaustion refuses.
--
-- Error allocation is global and split in two halves. Let @U0@ be the outward
-- upper bound of the whole source's control-polygon length and @N@ the number
-- of source steps. A span @[a,b]@ of any step is accepted when its outward gap
-- @upper - lower@ is at most
-- @(tolerance / 2) * (lower / U0 + (b - a) / N)@. Summed over the accepted
-- spans, the relative half is at most @(tolerance / 2) * L / U0 <=
-- tolerance / 2@, where @L <= U0@ is the arc length, since the chords' lower
-- bounds sum to at most @L@; the parameter half is exactly
-- @(tolerance / 2) * N / N = tolerance / 2@, since the accepted spans of each
-- step partition its parameter interval. The whole gap is therefore at most
-- the tolerance. The outward dyadic rounding of every enclosure lies inside
-- the compared gap, so it is charged to the same allowance. The relative half
-- alone never accepts a span straddling a turning point, whose chord stays a
-- fixed fraction of its polygon however small it becomes; the parameter half
-- shrinks only linearly with the span while such a gap shrinks faster. If
-- @U0@ is zero the relative half is zero and every span is stationary with
-- zero gap; a source with no steps has no spans and never divides by @N@.
-- The partition, and so the cost in spans, depends on the parameterization;
-- the Euclidean length enclosure and its global tolerance do not.
--
-- Arithmetic is budgeted before it is spent. Every source step is admitted
-- against the bit budget, with its located start, controls and rational
-- weights, before any length is observed; every generated child is admitted
-- with its parameters, and every observed enclosure endpoint, prefix and
-- total included, is admitted before it is compared. A query's request, and
-- the residual and bracket it returns, are admitted the same way. The two
-- budgets bound different things: the radical precision bounds scratch work
-- inside one root, while the bit budget bounds the source and every retained
-- exact value. A high precision is never refused for its value; an
-- irrational length at high precision is refused because its retained
-- enclosure endpoints exceed the budget, while an exact root stays small at
-- any precision.
module Moonlight.Planar.Curve.Measure
( Distance
, DistanceError (..)
, distance
, distanceValue
, MeasurePolicy
, measurePolicy
, measureTolerance
, measurePrecision
, measureBudget
, SubdivisionBudget
, SubdivisionBudgetError (..)
, subdivisionBudget
, budgetDepth
, budgetLeaves
, budgetBits
, BudgetObligation (..)
, RadicalPrecision
, RadicalPrecisionError (..)
, radicalPrecision
, radicalPrecisionBits
, LengthEnclosure
, lengthEnclosureLower
, lengthEnclosureUpper
, lengthEnclosureWidth
, MeasureObligation (..)
, MeasureError (..)
, MeasuredTrail
, measureSubpath
, measuredSource
, measuredSpans
, lengthBounds
, MeasuredSpan
, measuredSpanStep
, measuredSpanFrom
, measuredSpanTo
, measuredSpanStart
, measuredSpanPiece
, measuredSpanLength
, measuredSpanPrefix
, JoinSide (..)
, TrailSite
, siteSource
, siteStepIndex
, siteParameter
, sitePoint
, siteJoinSide
, siteJet
, ArcSample
, sampleSite
, sampleParameterFrom
, sampleParameterTo
, sampleResidual
, pointAtLength
, pointAtFraction
) where
import Control.Applicative ((<|>))
import Control.DeepSeq (NFData (..))
import Control.Monad (foldM, when)
import Data.Foldable (fold, toList, traverse_)
import Data.Sequence (Seq, ViewL (..), ViewR (..))
import qualified Data.Sequence as Seq
import Moonlight.Planar.Curve (CurveStep, Subpath, curveStepEnd, splitStep, stepControlPoints)
import Moonlight.Planar.Exact
( ExactPoint, ExactRational, ExactVector (..), PositiveExact, UnitInterval
, divideByPositive, exactHalf, exactPointBitWidth, exactRationalBitWidth, positiveExact
, positiveExactValue, translateExactPoint, unitHalf, unitIntervalValue, unitOne, unitZero )
import Moonlight.Planar.Internal.CurveBudget
( BudgetObligation (..), SubdivisionBudget, SubdivisionBudgetError (..), budgetBits, budgetDepth
, budgetLeaves, subdivisionBudget )
import Moonlight.Planar.Internal.CurveSource
( JoinSide (..), SourceStep, TrailSite, siteJet, siteJoinSide, siteParameter, sitePoint
, siteSource, siteStepIndex, sourceStepCurve, sourceStepIndex, sourceStepStart
, sourceSteps, spanBits, trailSite )
import Moonlight.Planar.Internal.ExactRational (unitMidpoint)
import Moonlight.Planar.Internal.Length
( LengthEnclosure, RadicalPrecision, RadicalPrecisionError (..), enclosureBetween
, euclideanLengthEnclosure, lengthEnclosureLower, lengthEnclosureUpper
, lengthEnclosureWidth, radicalPrecision, radicalPrecisionBits )
-- | A nonnegative distance, zero included: along a trail, or between curves.
newtype Distance = Distance ExactRational
deriving stock (Eq, Ord, Show)
instance NFData Distance where
rnf (Distance value) = rnf value
newtype DistanceError = NegativeDistance ExactRational
deriving stock (Eq, Show)
distance :: ExactRational -> Either DistanceError Distance
distance value
| value >= 0 = Right (Distance value)
| otherwise = Left (NegativeDistance value)
distanceValue :: Distance -> ExactRational
distanceValue (Distance value) = value
-- | Tolerance, radical precision, and the subdivision budget: depth, leaves,
-- and the largest admitted bit width of any span coordinate, weight,
-- parameter or observed enclosure endpoint. Each part arrives admitted.
data MeasurePolicy = MeasurePolicy !PositiveExact !RadicalPrecision !SubdivisionBudget
deriving stock (Eq, Show)
instance NFData MeasurePolicy where
rnf (MeasurePolicy tolerance precision budget) =
rnf tolerance `seq` rnf precision `seq` rnf budget
measurePolicy :: PositiveExact -> RadicalPrecision -> SubdivisionBudget -> MeasurePolicy
measurePolicy = MeasurePolicy
measureTolerance :: MeasurePolicy -> PositiveExact
measureTolerance (MeasurePolicy tolerance _ _) = tolerance
measurePrecision :: MeasurePolicy -> RadicalPrecision
measurePrecision (MeasurePolicy _ precision _) = precision
measureBudget :: MeasurePolicy -> SubdivisionBudget
measureBudget (MeasurePolicy _ _ budget) = budget
-- | The obligation a span could not discharge: the subdivision budget, or one
-- of measurement's own. A precision refusal carries the
-- span's rounding width and its share of the global allowance; subdividing
-- cannot help once rounding alone exceeds that share. An ambiguous distance
-- carries the best residual the enclosures certify, which exceeds the
-- inverse's allowance.
data MeasureObligation
= MeasureBudgetExhausted !BudgetObligation
| PrecisionExhausted !ExactRational !ExactRational
| AmbiguousDistance !ExactRational
deriving stock (Eq, Show)
-- | A refused span names its source step index (in 'closedTrailSteps' order for
-- a closed trail) and parameter bracket within that step. A distance above
-- the certified upper length is beyond the trail; one above the lower length
-- but not the upper is undecided at this enclosure. Neither is clamped. A
-- fraction whose share of the length uncertainty consumes the tolerance
-- carries that share and the tolerance. A query request refused before any
-- span is consulted carries its obligation alone. A trail with no steps has no
-- source span to sample.
data MeasureError
= SpanRefused !Int !UnitInterval !UnitInterval !MeasureObligation
| RequestRefused !MeasureObligation
| DistanceBeyondTrail !ExactRational !LengthEnclosure
| DistanceUnresolved !ExactRational !LengthEnclosure
| FractionBudgetExhausted !ExactRational !ExactRational
| EmptyTrailSample
deriving stock (Eq, Show)
-- | One accepted span: the source step it refines, its parameter bracket, its
-- located start, the child step obtained by subdivision, its enclosure, and
-- the cumulative enclosure of every span before it.
data MeasuredSpan = MeasuredSpan !Leaf !LengthEnclosure
deriving stock (Eq, Show)
data Leaf = Leaf !SourceStep !UnitInterval !UnitInterval !ExactPoint !CurveStep !LengthEnclosure
deriving stock (Eq, Show)
instance NFData MeasuredSpan where
rnf (MeasuredSpan (Leaf step from to start piece enclosure) prefix) =
rnf step `seq` rnf from `seq` rnf to `seq` rnf start `seq` rnf piece
`seq` rnf enclosure `seq` rnf prefix
measuredSpanStep :: MeasuredSpan -> Int
measuredSpanStep (MeasuredSpan (Leaf step _ _ _ _ _) _) = sourceStepIndex step
measuredSpanFrom :: MeasuredSpan -> UnitInterval
measuredSpanFrom (MeasuredSpan (Leaf _ from _ _ _ _) _) = from
measuredSpanTo :: MeasuredSpan -> UnitInterval
measuredSpanTo (MeasuredSpan (Leaf _ _ to _ _ _) _) = to
measuredSpanStart :: MeasuredSpan -> ExactPoint
measuredSpanStart (MeasuredSpan (Leaf _ _ _ start _ _) _) = start
measuredSpanPiece :: MeasuredSpan -> CurveStep
measuredSpanPiece (MeasuredSpan (Leaf _ _ _ _ piece _) _) = piece
measuredSpanLength :: MeasuredSpan -> LengthEnclosure
measuredSpanLength (MeasuredSpan (Leaf _ _ _ _ _ enclosure) _) = enclosure
measuredSpanPrefix :: MeasuredSpan -> LengthEnclosure
measuredSpanPrefix (MeasuredSpan _ prefix) = prefix
-- | An open trail is measured from its anchor. A closed trail is measured as
-- exactly one lap, with its seam at the anchor: its steps are
-- 'closedTrailSteps', the explicit closing step last.
data MeasuredTrail = MeasuredTrail !Subpath !MeasurePolicy !(Seq MeasuredSpan) !LengthEnclosure
deriving stock (Eq, Show)
instance NFData MeasuredTrail where
rnf (MeasuredTrail source policy spans total) =
rnf source `seq` rnf policy `seq` rnf spans `seq` rnf total
measuredSource :: MeasuredTrail -> Subpath
measuredSource (MeasuredTrail source _ _ _) = source
measuredSpans :: MeasuredTrail -> Seq MeasuredSpan
measuredSpans (MeasuredTrail _ _ spans _) = spans
-- | The trail's length lies in this enclosure, whose width is at most the
-- policy tolerance.
lengthBounds :: MeasuredTrail -> LengthEnclosure
lengthBounds (MeasuredTrail _ _ _ total) = total
-- | An answer to an inverse-length query: a site on its source, and a bracket
-- of that step's parameters containing the site's. Every parameter in the
-- bracket has arc distance within the residual of the request. The bracket is
-- certified, not maximal; it does not claim a unique inverse, which a
-- stationary span does not have.
data ArcSample = ArcSample !TrailSite !UnitInterval !UnitInterval !ExactRational
deriving stock (Eq, Show)
instance NFData ArcSample where
rnf (ArcSample site from to residual) =
rnf site `seq` rnf from `seq` rnf to `seq` rnf residual
sampleSite :: ArcSample -> TrailSite
sampleSite (ArcSample site _ _ _) = site
sampleParameterFrom :: ArcSample -> UnitInterval
sampleParameterFrom (ArcSample _ from _ _) = from
sampleParameterTo :: ArcSample -> UnitInterval
sampleParameterTo (ArcSample _ _ to _) = to
sampleResidual :: ArcSample -> ExactRational
sampleResidual (ArcSample _ _ _ residual) = residual
-- | Precision, the tolerance shares per unit of chord and per unit of one
-- step's parameter, and the bit budget.
data Measuring = Measuring !RadicalPrecision !ExactRational !ExactRational !Int
measureSubpath :: MeasurePolicy -> Subpath -> Either MeasureError MeasuredTrail
measureSubpath policy@(MeasurePolicy tolerance precision budget) source = do
-- Coordinates, weights and parameters first; no length is observed from a
-- step the budget has not admitted. Then each source polygon and their sums.
traverse_ (\step -> admitBits bits (whole step) (stepBits step)) selected
traverse_ (\(step, polygon, prefix) -> admitEnclosure bits (whole step) polygon *> admitEnclosure bits (whole step) prefix)
(Seq.zip3 selected polygons (Seq.drop 1 (Seq.scanl (<>) mempty polygons)))
(_, measured) <- foldM appendStep (leaves, Seq.empty) selected
let prefixes = Seq.scanl (<>) mempty (fmap leafLength measured)
traverse_ (\(Leaf step t0 t1 _ _ _, after) -> admitEnclosure bits (SpanRefused (sourceStepIndex step) t0 t1) after)
(Seq.zip measured (Seq.drop 1 prefixes))
pure (MeasuredTrail source policy
(Seq.zipWith (flip MeasuredSpan) prefixes measured) (fold (fmap leafLength measured)))
where
depth = budgetDepth budget
leaves = budgetLeaves budget
bits = budgetBits budget
selected = sourceSteps source
whole step = SpanRefused (sourceStepIndex step) unitZero unitOne
stepBits step = spanBits (sourceStepStart step) unitZero unitOne (sourceStepCurve step)
polygons = fmap (controlPolygon precision . sourceStepCurve) selected
polygonUpper = lengthEnclosureUpper (fold polygons)
-- A source whose control polygon has zero length is stationary; its every
-- span then has zero gap. A source with no steps has no spans, so its
-- parameter share is never read.
halfTolerance = positiveExactValue tolerance * exactHalf
lengthShare = either (const 0) (divideByPositive halfTolerance) (positiveExact polygonUpper)
parameterShare = either (const 0) (divideByPositive halfTolerance)
(positiveExact (fromIntegral (Seq.length selected)))
measuring = Measuring precision lengthShare parameterShare bits
appendStep (remaining, prefix) step = do
(remainingAfter, spans) <-
descend measuring step depth remaining (sourceStepStart step) unitZero unitOne (sourceStepCurve step)
pure (remainingAfter, prefix <> spans)
-- The binary subdivision tree is consumed directly, as in lowering; the depth
-- and leaf budgets bound traversal before any exponential tree exists.
descend
:: Measuring -> SourceStep -> Int -> Int -> ExactPoint -> UnitInterval -> UnitInterval -> CurveStep
-> Either MeasureError (Int, Seq Leaf)
descend measuring@(Measuring precision lengthShare parameterShare budget) source depth remaining from t0 t1 step
| remaining <= 0 = refuse (MeasureBudgetExhausted LeavesExhausted)
| width > budget = refuse (MeasureBudgetExhausted (BitsExhausted width))
| observed > budget = refuse (MeasureBudgetExhausted (BitsExhausted observed))
| lengthEnclosureWidth enclosure <= allowance =
Right (remaining - 1, Seq.singleton (Leaf source t0 t1 from step enclosure))
| optimisticGap <= optimisticAllowance && rounding > allowance =
refuse (PrecisionExhausted rounding allowance)
| depth == 0 = refuse (MeasureBudgetExhausted DepthExhausted)
| otherwise = do
let (left, right) = splitStep unitHalf step
middle = unitMidpoint t0 t1
splitPoint = translateExactPoint from (curveStepEnd left)
(afterLeft, leftLeaves) <- descend measuring source (depth - 1) remaining from t0 middle left
(afterRight, rightLeaves) <- descend measuring source (depth - 1) afterLeft splitPoint middle t1 right
pure (afterRight, leftLeaves <> rightLeaves)
where
refuse :: MeasureObligation -> Either MeasureError (Int, Seq Leaf)
refuse = Left . SpanRefused (sourceStepIndex source) t0 t1
width = spanBits from t0 t1 step
chord = chordLength precision step
polygon = controlPolygon precision step
observed = max (enclosureBits chord) (enclosureBits polygon)
enclosure = enclosureBetween chord polygon
spanShare = parameterShare * (unitIntervalValue t1 - unitIntervalValue t0)
allowance = lengthShare * lengthEnclosureLower chord + spanShare
-- The same comparison with rounding in the span's favour: if even that
-- fails, the gap is geometric and subdivision reduces it. If it passes but
-- rounding alone exceeds the allowance, subdivision cannot help: it halves
-- the parameter term while each child's rounding stays near @2^-precision@
-- per radical term.
optimisticGap = lengthEnclosureLower polygon - lengthEnclosureUpper chord
optimisticAllowance = lengthShare * lengthEnclosureUpper chord + spanShare
rounding = lengthEnclosureWidth chord + lengthEnclosureWidth polygon
-- | A point whose arc distance from the start is within the residual, at most
-- the policy tolerance, of the request. Only a request at most the certified
-- lower length is answered. The accepted span whose cumulative enclosures
-- bracket the request is bisected with the same kernel; a side is taken only
-- when enclosures separate from the request.
pointAtLength :: Distance -> MeasuredTrail -> Either MeasureError ArcSample
pointAtLength (Distance target) trail@(MeasuredTrail _ (MeasurePolicy tolerance _ budget) _ total)
| targetBits > bits = Left (RequestRefused (MeasureBudgetExhausted (BitsExhausted targetBits)))
| target > lengthEnclosureUpper total = Left (DistanceBeyondTrail target total)
| target > lengthEnclosureLower total = Left (DistanceUnresolved target total)
| otherwise = inverseLength (positiveExactValue tolerance) target trail >>= admitSample bits
where
targetBits = exactRationalBitWidth target
bits = budgetBits budget
-- | The request's fraction of the lower length bound, which differs from the
-- same fraction of the true length by at most the fraction of the total
-- width. That share is reserved from the tolerance before the inverse is
-- refined, so the widened residual stays within tolerance.
pointAtFraction :: UnitInterval -> MeasuredTrail -> Either MeasureError ArcSample
pointAtFraction fraction trail@(MeasuredTrail _ (MeasurePolicy tolerance _ budget) _ total)
| shareBits > bits = Left (RequestRefused (MeasureBudgetExhausted (BitsExhausted shareBits)))
| derivedBits > bits = Left (RequestRefused (MeasureBudgetExhausted (BitsExhausted derivedBits)))
| reserved >= limit = Left (FractionBudgetExhausted reserved limit)
| otherwise = inverseLength (limit - reserved) target trail >>= admitSample bits . widen
where
bits = budgetBits budget
share = unitIntervalValue fraction
target = share * lengthEnclosureLower total
-- The fraction first; the derived target and reserve only once it is admitted.
shareBits = exactRationalBitWidth share
derivedBits = max (exactRationalBitWidth target) (exactRationalBitWidth reserved)
limit = positiveExactValue tolerance
reserved = share * lengthEnclosureWidth total
widen (ArcSample site from to residual) = ArcSample site from to (residual + reserved)
-- | A returned sample's residual, bracket and point are retained exact values,
-- admitted like any other before the sample is returned.
admitSample :: Int -> ArcSample -> Either MeasureError ArcSample
admitSample budget sample@(ArcSample site from to residual)
| width > budget = Left (SpanRefused (siteStepIndex site) from to (MeasureBudgetExhausted (BitsExhausted width)))
| otherwise = Right sample
where
width = foldr (max . exactRationalBitWidth) (exactPointBitWidth (sitePoint site))
[residual, unitIntervalValue from, unitIntervalValue to]
-- | The query context shared by one inverse: the source, precision, bit
-- budget, residual allowance and target.
data Inverse = Inverse !Subpath !RadicalPrecision !Int !ExactRational !ExactRational
-- | Invariant: the target is at most the trail's certified lower length.
inverseLength :: ExactRational -> ExactRational -> MeasuredTrail -> Either MeasureError ArcSample
inverseLength limit target (MeasuredTrail source (MeasurePolicy _ precision budget) spans _) =
case firstMonotone (\span' -> lengthEnclosureUpper (spanAfter span') > target) spans of
-- No span ends certainly beyond the request, so it equals the trail's
-- exact length and the trail's end answers it.
Nothing -> case Seq.viewr spans of
EmptyR -> Left EmptyTrailSample
_ :> final -> boundary final
Just span'@(MeasuredSpan (Leaf step t0 t1 start piece _) before)
| lengthEnclosureLower (spanAfter span') >= target ->
bisect inverse step depth start t0 t1 piece before (spanAfter span')
-- The span's end is neither certainly before nor after the request.
| otherwise -> boundary span'
where
inverse = Inverse source precision (budgetBits budget) limit target
depth = budgetDepth budget
boundary span'@(MeasuredSpan (Leaf step _ t1 start piece _) _) =
let after = spanAfter span'
residual = max (target - lengthEnclosureLower after) (lengthEnclosureUpper after - target)
in if residual <= limit
then Right (ArcSample (trailSite source step t1 (translateExactPoint start (curveStepEnd piece))) t1 t1 residual)
else Left (SpanRefused (sourceStepIndex step) t1 t1 (AmbiguousDistance residual))
-- | Invariant: the arc distance at @tLo@ is at most the target and at @tHi@
-- at least it, witnessed by the separated enclosures @before@ and @after@.
-- As in 'descend', a piece is admitted before any enclosure is taken from it,
-- and the left child before its chord and polygon are observed.
bisect
:: Inverse -> SourceStep -> Int -> ExactPoint -> UnitInterval -> UnitInterval -> CurveStep
-> LengthEnclosure -> LengthEnclosure -> Either MeasureError ArcSample
bisect inverse@(Inverse source precision budget limit target) step depth from tLo tHi piece before after
| width > budget = refuse (MeasureBudgetExhausted (BitsExhausted width))
| pieceBits > budget = refuse (MeasureBudgetExhausted (BitsExhausted pieceBits))
| residual <= limit = Right (ArcSample (trailSite source step tLo from) tLo tHi residual)
| depth == 0 = refuse (MeasureBudgetExhausted DepthExhausted)
| leftWidth > budget = Left (SpanRefused (sourceStepIndex step) tLo tMiddle (MeasureBudgetExhausted (BitsExhausted leftWidth)))
| observed > budget = refuse (MeasureBudgetExhausted (BitsExhausted observed))
| lengthEnclosureUpper middle <= target =
bisect inverse step (depth - 1) splitPoint tMiddle tHi right middle after
| lengthEnclosureLower middle >= target =
bisect inverse step (depth - 1) from tLo tMiddle left before middle
| middleResidual <= limit =
Right (ArcSample (trailSite source step tMiddle splitPoint) tMiddle tMiddle middleResidual)
| otherwise = refuse (AmbiguousDistance middleResidual)
where
refuse :: MeasureObligation -> Either MeasureError ArcSample
refuse = Left . SpanRefused (sourceStepIndex step) tLo tHi
width = spanBits from tLo tHi piece
-- Every parameter of the bracket lies between the bracket's arc distances,
-- which differ by at most the piece's control polygon.
piecePolygon = controlPolygon precision piece
pieceBits = enclosureBits piecePolygon
residual = min (lengthEnclosureUpper piecePolygon)
(max (target - lengthEnclosureLower before) (lengthEnclosureUpper after - target))
(left, right) = splitStep unitHalf piece
tMiddle = unitMidpoint tLo tHi
splitPoint = translateExactPoint from (curveStepEnd left)
leftWidth = spanBits from tLo tMiddle left
leftChord = chordLength precision left
leftPolygon = controlPolygon precision left
middle = before <> enclosureBetween leftChord leftPolygon
observed = max (enclosureBits leftChord) (max (enclosureBits leftPolygon) (enclosureBits middle))
middleResidual = min (lengthEnclosureUpper piecePolygon)
(max (target - lengthEnclosureLower middle) (lengthEnclosureUpper middle - target))
spanAfter :: MeasuredSpan -> LengthEnclosure
spanAfter (MeasuredSpan leaf before) = before <> leafLength leaf
leafLength :: Leaf -> LengthEnclosure
leafLength (Leaf _ _ _ _ _ enclosure) = enclosure
-- | The first element satisfying a predicate that is monotone along the
-- sequence, by bisection.
firstMonotone :: (a -> Bool) -> Seq a -> Maybe a
firstMonotone holds items =
let (before, rest) = Seq.splitAt (Seq.length items `div` 2) items
in case Seq.viewl rest of
EmptyL -> Nothing
middle :< after
| holds middle -> firstMonotone holds before <|> Just middle
| otherwise -> firstMonotone holds after
chordLength :: RadicalPrecision -> CurveStep -> LengthEnclosure
chordLength precision step = euclideanLengthEnclosure precision [coordinates (curveStepEnd step)]
controlPolygon :: RadicalPrecision -> CurveStep -> LengthEnclosure
controlPolygon precision step =
euclideanLengthEnclosure precision (zipWith edge controls (drop 1 controls))
where
controls = toList (stepControlPoints step)
edge (ExactVector ax ay) (ExactVector bx by) = (bx - ax, by - ay)
coordinates :: ExactVector -> (ExactRational, ExactRational)
coordinates (ExactVector x y) = (x, y)
admitBits :: Int -> (MeasureObligation -> MeasureError) -> Int -> Either MeasureError ()
admitBits budget refuse width = when (width > budget) (Left (refuse (MeasureBudgetExhausted (BitsExhausted width))))
admitEnclosure :: Int -> (MeasureObligation -> MeasureError) -> LengthEnclosure -> Either MeasureError ()
admitEnclosure budget refuse = admitBits budget refuse . enclosureBits
enclosureBits :: LengthEnclosure -> Int
enclosureBits enclosure =
max (exactRationalBitWidth (lengthEnclosureLower enclosure)) (exactRationalBitWidth (lengthEnclosureUpper enclosure))