All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.math.patterns.MinkowskiSum Maven / Gradle / Ivy

Go to download

Open-source Java libraries, supporting generalized smart arrays and matrices with elements of any types, including a wide set of 2D-, 3D- and multidimensional image processing and other algorithms, working with arrays and matrices.

There is a newer version: 1.4.23
Show newest version
/*
 * 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.Point;
import net.algart.math.Range;
import net.algart.math.RectangularArea;

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.*;

final class MinkowskiSum extends AbstractPattern implements Pattern {
    private final Pattern[] summands;
    private final List optimizedSummands;
    private final Pattern[] projections;
    private volatile Reference> points = null;

    MinkowskiSum(Pattern[] patterns) {
        this(patterns, null);
    }

    private MinkowskiSum(Pattern[] patterns, List optimizedSummands) {
        super(getDimCountAndCheck(patterns));
        List allSummands = new ArrayList<>();
        for (Pattern ptn : patterns) {
            if (ptn instanceof MinkowskiSum) {
                allSummands.addAll(Arrays.asList(((MinkowskiSum) ptn).summands));
            } else {
                allSummands.add(ptn);
            }
            // An alternate idea could be using ptn.minkowskiDecomposition always.
            // But it requires to choose minimalPointCount, which is unknown here:
            // for example, maybe we do not want to decompose little rectangular patterns.
        }
        double[] minCoord = new double[dimCount]; // zero-filled (for further summing)
        double[] maxCoord = new double[dimCount]; // zero-filled (for further summing)
        for (Pattern ptn : allSummands) {
            for (int k = 0; k < dimCount; k++) {
                Range range = ptn.coordRange(k);
                minCoord[k] += range.min();
                maxCoord[k] += range.max();
            }
        }
        for (int k = 0; k < dimCount; k++) {
            this.coordRanges[k] = Range.valueOf(minCoord[k], maxCoord[k]);
            checkCoordRange(this.coordRanges[k]);
        }
        this.summands = allSummands.toArray(new Pattern[allSummands.size()]);
        if (optimizedSummands == null) {
            this.optimizedSummands = optimizeMinkowskiSum(allSummands);
        } else {
            this.optimizedSummands = optimizedSummands;
        }
        this.projections = new Pattern[dimCount]; //null-filled
    }

    @Override
    public long pointCount() {
        return points().size();
    }

//    @Override
//    public boolean contains(IPoint point) {
//        return points().contains(point.toPoint());
//    }

    @Override
    public Set points() {
        Set resultPoints = points == null ? null : points.get();
        if (resultPoints == null) {
            Pattern ptn = new SimplePattern(optimizedSummands.get(0).points());
            // this actualization is necessary for a case when minkowskiAdd method just returns MinkowskiSum instance
            for (int k = 1, n = optimizedSummands.size(); k < n; k++) {
                ptn = ptn.minkowskiAdd(optimizedSummands.get(k));
            }
            resultPoints = ptn.points(); // immutable set
            points = new SoftReference<>(resultPoints);
        }
        return resultPoints;
    }

    @Override
    public Range coordRange(int coordIndex) {
        return coordRanges[coordIndex];
    }

    @Override
    public RectangularArea coordArea() {
        return RectangularArea.valueOf(coordRanges);
    }

    @Override
    public boolean isSurelySinglePoint() {
        for (Pattern p : optimizedSummands) {
            if (!p.isSurelySinglePoint()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean isSurelyInteger() {
        if (surelyInteger == null) {
            boolean allInteger = true;
            for (Pattern p : optimizedSummands) {
                if (!p.isSurelyInteger()) {
                    allInteger = false;
                    break;
                }
            }
            surelyInteger = allInteger;
        }
        return surelyInteger;
    }

    @Override
    public Pattern projectionAlongAxis(int coordIndex) {
        checkCoordIndex(coordIndex);
        assert dimCount > 0;
        if (dimCount == 1) {
            throw new IllegalStateException("Cannot perform projection for 1-dimensional pattern");
        }
        synchronized (projections) {
            if (projections[coordIndex] == null) {
                Pattern[] newSummands = new Pattern[summands.length];
                for (int k = 0; k < newSummands.length; k++) {
                    newSummands[k] = summands[k].projectionAlongAxis(coordIndex);
                }
                projections[coordIndex] = new MinkowskiSum(newSummands);
            }
            return projections[coordIndex];
        }
    }

    // We could also optimize minBound()/maxBound() here

    @Override
    public Pattern shift(Point shift) {
        if (shift.coordCount() != dimCount) {
            throw new IllegalArgumentException("The number of shift coordinates " + shift.coordCount()
                + " is not equal to the number of pattern coordinates " + dimCount);
        }
        if (shift.isOrigin()) {
            return this;
        }
        Pattern[] newSummands = summands.clone();
        newSummands[0] = newSummands[0].shift(shift);
        List newOptimizedSummands = new ArrayList<>(optimizedSummands);
        newOptimizedSummands.set(0, newOptimizedSummands.get(0).shift(shift));
        return new MinkowskiSum(newSummands, newOptimizedSummands);
    }

    @Override
    public Pattern scale(double... multipliers) {
        Objects.requireNonNull(multipliers, "Null multipliers argument");
        if (multipliers.length != dimCount) {
            throw new IllegalArgumentException("Illegal number of multipliers: "
                + multipliers.length + " instead of " + dimCount);
        }
        Pattern[] newSummands = new Pattern[summands.length];
        for (int k = 0; k < newSummands.length; k++) {
            newSummands[k] = summands[k].scale(multipliers);
        }
        return new MinkowskiSum(newSummands);
    }

    @Override
    public Pattern minkowskiAdd(Pattern added) {
        Objects.requireNonNull(added, "Null added argument");
        Pattern[] newSummands = new Pattern[summands.length + 1];
        System.arraycopy(summands, 0, newSummands, 0, summands.length);
        newSummands[summands.length] = added;
        return new MinkowskiSum(newSummands);
    }

    @Override
    public List minkowskiDecomposition(int minimalPointCount) {
        ArrayList result = new ArrayList<>();
        for (Pattern summand : optimizedSummands) {
            result.addAll(summand.minkowskiDecomposition(minimalPointCount));
        }
        int numberOfPatternsWithAtLeast2Points = 0;
        for (Pattern ptn : result) {
            long pointCount = ptn.pointCount();
            if (pointCount >= 3) {
                return Collections.unmodifiableList(result);
            }
            if (pointCount >= 2) {
                numberOfPatternsWithAtLeast2Points++;
            }
        }
        if (numberOfPatternsWithAtLeast2Points <= 1) {
            return Collections.singletonList(this);
        }
        return Collections.unmodifiableList(result);
    }

    @Override
    public String toString() {
        int n = optimizedSummands.size();
        StringBuilder sb = new StringBuilder(dimCount + "D Minkowski sum of " + n + " patterns: ");
        for (int k = 0; k < n; k++) {
            if (k > 0) {
                sb.append(" (+) ");
            }
            sb.append(optimizedSummands.get(k));
        }
        return sb.toString();
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(summands) ^ getClass().getName().hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof MinkowskiSum && (obj == this || Arrays.equals(summands, ((MinkowskiSum) obj).summands));
    }

    static int getDimCountAndCheck(Pattern[] patterns) {
        Objects.requireNonNull(patterns, "Null patterns argument");
        if (patterns.length == 0) {
            throw new IllegalArgumentException("Empty patterns array");
        }
        Objects.requireNonNull(patterns[0], "Null pattern is the array");
        int result = patterns[0].dimCount();
        for (int k = 1; k < patterns.length; k++) {
            Objects.requireNonNull(patterns[k], "Null pattern #" + k + " is the array");
            if (patterns[k].dimCount() != result) {
                throw new IllegalArgumentException("Patterns dimensions mismatch: the first pattern has "
                    + result + " dimensions, but pattern #" + k + " has " + patterns[k].dimCount());
            }
        }
        return result;
    }

    private static List optimizeMinkowskiSum(List patterns) {
        Map numbersOfEquals = new HashMap<>();
        Map rectangularSummands = new HashMap<>();
        Pattern onePointSummand = null;
        for (Pattern pattern : patterns) {
            if (pattern instanceof OnePointPattern) {
                onePointSummand = onePointSummand == null ? pattern : onePointSummand.minkowskiAdd(pattern);
                if (!(onePointSummand instanceof OnePointPattern)) {
                    throw new AssertionError("Invalid OnePointPattern.minkowskiAdd implementation");
                }
            } else if (pattern instanceof UniformGridPattern ugPattern
                && ((UniformGridPattern) pattern).isActuallyRectangular())
            {
                ugPattern = new BasicRectangularPattern(ugPattern.originOfGrid(), ugPattern.stepsOfGrid(),
                    ugPattern.gridIndexArea().ranges());
                Point steps = Point.valueOf(ugPattern.stepsOfGrid());
                UniformGridPattern previousSummand = rectangularSummands.get(steps);
                pattern = previousSummand == null ? ugPattern : previousSummand.minkowskiAdd(ugPattern);
                if (!(pattern instanceof BasicRectangularPattern)) {
                    throw new AssertionError("Invalid RectangularUniformGridPattern.minkowskiAdd implementation");
                }
                rectangularSummands.put(steps, (BasicRectangularPattern) pattern);
            } else {
                Integer previousNumber = numbersOfEquals.get(pattern);
                if (previousNumber == null) {
                    numbersOfEquals.put(pattern, 1);
                } else {
                    numbersOfEquals.put(pattern, previousNumber + 1);
                }
            }
        }
        List> multiPatterns =
                new ArrayList<>(numbersOfEquals.entrySet());
        // Sorting by decreasing number of points
        multiPatterns.sort((o1, o2) -> {
            long pointCount1 = o1.getKey().pointCount();
            long pointCount2 = o2.getKey().pointCount();
            return Long.compare(pointCount2, pointCount1);
        });
        List result = new ArrayList<>(rectangularSummands.values());
        if (onePointSummand != null) {
            if (result.isEmpty()) {
                result.add(onePointSummand);
            } else {
                result.set(0, onePointSummand.minkowskiAdd(result.get(0)));
            }
        }
        Pattern last = null;
        for (Map.Entry multiPattern : multiPatterns) {
            Pattern p = multiPattern.getKey();
            int n = multiPattern.getValue();
            Pattern c = null;
            if (n > 1 || last != null) {
                c = p.carcass();
            }
            if (last != null && last.minkowskiAdd(p).equals(last.minkowskiAdd(c))) {
                // probably true, because patterns were sorted
                result.add(c);
            } else {
                result.add(p);
            }
            n--; // 1 pattern used (included into result);
            if (n > 0) {
                int maxMultiplier = p.maxCarcassMultiplier();
                for (int m = 1; ; ) {
                    if (m > n) {
                        m = n;
                    }
                    assert c != null; // because n > 0 after n--
                    result.add(c.multiply(m));
                    n -= m; // m patterns used now
                    if (n == 0) {
                        break;
                    }
                    assert n >= 0 : "Counter overflow while optimizing Minkowski sum";
                    if (2 * m >= 0 && // no overflow yet
                        2 * m <= maxMultiplier)
                    {
                        m *= 2;
                    }
                }
            }
            last = p;
        }
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy