net.algart.math.patterns.AbstractPattern 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 java.util.*;
import net.algart.math.*;
/**
* A skeletal implementation of the {@link Pattern} interface to minimize
* the effort required to implement this interface.
*
* All non-abstract methods are completely implemented here and may be not overridden in subclasses.
*
* @author Daniel Alievsky
*/
public abstract class AbstractPattern implements Pattern {
volatile Boolean surelyOrigin = null;
volatile Boolean surelyInteger = null;
final Range[] coordRanges; // null-filled by the constructor
final Pattern[] minBound; // null-filled by the constructor
final Pattern[] maxBound; // null-filled by the constructor
/**
* The number of space dimensions of this pattern.
*/
protected final int dimCount;
/**
* Creates a pattern with the given number of space dimensions.
*
* @param dimCount the number of space dimensions.
* @throws IllegalArgumentException if dimCount<=0
.
*/
protected AbstractPattern(int dimCount) {
if (dimCount <= 0) {
throw new IllegalArgumentException("Negative or zero dimCount=" + dimCount);
}
this.dimCount = dimCount;
this.coordRanges = new Range[dimCount]; // null-filled
this.minBound = new Pattern[dimCount]; // null-filled
this.maxBound = new Pattern[dimCount]; // null-filled
}
/**
* This implementation returns {@link #dimCount} field.
*
* @return the number of space dimensions of this pattern.
*/
public final int dimCount() {
return this.dimCount;
}
public abstract long pointCount();
/**
* This implementation returns (double){@link #pointCount()}
.
* Please override this method if the pattern can contain more than Long.MAX_VALUE
points.
*
* @return the number of {@link IPoint integer points} in this pattern.
*/
public double largePointCount() {
return (double) pointCount();
}
/**
* This implementation returns false
.
* Please override this method if the pattern can contain more than Long.MAX_VALUE
points.
*
* @return true
if the number of points in this pattern is greater than Long.MAX_VALUE
.
*/
public boolean isPointCountVeryLarge() {
return false;
}
public abstract Set points();
/**
* This implementation calls {@link #points()} method and returns a new set, built from the returned set of
* real points by conversion of every point to an integer point via {@link Point#toRoundedPoint()} method,
* as written in
* {@link Pattern#roundedPoints() comments to this method in Pattern interface}.
* Please override this method if there is more efficient way to get all rounded points.
*
* @return all points of this pattern, rounded to the nearest integer points.
* @throws TooManyPointsInPatternError if {@link #points()} method throws this exception
* (OutOfMemoryError
can be also thrown in this case).
*/
public Set roundedPoints() {
Set result = new HashSet<>();
for (Point p : points()) {
result.add(p.toRoundedPoint());
}
return Collections.unmodifiableSet(result);
}
public abstract Range coordRange(int coordIndex);
/**
* This implementation is based on the loop of calls of {@link #coordRange(int)} method
* for all coordinate indexes from 0
to {@link #dimCount()}-1
.
*
* @return the ranges from minimal to maximal grid index for all space dimensions.
*/
public RectangularArea coordArea() {
Range[] result = new Range[dimCount];
for (int k = 0; k < result.length; k++) {
result[k] = coordRange(k);
}
return RectangularArea.valueOf(result);
}
/**
* This implementation is based on the loop of calls of {@link #coordRange(int)} method
* for all coordinate indexes from 0
to {@link #dimCount()}-1
.
*
* @return minimal coordinates for all space dimensions as a point.
*/
public Point coordMin() {
double[] coordinates = new double[dimCount];
for (int k = 0; k < coordinates.length; k++) {
coordinates[k] = coordRange(k).min();
}
return Point.valueOf(coordinates);
}
/**
* This implementation is based on the loop of calls of {@link #coordRange(int)} method
* for all coordinate indexes from 0
to {@link #dimCount()}-1
.
*
* @return maximal coordinates for all space dimensions as a point.
*/
public Point coordMax() {
double[] coordinates = new double[dimCount];
for (int k = 0; k < coordinates.length; k++) {
coordinates[k] = coordRange(k).max();
}
return Point.valueOf(coordinates);
}
/**
* This implementation returns {@link #coordRange(int) coordRange}(coordIndex).{@link Range#toRoundedRange()
* toRoundedRange()}
.
*
* @param coordIndex the index of the coordinate (0 for x, 1 for y, 2 for z, etc.).
* @return the range from minimal to maximal coordinate with this index, rounded to the long
values.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #dimCount()}
.
*/
public IRange roundedCoordRange(int coordIndex) {
return coordRange(coordIndex).toRoundedRange();
}
/**
* This implementation is based on the loop of calls of {@link #roundedCoordRange(int)} method
* for all coordinate indexes from 0
to {@link #dimCount()}-1
.
*
* @return the ranges from minimal to maximal coordinate for all space dimensions,
* rounded to the long
values.
*/
public IRectangularArea roundedCoordArea() {
IRange[] result = new IRange[dimCount];
for (int k = 0; k < result.length; k++) {
result[k] = roundedCoordRange(k);
}
return IRectangularArea.valueOf(result);
}
public abstract boolean isSurelySinglePoint();
/**
* This implementation checks {@link #isSurelySinglePoint()}, and if it is true
,
* checks, whether the only element of {@link #points()} set is the origin.
*
* This method caches its results: the following calls will work faster.
*
* @return true
if it is one-point pattern containing the origin of coordinates as the single point.
*/
public boolean isSurelyOriginPoint() {
if (surelyOrigin == null) {
surelyOrigin = isSurelySinglePoint() && points().iterator().next().isOrigin();
// if isSurelySinglePoint(), then this method works quickly enough:
// usually there are no problems to get the only one point
}
return surelyOrigin;
}
/**
* This implementation calls {@link #points()} method and checks, whether all returned points are integer,
* i.e. {@link Point#isInteger()} method returns true
for all elements the returned set.
* If all points, returned by {@link #points()} call, are integer, this method returns true
,
* in other case it returns false
.
*
*
This method caches its results: the following calls will work faster.
*
*
Note: according the {@link Pattern#isSurelyInteger() comments to this method in Pattern interface},
* such implementation is correct only if {@link #minkowskiDecomposition(int)},
* {@link #unionDecomposition(int)} and {@link #allUnionDecompositions(int)} methods
* have default implementations (not overridden). If some of them are overridden and
* return some non-trivial results, this method must be also overridden.
*
*
Note: according the {@link Pattern#isSurelyInteger() comments to this method in Pattern interface},
* this method must be overridden if the number of points can be very large and a call of
* {@link #points()} method leads to a risk of {@link TooManyPointsInPatternError} / OutOfMemoryError
.
* In particular, this method should be usually overridden in implementations of {@link RectangularPattern}.
*
* @return true
if this pattern assuredly contain only {@link Point#isInteger() integer} points.
*/
public boolean isSurelyInteger() {
if (surelyInteger == null) {
boolean allInteger = true;
for (Point p : points()) {
if (!p.isInteger()) {
allInteger = false;
break;
}
}
surelyInteger = allInteger;
}
return surelyInteger;
}
/**
* This implementation calls {@link #roundedPoints()} method and constructs a new integer pattern on the base
* of this set, like as it is performed in {@link Patterns#newIntegerPattern(java.util.Collection)} method.
* Please override this method if there is more efficient way to round this pattern,
* for example, if this pattern is already an integer one.
*
* @return the integer pattern, geometrically nearest to this one.
* @throws TooManyPointsInPatternError if this pattern is not {@link DirectPointSetPattern} and
* not {@link RectangularPattern} and if, at the same time, the number
* of points is greater than Integer.MAX_VALUE
or,
* in some rare situations, is near this limit
* (OutOfMemoryError
can be also thrown instead of this exception).
*/
public UniformGridPattern round() {
return new BasicDirectPointSetUniformGridPattern(dimCount, roundedPoints());
// Here is a little optimization: we do not clone the result of roundedPoints() call.
// It is correct for any possible correct implementation of roundedPoints(),
// because this object is always immutable (it is a requirement of Pattern interface).
}
public abstract Pattern projectionAlongAxis(int coordIndex);
/*Repeat() min(?!us) ==> max */
/**
* This implementation calls {@link #points()} method and builds the result on the base of analysis
* of the returned point set.
* Please override this method if there is more efficient way to find the result,
* for example, if this pattern is a rectangular one.
*
* @param coordIndex the index of the coordinate (0 for x-axis , 1 for y-axis,
* 2 for za-xis, etc.).
* @return the minimal boundary of this pattern for the given axis.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #dimCount()}
.
* @throws TooManyPointsInPatternError if this pattern is not {@link DirectPointSetPattern} and
* not {@link RectangularPattern} and if, at the same time, the number
* of points is greater than Integer.MAX_VALUE
or,
* in some rare situations, is near this limit
* (OutOfMemoryError
can be also thrown instead of this exception).
*/
public Pattern minBound(int coordIndex) {
return minBound(coordIndex, false);
}
/*Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! */
/**
* This implementation calls {@link #points()} method and builds the result on the base of analysis
* of the returned point set.
* Please override this method if there is more efficient way to find the result,
* for example, if this pattern is a rectangular one.
*
* @param coordIndex the index of the coordinate (0 for x-axis , 1 for y-axis,
* 2 for za-xis, etc.).
* @return the maximal boundary of this pattern for the given axis.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #dimCount()}
.
* @throws TooManyPointsInPatternError if this pattern is not {@link DirectPointSetPattern} and
* not {@link RectangularPattern} and if, at the same time, the number
* of points is greater than Integer.MAX_VALUE
or,
* in some rare situations, is near this limit
* (OutOfMemoryError
can be also thrown instead of this exception).
*/
public Pattern maxBound(int coordIndex) {
return maxBound(coordIndex, false);
}
/*Repeat.AutoGeneratedEnd*/
/**
* This implementation just returns this object.
* Please override this method (together with {@link #maxCarcassMultiplier()}),
* if your class can provide better results.
*
*
Note: {@link AbstractUniformGridPattern} class provides much better implementation.
*
* @return the carcass of this pattern.
*/
public Pattern carcass() {
return this;
}
/**
* This implementation just returns 2.
* Please override this method (together with {@link #carcass()}),
* if your class can provide better results.
*
*
Note: {@link AbstractUniformGridPattern} class provides much better implementation.
*
* @return the maximal multiplier (≥2),
* for which the calculation of the Minkowski multiple can be optimized
* by using the {@link #carcass() carcass}.
*/
public int maxCarcassMultiplier() {
return 2;
}
public abstract Pattern shift(Point shift);
/**
* This implementation calls {@link #multiply(double) multiply(-1.0)}.
* There are no reasons to override this method usually.
*
* @return the symmetric pattern.
*/
public Pattern symmetric() {
return multiply(-1.0);
}
/**
* This implementation creates Java array double[]
by the call
* "a = new double[{@link #dimCount}]
", fills all its elements by
* multiplier
argument and then calls {@link #scale(double...) scale(a)}.
* There are no reasons to override this method usually.
*
* @param multiplier the scale along all coordinates.
* @return the scaled pattern.
* @throws TooLargePatternCoordinatesException
* if the set of scaled points does not fulfil the restrictions,
* described in the comments to {@link Pattern} interface,
* section "Coordinate restrictions".
*/
public Pattern multiply(double multiplier) {
double[] multipliers = new double[dimCount];
Arrays.fill(multipliers, multiplier);
return scale(multipliers);
}
public abstract Pattern scale(double... multipliers);
/**
* This implementation is based on the loop for all points returned by {@link #points()} method in both patterns
* and always returns a {@link DirectPointSetPattern direct point-set pattern},
* consisting of sums of all point pairs.
* This algorithm may be very slow for large patterns
* (O(NM) operations, N={@link #pointCount()}, M=added.{@link #pointCount()})
* and does not work at all if the number of resulting points is greater than Integer.MAX_VALUE
.
* Please override this method if there is better implementation.
*
* @param added another pattern.
* @return the Minkowski sum of this and another patterns.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if the numbers of space dimensions of both patterns are different.
* @throws TooManyPointsInPatternError for some forms of large patterns, if the number of points in this,
* added
or result pattern is greater than
* Integer.MAX_VALUE
or, maybe, is near this limit
* @throws TooLargePatternCoordinatesException
* if the resulting set of points does not fulfil the restrictions,
* described in the comments to {@link Pattern} interface,
* section "Coordinate restrictions".
*/
public Pattern minkowskiAdd(Pattern added) {
Objects.requireNonNull(added, "Null added argument");
if (added.dimCount() != this.dimCount) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ added.dimCount() + " instead of " + this.dimCount);
}
long addedPointCount = added.pointCount();
if (addedPointCount == 1) {
return shift(added.coordMin());
}
Set resultPoints = new HashSet<>();
Set points = points();
Set addedPoints = added.points();
for (Point p : points) {
for (Point q : addedPoints) {
resultPoints.add(p.add(q));
}
}
return new SimplePattern(resultPoints);
}
/**
* This implementation is based on the loop for all points returned by {@link #points()} method in both patterns
* and always returns a {@link DirectPointSetPattern direct point-set pattern}.
* This algorithm may be very slow for large patterns
* (O(NM) operations, N={@link #pointCount()}, M=added.{@link #pointCount()})
* and does not work at all if the number of resulting points is greater than Integer.MAX_VALUE
.
* Please override this method if there is better implementation.
*
* @param subtracted another pattern.
* @return the erosion of this pattern by the specified pattern
* or {@code null} if this erosion is the empty set.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if the numbers of space dimensions of both patterns are different.
* @throws TooManyPointsInPatternError for some forms of large patterns, if the number of points in this,
* subtracted
or result pattern is greater than
* Integer.MAX_VALUE
or, maybe, is near this limit
* @throws TooLargePatternCoordinatesException
* if the resulting set of points does not fulfil the restrictions,
* described in the comments to {@link Pattern} interface,
* section "Coordinate restrictions".
*/
public Pattern minkowskiSubtract(Pattern subtracted) {
Objects.requireNonNull(subtracted, "Null subtracted argument");
if (subtracted.dimCount() != this.dimCount) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ subtracted.dimCount() + " instead of " + this.dimCount);
}
Set subtractedPoints = subtracted.points();
Point minimal = null;
double minimalDistance = Double.POSITIVE_INFINITY;
for (Point p : subtractedPoints) {
double distance = p.distanceFromOrigin();
if (minimal == null || distance < minimalDistance) {
minimal = p;
minimalDistance = distance;
}
}
assert minimal != null : "Empty subtracted.points()";
boolean containsOrigin = minimal.isOrigin();
Set points = points();
Set resultPoints = new HashSet<>();
mainLoop:
for (Point p : points) {
// Formally, we need here a loop for infinity number of all points p in the space;
// but we can use the simple fact that the result is a subset of thisPattern.shift(-minimal)
if (!containsOrigin) {
p = p.subtract(minimal);
}
for (Point q : subtractedPoints) {
if (q.equals(minimal)) {
continue; // no sense to check
}
if (!points.contains(p.add(q))) {
continue mainLoop; // don't add p
}
}
resultPoints.add(p);
}
return resultPoints.isEmpty() ? null : Patterns.newPattern(resultPoints);
}
/**
* This implementation just returns Collections.<Pattern>singletonList(thisInstance)
.
*
* Note: {@link AbstractUniformGridPattern} class provides much better implementation for
* patterns, recognized as rectangular by {@link UniformGridPattern#isActuallyRectangular()} method.
*
* @param minimalPointCount this method usually does not decompose patterns that contain
* less than minimalPointCount
points.
* @return the decomposition of this pattern to Minkowski sum; always contains ≥1 elements.
* @throws IllegalArgumentException if the argument is negative.
*/
public List minkowskiDecomposition(int minimalPointCount) {
if (minimalPointCount < 0) {
throw new IllegalArgumentException("Negative minimalPointCount");
}
return Collections.singletonList(this);
}
/**
* This implementation just returns false
.
*
* @return true
if the Minkowski decomposition contains 2 or more elements;
* always false
in this implementation.
*/
public boolean hasMinkowskiDecomposition() {
return false;
}
/**
* This implementation returns {@link #allUnionDecompositions(int)
* allUnionDecompositions(minimalPointCount)}.get(0)
.
*
* @param minimalPointCount this method usually does not decompose patterns that contain
* less than minimalPointCount
points.
* @return a decomposition of this pattern into the union of patterns; always contains ≥1 elements.
* @throws IllegalArgumentException if the argument is negative.
*/
public List unionDecomposition(int minimalPointCount) {
return allUnionDecompositions(minimalPointCount).get(0);
}
/**
* This implementation just returns the list containing 1 list, containing
* this instance as the only element:
* Collections.singletonList(Collections.<Pattern>singletonList(thisInstance))
.
*
* Note: {@link AbstractUniformGridPattern} class provides much better implementation.
*
* @param minimalPointCount this method usually does not decompose patterns that contain
* less than minimalPointCount
points.
* @return several good variants of decomposition of this pattern to the union of patterns;
* the result always contains ≥1 elements,
* and all its elements also contain ≥1 elements.
* @throws IllegalArgumentException if the argument is negative.
*/
public List> allUnionDecompositions(int minimalPointCount) {
if (minimalPointCount < 0) {
throw new IllegalArgumentException("Negative minimalPointCount");
}
return Collections.singletonList(Collections.singletonList(this));
}
/**
* Returns true
if and only if all coordinates of the specified point lie
* in range −{@link #MAX_COORDINATE}≤xj≤{@link #MAX_COORDINATE}.
*
* Actually this method checks the 1st restriction for coordinates of any pattern:
* see comments to {@link Pattern} interface, section "Coordinate restrictions".
*
* @param point some point.
* @return whether this point is an allowed point for patterns.
* @throws NullPointerException if the argument is {@code null}.
*/
public static boolean isAllowedPoint(Point point) {
Objects.requireNonNull(point, "Null point");
for (int k = 0, n = point.coordCount(); k < n; k++) {
double coord = point.coord(k);
if (coord < -Pattern.MAX_COORDINATE || coord > Pattern.MAX_COORDINATE) {
return false;
}
}
return true;
}
/**
* Returns true
if and only if both boundaries of the specified range,
* a=range.{@link Range#min() min()}
and b=range.{@link Range#max() max()}
,
* lie in range
* −{@link #MAX_COORDINATE}≤a≤b≤{@link #MAX_COORDINATE}
* and, at the same time, the call {@link Patterns#isAllowedDifference(double, double)
* Patterns.isAllowedDifference}(a,b)
returns true
.
*
*
This method helps to check the 2nd restriction for coordinates of any pattern:
* see comments to {@link Pattern} interface, section "Coordinate restrictions".
*
* @param range some range.
* @return whether this range is an allowed coordinate range for patterns.
* @throws NullPointerException if the argument is {@code null}.
*/
public static boolean isAllowedCoordRange(Range range) {
Objects.requireNonNull(range, "Null range");
double min = range.min();
double max = range.max();
assert min <= max;
return min >= -MAX_COORDINATE && max <= MAX_COORDINATE && Patterns.isAllowedDifference(min, max);
}
/**
* Throws IndexOutOfBoundsException
* if coordIndex<0
or coordIndex>={@link #dimCount()}
.
* Does nothing in other case.
*
* @param coordIndex checked index of the coordinate.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #dimCount()}
.
*/
protected final void checkCoordIndex(int coordIndex) {
if (coordIndex < 0 || coordIndex >= dimCount) {
throw new IndexOutOfBoundsException("Coordinate index "
+ coordIndex + " is out of range 0.." + (dimCount - 1));
}
}
static void checkPoint(Point point) throws TooLargePatternCoordinatesException {
if (!isAllowedPoint(point)) {
throw new TooLargePatternCoordinatesException("Point " + point + " has one of coordinates "
+ " out of -" + MAX_COORDINATE + ".." + MAX_COORDINATE
+ " range and cannot be used for building a pattern");
}
}
static void checkCoordRange(Range range) throws TooLargePatternCoordinatesException {
Objects.requireNonNull(range, "Null range");
double min = range.min();
double max = range.max();
assert min <= max;
if (min < -MAX_COORDINATE || max > MAX_COORDINATE) {
throw new TooLargePatternCoordinatesException("Coordinate range " + range
+ " is out of -" + MAX_COORDINATE + ".." + MAX_COORDINATE
+ " range and cannot be used for building a pattern");
}
if (!Patterns.isAllowedDifference(min, max)) {
throw new TooLargePatternCoordinatesException("Coordinate range " + range
+ " has a size larger than " + MAX_COORDINATE + " and cannot be used for building a pattern");
}
}
final void fillCoordRangesWithCheck(Collection points) {
double[] minCoord = new double[dimCount];
double[] maxCoord = new double[dimCount];
Arrays.fill(minCoord, Double.POSITIVE_INFINITY);
Arrays.fill(maxCoord, Double.NEGATIVE_INFINITY);
for (Point p : points) {
checkPoint(p);
for (int k = 0; k < dimCount; k++) {
double coordinate = p.coord(k);
if (coordinate < minCoord[k]) {
minCoord[k] = coordinate;
}
if (coordinate > maxCoord[k]) {
maxCoord[k] = coordinate;
}
}
}
for (int k = 0; k < dimCount; k++) {
this.coordRanges[k] = Range.valueOf(minCoord[k], maxCoord[k]);
checkCoordRange(this.coordRanges[k]);
}
}
final Pattern minBound(int coordIndex, boolean alwaysSimple) {
checkCoordIndex(coordIndex);
synchronized (minBound) {
if (minBound[coordIndex] == null) {
Set points = points();
Map map = new HashMap<>();
for (Point p : points) {
Point projection = p.projectionAlongAxis(coordIndex);
Point bound = map.get(projection);
if (bound == null || p.coord(coordIndex) < bound.coord(coordIndex)) {
map.put(projection, p);
}
}
minBound[coordIndex] = alwaysSimple ?
new SimplePattern(map.values()) :
Patterns.newPattern(map.values());
}
return minBound[coordIndex];
}
}
final Pattern maxBound(int coordIndex, boolean alwaysSimple) {
checkCoordIndex(coordIndex);
synchronized (maxBound) {
if (maxBound[coordIndex] == null) {
Set points = points();
Map map = new HashMap<>();
for (Point p : points) {
Point projection = p.projectionAlongAxis(coordIndex);
Point bound = map.get(projection);
if (bound == null || p.coord(coordIndex) > bound.coord(coordIndex)) {
map.put(projection, p);
}
}
maxBound[coordIndex] = alwaysSimple ?
new SimplePattern(map.values()) :
Patterns.newPattern(map.values());
}
return maxBound[coordIndex];
}
}
}