packages feed

moonlight-planar-1.1.0.0: src-public/Moonlight/Planar/Internal/PowerDiagram/Mass.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Restricted exact observations of fixed-position, linearly reweighted power
-- cells. Preparation descends through the existing all-constraint clipper;
-- active source lines, strict omitted slacks and directed edge lengths glue
-- those local cells into one sufficient open parameter interval. This is not
-- an event-transition engine or a replacement for regular-site dispositions.
module Moonlight.Planar.Internal.PowerDiagram.Mass
  ( PreparedPowerMassSection
  , PowerMassPolynomial (..)
  , PowerMassBoundary (..)
  , PowerMassObligation (..)
  , PowerMassCertificate
  , powerMassCertificateObligation
  , powerMassCertificateConstant
  , powerMassCertificateRate
  , PowerMassBound
  , powerMassBoundParameter
  , powerMassBoundCertificates
  , PowerMassInterval
  , powerMassIntervalLower
  , powerMassIntervalUpper
  , PowerMassError (..)
  , preparePowerMassSection
  , powerMassSectionWindow
  , powerMassSectionSites
  , powerMassSectionDirection
  , powerMassSectionInterval
  , powerMassSectionCertificates
  , powerMassSectionPolynomials
  , evaluatePowerMasses
  ) where

import Control.DeepSeq (NFData (..))
import Data.Bifunctor (first)
import qualified Data.Foldable as Foldable
import qualified Data.List as List
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (mapMaybe)
import Data.Ratio ((%))
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.Generics (Generic)
import Moonlight.Planar.Convex (ConvexPolygon, convexPolygonPoints, retainConvexPolygon)
import Moonlight.Planar.Exact
  ( ExactAffineLine
  , ExactArithmeticError
  , ExactClipDisposition (..)
  , ExactClipError
  , ExactClosedHalfPlane
  , ExactHalfPlaneError
  , ExactPoint
  , ExactRational
  , ExactRetainedPolygon
  , ExactVector (..)
  , exactAffineLineCoefficients
  , exactClipRetainedPolygon
  , exactClosedHalfPlane
  , exactClosedHalfPlaneFromDirectedEdge
  , exactClosedHalfPlaneLine
  , exactCross
  , exactDivide
  , exactPointCoordinates
  , exactPointCross
  , exactRetainedPolygonPoints
  , exactVectorFromPoints
  )
import Moonlight.Planar.Internal.BoundaryCycle (cyclePairsNonEmpty, unorderedPairs)
import Moonlight.Planar.Internal.ExactRational (exactRationalFromNormalizedRatio)
import Moonlight.Planar.Internal.PowerDiagram.Generator
  ( ExactPowerGenerator
  , exactGeneratorAxis
  , exactPowerGeneratorLabel
  )
import Moonlight.Planar.Internal.PowerDiagram.Model
  ( PowerDiagramError
  , PowerSite
  , powerSiteExactPosition
  , powerSiteLabel
  )
import Moonlight.Planar.Internal.PowerDiagram.Section
  ( prepareExactPowerGenerator
  , validateAndSortSites
  )

-- | Coefficients of @constant + parameter * linear + parameter^2 * quadratic@.
-- The constructor is an observation, not an admission path to a section.
data PowerMassPolynomial = PowerMassPolynomial !ExactRational !ExactRational !ExactRational
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

instance Semigroup PowerMassPolynomial where
  PowerMassPolynomial a b c <> PowerMassPolynomial d e f =
    PowerMassPolynomial (a + d) (b + e) (c + f)

instance Monoid PowerMassPolynomial where
  mempty = PowerMassPolynomial 0 0 0

-- | Stable source identity within one cell's complete inequality family.
data PowerMassBoundary label
  = PowerMassWindowBoundary !Int
  | PowerMassSiteBoundary !label
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data PowerMassObligation label
  = PowerMassVertexSlack !label !ExactPoint !(PowerMassBoundary label)
  | PowerMassDirectedEdge !label !(PowerMassBoundary label)
  | PowerMassEmptyWitness !label !(NonEmpty (PowerMassBoundary label, ExactRational))
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | A retained strict affine inequality @constant + t * rate > 0@.
data PowerMassCertificate label =
  PowerMassCertificate !(PowerMassObligation label) !ExactRational !ExactRational
  deriving stock (Eq, Ord, Show)

instance NFData label => NFData (PowerMassCertificate label) where
  rnf (PowerMassCertificate obligation constant rate) = rnf obligation `seq` rnf constant `seq` rnf rate

powerMassCertificateObligation :: PowerMassCertificate label -> PowerMassObligation label
powerMassCertificateObligation (PowerMassCertificate obligation _ _) = obligation

powerMassCertificateConstant :: PowerMassCertificate label -> ExactRational
powerMassCertificateConstant (PowerMassCertificate _ constant _) = constant

powerMassCertificateRate :: PowerMassCertificate label -> ExactRational
powerMassCertificateRate (PowerMassCertificate _ _ rate) = rate

-- | Every retained certificate tied at this extremal root, not an arbitrary
-- event chosen from the simultaneous set.
data PowerMassBound label =
  PowerMassBound !ExactRational !(NonEmpty (PowerMassCertificate label))
  deriving stock (Eq, Ord, Show)

instance NFData label => NFData (PowerMassBound label) where
  rnf (PowerMassBound parameter certificates) = rnf parameter `seq` rnf certificates

powerMassBoundParameter :: PowerMassBound label -> ExactRational
powerMassBoundParameter (PowerMassBound parameter _) = parameter

powerMassBoundCertificates :: PowerMassBound label -> NonEmpty (PowerMassCertificate label)
powerMassBoundCertificates (PowerMassBound _ certificates) = certificates

data PowerMassInterval label =
  PowerMassInterval !(Maybe (PowerMassBound label)) !(Maybe (PowerMassBound label))
  deriving stock (Eq, Ord, Show)

instance NFData label => NFData (PowerMassInterval label) where
  rnf (PowerMassInterval lower upper) = rnf lower `seq` rnf upper

powerMassIntervalLower :: PowerMassInterval label -> Maybe (PowerMassBound label)
powerMassIntervalLower (PowerMassInterval lower _) = lower

powerMassIntervalUpper :: PowerMassInterval label -> Maybe (PowerMassBound label)
powerMassIntervalUpper (PowerMassInterval _ upper) = upper

-- No exported constructor or record-update surface can detach a polynomial
-- from its source, direction, active-line witnesses or validity interval.
data PreparedPowerMassSection label = PreparedPowerMassSection
  !ConvexPolygon
  !(NonEmpty (PowerSite label))
  !(Map label ExactRational)
  !(Map label (CertifiedMassCell label))
  ![PowerMassCertificate label]
  !(PowerMassInterval label)
  deriving stock (Eq, Show)

instance NFData label => NFData (PreparedPowerMassSection label) where
  rnf (PreparedPowerMassSection window sites direction cells certificates interval) =
    rnf window `seq` rnf sites `seq` rnf direction `seq` rnf cells `seq` rnf certificates `seq` rnf interval

data MassConstraint label = MassConstraint !(PowerMassBoundary label) !ExactAffineLine !ExactRational
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data AffineMassVertex label = AffineMassVertex
  !ExactPoint !ExactVector !(MassConstraint label) !(MassConstraint label)
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data CertifiedMassCell label
  = FullDimensionalMassCell !PowerMassPolynomial !(NonEmpty (AffineMassVertex label))
  | EmptyMassCell !(PowerMassCertificate label)
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

-- | Restrictions belong only to this prepared observation. Public regular
-- triangulations continue to support coincident and lower-dimensional sites.
data PowerMassError label
  = PowerMassSiteAdmission !(PowerDiagramError label)
  | PowerMassDirectionMismatch !(Set label) !(Set label)
  | PowerMassCoincidentSites !label !label
  | PowerMassWindowAdmission !ExactHalfPlaneError
  | PowerMassAxisAdmission !label !label !ExactHalfPlaneError
  | PowerMassCellClip !label !ExactClipError
  | PowerMassLowerDimensionalSeed !label !(NonEmpty ExactPoint)
  | PowerMassActiveIncidence !label !ExactPoint ![PowerMassBoundary label]
  | PowerMassParallelActiveBoundaries !label !ExactPoint
  | PowerMassEdgeIncidence !label ![PowerMassBoundary label]
  | PowerMassNonStrictSeed !(PowerMassObligation label) !ExactRational
  | PowerMassEmptyWitnessUnavailable !label
  | PowerMassArithmetic !ExactArithmeticError
  | PowerMassParameterOutsideCertificate !ExactRational !(PowerMassInterval label)
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

powerMassSectionWindow :: PreparedPowerMassSection label -> ConvexPolygon
powerMassSectionWindow (PreparedPowerMassSection window _ _ _ _ _) = window

powerMassSectionSites :: PreparedPowerMassSection label -> NonEmpty (PowerSite label)
powerMassSectionSites (PreparedPowerMassSection _ sites _ _ _ _) = sites

powerMassSectionDirection :: PreparedPowerMassSection label -> Map label ExactRational
powerMassSectionDirection (PreparedPowerMassSection _ _ direction _ _ _) = direction

powerMassSectionInterval :: PreparedPowerMassSection label -> PowerMassInterval label
powerMassSectionInterval (PreparedPowerMassSection _ _ _ _ _ interval) = interval

powerMassSectionCertificates :: PreparedPowerMassSection label -> [PowerMassCertificate label]
powerMassSectionCertificates (PreparedPowerMassSection _ _ _ _ certificates _) = certificates

powerMassSectionPolynomials :: PreparedPowerMassSection label -> Map label PowerMassPolynomial
powerMassSectionPolynomials (PreparedPowerMassSection _ _ _ cells _ _) = fmap massCellPolynomial cells

massCellPolynomial :: CertifiedMassCell label -> PowerMassPolynomial
massCellPolynomial (FullDimensionalMassCell coefficients _) = coefficients
massCellPolynomial (EmptyMassCell _) = mempty

-- | Prepare exact area observations for fixed positions, rational affine
-- weights and a fixed bounded convex window. Every label needs an explicit
-- rate, including zero. The expensive small-subset empty-witness search runs
-- only here; evaluation performs one exact interval check and O(n) polynomials.
preparePowerMassSection
  :: Ord label
  => ConvexPolygon
  -> NonEmpty (PowerSite label)
  -> Map label ExactRational
  -> Either (PowerMassError label) (PreparedPowerMassSection label)
preparePowerMassSection window submitted direction = do
  sites <- first PowerMassSiteAdmission (validateAndSortSites submitted)
  let labels = Set.fromList (fmap powerSiteLabel (NonEmpty.toList sites))
      directionLabels = Map.keysSet direction
      missing = labels Set.\\ directionLabels
      extra = directionLabels Set.\\ labels
  if Set.null missing && Set.null extra
    then Right ()
    else Left (PowerMassDirectionMismatch missing extra)
  case List.find samePosition (unorderedPairs (NonEmpty.toList sites)) of
    Just (left, right) -> Left (PowerMassCoincidentSites (powerSiteLabel left) (powerSiteLabel right))
    Nothing -> Right ()
  let retained = retainConvexPolygon window
  windowConstraints <- traverse windowConstraint (zip [0 ..] (NonEmpty.toList (cyclePairsNonEmpty (convexPolygonPoints window))))
  let generators = fmap prepareExactPowerGenerator sites
      -- Exact key equality was discharged above; pairing carries each rate
      -- through all construction rather than inventing a missing-rate default.
      trajectories = zip (NonEmpty.toList generators) (Map.elems direction)
  prepared <- traverse (prepareMassCell retained windowConstraints trajectories) trajectories
  let cells = Map.fromList (fmap (\(label, cell, _) -> (label, cell)) prepared)
      certificates = concatMap (\(_, _, obligations) -> obligations) prepared
  interval <- certificateInterval certificates
  pure (PreparedPowerMassSection window sites direction cells certificates interval)
 where
  samePosition :: (PowerSite label, PowerSite label) -> Bool
  samePosition (left, right) = powerSiteExactPosition left == powerSiteExactPosition right
  windowConstraint
    :: (Int, (ExactPoint, ExactPoint))
    -> Either (PowerMassError label) (MassConstraint label)
  windowConstraint (index, (from, to)) = do
    halfPlane <- first PowerMassWindowAdmission (exactClosedHalfPlaneFromDirectedEdge from to)
    pure (MassConstraint (PowerMassWindowBoundary index) (exactClosedHalfPlaneLine halfPlane) 0)

prepareMassCell
  :: Ord label
  => ExactRetainedPolygon
  -> [MassConstraint label]
  -> [(ExactPowerGenerator label, ExactRational)]
  -> (ExactPowerGenerator label, ExactRational)
  -> Either (PowerMassError label) (label, CertifiedMassCell label, [PowerMassCertificate label])
prepareMassCell retained windowConstraints trajectories (owner, ownerRate) = do
  let label = exactPowerGeneratorLabel owner
  siteConstraints <- traverse (siteConstraint label) (filter ((/= label) . exactPowerGeneratorLabel . fst) trajectories)
  let constraints = windowConstraints <> siteConstraints
  (disposition, _) <- first (PowerMassCellClip label)
    (exactClipRetainedPolygon retained (fmap constraintHalfPlane siteConstraints))
  case disposition of
    ExactClipLowerDimensional points -> Left (PowerMassLowerDimensionalSeed label points)
    ExactClipEmpty -> do
      witness <- maybe (Left (PowerMassEmptyWitnessUnavailable label)) Right (emptyWitness label constraints)
      pure (label, EmptyMassCell witness, [witness])
    ExactClipFullDimensional polygon -> do
      vertices <- traverse (prepareMassVertex label constraints) (exactRetainedPolygonPoints polygon)
      vertexCertificates <- traverse (vertexSlacks label constraints) vertices
      edgeCertificates <- traverse (directedEdgeCertificate label) (cyclePairsNonEmpty vertices)
      pure
        ( label
        , FullDimensionalMassCell (areaPolynomial vertices) vertices
        , concat (NonEmpty.toList vertexCertificates) <> NonEmpty.toList edgeCertificates
        )
 where
  siteConstraint label (competitor, competitorRate) = do
    let competitorLabel = exactPowerGeneratorLabel competitor
    axis <- first (PowerMassAxisAdmission label competitorLabel) (exactGeneratorAxis owner competitor)
    pure (MassConstraint (PowerMassSiteBoundary competitorLabel) axis (ownerRate - competitorRate))
  constraintHalfPlane :: MassConstraint label -> ExactClosedHalfPlane
  constraintHalfPlane (MassConstraint _ line _) = exactClosedHalfPlane line

constraintBoundary :: MassConstraint label -> PowerMassBoundary label
constraintBoundary (MassConstraint boundary _ _) = boundary

constraintCoefficients :: MassConstraint label -> (ExactRational, ExactRational, ExactRational, ExactRational)
constraintCoefficients (MassConstraint _ line rate) =
  let (a, b, c) = exactAffineLineCoefficients line in (a, b, c, rate)

constraintAtPoint :: ExactPoint -> MassConstraint label -> ExactRational
constraintAtPoint point constraint =
  let (x, y) = exactPointCoordinates point
      (a, b, c, _) = constraintCoefficients constraint
   in a * x + b * y + c

prepareMassVertex
  :: label
  -> [MassConstraint label]
  -> ExactPoint
  -> Either (PowerMassError label) (AffineMassVertex label)
prepareMassVertex label constraints point =
  case filter ((== 0) . constraintAtPoint point) constraints of
    [left, right] -> do
      let (a, b, _, leftRate) = constraintCoefficients left
          (c, d, _, rightRate) = constraintCoefficients right
          determinant = a * d - b * c
      if determinant == 0
        then Left (PowerMassParallelActiveBoundaries label point)
        else do
          velocityX <- first PowerMassArithmetic (exactDivide (b * rightRate - leftRate * d) determinant)
          velocityY <- first PowerMassArithmetic (exactDivide (leftRate * c - a * rightRate) determinant)
          pure (AffineMassVertex point (ExactVector velocityX velocityY) left right)
    active -> Left (PowerMassActiveIncidence label point (fmap constraintBoundary active))

strictCertificate
  :: PowerMassObligation label
  -> ExactRational
  -> ExactRational
  -> Either (PowerMassError label) (PowerMassCertificate label)
strictCertificate obligation constant rate
  | constant > 0 = Right (PowerMassCertificate obligation constant rate)
  | otherwise = Left (PowerMassNonStrictSeed obligation constant)

vertexSlacks
  :: Eq label
  => label
  -> [MassConstraint label]
  -> AffineMassVertex label
  -> Either (PowerMassError label) [PowerMassCertificate label]
vertexSlacks label constraints (AffineMassVertex point (ExactVector dx dy) left right) =
  traverse certificate (filter inactive constraints)
 where
  inactive constraint = constraintBoundary constraint /= constraintBoundary left
    && constraintBoundary constraint /= constraintBoundary right
  certificate constraint =
    let (a, b, _, rate) = constraintCoefficients constraint
     in strictCertificate
          (PowerMassVertexSlack label point (constraintBoundary constraint))
          (constraintAtPoint point constraint)
          (a * dx + b * dy + rate)

directedEdgeCertificate
  :: Eq label
  => label
  -> (AffineMassVertex label, AffineMassVertex label)
  -> Either (PowerMassError label) (PowerMassCertificate label)
directedEdgeCertificate label
    ( AffineMassVertex from (ExactVector fromDX fromDY) fromLeft fromRight
    , AffineMassVertex to (ExactVector toDX toDY) toLeft toRight
    ) =
  case filter shared [fromLeft, fromRight] of
    [edge] ->
      let (a, b, _, _) = constraintCoefficients edge
          ExactVector edgeX edgeY = exactVectorFromPoints from to
       in strictCertificate
            (PowerMassDirectedEdge label (constraintBoundary edge))
            (b * edgeX - a * edgeY)
            (b * (toDX - fromDX) - a * (toDY - fromDY))
    edges -> Left (PowerMassEdgeIncidence label (fmap constraintBoundary edges))
 where
  shared constraint = constraintBoundary constraint == constraintBoundary toLeft
    || constraintBoundary constraint == constraintBoundary toRight

areaPolynomial :: NonEmpty (AffineMassVertex label) -> PowerMassPolynomial
areaPolynomial = foldMap edgePolynomial . cyclePairsNonEmpty
 where
  half = exactRationalFromNormalizedRatio (1 % 2)
  edgePolynomial :: (AffineMassVertex label, AffineMassVertex label) -> PowerMassPolynomial
  edgePolynomial (AffineMassVertex from fromVelocity _ _, AffineMassVertex to toVelocity _ _) =
    let (fromX, fromY) = exactPointCoordinates from
        (toX, toY) = exactPointCoordinates to
        fromVector = ExactVector fromX fromY
        toVector = ExactVector toX toY
     in PowerMassPolynomial
          (half * exactPointCross from to)
          (half * (exactCross fromVelocity toVector + exactCross fromVector toVelocity))
          (half * exactCross fromVelocity toVelocity)

-- Small-subset Farkas search is deliberately preparation-only. For source
-- equations a*x+b*y+c(t)>=0, cancelling normals and sum lambda*c(t)<0
-- certifies emptiness, including contacts involving only the window.
emptyWitness :: label -> [MassConstraint label] -> Maybe (PowerMassCertificate label)
emptyWitness label constraints =
  Foldable.asum (fmap witness (pairWeights <> tripleWeights))
 where
  pairWeights = fmap oppositeWeights (unorderedPairs constraints)
  tripleWeights =
    [ cyclicWeights left middle right
    | left : remaining <- List.tails constraints
    , (middle, right) <- unorderedPairs remaining
    ]
  oppositeWeights
    :: (MassConstraint label, MassConstraint label)
    -> [(MassConstraint label, ExactRational)]
  oppositeWeights (left, right) =
    let (a, b, _, _) = constraintCoefficients left
        (c, d, _, _) = constraintCoefficients right
        (leftWeight, rightWeight) = if a /= 0 || c /= 0 then (abs c, abs a) else (abs d, abs b)
     in [(left, leftWeight), (right, rightWeight)]
  cyclicWeights
    :: MassConstraint label
    -> MassConstraint label
    -> MassConstraint label
    -> [(MassConstraint label, ExactRational)]
  cyclicWeights left middle right =
    let crossNormals :: MassConstraint label -> MassConstraint label -> ExactRational
        crossNormals firstConstraint secondConstraint =
          let (a, b, _, _) = constraintCoefficients firstConstraint
              (c, d, _, _) = constraintCoefficients secondConstraint
           in a * d - b * c
        weighted = [(left, crossNormals middle right), (middle, crossNormals right left), (right, crossNormals left middle)]
     in if all ((<= 0) . snd) weighted then fmap (fmap negate) weighted else weighted
  witness weighted =
    let positive = filter ((> 0) . snd) weighted
        normalX = sum (fmap (\(constraint, weight) -> let (a, _, _, _) = constraintCoefficients constraint in weight * a) weighted)
        normalY = sum (fmap (\(constraint, weight) -> let (_, b, _, _) = constraintCoefficients constraint in weight * b) weighted)
        constant = sum (fmap (\(constraint, weight) -> let (_, _, c, _) = constraintCoefficients constraint in weight * c) weighted)
        rate = sum (fmap (\(constraint, weight) -> let (_, _, _, d) = constraintCoefficients constraint in weight * d) weighted)
     in if all ((>= 0) . snd) weighted && normalX == 0 && normalY == 0 && constant < 0
          then fmap
            (\sources -> PowerMassCertificate (PowerMassEmptyWitness label sources) (negate constant) (negate rate))
            (NonEmpty.nonEmpty (fmap (\(constraint, weight) -> (constraintBoundary constraint, weight)) positive))
          else Nothing

certificateInterval
  :: [PowerMassCertificate label]
  -> Either (PowerMassError label) (PowerMassInterval label)
certificateInterval certificates = do
  bounds <- traverse root certificates
  pure (Foldable.foldl' intersectInterval (PowerMassInterval Nothing Nothing) (mapMaybe id bounds))
 where
  root
    :: PowerMassCertificate label
    -> Either (PowerMassError label) (Maybe (PowerMassInterval label))
  root certificate@(PowerMassCertificate _ constant rate)
    | rate == 0 = Right Nothing
    | otherwise = do
        parameter <- first PowerMassArithmetic (exactDivide (negate constant) rate)
        let bound = Just (PowerMassBound parameter (certificate :| []))
        pure (Just (if rate > 0 then PowerMassInterval bound Nothing else PowerMassInterval Nothing bound))
  intersectInterval :: PowerMassInterval label -> PowerMassInterval label -> PowerMassInterval label
  intersectInterval (PowerMassInterval lower upper) (PowerMassInterval otherLower otherUpper) =
    PowerMassInterval (tightest GT lower otherLower) (tightest LT upper otherUpper)
  tightest
    :: Ordering
    -> Maybe (PowerMassBound label)
    -> Maybe (PowerMassBound label)
    -> Maybe (PowerMassBound label)
  tightest _ Nothing right = right
  tightest _ left Nothing = left
  tightest direction left@(Just (PowerMassBound leftRoot leftCertificates)) right@(Just (PowerMassBound rightRoot rightCertificates)) =
    case compare leftRoot rightRoot of
      EQ -> Just (PowerMassBound leftRoot (leftCertificates <> rightCertificates))
      ordering -> if ordering == direction then left else right

-- | Only exact parameters strictly inside the retained sufficient interval
-- are admitted. Endpoint expiry requests recertification; it does not claim a
-- visibility event. No floating epsilon or implicit binary64 conversion exists.
evaluatePowerMasses
  :: ExactRational
  -> PreparedPowerMassSection label
  -> Either (PowerMassError label) (Map label ExactRational)
evaluatePowerMasses parameter section@(PreparedPowerMassSection _ _ _ cells _ _) =
  let interval = powerMassSectionInterval section
      aboveLower = maybe True ((parameter >) . powerMassBoundParameter) (powerMassIntervalLower interval)
      belowUpper = maybe True ((parameter <) . powerMassBoundParameter) (powerMassIntervalUpper interval)
      evaluatePolynomial (PowerMassPolynomial constant linear quadratic) =
        constant + parameter * (linear + parameter * quadratic)
   in if aboveLower && belowUpper
        then Right (fmap (evaluatePolynomial . massCellPolynomial) cells)
        else Left (PowerMassParameterOutsideCertificate parameter interval)