See the attached test-case. If you want to compile it, you need
fpp (https://github.com/oleg-nesterov/fpp), maxima, and Linux.

Compiled with '-a plot.cpp'

        $ ./test-plot -n 100000 | tail -n 1
        0.00285841338           0.000623551081

As you can see, taylor() uses less memory and it is more accurate
at least in this particular case. And it is more than 3 times faster
according to '-a bench.cpp'.

Once again, I am not trying to criticise your (nice!) implementation,
I just want to share the experimental results.

And of course, the implementation of taylor() is just POC, it is
suboptimal, only works for 2d case, etc. Not to mention that it is not
in pure Faust language ;)

Oleg.
import("stdfaust.lib");

fxy(x,y) = sin(x)*cos(y);

rx0 = 0;
rx1 = 2*ma.PI;
ry0 = 0;
ry1 = 2*ma.PI;

// table size = 128*128 = 16384
tabcub(x,y) = tabulateNd(1, fxy, (128,128, rx0,ry0, rx1,ry1, x,y)).cub;

// table size = 32*32*6 = 6144
taylor = tl22(${sin(x)*cos(y)}, 32,32, rx0,ry0, rx1,ry1);

N = 100000;

maxerr = abs : max ~ _;

process = (ba.time%N)/N * 2*ma.PI
        <: fxy - tabcub, fxy - taylor
         : par(i,2, maxerr);

// ----------------------------------------------------------------------------
// taylor approximation, 2 vars, 2nd order

mk_tl22tab = FPP(fxy, szx,szy, rx0,ry0, rx1,ry1) pure eval {
PERL:   sub mk_tltab
        {
                my ($FXY, $SZX,$SZY, $RX0,$RY0, $RX1,$RY1) = @_;
                s|[fL]$|| for $RX0,$RY0, $RX1,$RY1;

                my $mac = qq{block(
                FXY(x,y) := $FXY,
                [SZX,SZY, RX0,RY0, RX1,RY1] : [$SZX,$SZY, $RX0,$RY0, $RX1,$RY1],

                define(Fx(x,y),  diff(FXY(x,y), x)),
                define(Fy(x,y),  diff(FXY(x,y), y)),
                define(Fxy(x,y), diff(FXY(x,y), x,1,y,1)),
                define(Fxx(x,y), diff(FXY(x,y), x,2) / 2),
                define(Fyy(x,y), diff(FXY(x,y), y,2) / 2),

                dx: (RX1-RX0)/SZX,
                dy: (RY1-RY0)/SZY,
                for nx: 0 thru SZX-1 do (
                        xc: RX0 + dx * (nx + 1/2),
                        printf(true, "{~%"),
                for ny: 0 thru SZY-1 do (
                        yc: RY0 + dy * (ny + 1/2),
                        printf(true, "~t{ "),

                        printf(true, "~g, ", FXY(xc,yc)),
                        printf(true, "~g,~g, ", Fx(xc,yc),Fy(xc,yc)),
                        printf(true, "~g,~g,~g, ", Fxy(xc,yc), 
Fxx(xc,yc),Fyy(xc,yc)),

                        printf(true, " },~%")),
                        printf(true, " },~%")
                ))\$};

                qx{maxima --very-quiet <<<'$mac'};
        }

file:
        #define FLOAT $!(${pp_cpp::O_FLOAT})
        static FLOAT $taylor(FLOAT x, FLOAT y)
        {
                static const FLOAT taylor[$szx][$szy][6] = {
                        $!mk_tltab($fxy, $szx,$szy, $rx0,$ry0, $rx1,$ry1)
                };

                int nx = (x - $rx0) * $(szx/(rx1-rx0));
                int ny = (y - $ry0) * $(szy/(ry1-ry0));
                const FLOAT *const tl = taylor[nx][ny];

                FLOAT dx = x - ($rx0 + $((rx1-rx0)/szx) * (nx + .5));
                FLOAT dy = y - ($ry0 + $((ry1-ry0)/szy) * (ny + .5));

                return tl[0] + tl[1]*dx + tl[2]*dy + tl[3]*dx*dy + tl[4]*dx*dx 
+ tl[5]*dy*dy;
        }
        #undef FLOAT

exec:   $taylor
};

tl22 = FPP(taylor, x,y) pure { ($taylor($x,$y)) } (mk_tl22tab);

// ----------------------------------------------------------------------------
// copied from basics.lib, last commit aa5660b

tabulateNd(C,function,parameters) =
    environment {
        val =
            parameters
            // our sizes can be int, should be faster to calculate
            : (par(i, N, int),si.bus(N*3))
              // table size, waveform, read index
              <: (tableSize,wf,readIndex(idsGetRoundedNotFloored))
            : rdtable
        with {
        // for val we want the rounded version of id, the interpolators need 
the rounded down version
        // see: https://github.com/grame-cncm/faustlibraries/pull/152
        idsGetRoundedNotFloored =
            // table sizes and ids for each dimension
            sizesIds:
            (bs,par(i, N, _+0.5));
        };

        lin =
            parameters
            // our sizes can be int, should be faster to calculate
            : (par(i, N, int),si.bus(N*3))
              // the mixers need the float indexes and the values read from the 
tables
              <: (ids,tables(nrReadIndexes,readIndexes))
                 // the actual interpolation
            :mixers(0,nrReadIndexes)
        with {
            // the read indexes to form the closest points in the grid 
surrounding the point of interest.
            readIndexes =
                // for cleaner block diagrams
                si.bus(nParams) <:
                // the closest stored value at or below the point of interest
                ((readIndex(sizesIds) <:si.bus(nrReadIndexes))
                 // the offsets to get to the points around it
                , offsets)
                // add them up
                : ro.interleave(nrReadIndexes,2) : par(i, nrReadIndexes, +) ;
            offsets =
                // the size of a step for each dimension
                stepSizes
                // for each read index
                <: par(i, nrReadIndexes,
                       // sum up the N components that determine the final 
offset
                       par(j, N, switch(i,j)):>_)
            with {
                // the truth table of which step size to use is a binary 
counting table
                // so we use a variant on a function I wrote for slidingReduce
                switch(i,j) = _*int2bin(j,i,nrReadIndexes);
            };
            // since each interpolator has 2 inputs
            nrReadIndexes = pow(2,N);
        };

        cub =
            // same as .lin so far
            parameters
            : (par(i, N, int),si.bus(N*3))
              <: (ids,tables(nrReadIndexes,readIndexes))
            :mixers(1,nrReadIndexes)
        with {
            readIndexes =
                si.bus(nParams) <:
                ((stepSizes:ro.cross(N))
                , readIndex(sizesIds))
                // calculate the offsets for each interpolation read index and 
add them to the "base" readindex
                :cubifiers;
            // since each interpolator has 4 inputs
            nrReadIndexes = pow(4,N);
            // calculate the offsets and add them to the "base" readindex
            // we do this by creating a tree, where each branch has 4 
sub-branches
            // there are N splits, so we end up with nrReadIndexes "leaves"
            cubifiers =
                // iterate trough the dimensions, each feeding into the next
                seq(i, N,
                    si.bus(N-i-1),cubifier(pow(4,i)));
            // take a step size and ``len`` input indexes and create 4*len 
output indexes
            // offset the input index(es) with the amount needed for that 
dimension
            cubifier(len) =
                ((_<:si.bus(len*4))
                , (si.bus(len)<:si.bus(len*4)))
                : ro.interleave(len*4,2)
                :par(i, 4,
                     par(j, len, off(i)+_))
            with {
                // the hardcoded numbers are the same offsets as in 
``ba.tabulate``
                // but they get multiplied by the step size of the current 
dimension
                off(0,stepSize) = -1*stepSize;
                off(1,stepSize) = 0;
                off(2,stepSize) = 1*stepSize;
                off(3,stepSize) = 2*stepSize;
            };
        };

        // how many parameters have we been given?
        nParams = outputs(parameters);
        // the number of dimensions
        N = int(nParams/4);
        // the values we read from the tables
        tables(nrReadIndexes,readIndexes) =
            si.bus(nParams)<:
            // table size, waveform, read index for each table
            ((tableSize<:si.bus(nrReadIndexes))
            , (wf<:si.bus(nrReadIndexes))
            , readIndexes)
            :ro.interleave(nrReadIndexes,3)
             // the actual tables
            :par(i, nrReadIndexes, rdtable);

        // the interpolators
        mixers(linCub,nrReadIndexes)=
            (ro.cross(N),si.bus(nrReadIndexes))
            : seq(i, N, mixer(linCub,i));

        // the interpolator for a single dimension
        // linear version
        mixer(0,i) =
            mixerUniversal(i,2,(_,!,_),it.interpolate_linear) ;
        // cubic version
        mixer(1,i) =
            mixerUniversal(i,4,(_,!,_,!,_,!,_),it.interpolate_cubic) ;

        // i is the current dimension
        // mult is the number of inputs of each interpolator
        // sieve tells us which outputs to let trough and which to block, more 
on this soon
        mixerUniversal(i,mult,sieve,it) =
            // bypass the weights needed for the next dimension
            si.bus(N-i-1),
            // split our own weight
            // in the end we need one weight per interpolator
            // but since we need to interleave these with nrInterpolators*mult 
read values, we split it into as many busses as we have read values
            (((_<:si.bus(nrInterpolators(i)*mult))
              // the read values bypass this step
             , (si.bus(nrInterpolators(i)*mult)))
             // interleave the weights with the read values
             : ro.interleave(nrInterpolators(i)*mult,2)
               // the actual interpolators
             : par(i, nrInterpolators(i),
                   // take the id and turn it into the weight for this dimension
                   ((_<:(_-int(_)))
                    // throw away the extra weights we created for the 
interleave
                   ,sieve)
                   // a single interpolator
                   :it))
        with {
            // the number of interpolators for this dimension
            nrInterpolators(i) = pow(mult,N-i-1);
        };

        // total size of the table: s(0) * s(1)  ...  * s(N-2) * s(N-1)
        // N in, 1 out
        size(1) = _;
        size(N) = _*size(N-1);
        tableSize = size(N),par(i, N*3, !);
        // the size of a step for each dimension.
        // the first is 1, the second is the size of the first dimension times 
one
        // the third is the second number, times the size of the second 
dimension
        stepSizes =
            (int(1),si.bus(N-1),par(i, 3*N+1, !))
            : seq(i, N-1,
                  ((si.bus(i),(_<:(_,_)), si.bus(N-i-1))
                   :(si.bus(i+1),*,si.bus(N-i-2))));
        // Prepare the 'float' table read index for one parameter
        id(sizeX,r0,r1,x) = (x-r0)/(r1-r0)*(sizeX-1);
        // Prepare the 'float' table read index for all parameters
        ids =
            ro.interleave(N,4)
            : par(i, N, id) ;

        // one waveform parameter write value:
        wfp(prevSize,sizeX,r0,r1) =
            r0+
            ((float(int(ba.time%(prevSize*sizeX)/prevSize))*(r1-r0))
            /float(sizeX-1))
           ,(prevSize*sizeX);

        // all waveform parameters write values:
        wfps =
            ro.interleave(N,3)
            : (1,si.bus(3*N))
            : seq(i, N, si.bus(i),wfp, si.bus(3*N-(3*(i+1))))
            : (si.bus(N),!);

        // Create the table
        wf = (wfps,par(i, N, !)):function;

        // Limit the table read index in [0, mid] if C = 1
        // we do this both for the total table and per dimension
        rid(x,mid, 0) = int(x);
        rid(x,mid, 1) = max(int(0), min(int(x), mid));

        // for ``.val`` this is the stored value closest to the point of 
interest
        // for  ``.lin`` and ``cub`` it's the closest stored value at or below 
the point of interest
        readIndex(sizesIds) =
            // table sizes and ids for each dimension
            sizesIds
            // get the raw read index
            : ri
              // limit it and make it an int
            : riPost ;
        // helper function to route the arguments of rid
        riPost(size,ri) =
            rid(ri,size-int(1),C);
        // the raw read index
        ri =
            // interleave the sizes and the ids
            ro.interleave(N,2)
            // the first iteration gets:
            // 1 as the size of the previous dimension and 0 as the read index 
of the previous dimension
            : (1,0
                 // pass trough the sizes and ids
               ,si.bus(2*N))
              // for each dimension
            : seq(i, N,
                  // get the step size and the partial index for the next 
dimension
                  riN
                  // pass trough the sizes and ids for the next dimensions
                  , si.bus(2*(N-i-1))) ;

        // get the step size and the partial index for the next dimension
        riN(prevSize,prevIX,sizeX,idX) =
            // each step size is the previous step size times the current
            (prevSize*sizeX)
            // but the step we actually take in this dimension is the stepsize 
we calculated in the previous dimension
          , ( (prevSize*
               // round down and limit the id for this dimension to get the 
number of steps
               rid(int(idX),(sizeX-int(1)),C))
              // add the result of that to the one we calculated in the 
previous dimension
              +prevIX) ;

        // table sizes and ids for each dimension
        // just a helper function to simplify routing
        sizesIds =
            (
                // from sizes
                ( bs<:si.bus(N*2) )
                // from r0s,r1s,xs
              , si.bus(N*3)
            ) :
            // from sizes
            (si.bus(N)
             // takes (midX,r0,r1,x)
            ,ids);

        // the value of a single bit when binary counting
        int2bin(i,n,maxN) = int(int((n)/(1<<i))%int(2));
        // shortcut
        bs = si.bus(N);
};
_______________________________________________
Faudiostream-users mailing list
Faudiostream-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/faudiostream-users

Reply via email to