![JAR search and dependency download from the Maven repository](/logo.png)
net.algart.math.functions.ProjectiveOperator Maven / Gradle / Ivy
Show all versions of algart Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.algart.math.functions;
import net.algart.math.Point;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import java.util.Arrays;
/**
* Projective operator (projective transformation):
* O f(x) = f(y),
* yi =
* (aix + bi) / (cx + d)
* (ai means the line i of the matrix A),
* where the numeric n x n matrix A,
* the n-dimensional vectors b and c and the number d
* are parameters of the transformation.
* In other words, the argument of the function, the vector x, is mapped to the following vector y:
*
*
*
*
*
* y =
* y0
y1
...
yn−1
* =
*
*
* (A0x + b0) / (cx + d)
* (A1x + b1) / (cx + d)
* ...
* (An−1x + bn−1) / (cx + d)
*
* =
*
*
* (a00x0 + a01x1 + ...
* + a0,n−1xn−1 + b0) /
* (c0x0 + c1x1 + ...
* + cn−1xn−1 + d)
* (a10x0 + a11x1 + ...
* + a1,n−1xn−1 + b1) /
* (c0x0 + c1x1 + ...
* + cn−1xn−1 + d)
* ...
* (an−1,0x0
* + an−1,1x1 + ...
* + an−1,n−1xn−1
* + bn−1) /
* (c0x0 + c1x1 + ...
* + cn−1xn−1 + d)
*
*
*
*
*
* However, please note: we do not guarantee that the divisions in the formulas above are performed strictly
* by "c=a/b
" Java operator.
* They are possibly performed via the following code: "temp=1.0/b; c=a*temp;
"
* The difference here is very little and not important for most practical needs.
*
* Please note: if c vector is zero (all ci=0) —
* in other words, if this transformation is really affine — then an instance of this class
* is always an instance of its inheritor {@link LinearOperator}.
* This rule is provided by the instantiation methods
* {@link #getInstance(double[], double[], double[], double)} getInstance} and
* {@link #getInstanceByPoints(net.algart.math.Point[], net.algart.math.Point[])} getInstanceByPoints}.
*
* This class is immutable and thread-safe:
* there are no ways to modify settings of the created instance.
*
* @author Daniel Alievsky
* @see LinearOperator
*/
public class ProjectiveOperator
extends AbstractCoordinateTransformationOperator
implements CoordinateTransformationOperator {
final double[] a; // null if diagonal (in particular, identity)
final double[] diagonal; // null if non-diagonal or identity
final double[] b;
final double[] c; // null if all c[i]=0 (affine transformation)
final double d;
final int n;
final boolean zeroB;
ProjectiveOperator(double[] a, double[] diagonal, double[] b, double[] c, double d) {
assert b != null;
if (b.length == 0) {
throw new IllegalArgumentException("Empty b vector (no coordinates)");
}
if (a != null && a.length != (long) b.length * (long) b.length) {
throw new IllegalArgumentException("Illegal size of A matrix: a.length=" + a.length
+ " must be equal to b.length^2=" + (long) b.length * (long) b.length);
}
if (diagonal != null && diagonal.length != b.length) {
throw new IllegalArgumentException("b and diagonal vector lengths mismatch: diagonal.length="
+ diagonal.length + ", b.length=" + b.length);
}
if (c != null) {
if (c.length != b.length) {
throw new IllegalArgumentException("b and c vector lengths mismatch: b.length="
+ b.length + ", c.length=" + c.length);
}
}
this.n = b.length;
if (a != null) {
boolean isDiag = true;
boolean unitDiag = true;
for (int i = 0, disp = 0; i < this.n; i++) {
for (int j = 0; j < this.n; j++, disp++) {
if (i == j) {
unitDiag &= a[disp] == 1.0;
} else {
isDiag &= a[disp] == 0.0;
}
}
}
if (isDiag) {
if (!unitDiag) {
diagonal = new double[this.n];
for (int i = 0, disp = 0; i < this.n; i++, disp += this.n + 1) {
diagonal[i] = a[disp];
}
}
a = null;
}
} else if (diagonal != null) {
boolean unitDiag = true;
for (double v : diagonal) {
unitDiag &= v == 1.0;
}
if (unitDiag) {
diagonal = null;
}
}
this.a = a;
this.diagonal = diagonal;
this.b = b;
boolean zeroC = true;
if (c != null) {
for (double v : c) {
zeroC &= v == 0.0;
}
}
this.c = zeroC ? null : c;
this.d = d;
boolean zeroB = true;
for (double v : b) {
zeroB &= v == 0.0;
}
this.zeroB = zeroB;
}
/**
* Returns an instance of this class, describing the projective operator with the specified matrix A,
* the vectors b and c and the number d.
* See the {@link ProjectiveOperator comments to this class} for more details.
* The coordinates of the vectors b and c must be listed in b
and c
arrays.
* The elements of the matrix A must be listed, row by row, in the a
array:
* A={aij},
* aij=a[i*n+j]
,
* i is the index of the row (0..n-1),
* j is the index of the column (0..n-1),
* n=b.length
.
* The lengths of b
and c
arrays must be the same:
* b.length
=c.length
=n.
* The length a.length
of the a
array must be equal to its square n2.
* Empty arrays (n=0) are not allowed.
*
* Please note: the returned operator can have another A, b, c, d parameters
* (returned by {@link #a()}, {@link #b()}, {@link #c()}, {@link #d()} methods),
* than specified in the arguments of this method.
* Namely, all these numbers can be multiplied by some constant: such modification does not change
* the projective transformation.
*
*
In particular, if the arguments of this method really describe
* an affine transformation (c=0), then this method
* returns an instance of {@link LinearOperator} class, where all elements of A matrix
* and b vector are divided by d number.
*
*
The passed a
, b
and c
Java arrays are cloned by this method:
* no references to them are maintained by the created instance.
*
* @param a the elements of A matrix.
* @param b the coordinates of b vector.
* @param c the coordinates of c vector.
* @param d the d parameter.
* @return the projective operator described by these parameters.
* @throws NullPointerException if one of the arguments of the method is {@code null}.
* @throws IllegalArgumentException if b.length==0
, c.length==0
,
* b.length!=c.length
* or a.length!=b.length2
.
*/
public static ProjectiveOperator getInstance(double[] a, double[] b, double[] c, double d) {
Objects.requireNonNull(a, "Null A matrix");
Objects.requireNonNull(b, "Null b vector");
Objects.requireNonNull(c, "Null c vector");
if (c.length != b.length) {
throw new IllegalArgumentException("b and c vector lengths mismatch: b.length="
+ b.length + ", c.length=" + c.length);
}
if (a.length != ((long) b.length) * ((long) b.length)) {
throw new IllegalArgumentException("Illegal size of A matrix: a.length=" + a.length
+ " must be equal to b.length^2=" + (b.length * b.length));
}
// to be on the safe side, we check all this before following operations - we not wait for the constructor
boolean zeroC = true;
for (double v : c) {
zeroC &= v == 0.0;
}
if (zeroC) {
a = a.clone();
b = b.clone();
if (d != 1.0) {
for (int k = 0; k < a.length; k++) {
a[k] /= d;
}
for (int k = 0; k < b.length; k++) {
b[k] /= d;
}
}
return new LinearOperator(a, null, b);
}
return new ProjectiveOperator(a.clone(), null, b.clone(), c.clone(), d);
}
/**
* Returns the n-dimensional projective operator, that transforms (maps)
* the given n+2 points
* p0, p1, ..., pn+1 to
* the given another n+2 points
* q0, q1, ..., qn+1
* of the n-dimensional space. The parameter d in the returned operator is 1.0.
* In other words, the matrix A, the vectors b, c and the parameter d
* in the returned operator fulfil the following conditions:
*
*
*
*
*
* d = 1.0;
*
*
*
*
* (A0pk + b0) /
* (cpk + 1)
* (A1pk + b1) /
* (cpk + 1)
* ...
* (An−1pk + bn−1) /
* (cpk + 1)
*
* =
* qk0
qk1
...
* qk,n−1
* = qk for k = 0, 1, ..., n+1
*
*
*
*
* (ai means the line i of the matrix A).
* It is possible that there is no such operator
* or there are many different solutions (degenerated cases).
* In this case, this method still returns some operator, but some coefficients of A matrix,
* b and c vectors in the returned operator will probably be Double.NaN
,
* Double.POSITIVE_INFINITY
or Double.NEGATIVE_INFINITY
.
*
*
All passed points must be n-dimensional,
* where n+2=p.length=q.length
.
*
* @param q the destination points.
* @param p the source points.
* @return the n-dimensional projective operator, which maps pi to
* qi for all i=0,1,2,...,n+1.
* @throws NullPointerException if one of arguments of this method or one of elements of
* p
and q
arrays {@code null}.
* @throws IllegalArgumentException if the lengths of the passed p
and
* q
arrays are not equal,
* or if for some k
* p[k].{@link Point#coordCount() coordCount()}!=p.length-2
or
* q[k].{@link Point#coordCount() coordCount()}!=p.length-2
.
* @throws OutOfMemoryError if there is not enough Java memory for storing Java array
* double[n*(n+2)*n*(n+2)]
,
* where n+2=p.length
,
* or if n*(n+2)*n*(n+2)>Integer.MAX_VALUE
.
*/
public static ProjectiveOperator getInstanceByPoints(Point[] q, Point[] p) {
Objects.requireNonNull(p, "Null p argument");
Objects.requireNonNull(q, "Null q argument");
if (p.length != q.length) {
throw new IllegalArgumentException("p and q point arrays lengths mismatch: p.length=" +
p.length + ", q.length=" + q.length);
}
if (p.length == 0) {
throw new IllegalArgumentException("Empty p and q arrays");
}
final int n = p.length - 2;
long numberOfUnknowns = (long) n * (long) (n + 2); // number of unknowns
if (numberOfUnknowns > Integer.MAX_VALUE || numberOfUnknowns * numberOfUnknowns > Integer.MAX_VALUE) {
throw new OutOfMemoryError("Too large necessary matrix (more than Integer.MAX_VALUE elements)");
}
for (int k = 0; k < p.length; k++) {
if (p[k].coordCount() != n) {
throw new IllegalArgumentException("n+2 n-dimensional points are necessary to "
+ "find the projective operator, but we have " + (n + 2) + " points, "
+ "and the source point #" + k + " is " + p[k].coordCount() + "-dimensional");
}
if (q[k].coordCount() != n) {
throw new IllegalArgumentException("n+2 n-dimensional points are necessary to "
+ "find the projective operator, but we have " + (n + 2) + " points, "
+ "and the destination point #" + k + " is " + q[k].coordCount() + "-dimensional");
}
}
// px0*a00 + py0*a01 + pz0*a02 + ... + bx - qx0*px0*cx - qx0*py0*cy - qx0*pz0*cz* - ... = qx0 (*d=1)
// px1*a00 + py1*a01 + pz1*a02 + ... + bx - qx1*px1*cx - qx1*py1*cy - qx1*pz1*cz* - ... = qx1
// px2*a00 + py2*a01 + pz2*a02 + ... + bx - qx2*px2*cx - qx2*py2*cy - qx2*pz2*cz* - ... = qx2
// ...
// px0*a10 + py0*a11 + pz0*a12 + ... + by - qy0*px0*cx - qy0*py0*cy - qy0*pz0*cz* - ... = qy0 (*d=1)
// px1*a10 + py1*a11 + pz1*a12 + ... + by - qy1*px1*cx - qy1*py1*cy - qy1*pz1*cz* - ... = qy1
// px2*a10 + py2*a11 + pz2*a12 + ... + by - qy2*px2*cx - qy2*py2*cy - qy2*pz2*cz* - ... = qy2
// etc.
// In other words, here is m*m equation system Sv=t, m=(n+2)*n,
// for finding m unknowns a00, a01, ..., a10, a11, ..., bx, by, ..., cx, cy, ....
final int m = (int) numberOfUnknowns;
double[] s = new double[m * m]; // zero-filled
double[] t = new double[m];
double[] v = new double[m];
for (int i = 0, k = 0; i < n; i++) {
for (int pointIndex = 0; pointIndex < p.length; pointIndex++, k++) {
// filling a line of S matrix and an element of t vector #k=0,1,...,m-1
for (int j = 0, sOfs = k * m + i * n; j < n; j++, sOfs++) {
s[sOfs] = p[pointIndex].coord(j); // coefficient for aIJ
}
s[k * m + n * n + i] = 1.0; // coefficient for bx, by, ...
for (int j = 0, sOfs = k * m + n * n + n; j < n; j++, sOfs++) {
s[sOfs] = -q[pointIndex].coord(i) * p[pointIndex].coord(j); // coefficient for cx, cy, ...
}
t[k] = q[pointIndex].coord(i);
}
}
// for (int i = 0; i < m; i++) {
// for (int j = 0; j < n * n; j++) {
// System.out.printf("%7.3f ", s[i * m + j]);
// }
// System.out.print(" | ");
// for (int j = 0; j < n; j++) {
// System.out.printf("%7.3f ", s[i * m + n * n + j]);
// }
// System.out.print(" | ");
// for (int j = 0; j < n; j++) {
// System.out.printf("%7.3f ", s[i * m + n * n + n + j]);
// }
// System.out.printf("= %.7f%n", t[i]);
// }
LinearOperator.solveLinearEquationsSet(v, s, t);
// LinearOperator.solveLinearEquationsSetByCramerMethod(v, s, t);
assert v.length == m;
double[] a = new double[n * n];
double[] b = new double[n];
double[] c = new double[n];
System.arraycopy(v, 0, a, 0, n * n);
System.arraycopy(v, n * n, b, 0, n);
System.arraycopy(v, n * n + n, c, 0, n);
return getInstance(a, b, c, 1.0);
}
/**
* Returns an array containing A matrix.
*
*
The returned array is always newly created: it is not a reference
* to some internal data stored in this object.
*
* @return A matrix.
* @throws OutOfMemoryError if this instance was created by some creation method of
* the {@link LinearOperator} class,
* besides {@link LinearOperator#getInstance(double[], double[])},
* and the matrix is too large to be stored in Java memory
* or its size is greater than Integer.MAX_VALUE
.
*/
public final double[] a() {
if (a != null) {
return a.clone();
}
if ((long) n * (long) n > Integer.MAX_VALUE) {
throw new OutOfMemoryError("Too large matrix A (more than Integer.MAX_VALUE elements)");
}
double[] result = new double[n * n];
for (int i = 0, disp = 0; i < n; i++, disp += n + 1) {
result[disp] = diagonal == null ? 1.0 : diagonal[i];
}
return result;
}
/**
* Returns an array containing b vector.
*
*
The returned array is always newly created: it is not a reference
* to some internal data stored in this object.
*
* @return b vector.
*/
public final double[] b() {
return b.clone();
}
/**
* Returns an array containing c vector.
* In a case of {@link LinearOperator}, the result is always zero-filled.
*
*
The returned array is always newly created: it is not a reference
* to some internal data stored in this object.
*
* @return c vector.
*/
public final double[] c() {
return c == null ? new double[n] : c.clone();
}
/**
* Returns the d parameter.
* In a case of {@link LinearOperator}, the result is always 0.0.
*
* @return d parameter.
*/
public final double d() {
return d;
}
/**
* Returns an array containing the main diagonal of A matrix.
*
*
The returned array is always newly created: it is not a reference
* to some internal data stored in this object.
*
* @return the main diagonal of A matrix.
*/
public final double[] diagonal() {
if (diagonal != null) {
return diagonal.clone();
}
double[] result = new double[n];
if (a == null) {
for (int i = 0; i < n; i++) {
result[i] = 1.0;
}
} else {
for (int i = 0, disp = 0; i < n; i++, disp += n + 1) {
result[i] = a[i];
}
}
return result;
}
/**
* Returns the number of dimensions.
* The result is equal to the number of components in the b and c vectors.
*
* @return the number of dimensions.
*/
public final int n() {
return n;
}
/**
* Returns true
if and only if A matrix is diagonal,
* i.e. if aij=0.0 when i!=j.
*
* @return true
if and only if A matrix is diagonal.
*/
public final boolean isDiagonal() {
return a == null;
}
/**
* Returns true
if and only if A matrix is identity
* (i.e. if aij=0.0 when i!=j and
* aij=1.0 when i==j)
* and c vector is zero.
* In this case, this operator corresponds to a parallel shift.
* In this case, this object is always an instance of {@link LinearOperator}.
*
* @return true
if and only if this operator describes a parallel shift in the space.
*/
public final boolean isShift() {
boolean result = a == null && diagonal == null && c == null;
if (result && !(this instanceof LinearOperator)) {
throw new AssertionError("Shift operator must be an instance of " + LinearOperator.class);
}
return result;
}
/**
* Returns true
if and only if the b vector is zero,
* i.e. if bi=0.0 for all i.
* If {@link #isZeroB()} && {@link #isShift()}
,
* this operator is identity: it doesn't change the passed function.
*
* @return true
if and only if the b vector is zero.
*/
public final boolean isZeroB() {
return zeroB;
}
/**
* This implementation calculates destPoint
by the formula
* yi =
* (aix + bi) / (cx + d),
* where x=srcPoint
and y=destPoint
.
* See more details in the comments to {@link ProjectiveOperator this class}.
*
* @param destPoint the coordinates of the destinated point y, filled by this method.
* @param srcPoint the coordinates of the source point x.
* @throws NullPointerException if one of the arguments is {@code null}.
* @throws IllegalArgumentException if destPoint.length
or srcPoint.length
* is not equal to the {@link #n() number of dimensions}.
*/
public void map(double[] destPoint, double[] srcPoint) {
calculateAxPlusB(destPoint, srcPoint);
double divisor = d;
if (c != null) { // to be on the safe side: null is impossible in the current implementation
for (int i = 0; i < c.length; i++) {
divisor += c[i] * srcPoint[i];
}
}
if (divisor != 1.0) {
double multiplier = 1.0 / divisor;
for (int i = 0; i < destPoint.length; i++) {
destPoint[i] *= multiplier;
}
}
}
/**
* Returns a brief string description of this object.
*
* @return a brief string description of this object.
*/
public String toString() {
boolean shift = isShift();
StringBuilder sA = new StringBuilder();
if (isDiagonal()) {
if (diagonal != null) {
sA.append("diag[");
for (int k = 0; k < diagonal.length; k++) {
if (k > 0) {
sA.append(",");
}
sA.append(LinearFunc.goodFormat(diagonal[k]));
}
sA.append("]");
}
} else {
sA.append("A");
}
StringBuilder sB = new StringBuilder();
for (int k = 0; k < n; k++) {
if (k > 0) {
sB.append(",");
}
sB.append(LinearFunc.goodFormat(b[k]));
}
StringBuilder sC = new StringBuilder();
if (c != null) { // to be on the safe side: null is impossible in the current implementation
for (int k = 0; k < c.length; k++) {
if (k > 0) {
sC.append(",");
}
sC.append(LinearFunc.goodFormat(c[k]));
}
}
return "projective " + n + "-dimensional operator ("
+ sA + "x+b)/(cx+d), b=(" + sB + "), c=(" + sC + "), d=" + LinearFunc.goodFormat(d)
+ (shift ? " (shift)" : "");
}
public int hashCode() {
int result = (a != null ? Arrays.hashCode(a) : 0);
result = 37 * result + (diagonal != null ? Arrays.hashCode(diagonal) : 0);
result = 37 * result + Arrays.hashCode(b);
result = 37 * result + (c != null ? Arrays.hashCode(c) : 0);
result = 37 * result + Double.hashCode(d);
return result;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ProjectiveOperator po)) {
return false;
}
if (n != po.n) {
return false;
}
if (d != po.d) {
return false;
}
if ((a == null) == (po.a != null)) {
return false;
}
if (a != null) {
for (int k = 0; k < a.length; k++) {
if (a[k] != po.a[k]) {
return false;
}
}
}
if ((diagonal == null) == (po.diagonal != null)) {
return false;
}
if (diagonal != null) {
for (int k = 0; k < diagonal.length; k++) {
if (diagonal[k] != po.diagonal[k]) {
return false;
}
}
}
for (int k = 0; k < b.length; k++) {
if (b[k] != po.b[k]) {
return false;
}
}
if ((c == null) == (po.c != null)) {
return false;
}
if (c != null) {
for (int k = 0; k < c.length; k++) {
if (c[k] != po.c[k]) {
return false;
}
}
}
return true;
}
void calculateAxPlusB(double[] destPoint, double[] srcPoint) {
Objects.requireNonNull(destPoint, "Null destPoint");
Objects.requireNonNull(srcPoint, "Null srcPoint");
if (destPoint.length != n) {
throw new IllegalArgumentException("Illegal length of destPoint array: "
+ destPoint.length + " for " + this);
}
if (srcPoint.length != n) {
throw new IllegalArgumentException("Illegal length of srcPoint array: "
+ srcPoint.length + " for " + this);
}
if (a != null) {
System.arraycopy(b, 0, destPoint, 0, destPoint.length);
for (int i = 0, disp = 0; i < n; i++) {
double sum = 0.0;
for (int j = 0; j < n; j++, disp++) {
sum += a[disp] * srcPoint[j];
}
destPoint[i] = sum + b[i];
// Note! It is possible here to change the order of summimg: calculate
// b[i] + a[disp]*srcPoint[0]+...
// But such an order will lead to little different results than declared by Ax+b formula,
// because real sums in a computer are not commutative.
// For more accurate and expected results, we calculate this in a usual order.
}
} else if (diagonal != null) {
for (int i = 0; i < n; i++) {
destPoint[i] = diagonal[i] * srcPoint[i] + b[i];
}
} else {
for (int i = 0; i < n; i++) {
destPoint[i] = srcPoint[i] + b[i];
}
}
}
/**
* The simplest test for this class: finds a linear operator by pairs of points.
*/
static class Test {
boolean verbose = false;
int dimCount;
int numberOfTests;
long startSeed;
Random rnd;
Point[] p, q, r;
final void init(String[] args) {
int startArgIndex = 0;
if (startArgIndex < args.length && args[startArgIndex].equalsIgnoreCase("-v")) {
verbose = true;
startArgIndex++;
}
if (args.length < startArgIndex + 2) {
System.out.println("Usage: " + Test.class.getName() + " [-v] dimCount numberOfTests [randSeed]");
System.exit(0);
}
this.dimCount = Integer.parseInt(args[startArgIndex]);
this.numberOfTests = Integer.parseInt(args[startArgIndex]);
if (args.length < startArgIndex + 3) {
this.startSeed = new Random().nextLong();
} else {
this.startSeed = Long.parseLong(args[startArgIndex + 2]);
}
this.rnd = new Random(startSeed);
System.out.printf(Locale.US, "%d tests, randSeed = %d%n", numberOfTests, startSeed);
}
final void mainTest() {
long t1 = System.nanoTime();
double maxError = 0.0;
for (int testCount = 1; testCount <= numberOfTests; testCount++) {
newRndPoints(numberOfPoints(dimCount), dimCount);
CoordinateTransformationOperator o = getOperator();
mapPoints(o);
double error = maxDiff();
maxError = Math.max(maxError, error);
if (verbose) {
System.out.println(testCount + ": difference " + error + "; operator hash code " + o.hashCode());
}
if (error > 0.001) {
System.err.println(testCount + ": difference " + error +
" is BIG: " + o + " incorrectly maps " + java.util.Arrays.asList(p) + " to "
+ java.util.Arrays.asList(r) + " instead of " + java.util.Arrays.asList(q));
}
CoordinateTransformationOperator o2 = getOperator();
if (!o.equals(o2)) {
throw new AssertionError("Error in equals");
}
if (o2.hashCode() != o.hashCode()) {
throw new AssertionError("Error in hashCode");
}
}
long t2 = System.nanoTime();
System.out.printf(Locale.US,
"All tests done in %.3f seconds (%.2f mcs/test), maximal error = %g, randSeed = %d%n",
(t2 - t1) * 1e-9, (t2 - t1) * 1e-3 / numberOfTests, maxError, startSeed);
}
final void newRndPoints(int numberOfPoints, int dimCount) {
p = new Point[numberOfPoints];
q = new Point[numberOfPoints];
r = new Point[numberOfPoints];
double[] coordinates = new double[dimCount];
for (int k = 0; k < p.length; k++) {
for (int j = 0; j < dimCount; j++) {
coordinates[j] = rnd.nextDouble() - 0.5;
}
p[k] = Point.valueOf(coordinates);
for (int j = 0; j < dimCount; j++) {
coordinates[j] = rnd.nextDouble() - 0.5;
}
q[k] = Point.valueOf(coordinates);
}
}
final void mapPoints(CoordinateTransformationOperator o) {
for (int k = 0; k < p.length; k++) {
double[] srcPoint = p[k].coordinates();
double[] destPoint = new double[srcPoint.length];
o.map(destPoint, srcPoint);
r[k] = Point.valueOf(destPoint);
}
}
final double maxDiff() {
double result = 0.0;
for (int k = 0; k < p.length; k++) {
result = Math.max(result, q[k].subtract(r[k]).distanceFromOrigin());
}
return result;
}
int numberOfPoints(int dimCount) {
return dimCount + 2;
}
CoordinateTransformationOperator getOperator() {
return ProjectiveOperator.getInstanceByPoints(q, p);
}
public static void main(String[] args) {
Test test = new Test();
test.init(args);
test.mainTest();
}
}
}