packages feed

diagrams-lib-1.6: src/Diagrams/TwoD/Segment/Bernstein.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeFamilies #-}

-- |
-- Module      :  Diagrams.TwoD.Segment.Bernstein
-- Copyright   :  (c) 2014-2015 diagrams-lib team (see LICENSE)
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  diagrams-discuss@googlegroups.com
--
-- Bernstein polynomials, used internally by code to find
-- intersections of paths.  This module is probably not of any
-- relevance to most users of diagrams.
module Diagrams.TwoD.Segment.Bernstein (
  BernsteinPoly (..),
  listToBernstein,
  evaluateBernstein,
  degreeElevate,
  bernsteinDeriv,
  evaluateBernsteinDerivs,
) where

import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty.Compat as NE
import Diagrams.Core.V
import Diagrams.Parametric
import Linear.V1

-- | Compute the binomial coefficients of degree n.
binomials :: Num n => Int -> NonEmpty n
binomials n = NE.map fromIntegral $ NE.scanl (\x m -> x * (n - m + 1) `quot` m) 1 [1 .. n]

data BernsteinPoly n = BernsteinPoly
  { bernsteinDegree :: Int
  , bernsteinCoeffs :: NonEmpty n
  }
  deriving (Show, Functor)

type instance V (BernsteinPoly n) = V1
type instance N (BernsteinPoly n) = n
type instance Codomain (BernsteinPoly n) = V1

-- | Create a bernstein polynomial from a list of coëfficients.
listToBernstein :: Fractional n => [n] -> BernsteinPoly n
listToBernstein l = case NE.nonEmpty l of
  Nothing -> 0
  Just ne -> BernsteinPoly (length l - 1) ne

-- | Degree elevate a bernstein polynomial a number of times.
degreeElevate :: Fractional n => BernsteinPoly n -> Int -> BernsteinPoly n
degreeElevate b 0 = b
degreeElevate (BernsteinPoly lp p) times =
  degreeElevate (BernsteinPoly (lp + 1) (NE.head p :| inner p 1)) (times - 1)
 where
  n = fromIntegral lp

  inner (a :| []) _ = [a]
  inner (a :| b : rest) i = (i * a / (n + 1) + b * (1 - i / (n + 1))) : inner (b :| rest) (i + 1)

-- | Evaluate the bernstein polynomial.
evaluateBernstein :: Fractional n => BernsteinPoly n -> n -> n
evaluateBernstein (BernsteinPoly _ (b :| [])) _ = b
evaluateBernstein (BernsteinPoly lp (b' :| bs)) t = go t n (b' * u) 2 bs
 where
  u = 1 - t
  n = fromIntegral lp

  go tn bc tmp _ [b] = tmp + tn * bc * b
  go tn bc tmp i (b : rest) =
    go
      (tn * t) -- tn
      (bc * (n - i + 1) / i) -- bc
      ((tmp + tn * bc * b) * u) -- tmp
      (i + 1) -- i
      rest
  go _ _ _ _ [] = error "evaluateBernstein: impossible"

-- | Evaluate the bernstein polynomial and its derivatives.
evaluateBernsteinDerivs :: Fractional n => BernsteinPoly n -> n -> [n]
evaluateBernsteinDerivs b t
  | bernsteinDegree b == 0 = [evaluateBernstein b t]
  | otherwise = evaluateBernstein b t : evaluateBernsteinDerivs (bernsteinDeriv b) t

-- | Find the derivative of a bernstein polynomial.
bernsteinDeriv :: Fractional n => BernsteinPoly n -> BernsteinPoly n
bernsteinDeriv (BernsteinPoly _ (_ :| [])) = 0
bernsteinDeriv (BernsteinPoly lp p@(_ :| (a1 : as))) =
  -- BernsteinPoly (lp-1) $ map (* fromIntegral lp) $ zipWith (-) (drop 1 p) p
  BernsteinPoly (lp - 1) $ NE.zipWith (\a b -> (a - b) * fromIntegral lp) (a1 :| as) p

instance Fractional n => Parametric (BernsteinPoly n) where
  atParam b = V1 . evaluateBernstein b
instance Num n => DomainBounds (BernsteinPoly n)
instance Fractional n => EndValues (BernsteinPoly n)
instance Fractional n => Sectionable (BernsteinPoly n) where
  splitAtParam = bernsteinSplit
  reverseDomain (BernsteinPoly i xs) = BernsteinPoly i (NE.reverse xs)

-- | Split a bernstein polynomial.
bernsteinSplit :: Num n => BernsteinPoly n -> n -> (BernsteinPoly n, BernsteinPoly n)
bernsteinSplit (BernsteinPoly lp p) t =
  ( BernsteinPoly lp $ NE.map NE.head controls
  , BernsteinPoly lp $ NE.reverse $ NE.map NE.last controls
  )
 where
  interp a b = (1 - t) * a + t * b

  -- terp :: NonEmpty n -> [NonEmpty n]
  terp l@(_ :| as) = case NE.nonEmpty as of
    Nothing -> []
    Just as' ->
      let ctrs = NE.zipWith interp l as'
       in ctrs : terp ctrs
  controls = p :| terp p

instance Fractional n => Num (BernsteinPoly n) where
  ba@(BernsteinPoly la a) + bb@(BernsteinPoly lb b)
    | la < lb = BernsteinPoly lb $ NE.zipWith (+) (bernsteinCoeffs $ degreeElevate ba $ lb - la) b
    | la > lb = BernsteinPoly la $ NE.zipWith (+) a (bernsteinCoeffs $ degreeElevate bb $ la - lb)
    | otherwise = BernsteinPoly la $ NE.zipWith (+) a b

  ba@(BernsteinPoly la a) - bb@(BernsteinPoly lb b)
    | la < lb = BernsteinPoly lb $ NE.zipWith (-) (bernsteinCoeffs $ degreeElevate ba (lb - la)) b
    | la > lb = BernsteinPoly la $ NE.zipWith (-) a (bernsteinCoeffs $ degreeElevate bb (la - lb))
    | otherwise = BernsteinPoly la $ NE.zipWith (-) a b

  (BernsteinPoly la a) * (BernsteinPoly lb b) =
    BernsteinPoly (la + lb) $
      NE.zipWith (flip (/)) (binomials (la + lb)) $
        NE.map sum $
          NE.appendList
            (NE.map (NE.zipWith (*) a') (down b'))
            (map (NE.zipWith (*) (NE.reverse b')) (NE.tail $ NE.tails1 a'))
   where
    down (x :| xs) = NE.scanl (flip NE.cons) (x :| []) xs -- [[1], [2, 1], [3, 2, 1], ...
    a' = NE.zipWith (*) a (binomials la)
    b' = NE.zipWith (*) b (binomials lb)

  fromInteger a = BernsteinPoly 0 (fromInteger a :| [])

  signum (BernsteinPoly _ (a :| _)) = BernsteinPoly 0 (signum a :| [])

  abs = fmap abs