2014-11-16 62 views
13

我在Haskell中实现W3s recommended algorithm for converting SVG-path arcs from endpoint-arcs to center-arcs and back为什么我的SVG弧转换的实现不能通过QuickCheck?

type EndpointArc = (Double, Double, Double, Double 
        , Bool, Bool, Double, Double, Double) 

type CenterArc = (Double, Double, Double, Double 
       , Double, Double, Double) 

endpointToCenter :: EndpointArc -> CenterArc 

centerToEndpoint :: CenterArc -> EndpointArc 

See full implementation and test-code here

但我不能让这个属性来传递:

import Test.QuickCheck 
import Data.AEq ((~==)) 

instance Arbitrary EndpointArc where 
    arbitrary = do 
     ((x1,y1),(x2,y2)) <- arbitrary `suchThat` (\(u,v) -> u /= v) 
     rx    <- arbitrary `suchThat` (>0) 
     ry    <- arbitrary `suchThat` (>0) 
     phi    <- choose (0,2*pi) 
     (fA,fS)   <- arbitrary 
     return $ correctRadiiSize (x1, y1, x2, y2, fA, fS, rx, ry, phi) 

prop_conversionRetains :: EndpointArc -> Bool 
prop_conversionRetains earc = 
    let result = centerToEndpoint (endpointToCenter earc) 
    in earc ~== result 

有时候,这是由于浮点错误(这似乎超过IEEE754),但有时也有在结果的NaN。

(NaN,NaN,NaN,NaN,False,False,1.0314334509082723,2.732814841776921,1.2776112657142984) 

表示有没有解决办法,虽然我觉得我RX规模,RY如F.6.6.2 in W3's document描述。

import Numeric.Matrix 

m :: [[Double]] -> Matrix Double 
m = fromList 

toTuple :: Matrix Double -> (Double, Double) 
toTuple = (\[[x],[y]] -> (x,y)) . toList 

primed :: Double -> Double -> Double -> Double -> Double 
     -> (Double, Double) 
primed x1 y1 x2 y2 phi = toTuple $ 
    m [[ cos phi, sin phi] 
     ,[-sin phi, cos phi] 
     ] 
    * m [[(x1 - x2)/2] 
     ,[(y1 - y2)/2] 
     ] 

correctRadiiSize :: EndpointArc -> EndpointArc 
correctRadiiSize (x1, y1, x2, y2, fA, fS, rx, ry, phi) = 
    let (x1',y1') = primed x1 y1 x2 y2 phi 
     lambda = (x1'^2/rx^2) + (y1'^2/ry^2) 
     (rx',ry') | lambda <= 1 = (rx, ry) 
        | otherwise = ((sqrt lambda) * rx, (sqrt lambda) * ry) 
    in (x1, y1, x2, y2, fA, fS, rx', ry', phi) 

回答

13

好的,我想出了自己的想法。线索当然在W3s文件中:

在使用等式(F.6.6.3)放大半径的情况下,(F.6.5.2)的自由度为零并且确切地存在一个椭圆中心的解决方案。

F.6.5.2在我的代码是

(cx',cy') = (sq * rx * y1'/ry, sq * (-ry) * x1'/rx) 
       where sq = negateIf (fA == fS) $ sqrt 
         $ (rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2) 
         /(rx^2 * y1'^2 + ry^2 * x1'^2) 

,它指的是被开方数是

(rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2) 
/(rx^2 * y1'^2 + ry^2 * x1'^2) 

但当然,因为我们与彩车工作来说是不准确零但大约有时它可能是像-6.99496644301622e-17这是负面的东西!负数的平方根是一个复数,所以计算返回NaN。

这个诀窍的确是传播rx和ry已被调整大小以返回零并使sq为零而不是不必要地经历整个计算的事实,但快速解决方法仅仅是获取radicand的绝对值。

(cx',cy') = (sq * rx * y1'/ry, sq * (-ry) * x1'/rx) 
       where sq = negateIf (fA == fS) $ sqrt $ abs 
         $ (rx^2 * ry^2 - rx^2 * y1'^2 - ry^2 * x1'^2) 
         /(rx^2 * y1'^2 + ry^2 * x1'^2) 

之后还有一些浮点问题。首先误差超过什么是允许由IEEE754的~==运营商,所以我做了我自己approxEq

approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) = 
     abs (x1a - x1b ) < 0.001 
    && abs (y1a - y1b ) < 0.001 
    && abs (x2a - x2b ) < 0.001 
    && abs (y2a - y2b ) < 0.001 
    && abs (y2a - y2b ) < 0.001 
    && abs (rxa - rxb ) < 0.001 
    && abs (rya - ryb ) < 0.001 
    && abs (phia - phib) < 0.001 
    && fAa == fAb 
    && fSa == fSb 

prop_conversionRetains :: EndpointArc -> Bool 
prop_conversionRetains earc = 
    let result = centerToEndpoint (trace ("FIRST:" ++ show (endpointToCenter earc)) (endpointToCenter earc)) 
    in earc `approxEq` trace ("SECOND:" ++ show result) result 

这将启动把其中fa是越来越翻转的情况。现货神奇的数字:

FIRST:( - 5.988957688551294,-39.5430169665332,64.95929681921707,29.661347617532357,5.939852349879405,-1.2436798376040206,3.141592653589793

第二:(4.209851895761209,-73.01839718538467,-16.18776727286379,-6.067636747681732 ,,真,64.95929681921707,29.661347617532357,5.939852349879405)

***失败!可证伪(20次测试后):
(4.209851895761204,-73.01839718538467,-16.18776781572145,-6。0676366434916655,,真,64.95929681921707,29.661347617532357,5.939852349879405)

你猜对了! fA = abs dtheta > picenterToEndpoint,所以如果它是相关的,那么它可以以任何方式。

于是我拿出了英足总的条件和快速检查

approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) = 
     abs (x1a - x1b ) < 0.001 
    && abs (y1a - y1b ) < 0.001 
    && abs (x2a - x2b ) < 0.001 
    && abs (y2a - y2b ) < 0.001 
    && abs (y2a - y2b ) < 0.001 
    && abs (rxa - rxb ) < 0.001 
    && abs (rya - ryb ) < 0.001 
    && abs (phia - phib) < 0.001 
    -- && fAa == fAb 
    && fSa == fSb 

main = quickCheckWith stdArgs {maxSuccess = 50000} prop_conversionRetains 

这表明门槛approxEq仍不松懈足够增加测试次数。

approxEq (x1a, y1a, x2a, y2a, fAa, fSa, rxa, rya, phia) (x1b, y1b, x2b, y2b, fAb, fSb, rxb, ryb, phib) = 
     abs (x1a - x1b ) < 1 
    && abs (y1a - y1b ) < 1 
    && abs (x2a - x2b ) < 1 
    && abs (y2a - y2b ) < 1 
    && abs (y2a - y2b ) < 1 
    && abs (rxa - rxb ) < 1 
    && abs (rya - ryb ) < 1 
    && abs (phia - phib) < 1 
    -- && fAa == fAb 
    && fSa == fSb 

我终于可以通过大量的测试可靠地通过了。那么它的所有只是为了制作一些有趣的图形......我相信它足够准确:)

相关问题