packages feed

synthesizer-core-0.9: test/Test/Sound/Synthesizer/Plain/Analysis.hs

module Test.Sound.Synthesizer.Plain.Analysis (tests) where

import qualified Synthesizer.Plain.Analysis as Analysis

import qualified Algebra.Algebraic             as Algebraic
import qualified Algebra.RealField             as RealField
import qualified Algebra.Field                 as Field
import qualified Algebra.RealRing              as RealRing

import qualified Algebra.NormedSpace.Maximum   as NormedMax
import qualified Algebra.NormedSpace.Euclidean as NormedEuc
import qualified Algebra.NormedSpace.Sum       as NormedSum

import qualified MathObj.LaurentPolynomial as LPoly

import qualified Data.NonEmpty as NonEmpty
import Data.List (genericLength)

import qualified Test.QuickCheck as QC
import Test.QuickCheck (Property, property, (==>))
import Test.Utility (approxEqual)

import NumericPrelude.Numeric
import NumericPrelude.Base
import Prelude ()


volumeVectorMaximum :: (NormedMax.C y y, RealRing.C y) => [y] -> Bool
volumeVectorMaximum xs =
   Analysis.volumeVectorMaximum xs == Analysis.volumeMaximum xs

volumeVectorEuclidean ::
   (NormedEuc.C y y, Algebraic.C y, Eq y) =>
   NonEmpty.T [] y -> Bool
volumeVectorEuclidean xs =
   let ys = NonEmpty.flatten xs
   in  Analysis.volumeVectorEuclidean ys == Analysis.volumeEuclidean ys

volumeVectorEuclideanSqr ::
   (NormedEuc.Sqr y y, Field.C y, Eq y) =>
   NonEmpty.T [] y -> Bool
volumeVectorEuclideanSqr xs =
   let ys = NonEmpty.flatten xs
   in  Analysis.volumeVectorEuclideanSqr ys == Analysis.volumeEuclideanSqr ys

volumeVectorSum ::
   (NormedSum.C y y, RealField.C y) =>
   NonEmpty.T [] y -> Bool
volumeVectorSum xs =
   let ys = NonEmpty.flatten xs
   in  Analysis.volumeVectorSum ys == Analysis.volumeSum ys



bounds :: Ord a => NonEmpty.T [] a -> Bool
bounds xs =
   Analysis.bounds xs  ==  (NonEmpty.minimum xs, NonEmpty.maximum xs)


spread :: RealField.C a => (a,a) -> Bool
spread b =
   sum (map snd (Analysis.spread b)) == one


histogramDiscrete :: NonEmpty.T [] Int -> Bool
histogramDiscrete xs =
   Analysis.histogramDiscreteArray xs ==
   Analysis.histogramDiscreteIntMap xs

withEmptyHistogram ::
   (NonEmpty.T [] y -> (Int, [y])) ->
   [y] -> (Int, [y])
withEmptyHistogram f =
   maybe (error "no bounds", []) f . NonEmpty.fetch

histogramDiscreteLength :: [Int] -> Bool
histogramDiscreteLength xs =
   sum (snd (withEmptyHistogram Analysis.histogramDiscreteIntMap xs))
   ==
   length xs

histogramDiscreteConcat :: [Int] -> [Int] -> Bool
histogramDiscreteConcat xs ys =
   let xHist = withEmptyHistogram Analysis.histogramDiscreteIntMap xs
       yHist = withEmptyHistogram Analysis.histogramDiscreteIntMap ys
       xyHist0 =
          LPoly.add
             (uncurry LPoly.Cons xHist)
             (uncurry LPoly.Cons yHist)
       xyHist1 =
          uncurry LPoly.Cons
             (withEmptyHistogram Analysis.histogramDiscreteIntMap (xs++ys))
   in  if null (LPoly.coeffs xyHist0)
         then LPoly.coeffs xyHist0 == LPoly.coeffs xyHist1
         else xyHist0 == xyHist1


histogramLinear :: NonEmpty.T [] Int -> Bool
histogramLinear xs =
   let ys = fmap fromIntegral xs :: NonEmpty.T [] Double
   in  Analysis.histogramLinearArray ys ==
       Analysis.histogramLinearIntMap ys


histogramLinearLength :: NonEmpty.T [] Int -> Bool
histogramLinearLength xs =
   let ys = fmap fromIntegral xs :: NonEmpty.T [] Double
   in  approxEqual 1e-10
          (genericLength $ NonEmpty.tail ys)
          (sum (snd (Analysis.histogramLinearIntMap ys)))
{-
With eps = 1e-15

Falsifiable, after 83 tests:
-20
[32,-41,11,-25,-17,-27,32,-36,7,-36,38]

Falsifiable, after 78 tests:
10
[-35,-28,-28,-24,-4,-29,-14,-29,-20,7,33,-2,-14,-4,7,-40,-5,-12]
-}



centroid :: (Field.C a, Eq a) => [a] -> Property
centroid xs =
   sum xs /= zero ==>
      Analysis.centroid xs == Analysis.centroidAlt xs
-- Test.QuickCheck.property (\xs -> sum xs /= 0 Test.QuickCheck.==> propCentroid (xs::[Rational]))

histogramDCOffset :: NonEmpty.T (NonEmpty.T []) Int -> Property
histogramDCOffset xs =
   let x1 = NonEmpty.flatten xs
       x  = NonEmpty.flatten x1
       (offset, hist) = Analysis.histogramDiscreteArray x1
   in  sum x /= 0 ==>
          fromIntegral offset + Analysis.centroid (map fromIntegral hist) ==
          (Analysis.directCurrentOffset (map fromIntegral x) :: Rational)


genSmall :: (Functor f, QC.Arbitrary (f Int)) => QC.Gen (f Int)
genSmall = fmap (fmap (flip mod 1000)) QC.arbitrary

forAllSmall ::
   (Functor f, QC.Arbitrary (f Int), Show (f Int), QC.Testable prop) =>
   (f Int -> prop) -> Property
forAllSmall = QC.forAll genSmall


tests :: [(String, Property)]
tests =
   ("volumeVectorMaximum", property (volumeVectorMaximum :: [Rational] -> Bool)) :
   -- property may fail due to rounding errors, but so far the computation is exactly the same
   ("volumeVectorEuclidean", property (volumeVectorEuclidean :: NonEmpty.T [] Double -> Bool)) :
   ("volumeVectorEuclideanSqr", property (volumeVectorEuclideanSqr :: NonEmpty.T [] Rational -> Bool)) :
   ("volumeVectorSum", property (volumeVectorSum :: NonEmpty.T [] Rational -> Bool)) :
   ("bounds", property (bounds :: NonEmpty.T [] Rational -> Bool)) :
   ("spread", property (spread :: (Rational,Rational) -> Bool)) :
   ("histogramDiscrete", property (forAllSmall histogramDiscrete)) :
   ("histogramDiscreteLength", property (forAllSmall histogramDiscreteLength)) :
   ("histogramDiscreteConcat",
      property $ forAllSmall $ \x -> forAllSmall $ \y ->
         histogramDiscreteConcat x y) :
   ("histogramLinear", property (forAllSmall histogramLinear)) :
   ("histogramLinearLength", property (forAllSmall histogramLinearLength)) :
   ("centroid", property (centroid :: [Rational] -> Property)) :
   ("histogramDCOffset", property (forAllSmall histogramDCOffset)) :
   []