net.algart.math.patterns.Patterns 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.patterns;
import net.algart.math.IPoint;
import net.algart.math.IRange;
import net.algart.math.Point;
import net.algart.math.functions.Func;
import java.math.BigDecimal;
import java.util.*;
/**
* A set of static methods operating with and returning {@link Pattern patterns}.
*
* This class cannot be instantiated.
*
* @author Daniel Alievsky
*/
public class Patterns {
private Patterns() {
}
private static final BigDecimal BIG_DECIMAL_MAX_COORDINATE = new BigDecimal(Pattern.MAX_COORDINATE);
// TODO!! Note: this method always returns SimplePattern or UniformGridPattern.
public static DirectPointSetPattern newPattern(Collection points) {
Objects.requireNonNull(points, "Null points argument");
points = new ArrayList<>(points);
boolean allInteger = true;
for (Point p : points) {
Objects.requireNonNull(p, "Null point in the set");
allInteger &= p.isInteger() & AbstractUniformGridPattern.isAllowedGridIndex(p.toIntegerPoint());
}
if (allInteger) {
HashSet integerPoints = new HashSet<>();
for (Point p : points) {
integerPoints.add(p.toIntegerPoint());
}
return newIntegerPattern(integerPoints);
}
if (points.size() <= 2) {
final Point[] pointsArray = points.toArray(new Point[0]);
if (pointsArray.length == 1 || pointsArray[0].equals(pointsArray[1])) {
return new OnePointPattern(pointsArray[0]);
} else {
return new TwoPointsPattern(pointsArray[0], pointsArray[1]);
}
} else {
return new SimplePattern(points);
}
}
// TODO!! Note: this method always returns SimplePattern or UniformGridPattern.
public static DirectPointSetPattern newPattern(Point... points) {
Objects.requireNonNull(points, "Null points argument");
return newPattern(List.of(points));
}
public static DirectPointSetUniformGridPattern newUniformGridPattern(
Point originOfGrid,
double[] stepsOfGrid,
Collection gridIndexes) {
Objects.requireNonNull(originOfGrid, "Null originOfGrid");
Objects.requireNonNull(gridIndexes, "Null gridIndexes argument");
return new BasicDirectPointSetUniformGridPattern(originOfGrid, stepsOfGrid, new HashSet<>(gridIndexes));
}
/**
* Creates new pattern consisting of all specified points.
*
* The returned pattern is so-called simple, or multipoint pattern:
* it means that its implementation is based on a usual set of {@link IPoint} instances.
* For such pattern, the {@link Pattern#dimCount() number of dimensions} is equal to
* the {@link IPoint#coordCount() number of coordinates}
* in all passed points (it must be the same for all source points).
* The {@link UniformGridPattern#isActuallyRectangular()} method is non-overridden {@link AbstractUniformGridPattern#isActuallyRectangular()} method.
* The {@link Pattern#minkowskiAdd(Pattern)} method returns a new simple pattern
* according the definition of Minkowski sum; this method requires O(NM) operations,
* where N and M is the number of points in both patterns, and may work slowly.
* The {@link Pattern#minkowskiDecomposition(int)} method returns the list containing
* this pattern instance as the only element.
* The equals
method returns true
if and only if the passed object is also simple
* pattern, consisting of the same points and created by this or equivalent method from this package.
*
*
The coordinates of all points must be in range -Long.MAX_VALUE/2..Long.MAX_VALUE/2
,
* excepting the only case when the number of points is 1.
*
*
The returned pattern will be "safe" in the sense that no references to the passed set are maintained by it.
* In other words, this method always allocates new set (probably HashSet
) and copies
* the passed set into it.
*
* @param points the source points set.
* @return the pattern consisting of all source points.
* @throws NullPointerException if points
argument is {@code null}
* or some of passed points is {@code null}.
* @throws IllegalArgumentException if points
argument is an empty set,
* or if some points have different number of coordinates,
* or if there are 2 or more points and some point coordinates are out of range
* -Long.MAX_VALUE/2..Long.MAX_VALUE/2
.
*/
public static DirectPointSetUniformGridPattern newIntegerPattern(Collection points) {
Objects.requireNonNull(points, "Null points argument");
HashSet gridIndexes = new HashSet<>(points);
return new BasicDirectPointSetUniformGridPattern(
gridIndexes.isEmpty() ? 1 : gridIndexes.iterator().next().coordCount(),
gridIndexes);
}
/**
* Equivalent to {@link #newIntegerPattern
* newIntegerPattern}(new HashSet(Arrays.asList(points)))
.
*
* @param points the source points set.
* @return the pattern consisting of all source points.
* @throws NullPointerException if points
argument is {@code null}
* or some of passed points is {@code null}.
* @throws IllegalArgumentException if points
argument is an empty array,
* or if some points have different number of coordinates,
* or if there are 2 or more points and some point coordinates are out of range
* -Long.MAX_VALUE/2..Long.MAX_VALUE/2
.
*/
public static DirectPointSetUniformGridPattern newIntegerPattern(IPoint... points) {
Objects.requireNonNull(points, "Null points argument");
return newIntegerPattern(List.of(points));
}
/**
* A concrete variant of {@link #newIntegerPattern} method returning
* the pattern consisting of all such points inside n-dimensional sphere
* with the given radius r and center.
*
* More precisely, let
* C=(c0,c1,...,cn-1)
* is the passed center point.
* The result of this method consists of all points
* A=(x0,x1,...,xn-1)
* that |OA|2 =
* (x0-c0)2
* + (x1-c1)2
* + ... + (xn-1-cn-1)2
* ≤ r2.
*
*
The {@link Pattern#roundedCoordRange(int)} method in the returned pattern works very quickly,
* unlike patterns created by {@link #newIntegerPattern} method.
*
*
Please note: integer values of the radius usually produce "non-beautiful" patterns
* with very uneven edges. For example, in 2-dimensional case (circles)
* we recommend to pass r~k-0.2, where k is a positive integer.
*
*
Please be careful: too large values of r
argument may lead to
* very long execution and even to OutOfMemoryError
,
* especially for 2- and 3-dimensional patterns.
* Moreover, there are no ways to interrupt this method or to show its execution progress.
* We recommend to restrict the passed radius by some suitable value,
* for example, 1000–2000 or less.
*
* @param center the center of the sphere (circle in 2-dimensional case).
* @param r the radius of the sphere.
* @return the pattern consisting of all points inside this sphere (circle in 2-dimensional case).
* @throws IllegalArgumentException if r<0.0
,
* @throws TooManyPointsInPatternError if r
is about Integer.MAX_VALUE
or greater.
*/
public static UniformGridPattern newSphereIntegerPattern(Point center, final double r) {
Objects.requireNonNull(center, "Null center argument");
if (r < 0.0) {
throw new IllegalArgumentException("Negative sphere radius");
}
double[] semiAxes = new double[center.coordCount()];
java.util.Arrays.fill(semiAxes, r);
return newEllipsoidIntegerPattern(center, semiAxes);
/* OLD ALGORITHM
final int n = center.coordCount();
if (n == 1)
return newIntegerPattern(newRectangularIntegerPattern(IRange.valueOf(
StrictMath.round(StrictMath.ceil(center.coord(0) - r)),
StrictMath.round(StrictMath.floor(center.coord(0) + r)))).roundedPoints());
int ir = (int) (r + 2.0);
if (ir == Integer.MAX_VALUE)
throw new TooManyPointsInPatternError("Too large desired " + n + "D sphere radius: " + r);
final IRange[] oneSegmentCoordRanges = new IRange[n];
HashSet points = new HashSet();
for (int k = 0; k < oneSegmentCoordRanges.length; k++) {
addPointsToSphere(points, new double[]{center.coord(k)}, new long[1], 0, ir, r * r, 0.0);
// for checking maximal radius by common algorithm; new double/long here are zero-filled
oneSegmentCoordRanges[k] = new BasicUniformGridPattern(1, points).gridIndexRange(0);
points.clear();
}
addPointsToSphere(points, center.coordinates(), new long[n], 0, ir, r * r, 0.0);
return new BasicUniformGridPattern(n, points) {
@Override
public IRange gridIndexRange(int coordIndex) {
return oneSegmentCoordRanges[coordIndex];
}
public String toString() {
return super.toString() + " ("
+ (dimCount == 1 ? "segment" : dimCount == 2 ? "circle" : "sphere")
+ ", r = " + r + ")";
}
};
*/
}
public static UniformGridPattern newEllipsoidIntegerPattern(Point center, double... semiAxes) {
Objects.requireNonNull(center, "Null center argument");
Objects.requireNonNull(semiAxes, "Null semiAxes argument");
final int n = center.coordCount();
if (semiAxes.length != n) {
throw new IllegalArgumentException("Number of semi-axes " + semiAxes.length
+ " is not equal to center.coordCount()=" + n);
}
final double[] semiAxesClone = semiAxes.clone();
final double[] semiAxesInv = new double[n];
final int[] semiAxesUpperBounds = new int[n];
for (int k = 0; k < n; k++) {
double semiAxis = semiAxesClone[k];
if (semiAxis < 0.0) {
throw new IllegalArgumentException("Negative semiAxes[" + k + "] = " + semiAxis);
}
semiAxesUpperBounds[k] = (int) (semiAxis + 2.0);
if (semiAxesUpperBounds[k] == Integer.MAX_VALUE) {
throw new TooManyPointsInPatternError("Too large desired " + n + "D ellipsoid: semiAxes["
+ k + "]=" + semiAxis);
}
semiAxesInv[k] = 1.0 / semiAxis; // maybe Infinity
}
if (n == 1) {
return newIntegerPattern(newRectangularIntegerPattern(IRange.valueOf(
StrictMath.round(StrictMath.ceil(center.coord(0) - semiAxesClone[0])),
StrictMath.round(StrictMath.floor(center.coord(0) + semiAxesClone[0])))).roundedPoints());
}
final IRange[] oneSegmentCoordRanges = new IRange[n];
HashSet points = new HashSet<>();
for (int k = 0; k < oneSegmentCoordRanges.length; k++) {
addPointsToEllipsoid(points,
new double[]{center.coord(k)},
new int[]{semiAxesUpperBounds[k]},
new double[]{semiAxesInv[k]},
new long[]{0}, 0, 0.0);
// 1-dimensional "ellipsoid" to get maximal radius by common algorithm
oneSegmentCoordRanges[k] = new BasicDirectPointSetUniformGridPattern(1, points).gridIndexRange(0);
points.clear();
}
addPointsToEllipsoid(points, center.coordinates(), semiAxesUpperBounds, semiAxesInv,
new long[n], // zero-filled
0, 0.0);
return new BasicDirectPointSetUniformGridPattern(n, points) {
@Override
public IRange gridIndexRange(int coordIndex) {
return oneSegmentCoordRanges[coordIndex];
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (int k = 0; k < dimCount; k++) {
if (k > 0) {
sb.append(",");
}
sb.append(semiAxesClone[k]);
}
return super.toString() + " ("
+ (dimCount == 1 ? "segment" : dimCount == 2 ? "ellipse" : "ellipsoid")
+ ", semiAxes = " + sb + ")";
}
};
}
public static Pattern newSurface(Pattern projection, final Func surface) {
Objects.requireNonNull(projection, "Null projection argument");
Objects.requireNonNull(surface, "Null surface argument");
final int dimCount = projection.dimCount();
final Set projectionPoints = projection.points();
Set resultPoints = new HashSet<>();
double[] coordinates = new double[dimCount];
double[] resultPoint = new double[dimCount + 1];
for (Point projectionPoint : projectionPoints) {
projectionPoint.coordinates(coordinates);
System.arraycopy(coordinates, 0, resultPoint, 0, dimCount);
resultPoint[dimCount] = surface.get(coordinates);
resultPoints.add(Point.valueOf(resultPoint));
}
return new SimplePattern(resultPoints) {
public String toString() {
return super.toString() + " (surface " + surface + ")";
}
};
}
public static UniformGridPattern newSpaceSegment(
UniformGridPattern projection,
final Func minSurface,
final Func maxSurface,
double lastCoordinateOfOrigin,
double lastCoordinateStep) {
Objects.requireNonNull(projection, "Null projection argument");
Objects.requireNonNull(minSurface, "Null minSurface argument");
Objects.requireNonNull(maxSurface, "Null maxSurface argument");
if (lastCoordinateStep <= 0.0) {
throw new IllegalArgumentException("Zero or negative last step of the grid is not allowed");
}
final int dimCount = projection.dimCount();
final Set projectionIndexes = projection.gridIndexes();
final Point projectionOrigin = projection.originOfGrid();
final double[] projectionSteps = projection.stepsOfGrid();
double[] origin = new double[dimCount + 1];
projectionOrigin.coordinates(origin);
origin[dimCount] = lastCoordinateOfOrigin;
double[] steps = new double[dimCount + 1];
System.arraycopy(projectionSteps, 0, steps, 0, dimCount);
steps[dimCount] = lastCoordinateStep;
Set resultIndexes = new HashSet<>();
double[] coordinates = new double[dimCount];
long[] resultIndex = new long[dimCount + 1];
for (IPoint projectionIndex : projectionIndexes) {
projectionIndex.scaleAndShift(coordinates, projectionSteps, projectionOrigin);
double min = minSurface.get(coordinates);
double max = maxSurface.get(coordinates);
if (min > max) {
continue;
}
long minIndex = (long) StrictMath.ceil(lastCoordinateStep == 1.0 ? min - lastCoordinateOfOrigin :
(min - lastCoordinateOfOrigin) / lastCoordinateStep);
long maxIndex = (long) StrictMath.floor(lastCoordinateStep == 1.0 ? max - lastCoordinateOfOrigin :
(max - lastCoordinateOfOrigin) / lastCoordinateStep);
projectionIndex.coordinates(resultIndex);
for (long i = minIndex; i <= maxIndex; i++) {
resultIndex[dimCount] = i;
resultIndexes.add(IPoint.valueOf(resultIndex));
}
}
if (resultIndexes.isEmpty()) {
throw new IllegalArgumentException("Empty pattern: in all points of the projection pattern "
+ "the minimal surface is above the maximal surface");
}
return new BasicDirectPointSetUniformGridPattern(Point.valueOf(origin), steps, resultIndexes) {
public String toString() {
return super.toString() + " (segment between surfaces " + minSurface + " and " + maxSurface + ")";
}
};
}
public static RectangularPattern newRectangularUniformGridPattern(
Point originOfGrid,
double[] stepsOfGrid,
IRange... gridIndexRanges) {
Objects.requireNonNull(originOfGrid, "Null originOfGrid");
Objects.requireNonNull(gridIndexRanges, "Null gridIndexRanges argument");
if (gridIndexRanges.length == 0) {
throw new IllegalArgumentException("Empty gridIndexRanges array");
}
return new BasicRectangularPattern(originOfGrid, stepsOfGrid, gridIndexRanges);
}
/**
* Creates new {@link UniformGridPattern#isActuallyRectangular() rectangular} pattern
* (n-dimensional rectangular parallelepiped), consisting of all such points
* (x0,x1,...,xn-1) that
* ranges[i].{@link IRange#min()
* min()}
<=xi<=ranges[i].{@link IRange#max()
* max()}
, i=0,1,...,n-1, n=ranges.length
.
* The number of space dimensions of the returned pattern will be equal to the length of ranges
array.
*
* In the returned pattern,
* the {@link UniformGridPattern#isActuallyRectangular()} method returns true
always.
* The {@link Pattern#minkowskiAdd(Pattern)} method creates a new rectangular pattern (via this method),
* if its argument is also rectangular, according the definition of Minkowski sum;
* in other case, it returns new {@link #newIntegerPattern simple} pattern and works slowly
* (O(NM) operations, where N and M is the number of points in both patterns).
* The {@link Pattern#minkowskiDecomposition(int)} method returns the list containing
* ~log2(N) 2-point simple patterns or, maybe,
* the list containing one 1-point pattern if this rectangular pattern is degenerate
* (1-point).
* The equals
method returns true
if and only if the passed object is also rectangular
* pattern, consisting of the same points and created by this or equivalent method from this package.
*
*
The returned pattern will be "safe" in the sense that no references to the passed array are maintained by it.
* In other words, this method always allocates new array for storing ranges and copies
* the passed ranges into it.
*
* @param ranges the source ranges describing n-dimensional rectangular parallelepiped.
* @return the rectangular pattern.
* @throws NullPointerException if ranges
argument is {@code null}
* or some of passed ranges are {@code null}.
* @throws IllegalArgumentException if ranges
argument is empty (contains no elements).
*/
public static RectangularPattern newRectangularIntegerPattern(IRange... ranges) {
Objects.requireNonNull(ranges, "Null ranges argument");
if (ranges.length == 0) {
throw new IllegalArgumentException("Empty ranges array");
}
return new BasicRectangularPattern(ranges);
}
/**
* Equivalent to {@link #newRectangularIntegerPattern newRectangularIntegerPattern(ranges)},
* where ranges[k]
is {@link IRange#valueOf(long, long)
* IRange.valueOf}(min.{@link IPoint#coord(int) coord(k)}, max.{@link IPoint#coord(int) coord(k)})
.
* The number of dimensions of the created pattern is equal to the number of coordinates
* of min
and max
points.
*
*
The number of coordinates of min
and max
points must be the same.
* All coordinates of min
point must not be greater than the corresponding coordinates
* of the max
points.
*
* @param min starting points of the parallelepiped (left top vertex in 2D case), inclusive.
* @param max ending points of the parallelepiped (left top vertex in 2D case), inclusive.
* @return the rectangular pattern, two vertices of which are the passed points.
* @throws NullPointerException if one of the arguments is {@code null}.
* @throws IllegalArgumentException if the numbers of coordinates of min
and max
* points are different,
* or if min.{@link IPoint#coord(int) coord}(k) >
* max.{@link IPoint#coord(int) coord}(k)
for some k
,
* or if the difference between these coordinates is
* >=Long.MAX_VALUE
.
*/
public static UniformGridPattern newRectangularIntegerPattern(IPoint min, IPoint max) {
int n = min.coordCount();
if (n != max.coordCount()) {
throw new IllegalArgumentException("Coordinates count mismatch: \"min\" is "
+ n + "-dimensional, \"max\" is " + max.coordCount() + "-dimensional");
}
IRange[] ranges = new IRange[n];
for (int k = 0; k < n; k++) {
ranges[k] = IRange.valueOf(min.coord(k), max.coord(k));
}
return new BasicRectangularPattern(ranges);
}
// /**
// * Returns true
if and only if the given pattern was created by
// * {@link #newRectangularIntegerPattern(IRange...)} or equivalent method from this package.
// *
// *
Please compare: unlike this, {@link UniformGridPattern#isRectangular()} method returns true
// * for any rectangular patterns, in particular, for ones created by {@link #newIntegerPattern(Collection)}
// * method if this set is rectangular (for example, the single point).
// *
// * @param pattern the checked pattern.
// * @return true
if and only if the given pattern was created by
// * {@link #newRectangularIntegerPattern(IRange...)} or equivalent method from this package.
// */
// public static boolean isRectangular(Pattern pattern) {
// if (!NEW_FLOATING_POINT_PATTERNS) {
// return pattern instanceof RectangularIntegerPattern || pattern instanceof OnePointIntegerPattern;
// }
// return pattern instanceof RectangularGridPattern;
// }
/**
* Creates new pattern, representing the Minkowski sum of all passed patterns.
* Please see Wikipedia about the
* "Minkowski sum" term. See also {@link Pattern#minkowskiAdd(Pattern)} method for comparison.
*
*
Note: this method does not actually calculate the point set of the sum;
* if necessary, you will be able to get all points later by {@link Pattern#roundedPoints()} method.
*
*
In the returned pattern,
* the {@link Pattern#minkowskiDecomposition(int)} method returns
* the summary list of Minkowski decompositions of all patterns passed to this method.
* The {@link UniformGridPattern#isActuallyRectangular()} method is non-overridden
* {@link AbstractUniformGridPattern#isActuallyRectangular()} method.
* The {@link Pattern#minkowskiAdd(Pattern)} method creates a new Minkowski sum by this method,
* that contains an additional summand equal to the added pattern.
* The equals
method returns true
if and only if the passed object is also Minkowski
* sum, consisting of the same summands and created by this or equivalent method from this package.
*
*
If all passed patterns are {@link UniformGridPattern#isActuallyRectangular() rectangular}, the behavior of this method
* is another: it just returns {@link #newRectangularIntegerPattern new rectangular pattern}
* equal to the Minkowski sum of all passed patterns.
*
*
The returned pattern will be "safe" in the sense that no references to the passed array are maintained by it.
* In other words, this method always allocates new array for storing summands and copies
* the passed summands into it.
*
* @param patterns the summands of the returned Minkowski sum.
* @return the Minkowski sum.
* @throws NullPointerException if patterns
argument is {@code null}
* or some of passed patterns are {@code null}.
* @throws IllegalArgumentException if patterns
argument is an empty array,
* or if some patterns have different number of dimensions,
* or if some of the {@link Pattern#roundedCoordRange(int)
* coordinate ranges} in the
* precise result are out of Long.MIN_VALUE..Long.MAX_VALUE
range
* or have the size (maximum - minimum), greater or equal to
* Long.MAX_VALUE
.
*/
public static Pattern newMinkowskiSum(Pattern... patterns) {
return newMinkowskiSum(List.of(patterns));
}
//TODO!! not forget to comment a case when all patterns are compatible rectangular patterns
//TODO!! write about limited precision: the result can little differ from the precise sum,
// if some of pattern are not integer patterns
public static Pattern newMinkowskiSum(Collection patterns) {
Objects.requireNonNull(patterns, "Null patterns argument");
if (patterns.isEmpty()) {
throw new IllegalArgumentException("Empty patterns array");
}
Pattern[] patternsArray = patterns.toArray(new Pattern[0]);
// cloning before checking guarantees correct behaviour while multithreading
boolean allCompatibleRectangular = true;
Pattern first = patternsArray[0];
for (int k = 0; k < patternsArray.length; k++) {
Objects.requireNonNull(patternsArray[k], "Null pattern #" + k + " in the list");
if (patternsArray[k].dimCount() != first.dimCount()) {
throw new IllegalArgumentException("Patterns dimensions mismatch: the first pattern has "
+ first.dimCount() + " dimensions, but pattern #" + k + " has " + patternsArray[k].dimCount());
}
if (allCompatibleRectangular // important to check it before typecast
&& !(patternsArray[k] instanceof RectangularPattern
&& ((UniformGridPattern) patternsArray[k]).stepsOfGridEqual((UniformGridPattern) first))) {
allCompatibleRectangular = false;
}
}
if (allCompatibleRectangular) {
UniformGridPattern ugFirst = (UniformGridPattern) first;
Pattern result = new BasicRectangularPattern(ugFirst.originOfGrid(), ugFirst.stepsOfGrid(),
ugFirst.gridIndexArea().ranges());
// the actualization is necessary to be sure in the implementation of minkowskiAdd method
for (int k = 1; k < patternsArray.length; k++) {
result = result.minkowskiAdd(patternsArray[k]);
if (!(result instanceof RectangularPattern)) {
throw new AssertionError("Invalid SimpleRectangularGridPattern.minkowskiAdd implementation");
}
}
if (result.pointCount() == 1) {
return new OnePointPattern(result.coordMin());
} else {
return result;
}
} else {
return new MinkowskiSum(patternsArray);
}
}
/**
* Equivalent to {@link #newMinkowskiSum(Pattern...) newMinkowskiSum(patterns)}, where pattern
* is the array with the length n
containing n
copies of the passed pattern.
* Some algorithms of this package work better with such form of the Minkowski sums.
*
* @param pattern the pattern.
* @param n the multiplier.
* @return the Minkowski multiple of the passed pattern.
* @throws NullPointerException if pattern
argument is {@code null}.
* @throws IllegalArgumentException if n <= 0
.
*/
public static Pattern newMinkowskiMultiplePattern(Pattern pattern, int n) {
Objects.requireNonNull(pattern, "Null pattern argument");
if (n <= 0) {
throw new IllegalArgumentException("Negative or zero n argument");
}
Pattern[] patterns = new Pattern[n];
java.util.Arrays.fill(patterns, pattern);
return new MinkowskiSum(patterns);
}
/**
* Creates new pattern, representing the set-theoretic union of the passed patterns.
* This method does not calculate the point set of the union;
* if necessary, you will be able to get all points later by {@link Pattern#roundedPoints()} method.
*
* In the returned pattern,
* the {@link Pattern#unionDecomposition(int)} method returns
* the summary list of union decompositions of all patterns passed to this method,
* and the {@link Pattern#allUnionDecompositions(int)} method returns the same list
* as the only element of its result.
* The {@link Pattern#minkowskiDecomposition(int)} method is non-overridden
* {@link AbstractPattern#minkowskiDecomposition(int)} method.
* The {@link UniformGridPattern#isActuallyRectangular()} method is non-overridden
* {@link AbstractUniformGridPattern#isActuallyRectangular()} method.
* The {@link Pattern#minkowskiAdd(Pattern)} method returns a new simple pattern
* according the definition of Minkowski sum; this method requires O(NM) operations,
* where N and M is the number of points in both patterns, and may work slowly.
* The {@link Pattern#minkowskiDecomposition(int)} method returns the list containing
* this pattern instance as the only element.
* The equals
method returns true
if and only if the passed object is also union
* of patterns, consisting of the same summands and created by this or equivalent method from this package.
*
*
The returned pattern will be "safe" in the sense that no references to the passed array are maintained by it.
* In other words, this method always allocates new array for storing summands and copies
* the passed summands into it.
*
* @param patterns the summands of the returned union.
* @return the set-theoretic union.
* @throws NullPointerException if patterns
argument is {@code null}
* or some of passed patterns are {@code null}.
* @throws IllegalArgumentException if patterns
argument is an empty array,
* or if some patterns have different number of dimensions,
* or if some of the {@link Pattern#roundedCoordRange(int) coordinate ranges} in the
* precise result have the size (maximum - minimum), greater or equal to
* Long.MAX_VALUE
.
*/
public static Pattern newUnion(Pattern... patterns) {
Objects.requireNonNull(patterns, "Null patterns argument");
return new Union(patterns.clone());
// cloning guarantees correct behaviour while multithreading
}
public static Pattern newUnion(Collection patterns) {
Objects.requireNonNull(patterns, "Null patterns argument");
return new Union(patterns.toArray(new Pattern[patterns.size()]));
// cloning guarantees correct behaviour while multithreading
}
/**
* Returns true
if and only if the absolute value of the precise mathematical difference
* |x1
−x2
| is not greater than {@link Pattern#MAX_COORDINATE}:
* |x1
−x2
|≤{@link Pattern#MAX_COORDINATE}.
*
* Note: this condition is checked absolutely strictly with ideal precision.
* If this difference is near to {@link Pattern#MAX_COORDINATE} value,
* this method uses BigDecimal
class to provide absolutely correct result.
*
*
Special cases: if any of two arguments contains NaN
or an infinity,
* this method returns false
.
*
* @param x1 the first number.
* @param x2 the second number.
* @return if the mathematically precise difference between these numbers is ≤{@link Pattern#MAX_COORDINATE}.
*/
public static boolean isAllowedDifference(double x1, double x2) {
if (Double.isNaN(x1) || Double.isNaN(x2) || Double.isInfinite(x1) || Double.isInfinite(x2)) {
return false;
}
@SuppressWarnings("ManualMinMaxCalculation") double a = x1 <= x2 ? x1 : x2;
@SuppressWarnings("ManualMinMaxCalculation") double b = x1 <= x2 ? x2 : x1;
double diff = b - a;
// - according to IEEE 754, it is the nearest double value to the mathematical difference b-a
if (diff < Pattern.MAX_COORDINATE - 1) { // -1 and +1 to be on the safe side
return true;
}
if (diff > Pattern.MAX_COORDINATE + 1) { // in particular, if x1 or x2 is infinite
return false;
}
BigDecimal bigA = new BigDecimal(a);
BigDecimal bigB = new BigDecimal(b);
BigDecimal bigDiff = bigB.subtract(bigA);
return bigDiff.compareTo(BIG_DECIMAL_MAX_COORDINATE) <= 0;
}
private static void addPointsToSphere(
Set points, double[] center, long[] coordinates,
int lastCoordinatesCount, int ir, double rSqr, double rSum) {
int dimCount = coordinates.length;
int currentCoordIndex = dimCount - 1 - lastCoordinatesCount;
int min = (int) center[currentCoordIndex] - ir;
int max = (int) center[currentCoordIndex] + ir;
if (currentCoordIndex == 0) {
for (int i = min; i <= max; i++) {
double diff = i - center[currentCoordIndex];
if (rSum + diff * diff <= rSqr) {
coordinates[0] = i;
points.add(IPoint.valueOf(coordinates));
}
}
} else {
for (int i = min; i <= max; i++) {
coordinates[currentCoordIndex] = i;
double diff = i - center[currentCoordIndex];
addPointsToSphere(points, center, coordinates, lastCoordinatesCount + 1,
ir, rSqr, rSum + diff * diff);
}
}
}
private static void addPointsToEllipsoid(
Set points, double[] center, int[] semiAxesUpperBounds, double[] semiAxesInv,
long[] coordinates, int lastCoordinatesCount, double sum) {
int dimCount = coordinates.length;
int currentCoordIndex = dimCount - 1 - lastCoordinatesCount;
double rInv = semiAxesInv[currentCoordIndex];
int min = (int) center[currentCoordIndex] - semiAxesUpperBounds[currentCoordIndex];
int max = (int) center[currentCoordIndex] + semiAxesUpperBounds[currentCoordIndex];
if (currentCoordIndex == 0) {
for (int i = min; i <= max; i++) {
double diff = i - center[currentCoordIndex];
double ratio = diff == 0.0 ? 0.0 : diff * rInv; // rInv is infinite for zero semi-axis
double newSum = sum + ratio * ratio; // x^2/a^2 + y^2/b^2 + ... : it must be <=1.0
if (newSum <= 1.000000001) { // theoretically possible that 5 * 1.0/5.0 > 1.0
coordinates[0] = i;
points.add(IPoint.valueOf(coordinates));
}
}
} else {
for (int i = min; i <= max; i++) {
double diff = i - center[currentCoordIndex];
double ratio = diff == 0.0 ? 0.0 : diff * rInv; // rInv is infinite for zero semi-axis
double newSum = sum + ratio * ratio;
if (newSum <= 1.000000001) { // little optimization: avoiding extra loop for some coordinates
coordinates[currentCoordIndex] = i;
addPointsToEllipsoid(points, center, semiAxesUpperBounds, semiAxesInv,
coordinates, lastCoordinatesCount + 1, newSum);
}
}
}
}
}