#ifdef HAVE_LIBFFTW3
#include "fftw++.h"
static const char *rectangular="matrix must be rectangular";
#else
static const char *installFFTW=
"Please install fftw3, then ./configure; make";
#endif
#ifdef HAVE_EIGEN_DENSE
#include <Eigen/Dense>
typedef std::complex<double> Complex;
static const char *square="matrix must be square";
using Eigen::MatrixXd;
using Eigen::MatrixXcd;
using Eigen::RealSchur;
using Eigen::ComplexSchur;
#else
static const char *installEIGEN=
"Please install eigen3, then ./configure; make";
#endif
using types::booleanArray;
using types::IntArray;
using types::IntArray2;
using types::realArray;
using types::realArray2;
using types::realArray3;
using types::pairArray;
using types::pairArray2;
using types::pairArray3;
using types::tripleArray2;
typedef callable callableReal;
void outOfBounds(const char *op, size_t len, Int n)
{
ostringstream buf;
buf << op << " array of length " << len << " with out-of-bounds index " << n;
error(buf);
}
T *A,*B;
copyArray2C(A,a,false);
copyArray2C(B,b,false);
for(size_t i=0; i < n; ++i) {
T *Ai=A+i*nb;
array *ci=new array(nb0);
(*c)[i]=ci;
for(size_t j=0; j < nb0; ++j) {
T sum=T();
size_t kj=j;
for(size_t k=0; k < nb; ++k, kj += nb0)
sum += Ai[k]*B[kj];
(*ci)[j]=sum;
}
}
delete[] B;
delete[] A;
return c;
}
// Compute transpose(A)*A where A is an n x m matrix.
template<class T>
array *AtA(array *a)
{
size_t n=checkArray(a);
size_t m=n == 0 ? 0 : checkArray(read<array*>(a,0));
array *c=new array(m);
T *A;
copyArray2C(A,a,false);
for(size_t i=0; i < m; ++i) {
array *ci=new array(m);
(*c)[i]=ci;
for(size_t j=0; j < m; ++j) {
T sum=T();
size_t kj=j;
size_t ki=i;
for(size_t k=0; k < n; ++k, kj += m, ki += m)
sum += A[ki]*A[kj];
(*ci)[j]=sum;
}
}
// Transpose an n x n matrix in place.
void transpose(double *a, size_t n)
{
for(size_t i=1; i < n; i++) {
for(size_t j=0; j < i; j++) {
size_t ij=n*i+j;
size_t ji=n*j+i;
double temp=a[ij];
a[ij]=a[ji];
a[ji]=temp;
}
}
}
// Invert an n x n array in place.
void inverse(double *M, size_t n)
{
if(n == 2) {
real a=M[0];
real b=M[1];
real c=M[2];
real d=M[3];
real det=a*d-b*c;
if(det == 0.0)
error(singular);
det=1.0/det;
M[0]=d*det;
M[1]=-b*det;
M[2]=-c*det;
M[3]=a*det;
return;
}
if(n == 3) {
real a=M[0], b=M[1], c=M[2];
real d=M[3], e=M[4], f=M[5];
real g=M[6], h=M[7], i=M[8];
real A=e*i-f*h;
real B=f*g-d*i;
real C=d*h-e*g;
real det=a*A+b*B+c*C;
if(det == 0.0)
error(singular);
det=1.0/det;
size_t col=0, row=0;
// This is the main loop over the columns to be reduced.
for(size_t i=0; i < n; i++) {
real big=0.0;
// This is the outer loop of the search for a pivot element.
for(size_t j=0; j < n; j++) {
double *aj=M+n*j;
if(pivot[j] != 1) {
for(size_t k=0; k < n; k++) {
if(pivot[k] == 0) {
real temp=fabs(aj[k]);
if(temp >= big) {
big=temp;
row=j;
col=k;
}
} else if(pivot[k] > 1) {
inverseDeallocate();
error(singular);
}
}
}
}
++(pivot[col]);
// Interchange rows, if needed, to put the pivot element on the diagonal.
double *acol=M+n*col;
if(row != col) {
double *arow=M+n*row;
for(size_t k=0; k < n; k++) {
real temp=arow[k];
arow[k]=acol[k];
acol[k]=temp;
}
}
Row[i]=row;
Col[i]=col;
// Divide the pivot row by the pivot element.
real denom=acol[col];
if(denom == 0.0) {
inverseDeallocate();
error(singular);
}
real pivinv=1.0/denom;
acol[col]=1.0;
for(size_t k=0; k < n; k++)
acol[k]=acol[k]*pivinv;
// Reduce all rows except for the pivoted one.
for(size_t k=0; k < n; k++) {
if(k != col) {
double *ak=M+n*k;
real akcol=ak[col];
ak[col]=0.0;
for(size_t j=0; j < n; j++)
ak[j] -= acol[j]*akcol;
}
}
}
// Unscramble the inverse matrix in view of the column interchanges.
for(size_t k=n; k > 0;) {
k--;
size_t r=Row[k];
size_t c=Col[k];
if(r != c) {
for(size_t j=0; j < n; j++) {
double *aj=M+n*j;
real temp=aj[r];
aj[r]=aj[c];
aj[c]=temp;
}
}
}
inverseDeallocate();
}
}
// Create an empty array.
array* :emptyArray()
{
return new array(0);
}
// Create a new array (technically a vector).
// This array will be multidimensional. First the number of dimensions
// is popped off the stack, followed by each dimension in reverse order.
// The array itself is technically a one dimensional array of one
// dimension arrays and so on.
array* :newDeepArray(Int depth)
{
assert(depth > 0);
Int *dims = new Int[depth];
for (Int index = depth-1; index >= 0; index--) {
Int i=pop<Int>(Stack);
if(i < 0) error("cannot create a negative length array");
dims[index]=i;
}
// Creates an array with elements already specified. First, the number
// of elements is popped off the stack, followed by each element in
// reverse order.
array* :newInitializedArray(Int n)
{
assert(n >= 0);
array *a = new array(n);
for (Int index = n-1; index >= 0; index--)
(*a)[index] = pop(Stack);
return a;
}
// Similar to newInitializedArray, but after the n elements, append another
// array to it.
array* :newAppendedArray(array* tail, Int n)
{
assert(n >= 0);
array *a = new array(n);
for (Int index = n-1; index >= 0; index--)
(*a)[index] = pop(Stack);
// Produce an array of n deep copies of value.
// typeDepth is the true depth of the array determined at compile-time when the
// operations for the array type are added. This typeDepth argument is
// automatically pushed on the stack and is not visible to the user.
array* :copyArrayValue(Int n, item value, Int depth=Int_MAX, Int typeDepth)
{
if(n < 0) error("cannot create a negative length array");
if(depth < 0) error("cannot copy to a negative depth");
if(depth > typeDepth) depth=typeDepth;
return new array((size_t) n, value, depth);
}
// Deep copy of array.
// typeDepth is the true depth of the array determined at compile-time when the
// operations for the array type are added. This typeDepth argument is
// automatically pushed on the stack and is not visible to the user.
array* :copyArray(array *a, Int depth=Int_MAX, Int typeDepth)
{
if(a == 0) vm::error(dereferenceNullArray);
if(depth < 0) error("cannot copy to a negative depth");
if(depth > typeDepth) depth=typeDepth;
return a->copyToDepth(depth);
}
// Read an element from an array. Checks for initialization & bounds.
item :arrayRead(array *a, Int n)
{
item& i=arrayRead(a,n);
if (i.empty()) {
ostringstream buf;
buf << "read uninitialized value from array at index " << n;
error(buf);
}
return i;
}
// Slice a substring from an array.
item :arraySliceRead(array *a, Int left, Int right)
{
checkArray(a);
return a->slice(left, right);
}
// Slice a substring from an array. This implements the cases a[i:] and a[:]
// where the endpoint is not given, and assumed to be the length of the array.
item :arraySliceReadToEnd(array *a, Int left)
{
size_t len=checkArray(a);
return a->slice(left, (Int)len);
}
// Read an element from an array of arrays. Check bounds and initialize
// as necessary.
item :arrayArrayRead(array *a, Int n)
{
item& i=arrayRead(a,n);
if (i.empty()) i=new array(0);
return i;
}
// Write an element to an array. Increase size if necessary.
// TODO: Add arrayWriteAndPop
item :arrayWrite(array *a, Int n, item value)
{
size_t len=checkArray(a);
bool cyclic=a->cyclic();
if(cyclic && len > 0) n=imod(n,len);
else {
if(cyclic) outOfBounds("writing cyclic",len,n);
if(n < 0) outOfBounds("writing",len,n);
if(len <= (size_t) n)
a->resize(n+1);
}
(*a)[n] = value;
return value;
}
array * :arraySliceWrite(array *dest, Int left, Int right, array *src)
{
checkArray(src);
checkArray(dest);
dest->setSlice(left, right, src);
return src;
}
// Check to see if an array element is initialized.
bool :arrayInitializedHelper(Int n, array *a)
{
size_t len=checkArray(a);
bool cyclic=a->cyclic();
if(cyclic && len > 0) n=imod(n,len);
else if(n < 0 || n >= (Int) len) return false;
item&i=(*a)[(unsigned) n];
return !i.empty();
}
// Returns the initialize method for an array.
callable* :arrayInitialized(array *a)
{
return new thunk(new bfunc(arrayInitializedHelper),a);
}
// The helper function for the cyclic method that sets the cyclic flag.
void :arrayCyclicHelper(bool b, array *a)
{
checkArray(a);
a->cyclic(b);
}
// Set the cyclic flag for an array.
callable* :arrayCyclic(array *a)
{
return new thunk(new bfunc(arrayCyclicHelper),a);
}
// The helper function for the push method that does the actual operation.
item :arrayPushHelper(item x, array *a)
{
checkArray(a);
a->push(x);
return x;
}
// Returns the push method for an array.
callable* :arrayPush(array *a)
{
return new thunk(new bfunc(arrayPushHelper),a);
}
// The helper function for the append method that appends b to a.
void :arrayAppendHelper(array *b, array *a)
{
checkArray(a);
size_t size=checkArray(b);
for(size_t i=0; i < size; i++)
a->push((*b)[i]);
}
// Returns the append method for an array.
callable* :arrayAppend(array *a)
{
return new thunk(new bfunc(arrayAppendHelper),a);
}
// The helper function for the pop method.
item :arrayPopHelper(array *a)
{
size_t asize=checkArray(a);
if(asize == 0)
error("cannot pop element from empty array");
return a->pop();
}
// Returns the pop method for an array.
callable* :arrayPop(array *a)
{
return new thunk(new bfunc(arrayPopHelper),a);
}
// The helper function for the insert method.
item :arrayInsertHelper(Int i, array *x, array *a)
{
size_t asize=checkArray(a);
checkArray(x);
if(a->cyclic() && asize > 0) i=imod(i,asize);
if(i < 0 || i > (Int) asize)
outOfBounds("inserting",asize,i);
(*a).insert((*a).begin()+i,(*x).begin(),(*x).end());
}
// Returns the insert method for an array.
callable* :arrayInsert(array *a)
{
return new thunk(new bfunc(arrayInsertHelper),a);
}
// Returns the delete method for an array.
callable* :arrayDelete(array *a)
{
return new thunk(new bfunc(arrayDeleteHelper),a);
}
// Return array formed by indexing array a with elements of integer array b
array* :arrayIntArray(array *a, array *b)
{
size_t asize=checkArray(a);
size_t bsize=checkArray(b);
array *r=new array(bsize);
bool cyclic=a->cyclic();
for(size_t i=0; i < bsize; i++) {
Int index=read<Int>(b,i);
if(cyclic && asize > 0) index=imod(index,asize);
else
if(index < 0 || index >= (Int) asize)
outOfBounds("reading",asize,index);
(*r)[i]=(*a)[index];
}
return r;
}
// returns the complement of the integer array a in {0,2,...,n-1},
// so that b[complement(a,b.length)] yields the complement of b[a].
Intarray* complement(Intarray *a, Int n)
{
size_t asize=checkArray(a);
array *r=new array(0);
bool *keep=new bool[n];
for(Int i=0; i < n; ++i) keep[i]=true;
for(size_t i=0; i < asize; ++i) {
Int j=read<Int>(a,i);
if(j >= 0 && j < n) keep[j]=false;
}
for(Int i=0; i < n; i++)
if(keep[i]) r->push(i);
delete[] keep;
return r;
}
// Generate the sequence {f(i) : i=0,1,...n-1} given a function f and integer n
Intarray* :arraySequence(callable *f, Int n)
{
if(n < 0) n=0;
array *a=new array(n);
for(Int i=0; i < n; ++i) {
Stack->push(i);
f->call(Stack);
(*a)[i]=pop(Stack);
}
return a;
}
// Apply a function to each element of an array
array* :arrayFunction(callable *f, array *a)
{
size_t size=checkArray(a);
array *b=new array(size);
for(size_t i=0; i < size; ++i) {
Stack->push((*a)[i]);
f->call(Stack);
(*b)[i]=pop(Stack);
}
return b;
}
// a is a rectangular 3D array; perm is an Int array indicating the type of
// permutation (021 or 120, etc; original is 012).
// Transpose by sending respective members to the permutated locations:
// return the array obtained by putting a[i][j][k] into position perm{ijk}.
array* :array3Transpose(array *a, array *perm)
{
const size_t DIM=3;
if(checkArray(perm) != DIM) {
ostringstream buf;
buf << "permutation array must have length " << DIM;
error(buf);
}
size_t* size=new size_t[DIM];
for(size_t i=0; i < DIM; ++i) size[i]=DIM;
for(size_t i=0; i < DIM; ++i) {
Int p=read<Int>(perm,i);
size_t P=(size_t) p;
if(p < 0 || P >= DIM) {
ostringstream buf;
buf << "permutation index out of range: " << p;
error(buf);
}
size[P]=P;
}
for(size_t i=0; i < DIM; ++i)
if(size[i] == DIM) error("permutation indices must be distinct");
// Find the index of the nth true value in a boolean array or -1 if not found.
// If n is negative, search backwards.
Int find(boolarray *a, Int n=1)
{
size_t size=checkArray(a);
Int j=-1;
if(n > 0)
for(size_t i=0; i < size; i++)
if(read<bool>(a,i)) {
n--; if(n == 0) {j=(Int) i; break;}
}
if(n < 0)
for(size_t i=size; i > 0;)
if(read<bool>(a,--i)) {
n++; if(n == 0) {j=(Int) i; break;}
}
return j;
}
// Find all indices of true values in a boolean array.
Intarray *findall(boolarray *a)
{
size_t size=checkArray(a);
array *b=new array(0);
for(size_t i=0; i < size; i++) {
if(read<bool>(a,i)) {
b->push((Int) i);
}
}
return b;
}
// construct vector obtained by replacing those elements of b for which the
// corresponding elements of a are false by the corresponding element of c.
array* :arrayConditional(array *a, array *b, array *c)
{
size_t size=checkArray(a);
array *r=new array(size);
if(b && c) {
checkArrays(a,b);
checkArrays(b,c);
for(size_t i=0; i < size; i++)
(*r)[i]=read<bool>(a,i) ? (*b)[i] : (*c)[i];
} else {
r->clear();
if(b) {
checkArrays(a,b);
for(size_t i=0; i < size; i++)
if(read<bool>(a,i)) r->push((*b)[i]);
} else if(c) {
checkArrays(a,c);
for(size_t i=0; i < size; i++)
if(!read<bool>(a,i)) r->push((*c)[i]);
}
}
return r;
}
// Return an n x n identity matrix.
realarray2 *identity(Int n)
{
return Identity(n);
}
// Return the inverse of an n x n matrix a using Gauss-Jordan elimination.
realarray2 *inverse(realarray2 *a)
{
size_t n=checkArray(a);
double *A;
copyArray2C(A,a,true,0,NoGC);
inverse(A,n);
a=copyCArray2(n,n,A);
delete[] A;
return a;
}
// Solve the linear equation ax=b by LU decomposition, returning the
// solution x, where a is an n x n matrix and b is an array of length n.
// If no solution exists, return an empty array.
realarray *solve(realarray2 *a, realarray *b, bool warn=true)
{
size_t n=checkArray(a);
real *A;
copyArray2C(A,a);
size_t *index=new size_t[n];
if(LUdecompose(A,n,index,warn) == 0)
return new array(0);
array *x=new array(n);
real *B;
copyArrayC(B,b);
for(size_t i=0; i < n; ++i) {
size_t ip=index[i];
real sum=B[ip];
B[ip]=B[i];
real *Ai=A+i*n;
for(size_t j=0; j < i; ++j)
sum -= Ai[j]*B[j];
B[i]=sum;
}
for(size_t i=n; i > 0;) {
--i;
real sum=B[i];
real *Ai=A+i*n;
for(size_t j=i+1; j < n; ++j)
sum -= Ai[j]*B[j];
B[i]=sum/Ai[i];
}
for(size_t i=0; i < n; ++i)
(*x)[i]=B[i];
delete[] index;
delete[] B;
delete[] A;
return x;
}
// Solve the linear equation ax=b by LU decomposition, returning the
// solution x, where a is an n x n matrix and b is an n x m matrix.
// If no solution exists, return an empty array.
realarray2 *solve(realarray2 *a, realarray2 *b, bool warn=true)
{
size_t n=checkArray(a);
real *A,*B;
copyArray2C(A,a);
copyArray2C(B,b,false);
size_t *index=new size_t[n];
if(LUdecompose(A,n,index,warn) == 0)
return new array(0);
array *x=new array(n);
for(size_t i=0; i < n; ++i) {
real *Ai=A+i*n;
real *Bi=B+i*m;
real *Bip=B+index[i]*m;
for(size_t k=0; k < m; ++k) {
real sum=Bip[k];
Bip[k]=Bi[k];
size_t jk=k;
for(size_t j=0; j < i; ++j, jk += m)
sum -= Ai[j]*B[jk];
Bi[k]=sum;
}
}
for(size_t i=n; i > 0;) {
--i;
real *Ai=A+i*n;
real *Bi=B+i*m;
for(size_t k=0; k < m; ++k) {
real sum=Bi[k];
size_t jk=(i+1)*m+k;
for(size_t j=i+1; j < n; ++j, jk += m)
sum -= Ai[j]*B[jk];
Bi[k]=sum/Ai[i];
}
}
for(size_t i=0; i < n; ++i) {
real *Bi=B+i*m;
array *xi=new array(m);
(*x)[i]=xi;
for(size_t j=0; j < m; ++j)
(*xi)[j]=Bi[j];
}
delete[] index;
delete[] B;
delete[] A;
return x;
}
// Compute the determinant of an n x n matrix.
real determinant(realarray2 *a)
{
real *A;
copyArray2C(A,a);
size_t n=checkArray(a);
real det=LUdecompose(A,n,NULL,false);
size_t n1=n+1;
for(size_t i=0; i < n; ++i)
det *= A[i*n1];
delete[] A;
return det;
}
realarray *Operator *(realarray2 *a, realarray *b)
{
size_t n=checkArray(a);
size_t m=checkArray(b);
array *c=new array(n);
real *B;
copyArrayC(B,b);
for(size_t i=0; i < n; ++i) {
array *ai=read<array*>(a,i);
if(checkArray(ai) != m) error(incommensurate);
real sum=0.0;
for(size_t j=0; j < m; ++j)
sum += read<real>(ai,j)*B[j];
(*c)[i]=sum;
}
delete[] B;
return c;
}
// Compute the dot product of vectors a and b.
real dot(realarray *a, realarray *b)
{
size_t n=checkArrays(a,b);
real sum=0.0;
for(size_t i=0; i < n; ++i)
sum += read<real>(a,i)*read<real>(b,i);
return sum;
}
// Compute the complex dot product of vectors a and b.
pair dot(pairarray *a, pairarray *b)
{
size_t n=checkArrays(a,b);
pair sum=zero;
for(size_t i=0; i < n; ++i)
sum += read<pair>(a,i)*conj(read<pair>(b,i));
return sum;
}
// Solve the problem L\inv f, where f is an n vector and L is the n x n matrix
//
// [ b[0] c[0] a[0] ]
// [ a[1] b[1] c[1] ]
// [ a[2] b[2] c[2] ]
// [ ... ]
// [ c[n-1] a[n-1] b[n-1] ]
realarray *tridiagonal(realarray *a, realarray *b, realarray *c, realarray *f)
{
size_t n=checkArrays(a,b);
checkEqual(n,checkArray(c));
checkEqual(n,checkArray(f));
array *up=new array(n);
array& u=*up;
if(n == 0) return up;
// Special case: zero Dirichlet boundary conditions
if(read<real>(a,0) == 0.0 && read<real>(c,n-1) == 0.0) {
real temp=read<real>(b,0);
if(temp == 0.0) dividebyzero();
temp=1.0/temp;
real *work=new real[n];
u[0]=read<real>(f,0)*temp;
work[0]=-read<real>(c,0)*temp;
for(size_t i=1; i < n; i++) {
real temp=(read<real>(b,i)+read<real>(a,i)*work[i-1]);
if(temp == 0.0) {delete[] work; dividebyzero();}
temp=1.0/temp;
u[i]=(read<real>(f,i)-read<real>(a,i)*read<real>(u,i-1))*temp;
work[i]=-read<real>(c,i)*temp;
}
for(size_t i=n-1; i >= 1; i--)
u[i-1]=read<real>(u,i-1)+work[i-1]*read<real>(u,i);
delete[] work;
return up;
}
real binv=read<real>(b,0);
if(binv == 0.0) dividebyzero();
binv=1.0/binv;
real *gamma=new real[n-2];
real *delta=new real[n-2];
gamma[0]=read<real>(c,0)*binv;
delta[0]=read<real>(a,0)*binv;
u[0]=read<real>(f,0)*binv;
real beta=read<real>(c,n-1);
real fn=read<real>(f,n-1)-beta*read<real>(u,0);
real alpha=read<real>(b,n-1)-beta*delta[0];
// Find a root for the specified continuous (but not necessarily
// differentiable) function. Whatever value t is returned, it is guaranteed
// that t is within [a, b] and within tolerance of a sign change.
// An error is thrown if fa and fb are both positive or both negative.
//
// In this implementation, the binary search is interleaved
// with a modified version of quadratic interpolation.
// This is a C++ port of the Asymptote routine written by Charles Staats III.
real _findroot(callableReal *f, real a, real b, real tolerance,
real fa, real fb)
{
if(fa == 0.0) return a;
if(fb == 0.0) return b;
const char* oppsign="fa and fb must have opposite signs";
int sign;
// If halving the interval already puts us within tolerance,
// don't bother with the interpolation step.
if(b-a >= twicetolerance) {
real factor=1.0/(b-a);
real q_A=2.0*(fa-2.0*ft+fb)*factor*factor;
real q_B=(fb-fa)*factor;
quadraticroots Q=quadraticroots(q_A,q_B,ft);
// If the interpolation somehow failed, continue on to the next binary
// search step. This may or may not be possible, depending on what
// theoretical guarantees are provided by the quadraticroots function.
real root;
bool found=Q.roots > 0;
if(found) {
root=t+Q.t1;
if(root <= a || root >= b) {
if(Q.roots == 1) found=false;
else {
root=t+Q.t2;
if(root <= a || root >= b) found=false;
}
}
}
// If the interpolated value is close to one edge of
// the interval, move it farther away from the edge in
// an effort to catch the root in the middle.
real margin=(b-a)*1.0e-3;
if(t-a < margin) t=a+2.0*(t-a);
else if(b-t < margin) t=b-2.0*(b-t);
real simpson(callableReal *f, real a, real b, real acc=DBL_EPSILON,
real dxmax=0)
{
real integral;
if(dxmax <= 0) dxmax=fabs(b-a);
callable *oldFunc=Func;
Func=f;
FuncStack=Stack;
if(!simpson(integral,wrapFunction,a,b,acc,dxmax))
error("nesting capacity exceeded in simpson");
Func=oldFunc;
return integral;
}
// Compute the fast Fourier transform of a pair array
pairarray* fft(pairarray *a, Int sign=1)
{
#ifdef HAVE_LIBFFTW3
unsigned n=(unsigned) checkArray(a);
array *c=new array(n);
if(n) {
Complex *f=utils::ComplexAlign(n);
fftwpp::fft1d Forward(n,intcast(sign),f);
Intarray2 *triangulate(pairarray *z)
{
size_t nv=checkArray(z);
// Call robust version of Gilles Dumoulin's port of Paul Bourke's
// triangulation code.
real norm(realarray *a)
{
size_t n=checkArray(a);
real M=0.0;
for(size_t i=0; i < n; ++i) {
real x=fabs(vm::read<real>(a,i));
if(x > M) M=x;
}
return M;
}
real norm(realarray2 *a)
{
size_t n=checkArray(a);
real M=0.0;
for(size_t i=0; i < n; ++i) {
vm::array *ai=vm::read<vm::array*>(a,i);
size_t m=checkArray(ai);
for(size_t j=0; j < m; ++j) {
real a=fabs(vm::read<real>(ai,j));
if(a > M) M=a;
}
}
return M;
}
real norm(triplearray2 *a)
{
size_t n=checkArray(a);
real M=0.0;
for(size_t i=0; i < n; ++i) {
vm::array *ai=vm::read<vm::array*>(a,i);
size_t m=checkArray(ai);
for(size_t j=0; j < m; ++j) {
real a=vm::read<triple>(ai,j).abs2();
if(a > M) M=a;
}
}
return sqrt(M);
}