/*
* Copyright (c) 2011-2015 EPFL DATA Laboratory
* Copyright (c) 2014-2015 The Squall Collaboration (see NOTICE)
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.epfl.data.squall.ewh.algorithms;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import ch.epfl.data.squall.ewh.algorithms.optimality.MaxAvgOptimality;
import ch.epfl.data.squall.ewh.algorithms.optimality.OptimalityMetricInterface;
import ch.epfl.data.squall.ewh.algorithms.optimality.WeightFunction;
import ch.epfl.data.squall.ewh.data_structures.ExtremePositions;
import ch.epfl.data.squall.ewh.data_structures.JoinMatrix;
import ch.epfl.data.squall.ewh.data_structures.MatrixIntInt;
import ch.epfl.data.squall.ewh.data_structures.Region;
import ch.epfl.data.squall.ewh.data_structures.SimpleMatrix;
import ch.epfl.data.squall.ewh.main.PushStatisticCollector;
import ch.epfl.data.squall.ewh.utilities.TooSmallMaxWeightException;
import ch.epfl.data.squall.predicates.ComparisonPredicate;
import ch.epfl.data.squall.utilities.MyUtilities;
import ch.epfl.data.squall.utilities.SystemParameters;
/*
* For growing size regions (1x1, 2x1, 2x2, etc)
** if frequency = 0, #rectangles = 0
** else
find smallest candidate containing region
if weight < CONSTANT, output is # rectangles = 1, List<CandRegions>
else try best horizontal or vertical partitioning, output is #rectangles, List<CandRegions>
* Output is partitioning on the original matrix (the biggest rectangle)
* Run binary search to get desired #rectangles = J
** Lower bound for J joiners is Wmin = (a*2*sqrt(freq) + b*freq)/J
** Upper bound is Wmax = a * (|R| + |S|) + b*freq
* Overall
* A1: We do not add regions with weight <= maxWeight
* All the other regions (both candidate and non-candidate) are added.
* These regions have at least 2 rectangles
*/
public class BSPAlgorithm implements TilingAlgorithm {
private static Logger LOG = Logger.getLogger(BSPAlgorithm.class);
private Map _map;
private int _j;
// build based on _wf; it's the precomputation of the original matrix
private WeightPrecomputation _wp;
private WeightFunction _wf;
// joinCondition, necessary for figuring out who is a candidate
private ComparisonPredicate _cp;
private ShallowCoarsener _coarsener;
private StringBuilder _sb;
// smallest value for double
private final double DELTA = 0.01;
// how many times to encounter _j joiners to exit the loop
// increase this constant to get possibly better load balance (lower actual
// maxRegionWeight)
// used in binary search
private final int EXACT_ENCOUNTERED_COUNTER_BREAK = 1;
// For large number of joiners, it may take too much time to reach exactly
// the required number of joiners
// If the obtained number of joiners is within CLOSE_PERCENTAGE of the
// desired number of joiners
// we terminate the loop and return the obtained regions
// To effectively comment this out, just set this to a extremely small
// number
private final double CLOSE_PERCENTAGE = 0.05;
// we exit the loop if any result < _j is found, and this many iterations
// are reached
private final int MAX_ITERATIONS = 20; // 10;
public enum COVERAGE_MODE {
DENSE, // obsolete: requires less memory in total (not sure)
SPARSE // complexity is much lower, works nice in practice
// (because it builds rounded matrix only on the sample matrix
// cell candidates, it expects monotonicity)
};
private COVERAGE_MODE _cmode = COVERAGE_MODE.SPARSE;
public enum DIVISION_MODE {
LINEAR, // theoretically should be slower, but in practice it is faster;
// tested to a great extent
LOG // it is also suspicious that it yields different regions than with
// LINEAR (theta_hyrack z0)
}
// private DIVISION_MODE _dmode = DIVISION_MODE.LINEAR;
private DIVISION_MODE _dmode = DIVISION_MODE.LOG;
// coarsened points;
private Map<Segment, ExtremePositions> _segmentExtremes;
// used only for COVERAGE_MODE.SPARSE
private SortedMap<Integer, List<Region>> _sccCoarsenedRegions;
private BSPAlgorithm(Map map, int j, WeightFunction wf) {
_map = map;
_j = j;
_wf = wf;
}
public BSPAlgorithm(Map map, int j, WeightFunction wf,
ShallowCoarsener coarsener) {
this(map, j, wf);
_coarsener = coarsener;
}
public BSPAlgorithm(Map map, int j, WeightFunction wf,
ShallowCoarsener coarsener, COVERAGE_MODE cmode) {
this(map, j, wf, coarsener);
_cmode = cmode;
}
@Override
public WeightPrecomputation getPrecomputation() {
return _wp;
}
@Override
public WeightFunction getWeightFunction() {
return _wf;
}
// noone is calling it as it depends on the context whether the expected
// region is original or coarsened
@Override
public double getWeight(Region region) {
return _wp.getWeight(region);
}
@Override
public List<Region> partition(JoinMatrix joinMatrix, StringBuilder sb) {
_sb = sb;
_cp = joinMatrix.getComparisonPredicate();
_coarsener.setOriginalMatrix(joinMatrix, sb);
_wp = _coarsener.getPrecomputation();
if (_wp == null) {
// only for InputShallowCoarsener
long start = System.currentTimeMillis();
if (SystemParameters.MONOTONIC_PRECOMPUTATION) {
_wp = new DenseMonotonicWeightPrecomputation(_wf, joinMatrix,
_map);
} else {
_wp = new DenseWeightPrecomputation(_wf, joinMatrix);
}
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: Precomputation done in BSP takes "
+ elapsed + " seconds.");
sb.append("\nPrecomputation done in BSP takes ").append(elapsed)
.append(" seconds.\n");
}
// assign something to each candidate rounded matrix cell
long start = System.currentTimeMillis();
List<Region> candidateRoundedCells = _coarsener
.getCandidateRoundedCells(_map);
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: getCandidateRoundedCells takes "
+ elapsed + " seconds.");
// used to avoid expensive entire join matrix weightPrecomputation
SimpleMatrix deltaCoarsenedMatrix = null;
if (MyUtilities.isIIPrecPWeight(_map)) {
deltaCoarsenedMatrix = new MatrixIntInt(
_coarsener.getNumXCoarsenedPoints(),
_coarsener.getNumYCoarsenedPoints());
}
int elementsChanged = assignNonZeroToRoundedCandidates(
candidateRoundedCells, joinMatrix, deltaCoarsenedMatrix, sb);
if (elementsChanged > 0) {
_wp = recomputePrecomputation(joinMatrix, deltaCoarsenedMatrix);
} else {
// even if no change, we have to compute the coarsenedPrecomputation
if (SystemParameters.COARSE_PRECOMPUTATION) {
start = System.currentTimeMillis();
_wp = new PWeightPrecomputation(_wf, _coarsener, _wp);
elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: SecondPrecomputation is only pWeightPrecomputation (elements = 0) takes "
+ elapsed + " seconds.");
}
}
// line extremes precomputation
if (_cmode == COVERAGE_MODE.SPARSE) {
start = System.currentTimeMillis();
precomputeSegmentExtremes(joinMatrix);
elapsed = (System.currentTimeMillis() - start) / 1000.00;
LOG.info("Part of BSP algorithm: precomputeSegmentExtremes takes "
+ elapsed + " seconds.");
start = System.currentTimeMillis();
_sccCoarsenedRegions = generateSCCCoarsenedRegions();
elapsed = (System.currentTimeMillis() - start) / 1000.00;
LOG.info("Part of BSP algorithm: generatedSCCCoarsenedRegions takes "
+ elapsed
+ " seconds. Total used memory is "
+ MyUtilities.getUsedMemoryMBs() + " MBs.");
}
// condition checks
int numCandidateGridCells = candidateRoundedCells.size();
LOG.info("Part of BSP algorithm: The number of candidate grid cells in the rounded matrix is "
+ numCandidateGridCells);
_sb.append(
"\nThe number of candidate grid cells in the rounded matrix is ")
.append(numCandidateGridCells).append(".\n");
if (numCandidateGridCells < _j) {
// one joiner must have at least one cell
throw new RuntimeException(
"Too coarse-grained partitioning, not enough cells!");
}
List<Region> regions = binarySearch(joinMatrix);
if (SystemParameters.getBooleanIfExist(_map, "CHECK_STATISTICS")) {
MyUtilities.compareActualAndSampleJM(joinMatrix, _coarsener, _wp);
}
return regions;
}
// for each candidate query cell which has no output elements, assign one
// very small output
// * joinMatrix.getNumCandidates is based on the number of non-zero entries
// ** but this method is used only in Okcan algorithms
// * joinMatrix.isEmpty is used nowhere
// * In BSP, we have minimizeToNonEmpty, that's why this method is necessary
// old name of the method is assignWeightsToCandidateRoundedCells
private int assignNonZeroToRoundedCandidates(
List<Region> candidateRoundedCells, JoinMatrix joinMatrix,
SimpleMatrix deltaCoarsenedMatrix, StringBuilder sb) {
int elementsChanged = 0;
int minPositiveValue = joinMatrix.getMinPositiveValue();
for (Region region : candidateRoundedCells) {
if (_wp.isEmpty(region)) {
// assign minimal output value to an arbitrary cell within the
// grid cell
int x1 = region.get_x1();
int y1 = region.get_y1();
joinMatrix.setMinPositiveValue(x1, y1);
if (deltaCoarsenedMatrix != null) {
// used to avoid expensive entire join matrix
// weightPrecomputation
int cx1 = _coarsener.getCoarsenedXCoordinate(x1);
int cy1 = _coarsener.getCoarsenedYCoordinate(y1);
deltaCoarsenedMatrix.setElement(minPositiveValue, cx1, cy1);
}
elementsChanged++;
}
}
LOG.info("Part of BSP algorithm: This many candidate rounded cells had no output tuples: "
+ elementsChanged + ".");
sb.append("\nThis many candidate grid cells had no output tuples: ")
.append(elementsChanged).append(".\n");
return elementsChanged;
}
private WeightPrecomputation recomputePrecomputation(JoinMatrix joinMatrix,
SimpleMatrix deltaCoarsenedMatrix) {
WeightPrecomputation denseWP = null;
PWeightPrecomputation pWP = null;
MyUtilities.checkIIPrecValid(_map);
if (MyUtilities.isIIPrecDense(_map)) {
long start = System.currentTimeMillis();
// compute pWeightPrecomputation by recomputing the entire
// Dense(Monotonic)WeightPrecomputation
if (SystemParameters.MONOTONIC_PRECOMPUTATION) {
// needs to use the same coarsener in order to not miss some of
// the minPositiveValues set from above
// namely, the above code may set the minPositiveValue in a
// sample matrix cell which a different coarsener does not
// consider a candidate
denseWP = new DenseMonotonicWeightPrecomputation(_wf,
joinMatrix, _map, _coarsener);
} else {
denseWP = new DenseWeightPrecomputation(_wf, joinMatrix);
}
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: Recomputing Dense(Monotonic)WeightPrecomputation (elementsChanged > 0) takes "
+ elapsed + " seconds.");
start = System.currentTimeMillis();
if (SystemParameters.COARSE_PRECOMPUTATION) {
denseWP = new PWeightPrecomputation(_wf, _coarsener, denseWP);
}
elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: Final PWeightPrecomputation (elementsChanged > 0) takes "
+ elapsed + " seconds.");
}
if (MyUtilities.isIIPrecPWeight(_map)) {
long start = System.currentTimeMillis();
// COARSE_PRECOMPUTATION must be true
if (SystemParameters.COARSE_PRECOMPUTATION) {
pWP = new PWeightPrecomputation(_wf, _coarsener, _wp);
pWP.addDeltaMatrix(deltaCoarsenedMatrix);
}
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: Computing pWeightPrecomputation (elementsChanged > 0) takes "
+ elapsed + " seconds.");
}
if (MyUtilities.isIIPrecBoth(_map)) {
// implies we entered into the two previous if branches
long start = System.currentTimeMillis();
MyUtilities.checkEquality(denseWP, pWP);
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
LOG.info("Part of BSP algorithm: Checking two WeightPrecomputation (elementsChanged > 0) takes "
+ elapsed + " seconds.");
}
if (pWP != null) {
// by default setting pWP
return pWP;
} else {
return denseWP;
}
}
private List<Region> binarySearch(JoinMatrix joinMatrix) {
// setting up bounds
int xSize = joinMatrix.getXSize();
int ySize = joinMatrix.getYSize();
int freq = _wp.getTotalFrequency();
double singleJoinerLowerBound = _wf.getWeight(
(int) (2 * Math.sqrt(freq / _j)), freq / _j); // we could use
// (double)freq,
// but it is a
// lower bound
// anyway
double singleJoinerUpperBound = _wf.getWeight((xSize + ySize), freq);
if (_j == 1) {
// necessary to ensure bestRegion!=null when J = 1
singleJoinerUpperBound += DELTA;
}
// actual binary search
double bestAlgMaxWeight = Double.MAX_VALUE;
double bestActualMaxWeight = Double.MAX_VALUE;
List<Region> bestRegions = null;
int exactEncounteredCounter = 0;
boolean terminate = false;
int iterations = 0;
boolean isFirstEnter = true; // the number of iterations do not change
// if TooSmallMaxWeightException
// encountered
while ((singleJoinerLowerBound <= singleJoinerUpperBound)
&& (exactEncounteredCounter < EXACT_ENCOUNTERED_COUNTER_BREAK)
&& (!terminate)
&& (iterations < MAX_ITERATIONS || bestRegions == null)) {
/*
* Optimization 1 // we favor the first trial to smaller value, such
* that we limit the number of loop iterations // thus, we start
* from relatively small maxValue(large numJoiners), which is
* hopefully less than _j // we lose all the benefits if we try too
* many times with numJoiners > _j // This is good for large _j > 8
* double factor = _j / 4.0; if (factor < 1) factor = 1; double
* currentAlgMaxWeight = (singleJoinerLowerBound * factor +
* singleJoinerUpperBound) / (factor + 1.0);
*/
/*
* Optimization 2 The solution is much closer to the
* singleJoinerLowerBound than to the singleJoinerUpperBound We want
* to have normal binary search, but also to: Take care not to have
* too many unsuccessful tries with high number of joiners, as on
* large p (e.g. p = 320) those runs are much longer (19s compared
* to 5s)
*/
int multiplier = 5; // a number which works for low-selectivity
// joins (e.g. jps)
double currentAlgMaxWeight = (singleJoinerLowerBound + singleJoinerUpperBound) / 2;
if (isFirstEnter) {
isFirstEnter = false;
currentAlgMaxWeight = (singleJoinerLowerBound + multiplier
* singleJoinerLowerBound) / 2;
}
// uncomment when no optimization is used
// double currentAlgMaxWeight = (singleJoinerLowerBound +
// singleJoinerUpperBound) / 2;
LOG.info("CurrentMaxWeight = " + currentAlgMaxWeight
+ ", binary search in [" + singleJoinerLowerBound + ", "
+ singleJoinerUpperBound + "]");
// actual work for the given maxWeight
Map<String, RegionPartitioning> regPart;
try {
regPart = drtile(currentAlgMaxWeight, joinMatrix);
} catch (TooSmallMaxWeightException e) {
// maxWeight was too small, we need to increase it
// this is not counted anywhere
LOG.info("TooSmallMaxWeightException for currentAlgMaxWeight = "
+ currentAlgMaxWeight);
singleJoinerLowerBound = currentAlgMaxWeight + DELTA;
continue;
}
int numJoiners = getNumJoiners(regPart, joinMatrix);
List<Region> regions = getRegions(regPart, joinMatrix);
OptimalityMetricInterface opt = new MaxAvgOptimality(joinMatrix,
regions, _wp);
_sb.append("\nAt time ").append(
PushStatisticCollector.getWallClockTime());
_sb.append("\nIntermediate result for maxWeight ")
.append(currentAlgMaxWeight).append(": \n");
_sb.append(Region.toString(regions, opt, "Intermediate")).append(
"\n");
// conditions
if (numJoiners < _j) {
if (MyUtilities.computePercentage(numJoiners, _j) < CLOSE_PERCENTAGE) {
terminate = true;
}
double currentActualMaxWeight = opt.getActualMaxRegionWeight();
if (currentActualMaxWeight < bestActualMaxWeight) {
// interestingly, a higher algMaxWeight can yield a lower
// actualMaxWeight
// that's why we optimize for actual weights
bestActualMaxWeight = currentActualMaxWeight;
bestAlgMaxWeight = currentAlgMaxWeight;
bestRegions = regions;
}
// subtracting a small value such that we can leave the while
// loop
singleJoinerUpperBound = currentAlgMaxWeight - DELTA;
} else if (numJoiners > _j) {
// adding a small value such that we can leave the while loop
singleJoinerLowerBound = currentAlgMaxWeight + DELTA;
} else if (numJoiners == _j) {
exactEncounteredCounter++;
// we try to find the minimum weight for which we do not have to
// use _j + 1 joiners
double currentActualMaxWeight = opt.getActualMaxRegionWeight();
if (currentActualMaxWeight < bestActualMaxWeight) {
// interestingly, a higher algMaxWeight can yield a lower
// actualMaxWeight
// that's why we optimize for actual weights
bestActualMaxWeight = currentActualMaxWeight;
bestAlgMaxWeight = currentAlgMaxWeight;
bestRegions = regions;
}
// there is maybe a smaller maxWeight which yields better
// load-balance, and the same _j
singleJoinerUpperBound = currentAlgMaxWeight - DELTA;
// hard to make this generic:
// slow down boundary change to remain within these number of
// joiners
// singleJoinerUpperBound = singleJoinerLowerBound +
// (currentMaxWeight - singleJoinerLowerBound) * 2 * 0.7;
}
iterations++;
}
LOG.info("Total BSP iterations = " + iterations
+ ", exactEncounteredCounter = " + exactEncounteredCounter
+ ", terminate = " + terminate);
_sb.append("\nFinal maxWeight = ").append(bestAlgMaxWeight)
.append("\n");
return bestRegions;
/*
* // Old version which starts from the large maxWeight // actual binary
* search double currentMaxWeight = singleJoinerUpperBound; Map<String,
* RegionPartitioning> regPart = drtile(currentMaxWeight, joinMatrix);
* int numJoiners = getNumJoiners(regPart, joinMatrix); List<Region>
* regions = getRegions(regPart, joinMatrix, _wp); double lastMaxWeight
* = 0; List<Region> lastRegions = null; while (numJoiners < _j){
* _sb.append
* ("\nAt time ").append(PushStatisticCollector.getWallClockTime());
* _sb.
* append("\nIntermediate result for maxWeight ").append(currentMaxWeight
* ).append(": ").append(Region.toString(regions, _wp)).append("\n");
*
* lastMaxWeight = currentMaxWeight; lastRegions = regions;
*
* currentMaxWeight /= 2; regPart = drtile(currentMaxWeight,
* joinMatrix); numJoiners = getNumJoiners(regPart, joinMatrix); regions
* = getRegions(regPart, joinMatrix, _wp); }
* _sb.append("\nFinal maxWeight = "
* ).append(lastMaxWeight).append("\n"); return lastRegions;
*/
/*
* Old version which starts from the small maxWeights // actual binary
* search double currentMaxWeight = singleJoinerLowerBound; Map<String,
* RegionPartitioning> regPart = drtile(currentMaxWeight, joinMatrix);
* int numJoiners = getNumJoiners(regPart, joinMatrix); List<Region>
* regions = getRegions(regPart, joinMatrix, _wp); while (numJoiners >
* _j){
* _sb.append("\nAt time ").append(PushStatisticCollector.getWallClockTime
* ()); _sb.append("\nIntermediate result for maxWeight ").append(
* currentMaxWeight).append(": ").append(Region.toString(regions,
* _wp)).append("\n");
*
* singleJoinerLowerBound = currentMaxWeight; currentMaxWeight *= 2;
* regPart = drtile(currentMaxWeight, joinMatrix); numJoiners =
* getNumJoiners(regPart, joinMatrix); regions = getRegions(regPart,
* joinMatrix, _wp); }
*
* // this is only necessary if we want to tweak the max region size in
* a more fine-grained fashion double upperBound = currentMaxWeight;
* if(upperBound > singleJoinerUpperBound){ upperBound =
* singleJoinerUpperBound; } // further call : I believe ratio is 2 if
* we do not invoke it further
* _sb.append("\nFinal maxWeight = ").append(
* currentMaxWeight).append("\n"); return regions;
*/
}
private Map<String, RegionPartitioning> drtile(double maxWeight,
JoinMatrix joinMatrix) throws TooSmallMaxWeightException {
Map<String, RegionPartitioning> regPart = new HashMap<String, RegionPartitioning>();
int coarsenedXTotalSize = _coarsener.getNumXCoarsenedPoints();
int coarsenedYTotalSize = _coarsener.getNumYCoarsenedPoints();
if (_cmode == COVERAGE_MODE.DENSE) {
// assumed non-monotonicity
for (int halfPerimeter = 2; halfPerimeter <= coarsenedXTotalSize
+ coarsenedYTotalSize; halfPerimeter++) {
for (int coarsenedRegionXSize = 1; coarsenedRegionXSize < halfPerimeter; coarsenedRegionXSize++) {
int coarsenedRegionYSize = halfPerimeter
- coarsenedRegionXSize;
for (int ci = 0; ci <= coarsenedXTotalSize
- coarsenedRegionXSize; ci++) {
for (int cj = 0; cj <= coarsenedYTotalSize
- coarsenedRegionYSize; cj++) {
int x1 = _coarsener.getOriginalXCoordinate(ci,
false);
int y1 = _coarsener.getOriginalYCoordinate(cj,
false);
int x2 = _coarsener.getOriginalXCoordinate(ci
+ coarsenedRegionXSize - 1, true);
int y2 = _coarsener.getOriginalYCoordinate(cj
+ coarsenedRegionYSize - 1, true);
Region region = new Region(x1, y1, x2, y2);
partitionRegion(region, maxWeight, regPart);
}
}
}
}
} else {
for (Map.Entry<Integer, List<Region>> entry : _sccCoarsenedRegions
.entrySet()) {
List<Region> regions = entry.getValue();
for (Region region : regions) {
// regions with the same perimeter can be processed in any
// order
partitionRegion(region, maxWeight, regPart);
}
}
}
return regPart;
}
private void partitionRegion(Region originalRegion, double maxWeight,
Map<String, RegionPartitioning> regPart)
throws TooSmallMaxWeightException {
Region coarsenedRegion = originalRegion;
// if the condition below is not fulfilled, coarsenedRegion is just an
// alias to originalRegion
if (SystemParameters.COARSE_PRECOMPUTATION) {
coarsenedRegion = _coarsener
.translateOriginalToCoarsenedRegion(originalRegion);
}
if (!_wp.isEmpty(coarsenedRegion)) {
if (_wp.getWeight(originalRegion) <= maxWeight) {
// I won't add it; for non-existing regions I assume num of
// rectangles 1 (if non-empty region), or 0(otherwise)
// addRegion(region, regPart, wf);
} else {
addRegionWithPartitioning(originalRegion, maxWeight, regPart);
}
}
}
/*
* //region is Smallest Candidate Containing Region private void
* addRegion(Region region, Map<String, RegionPartitioning> regPart) {
* region.minimizeToNotEmpty(_wp); String regionHash =
* region.getHashString(); RegionPartitioning rp = new
* RegionPartitioning(region); regPart.put(regionHash, rp); }
*/
private void addRegionWithPartitioning(Region region, double maxWeight,
Map<String, RegionPartitioning> regPart)
throws TooSmallMaxWeightException {
int x1 = region.get_x1();
int y1 = region.get_y1();
int x2 = region.get_x2();
int y2 = region.get_y2();
// version for regPart with original regions : does not create
// coarsenedRegion (instead use region)
Region coarsenedRegion = _coarsener
.translateOriginalToCoarsenedRegion(region);
String coarsenedRegionHash = coarsenedRegion.getHashString();
// A1: Having the weight on the original region bigger than maxWeight
// does not necessarily mean we will have two rectangles inside
Region sccOriginal = null;
if (_cmode == COVERAGE_MODE.DENSE) {
// version for regPart with original regions
// Region originalRegion = new Region(region);
// Region coarsenedRegion =
// _coarsener.translateOriginalToCoarsenedRegion(originalRegion);
coarsenedRegion.minimizeToNotEmptyCoarsened(_wp, _coarsener); // in-place
// modification
sccOriginal = _coarsener
.translateCoarsenedToOriginalRegion(coarsenedRegion);
if (!Region.equalPosition(region, sccOriginal)) {
// sccCoarsened is smaller than region;
// it is already examined before
// Only coarsened scc are stored; getNumJoiners and
// addRectangles methods to be changed
/*
* When low-density regions are stored: O(n^4) times invoked
* String sccCoarsenedHash = sccCoarsened.getHashString();
* if(regPart.containsKey(sccCoarsenedHash)){ // if there is an
* entry, that means more than a single rectangle; need to be
* copied RegionPartitioning sccRP =
* regPart.get(sccCoarsenedHash); regPart.put(regionHash,
* sccRP); }
*/
return;
}
} else if (_cmode == COVERAGE_MODE.SPARSE) {
// we generate only sccCoarsened regions in this mode
sccOriginal = new Region(region);
}
// computing the minimum theoretical number of rectangles to cover the
// region
// we are reporting on the level of coarsened grid cells (as a candidate
// grid cells has one artificially added output)
double dblQtyMaxWeight = _wp.getWeight(sccOriginal) / maxWeight;
// double dblQtyMaxWeight = _wp.getWeight(scc) / maxWeight;
int qtyMaxWeigh = (int) dblQtyMaxWeight;
if (qtyMaxWeigh != dblQtyMaxWeight) {
qtyMaxWeigh++;
}
if (dblQtyMaxWeight <= 1) {
// no need to put it in, as it need less than 1 rectangle
return;
}
int cx1 = _coarsener.getCoarsenedXCoordinate(x1);
int cy1 = _coarsener.getCoarsenedYCoordinate(y1);
int cx2 = _coarsener.getCoarsenedXCoordinate(x2);
int cy2 = _coarsener.getCoarsenedYCoordinate(y2);
// if we are of the unit size and bigger than weight, this is an
// exception
if (isUnitSize(cx1, cy1, cx2, cy2)) {
_sb.append("\nImpossible to achieve maxWeight less than ")
.append(maxWeight).append(" with ").append(_coarsener)
.append("\n");
throw new TooSmallMaxWeightException(maxWeight, _coarsener);
}
// I reach this point if
// a) region = sccCoarsened, meaning that there are elements on each
// boundary bucket
// b) its weight is higher than maxWeight
int bestNumRectangles = Integer.MAX_VALUE;
String bestFirstRegionHash = null;
String bestSecondRegionHash = null;
// try horizontal partitioning
int lowerBound = cx1;
int upperBound = cx2 - 1; // such that the second region has at least
// one coarsened point to cover
if (_dmode == DIVISION_MODE.LOG) {
while (lowerBound <= upperBound) {
if (bestNumRectangles == qtyMaxWeigh) {
// achieved theoretical optimum, no need to try other
// combinations
break;
}
int middleCoarsened = (lowerBound + upperBound) / 2;
int middleOriginal = _coarsener.getOriginalXCoordinate(
middleCoarsened, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(
x1, y1, middleOriginal, y2, cx1, cy1, middleCoarsened,
cy2, regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(
middleOriginal + 1, y1, x2, y2, middleCoarsened + 1,
cy1, cx2, cy2, regPart);
int currentNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (currentNumRectangles < bestNumRectangles) {
bestNumRectangles = currentNumRectangles;
bestFirstRegionHash = first.getRegionHash();
bestSecondRegionHash = second.getRegionHash();
}
// let's decide where to go
// find the number of joiners of the first right neighbor which
// has different number of joiners than me
int neighborNumRectangles = findFirstDifferentRightNeighborHor(
x1, y1, x2, y2, cx1, cy1, cx2, cy2,
currentNumRectangles, middleCoarsened, upperBound,
regPart);
if (currentNumRectangles < neighborNumRectangles) {
upperBound = middleCoarsened - 1;
} else if (currentNumRectangles > neighborNumRectangles) {
lowerBound = middleCoarsened + 1;
}
}
} else if (_dmode == DIVISION_MODE.LINEAR) {
for (int ci = lowerBound; ci <= upperBound; ci++) {
if (bestNumRectangles == qtyMaxWeigh) {
// achieved theoretical optimum, no need to try other
// combinations
break;
}
int i = _coarsener.getOriginalXCoordinate(ci, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(
x1, y1, i, y2, cx1, cy1, ci, cy2, regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(
i + 1, y1, x2, y2, ci + 1, cy1, cx2, cy2, regPart);
// TODO To achieve better load balance, we could either
// a) rely on the right maxWeight (probably the right way to do
// it for large _j)
// b) if(firstNumRectangles == 1 && secondNumRectangles == 1) &&
// (they have the same bestNumRectangles),
// pick one which minimizes Math.abs|weight(secondRegion) -
// weight(firstRegion)|
int currentNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (currentNumRectangles < bestNumRectangles) {
bestNumRectangles = currentNumRectangles;
bestFirstRegionHash = first.getRegionHash();
bestSecondRegionHash = second.getRegionHash();
}
}
}
// try vertical partitioning
lowerBound = cy1;
upperBound = cy2 - 1; // such that the second region has at least one
// coarsened point to cover
if (_dmode == DIVISION_MODE.LOG) {
while (lowerBound <= upperBound) {
if (bestNumRectangles == qtyMaxWeigh) {
// achieved theoretical optimum, no need to try other
// combinations
break;
}
int middleCoarsened = (lowerBound + upperBound) / 2;
int middleOriginal = _coarsener.getOriginalYCoordinate(
middleCoarsened, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(
x1, y1, x2, middleOriginal, cx1, cy1, cx2,
middleCoarsened, regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(
x1, middleOriginal + 1, x2, y2, cx1,
middleCoarsened + 1, cx2, cy2, regPart);
int currentNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (currentNumRectangles < bestNumRectangles) {
bestNumRectangles = currentNumRectangles;
bestFirstRegionHash = first.getRegionHash();
bestSecondRegionHash = second.getRegionHash();
}
// let's decide where to go
// find the number of joiners of the first right neighbor which
// has different number of joiners than me
int neighborNumRectangles = findFirstDifferentRightNeighborVer(
x1, y1, x2, y2, cx1, cy1, cx2, cy2,
currentNumRectangles, middleCoarsened, upperBound,
regPart);
if (currentNumRectangles < neighborNumRectangles) {
upperBound = middleCoarsened - 1;
} else if (currentNumRectangles > neighborNumRectangles) {
lowerBound = middleCoarsened + 1;
}
}
} else if (_dmode == DIVISION_MODE.LINEAR) {
for (int ci = lowerBound; ci <= upperBound; ci++) {
if (bestNumRectangles == qtyMaxWeigh) {
// achieved theoretical optimum, no need to try other
// combinations
break;
}
int i = _coarsener.getOriginalYCoordinate(ci, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(
x1, y1, x2, i, cx1, cy1, cx2, ci, regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(
x1, i + 1, x2, y2, cx1, ci + 1, cx2, cy2, regPart);
// TODO To achieve better load balance, we could either
// a) rely on the right maxWeight (probably the right way to do
// it for large _j)
// b) if(firstNumRectangles == 1 && secondNumRectangles == 1) &&
// (they have the same bestNumRectangles),
// pick one which minimizes Math.abs|weight(secondRegion) -
// weight(firstRegion)|
int currentNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (currentNumRectangles < bestNumRectangles) {
bestNumRectangles = currentNumRectangles;
bestFirstRegionHash = first.getRegionHash();
bestSecondRegionHash = second.getRegionHash();
}
}
}
if (bestNumRectangles < 2) {
throw new RuntimeException("Developer error!");
}
RegionPartitioning rp = new RegionPartitioning(region,
bestFirstRegionHash, bestSecondRegionHash, bestNumRectangles);
regPart.put(coarsenedRegionHash, rp);
}
// upperBound is the last possible position of the divisor, such that the
// second part has at least one coarsened point
// return numOfJoiners
private int findFirstDifferentRightNeighborHor(int x1, int y1, int x2,
int y2, int cx1, int cy1, int cx2, int cy2,
int currentNumRectangles, int currentMiddle, int upperBound,
Map<String, RegionPartitioning> regPart) {
int rightNeighbor = currentMiddle + 1;
while (rightNeighbor <= upperBound) {
int rightOriginal = _coarsener.getOriginalXCoordinate(
rightNeighbor, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(x1,
y1, rightOriginal, y2, cx1, cy1, rightNeighbor, cy2,
regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(
rightOriginal + 1, y1, x2, y2, rightNeighbor + 1, cy1, cx2,
cy2, regPart);
int rightNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (rightNumRectangles != currentNumRectangles) {
return rightNumRectangles;
}
rightNeighbor++;
}
// if all of them are the same, I return infinity, such that the binary
// search goes on the left side
return Integer.MAX_VALUE;
}
// upperBound is the last possible position of the divisor, such that the
// second part has at least one coarsened point
// return numOfJoiners
private int findFirstDifferentRightNeighborVer(int x1, int y1, int x2,
int y2, int cx1, int cy1, int cx2, int cy2,
int currentNumRectangles, int currentMiddle, int upperBound,
Map<String, RegionPartitioning> regPart) {
int rightNeighbor = currentMiddle + 1;
while (rightNeighbor <= upperBound) {
int rightOriginal = _coarsener.getOriginalYCoordinate(
rightNeighbor, true);
RegionHashNumRectangles first = createRegionHashNumCoarsened(x1,
y1, x2, rightOriginal, cx1, cy1, cx2, rightNeighbor,
regPart);
RegionHashNumRectangles second = createRegionHashNumCoarsened(x1,
rightOriginal + 1, x2, y2, cx1, rightNeighbor + 1, cx2,
cy2, regPart);
int rightNumRectangles = first.getNumRectangles()
+ second.getNumRectangles();
if (rightNumRectangles != currentNumRectangles) {
return rightNumRectangles;
}
rightNeighbor++;
}
// if all of them are the same, I return infinity, such that the binary
// search goes on the left side
return Integer.MAX_VALUE;
}
/*
* // invoked many times private RegionHashNumRectangles
* createRegionHashNum(int x1, int y1, int x2, int y2, Map<String,
* RegionPartitioning> regPart) { Region region = new Region(x1, y1, x2,
* y2); String regionHash = region.getHashString(); int numRectangles =
* getNumJoiners(regPart, region, regionHash); return new
* RegionHashNumRectangles(regionHash, numRectangles); }
*/
// invoked many times; more efficient version;
// for the new version of the code, first four arguments are not necessary
// (if I remove them (and the unnecessary commands), no performance
// improvement)
private RegionHashNumRectangles createRegionHashNumCoarsened(int x1,
int y1, int x2, int y2, int cx1, int cy1, int cx2, int cy2,
Map<String, RegionPartitioning> regPart) {
Region regionCoarsened = new Region(cx1, cy1, cx2, cy2);
String coarsenedRegionHash = regionCoarsened.getHashString();
int numRectangles = getNumJoinersCoarsened(regPart, regionCoarsened);
return new RegionHashNumRectangles(coarsenedRegionHash, numRectangles);
// version for regPart with original regions
// Region regionOriginal = new Region(x1, y1, x2, y2);
// Region regionCoarsened = new Region(cx1, cy1, cx2, cy2);
// String originalRegionHash = regionOriginal.getHashString();
// int numRectangles = getNumJoinersCoarsened(regPart, regionCoarsened);
// return new RegionHashNumRectangles(originalRegionHash,
// numRectangles);
}
@Override
public String toString() {
return "BSPAlgorithm with " + _coarsener;
}
// invoked once per binary search iteration
private int getNumJoiners(Map<String, RegionPartitioning> regPart,
JoinMatrix joinMatrix) {
String coarsenedMatrixHash = getCoarsenedRegionMatrixHash(regPart,
joinMatrix);
Region coarsenedRegion = new Region(coarsenedMatrixHash);
return getNumJoinersCoarsened(regPart, coarsenedRegion);
// version for regPart with original regions
// String matrixHash = getRegionMatrixHash(regPart, joinMatrix);
// Region originalRegion = new Region(matrixHash);
// Region coarsenedRegion =
// _coarsener.translateOriginalToCoarsenedRegion(originalRegion);
// return getNumJoinersCoarsened(regPart, coarsenedRegion);
}
/*
* // invoked many times private int getNumJoiners(Map<String,
* RegionPartitioning> regPart, Region region, String regionHash) { // Only
* coarsened scc are stored Region scc = new Region(regionHash);
* scc.minimizeToNotEmpty(_wp); Region sccCoarsened =
* getEnclosingRegion(scc); String sccCoarsenedHash =
* sccCoarsened.getHashString(); if(regPart.containsKey(sccCoarsenedHash)){
* return regPart.get(sccCoarsenedHash).getNumRectangles(); }else{ // if we
* are asking for it, it must have at least one rectangle return 1; } }
*/
// this should be where most of the processing time is spent (call from
// createRegionHashNumCoarsened, which is called from
// findFirstDifferentRightNeighborHor)
// more efficient version:
private int getNumJoinersCoarsened(Map<String, RegionPartitioning> regPart,
Region regionCoarsened) {
regionCoarsened.minimizeToNotEmptyCoarsened(_wp, _coarsener); // in-place
// modification
String regionCoarsenedHash = regionCoarsened.getHashString();
if (regPart.containsKey(regionCoarsenedHash)) {
return regPart.get(regionCoarsenedHash).getNumRectangles();
} else {
// if we are asking for it, it must have at least one rectangle
return 1;
}
// version for regPart with original regions
// Fixed: TODO not a bottleneck: We could make everything by default
// coarsened and avoid translations
// Region sccOriginal =
// _coarsener.translateCoarsenedToOriginalRegion(regionCoarsened);
//
// String sccOriginalHash = sccOriginal.getHashString();
// if(regPart.containsKey(sccOriginalHash)){
// return regPart.get(sccOriginalHash).getNumRectangles();
// }else{
// // if we are asking for it, it must have at least one rectangle
// return 1;
// }
/*
* When low-density regions are stored: O(n^4) times invoked
* if(regPart.containsKey(regionHash)){ return
* regPart.get(regionHash).getNumRectangles(); }else{ // A1: regions
* with more than 1 rectangles are explicitly present
*
* // if region was not passed, we instantiate it from hash if(region ==
* null){ region = new Region(regionHash); }
*
* if(wf.isNotEmpty(region)){ return 1; }else{ return 0; } }
*/
}
// invoked once per binary search iteration
private List<Region> getRegions(Map<String, RegionPartitioning> regPart,
JoinMatrix joinMatrix) {
// Only coarsened scc are stored
String coarsenedMatrixHash = getCoarsenedRegionMatrixHash(regPart,
joinMatrix);
Region coarsenedRegion = new Region(coarsenedMatrixHash);
coarsenedRegion.minimizeToNotEmptyCoarsened(_wp, _coarsener); // in-place
// modification
String coarsenedHash = coarsenedRegion.getHashString();
if (regPart.containsKey(coarsenedHash)) {
return regPart.get(coarsenedHash).getRectangles(regPart, this);
} else {
// weight(matrixRegion) < maxWeight; that's why it was not inserted
List<Region> regions = new ArrayList<Region>();
// we are reporting on the level of coarsened grid cells (as a
// candidate grid cells has one artificially added output)
Region sccOriginal = _coarsener
.translateCoarsenedToOriginalRegion(coarsenedRegion);
regions.add(sccOriginal);
// regions.add(scc);
return regions;
}
// version for regPart with original regions
// String matrixHash = getRegionMatrixHash(regPart, joinMatrix);
// Region originalRegion = new Region(matrixHash);
// Region coarsenedRegion =
// _coarsener.translateOriginalToCoarsenedRegion(originalRegion);
// coarsenedRegion.minimizeToNotEmptyCoarsened(_wp, _coarsener); //
// in-place modification
// Region sccOriginal =
// _coarsener.translateCoarsenedToOriginalRegion(coarsenedRegion);
// String sccOriginalHash = sccOriginal.getHashString();
// if(regPart.containsKey(sccOriginalHash)){
// return regPart.get(sccOriginalHash).getRectangles(regPart, this);
// }else{
// // weight(matrixRegion) < maxWeight; that's why it was not inserted
// List<Region> regions = new ArrayList<Region>();
// // we are reporting on the level of coarsened grid cells (as a
// candidate grid cells has one artificially added output)
// regions.add(sccOriginal);
// // regions.add(scc);
// return regions;
// }
/*
* Old way originalRegion.minimizeToNotEmpty(_wp); Region sccCoarsened =
* getEnclosingRegion(originalRegion);
*/
}
// version for regPart with original regions
// private String getRegionMatrixHash(Map<String, RegionPartitioning>
// regPart, JoinMatrix joinMatrix) {
// int xSize = joinMatrix.getXSize() - 1;
// int ySize = joinMatrix.getYSize() - 1;
// Region region = new Region(0, 0, xSize, ySize);
// return region.getHashString();
// }
private String getCoarsenedRegionMatrixHash(
Map<String, RegionPartitioning> regPart, JoinMatrix joinMatrix) {
// int xSize = joinMatrix.getXSize() - 1;
// int ySize = joinMatrix.getYSize() - 1;
int xSize = _coarsener.getNumXCoarsenedPoints() - 1;
int ySize = _coarsener.getNumYCoarsenedPoints() - 1;
Region region = new Region(0, 0, xSize, ySize);
return region.getHashString();
}
// originally in coarsener
/*
* We are using this method as we want to reduce the number of rectangles
* which we take into account for DP algorithm Namely, a regions either
* contains the grid cell as a whole, or not at all.
*/
public Region getEnclosingRegion(Region scc) {
int cx1 = _coarsener.getCoarsenedXCoordinate(scc.get_x1());
int cy1 = _coarsener.getCoarsenedYCoordinate(scc.get_y1());
int cx2 = _coarsener.getCoarsenedXCoordinate(scc.get_x2());
int cy2 = _coarsener.getCoarsenedYCoordinate(scc.get_y2());
int x1 = _coarsener.getOriginalXCoordinate(cx1, false);
int y1 = _coarsener.getOriginalYCoordinate(cy1, false);
int x2 = _coarsener.getOriginalXCoordinate(cx2, true);
int y2 = _coarsener.getOriginalYCoordinate(cy2, true);
return new Region(x1, y1, x2, y2);
}
// coarsened coordinates
public boolean isUnitSize(int cx1, int cy1, int cx2, int cy2) {
return cx1 == cx2 && cy1 == cy2;
}
// monotonic optimization not worthed - similar as for
// getCandidateRoundedCells
public List<Region> getGridCells() {
List<Region> regions = new ArrayList<Region>();
for (int i = 0; i < _coarsener.getNumXCoarsenedPoints(); i++) {
for (int j = 0; j < _coarsener.getNumYCoarsenedPoints(); j++) {
int x1 = _coarsener.getOriginalXCoordinate(i, false);
int y1 = _coarsener.getOriginalYCoordinate(j, false);
int x2 = _coarsener.getOriginalXCoordinate(i, true);
int y2 = _coarsener.getOriginalYCoordinate(j, true);
Region region = new Region(x1, y1, x2, y2);
regions.add(region);
}
}
return regions;
}
// end of coarsener
// Take advantage of sparseness
// monotonic optimization not worthed - similar as for
// getCandidateRoundedCells
private void precomputeSegmentExtremes(JoinMatrix joinMatrix) {
_segmentExtremes = new HashMap<Segment, ExtremePositions>();
// first we compute the extreme points per line
// lines with no candidate grid cells are not added
for (int i = 0; i < _coarsener.getNumXCoarsenedPoints(); i++) {
int x1 = _coarsener.getOriginalXCoordinate(i, false);
int x2 = _coarsener.getOriginalXCoordinate(i, true);
Segment segment = new Segment(i, i);
boolean isSetMostLeft = false;
for (int j = 0; j < _coarsener.getNumYCoarsenedPoints(); j++) {
int y1 = _coarsener.getOriginalYCoordinate(j, false);
int y2 = _coarsener.getOriginalYCoordinate(j, true);
Region region = new Region(x1, y1, x2, y2);
if (MyUtilities
.isCandidateRegion(joinMatrix, region, _cp, _map)) {
if (!isSetMostLeft) {
ExtremePositions ep = new ExtremePositions(j, j);
_segmentExtremes.put(segment, ep);
isSetMostLeft = true;
} else {
// ep already exists
ExtremePositions ep = _segmentExtremes.get(segment);
ep.setMostRight(j); // update the last position (i) with
// value j in place
}
}
}
}
// height is total number of points of the region per dimension X
for (int height = 2; height <= _coarsener.getNumXCoarsenedPoints(); height++) {
for (int i = 0; i <= _coarsener.getNumXCoarsenedPoints() - height; i++) {
int cx1 = i;
int cx2 = i + height - 1;
int upDivider = i; // arbitrary point in between is a divider
int lowDivider = i + 1;
Segment s1 = new Segment(cx1, upDivider);
Segment s2 = new Segment(lowDivider, cx2);
ExtremePositions ep1 = _segmentExtremes.get(s1);
if (ep1 == null) {
continue; // do not analyze this segment, as the topLine
// does not exist
}
ExtremePositions ep2 = _segmentExtremes.get(s2);
if (ep2 == null) {
ep2 = findCandSegment(s2);
// if it's still null, s2 does not exist, so we do not
// analyze this segment at all
if (ep2 == null) {
continue;
}
}
int mostLeft = Math.min(ep1.getMostLeft(),
ep2.getMostLeft());
int mostRight = Math.max(ep1.getMostRight(),
ep2.getMostRight());
// adding multi-line segments
Segment segment = new Segment(cx1, cx2);
ExtremePositions ep = new ExtremePositions(mostLeft, mostRight);
_segmentExtremes.put(segment, ep);
}
}
}
/*
* Given a horizontal segment, peel off top non-candidate lines Could be
* done in O(logB) time, where B is the number of buckets but it's really
* not on the critical path (precomputation is reduced from O(B^3) (which is
* by the way never the case to be so bad) to O(B^2 logB))
*/
private ExtremePositions findCandSegment(Segment segment) {
int lowerPos = segment.getLowerPos();
int upperPos = segment.getUpperPos();
// check upperPos
Segment s2 = new Segment(upperPos, upperPos);
if (!_segmentExtremes.containsKey(s2)) {
// the original region cannot exist, and thus cannot be divided into
// two segments,
// The segment passed to this method is the upper one
return null;
}
// move lowerPos
Segment s1 = new Segment(lowerPos, lowerPos);
while (!_segmentExtremes.containsKey(s1)) {
lowerPos++;
s1 = new Segment(lowerPos, lowerPos);
}
return _segmentExtremes.get(new Segment(lowerPos, upperPos));
}
// SortedMap<Perimeter, Region>
// Perimeter is in terms of coarsened matrix, Regions are in terms of
// original Matrix
// no performance improvement if I keep coarsenedRegions instead
private SortedMap<Integer, List<Region>> generateSCCCoarsenedRegions() {
SortedMap<Integer, List<Region>> result = new TreeMap<Integer, List<Region>>();
// two outer loops go over all the horizontal segments
// height is total number of points of the region per dimension X
for (int height = 1; height <= _coarsener.getNumXCoarsenedPoints(); height++) {
for (int i = 0; i <= _coarsener.getNumXCoarsenedPoints() - height; i++) {
int cx1 = i;
int cx2 = i + height - 1;
Segment segment = new Segment(cx1, cx2);
ExtremePositions ep = _segmentExtremes.get(segment);
if (ep == null) {
// do not analyze this segment, as it does not exist in the
// _segmentExtremes
// this means that either topLine or bottomLine does not
// exist
continue;
}
int mostLeft = ep.getMostLeft();
int mostRight = ep.getMostRight();
Segment topLine = new Segment(cx1, cx1);
ExtremePositions epTop = _segmentExtremes.get(topLine);
int topLineMostRight = epTop.getMostRight();
Segment bottomLine = new Segment(cx2, cx2);
ExtremePositions epBottom = _segmentExtremes.get(bottomLine);
int bottomLineMostLeft = epBottom.getMostLeft();
for (int p = mostLeft; p <= topLineMostRight; p++) {
for (int q = Math.max(p, bottomLineMostLeft); q <= mostRight; q++) {
// cx1, cx2 already exists, cy1 = p, cy2 = q
Region coarsened = new Region(cx1, p, cx2, q);
int halfPerimeter = coarsened.getHalfPerimeter();
int x1 = _coarsener.getOriginalXCoordinate(cx1, false);
int y1 = _coarsener.getOriginalYCoordinate(p, false);
int x2 = _coarsener.getOriginalXCoordinate(cx2, true);
int y2 = _coarsener.getOriginalYCoordinate(q, true);
Region region = new Region(x1, y1, x2, y2);
addToCollection(result, halfPerimeter, region);
}
}
}
}
return result;
}
private void addToCollection(SortedMap<Integer, List<Region>> collection,
int halfPerimeter, Region region) {
List<Region> regions = null;
if (collection.containsKey(halfPerimeter)) {
regions = collection.get(halfPerimeter);
} else {
regions = new ArrayList<Region>();
collection.put(halfPerimeter, regions);
}
regions.add(region);
}
// end of sparseness
// hashes are for coarsened regions, but the regions are actually original
public class RegionPartitioning {
private Region _region;
private String _bestFirstSubregionHash = null;
private String _bestSecondSubregionHash = null;
private int _numRectangles; // a rectangle is of maximum weight
// maxWeight
// weight of a region less than maxWeight
public RegionPartitioning(Region region) {
_region = region;
_numRectangles = 1;
}
// weight of a region bigger than maxWeight; it is partitioned into 2
// subregions, such that the total numRectangles is minimized
public RegionPartitioning(Region region, String bestFirstSubregionHash,
String bestSecondSubregionHash, int numRectangles) {
_region = region;
_bestFirstSubregionHash = bestFirstSubregionHash;
_bestSecondSubregionHash = bestSecondSubregionHash;
_numRectangles = numRectangles;
}
public Region getRegion() {
return _region;
}
public int getNumRectangles() {
return _numRectangles;
}
public String getBestFirstSubregionHash() {
return _bestFirstSubregionHash;
}
public String getBestSecondSubregionHash() {
return _bestSecondSubregionHash;
}
// invoked once per binary search iteration
public List<Region> getRectangles(
Map<String, RegionPartitioning> regPart, BSPAlgorithm bsp) {
List<Region> allRegions = new ArrayList<Region>();
addRectangles(allRegions, _bestFirstSubregionHash, regPart, bsp);
addRectangles(allRegions, _bestSecondSubregionHash, regPart, bsp);
return allRegions;
}
// allRegions appended with regions
private void addRectangles(List<Region> allRegions,
String coarsenedRegionHash,
Map<String, RegionPartitioning> regPart, BSPAlgorithm bsp) {
Region coarsenedRegion = new Region(coarsenedRegionHash);
int numRectangles = bsp.getNumJoinersCoarsened(regPart,
coarsenedRegion);
coarsenedRegion.minimizeToNotEmptyCoarsened(_wp, _coarsener); // in-place
// modification
// we ignore regions with 0 rectangles; should not appear anyway
// (look at A1 assumption)
if (numRectangles == 1) {
// we are reporting on the level of coarsened grid cells (as a
// candidate grid cells has one artificially added output)
Region sccOriginal = _coarsener
.translateCoarsenedToOriginalRegion(coarsenedRegion);
allRegions.add(sccOriginal);
// allRegions.add(scc);
} else if (numRectangles > 1) {
// recursive invocation
// Only coarsened scc are stored, because of candidate grid
// cells which contain only 1 element per grid cell
String coarsenedHash = coarsenedRegion.getHashString();
allRegions.addAll(regPart.get(coarsenedHash).getRectangles(
regPart, bsp));
/*
* When low-density regions are stored: (not on critical path,
* invoked only at the end)
* allRegions.addAll(regPart.get(regionHash
* ).getRectangles(regPart, wp));
*/
}
}
// version for regPart with original regions
// private void addRectangles(List<Region> allRegions, String
// regionHash, Map<String, RegionPartitioning> regPart, BSPAlgorithm
// bsp) {
// Region originalRegion = new Region(regionHash);
// Region coarsenedRegion =
// _coarsener.translateOriginalToCoarsenedRegion(originalRegion);
// int numRectangles = bsp.getNumJoinersCoarsened(regPart,
// coarsenedRegion);
// coarsenedRegion.minimizeToNotEmptyCoarsened(_wp, _coarsener); //
// in-place modification
// Region sccOriginal =
// _coarsener.translateCoarsenedToOriginalRegion(coarsenedRegion);
//
// // we ignore regions with 0 rectangles; should not appear anyway
// (look at A1 assumption)
// if(numRectangles == 1){
// //we are reporting on the level of coarsened grid cells (as a
// candidate grid cells has one artificially added output)
// allRegions.add(sccOriginal);
//
// //allRegions.add(scc);
// }else if (numRectangles > 1){
// //recursive invocation
//
// // Only coarsened scc are stored, because of candidate grid cells
// which contain only 1 element per grid cell
// String sccOriginalHash = sccOriginal.getHashString();
// allRegions.addAll(regPart.get(sccOriginalHash).getRectangles(regPart,
// bsp));
//
// /* When low-density regions are stored: (not on critical path,
// invoked only at the end)
// allRegions.addAll(regPart.get(regionHash).getRectangles(regPart,
// wp));
// */
// }
// }
}
private class Segment {
// coarsened points
private int _lowerPos, _upperPos;
public Segment(int lowerPos, int upperPos) {
_lowerPos = lowerPos;
_upperPos = upperPos;
}
public Segment(String hash) {
String[] parts = hash.split("-");
_lowerPos = Integer.parseInt(new String(parts[0]));
_upperPos = Integer.parseInt(new String(parts[1]));
}
public int getLowerPos() {
return _lowerPos;
}
public int getUpperPos() {
return _upperPos;
}
public void setLowerPos(int lowerPos) {
_lowerPos = lowerPos;
}
public void setUpperPos(int upperPos) {
_upperPos = upperPos;
}
@Override
public String toString() {
return "" + _lowerPos + "-" + _upperPos;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + _lowerPos;
result = prime * result + _upperPos;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Segment other = (Segment) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (_lowerPos != other._lowerPos)
return false;
if (_upperPos != other._upperPos)
return false;
return true;
}
private BSPAlgorithm getOuterType() {
return BSPAlgorithm.this;
}
}
private class RegionHashNumRectangles {
private String _regionHash;
private int _numRectangles;
public RegionHashNumRectangles(String regionHash, int numRectangles) {
_regionHash = regionHash;
_numRectangles = numRectangles;
}
public String getRegionHash() {
return _regionHash;
}
public int getNumRectangles() {
return _numRectangles;
}
public void setRegionHash(String regionHash) {
_regionHash = regionHash;
}
public void setNumRectangles(int numRectangles) {
_numRectangles = numRectangles;
}
}
@Override
public String getShortName() {
String shortName = "bsp_";
if (_coarsener instanceof InputShallowCoarsener) {
shortName += "i";
} else if (_coarsener instanceof InputOutputShallowCoarsener) {
shortName += "io";
} else if (_coarsener instanceof OutputShallowCoarsener) {
shortName += "o";
} else {
throw new RuntimeException("Unsupported coarsener "
+ _coarsener.toString());
}
return shortName;
}
}