/*
* 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.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import ch.epfl.data.squall.ewh.algorithms.optimality.WeightFunction;
import ch.epfl.data.squall.ewh.data_structures.JoinMatrix;
import ch.epfl.data.squall.ewh.data_structures.Point;
import ch.epfl.data.squall.ewh.data_structures.Region;
import ch.epfl.data.squall.utilities.SystemParameters;
/*
* Markov chain Monte carlo Random Walk
*/
public class MCMCRandomWalkAlgorithm implements TilingAlgorithm {
private Map _map;
private int _j;
private Random _randomGen = new Random();
private static final double THRESHOLD_PROB = 0.8; // 80%
private static final double MIN_SHIFT_PORTION = 0.01;
private static final int MAX_GAUSS_RANDOM = 4;
public MCMCRandomWalkAlgorithm(Map map, int j) {
_map = map;
_j = j;
}
@Override
public WeightPrecomputation getPrecomputation() {
throw new RuntimeException("Implement me please!");
}
@Override
public WeightFunction getWeightFunction() {
throw new RuntimeException("Implement me!");
}
@Override
public double getWeight(Region region) {
throw new RuntimeException("Implement me!");
}
@Override
public List<Region> partition(JoinMatrix joinMatrix, StringBuilder sb) {
WeightPrecomputation wp = null;
if (SystemParameters.MONOTONIC_PRECOMPUTATION) {
wp = new DenseMonotonicWeightPrecomputation(
new WeightFunction(1, 1), joinMatrix, _map); // TODO make
// them
// parameters
} else {
wp = new DenseWeightPrecomputation(new WeightFunction(1, 1),
joinMatrix); // TODO make them parameters
}
List<Region> previousRegions = initializeRegions(joinMatrix);
Goodness previousGoodness = new Goodness(joinMatrix, previousRegions,
wp);
List<Region> bestRegions = previousRegions;
Goodness bestGoodness = previousGoodness;
for (int i = 0; i < 10; i++) {
double avgWeight = getAvgWeight(previousRegions, wp);
int shiftedRegionNum = chooseRegion(previousRegions, avgWeight);
Region choosenRegion = previousRegions.get(shiftedRegionNum);
boolean isSmallerWeight = wp.getWeight(choosenRegion) < avgWeight;
Region shiftedRegion = randomShift(choosenRegion, wp,
isSmallerWeight, joinMatrix.getXSize(),
joinMatrix.getYSize());
List<Region> currentRegions = adjustRegions(previousRegions,
shiftedRegionNum, shiftedRegion);
Goodness currentGoodness = new Goodness(joinMatrix, currentRegions,
wp);
double alpha = currentGoodness.compare(previousGoodness);
if (alpha > 1 && currentGoodness.compare(bestGoodness) > 1) {
// we are better than previous mapping, and better than the
// previously known best mapping
bestRegions = currentRegions;
bestGoodness = currentGoodness;
}
boolean accept = isAccept(alpha);
if (accept) {
previousRegions = currentRegions;
previousGoodness = new Goodness(joinMatrix, previousRegions, wp);
}
}
return bestRegions;
}
/*
* Initial regions covers all the 1-elements by using a simple algorithm /
* division on rows for example
*/
private static List<Region> initializeRegions(JoinMatrix joinMatrix) {
// TODO Auto-generated method stub
return null;
}
/*
* We opt for a region further away from avgWeight with higher probability
*/
private static int chooseRegion(List<Region> regions, double avgWeight) {
// TODO Auto-generated method stub
return 0;
}
/*
* Randomly pick one of the four corners If the region is smaller than AVG,
* with higher probability choose to change both x,y such that region grows
* otherwise, with higher probability choose to change both x,y such that
* regions decreases Step size is Gauss 0 (dim/J*100), dim/J
*
* Make sure that the step is not outside of the matrix; and that there are
* some candidate cells
*
* isSmaller - does current region has smaller weight than avgWeight
*/
private Region randomShift(Region region, WeightPrecomputation wf,
boolean isSmallerWeight, int xSize, int ySize) {
int cornerNum = -1;
Point cornerToShift = null;
Point shiftedCorner = null;
Region result;
boolean exitCondition = true;
do {
cornerNum = _randomGen.nextInt(4);
cornerToShift = region.getCorner(cornerNum);
TwoBoolean tb = getChangeDirection(cornerNum, isSmallerWeight);
// finally, compute shifts
int shiftX = generateShift(xSize, tb.getFirst());
int shiftY = generateShift(ySize, tb.getSecond());
shiftedCorner = cornerToShift.shift(shiftX, shiftY);
// now check the conditions
// first, move the point inside the matrix and check if any shift
// from the original point is made
shiftedCorner = putWithinMatrix(shiftedCorner, xSize, ySize);
boolean isCornerShifted = !cornerToShift.equals(shiftedCorner);
if (!isCornerShifted)
continue;
// second, check if the region contains at least one candidate cell
result = region.shiftCorner(cornerNum, cornerToShift);
if (wf.isEmpty(result))
continue;
// third, minimize the perimeter, give the candidate cells inside
// the result region
// TODO comment result = result.minimizeToNonEmpty(cornerNum, wf);
// TODO Optimization: if increase in size, we could check only the
// added part of the rectangle for candidates
break;
} while (true);
return result;
}
public TwoBoolean getChangeDirection(int cornerNum, boolean isSmallerWeight) {
// each coordinate has bigger chance either to reduce or to increase
boolean reduceX = false;
boolean reduceY = false;
// if isSmaller
if (cornerNum < 2) {
reduceX = true;
}
if (cornerNum % 2 == 0) {
reduceY = true;
}
if (!isSmallerWeight) {
// isBigger is an opposite
reduceX = !reduceX;
reduceY = !reduceY;
}
// with higher probability do what was suggested; otherwise take an
// opposite step
double shiftProb = _randomGen.nextDouble();
if (shiftProb >= THRESHOLD_PROB) {
reduceX = !reduceX;
reduceY = !reduceY;
}
return new TwoBoolean(reduceX, reduceY);
}
// the output of this Gaussian is from [-MAX_GAUSS_RANDOM,
// MAX_GAUSS_RANDOM], where the MAX_GAUSS_RANDOM = 4, in more than 99.9% of
// cases
private int generateShift(int dimSize, boolean toReduce) {
// boundaries for the shift
int minShift = (int) (MIN_SHIFT_PORTION * dimSize / _j);
int maxShift = dimSize / _j;
double random = Math.abs(_randomGen.nextGaussian());
int shift = (int) (minShift + random / MAX_GAUSS_RANDOM
* (maxShift - minShift));
if (shift > maxShift) {
// just in case that we generated a value higher than
// MAX_GAUSS_RANDOM
shift = maxShift;
}
if (toReduce) {
shift = -shift;
}
return shift;
}
/*
* If the point is outside of the matrix, move it inside
*/
private Point putWithinMatrix(Point corner, int xSize, int ySize) {
int shiftedX = putWithinMatrixDim(corner.get_x(), xSize);
int shiftedY = putWithinMatrixDim(corner.get_y(), ySize);
return new Point(shiftedX, shiftedY);
}
private int putWithinMatrixDim(int position, int size) {
if (position < 0) {
position = 0;
}
if (position > size - 1) {
position = size - 1;
}
return position;
}
/*
* shiftedRegion is a separate copy of a modification of
* regions.get(shiftRegionNum) Adjusting: randomly pick according to the
* leftover proportion the smallest increase from neighbors smallest total
* number of recursions
*
* You also have to take candidate cells into account
*/
private static List<Region> adjustRegions(List<Region> regions,
int shiftedRegionNum, Region shiftedRegion) {
// TODO priority
return null;
}
/*
* If alpha > 1, return true; otherwise return true with probability alpha
*/
private boolean isAccept(double alpha) {
if (alpha >= 1) {
return true;
} else {
double rndNumber = _randomGen.nextDouble();
return (rndNumber > alpha);
}
// TODO simulated annealing
}
private static double getAvgWeight(List<Region> regions,
WeightPrecomputation wf) {
double totalWeight = 0;
for (Region region : regions) {
totalWeight += wf.getWeight(region);
}
return totalWeight / regions.size();
}
private static double getLstWeight(List<Region> regions,
WeightPrecomputation wf) {
double lst = 0;
for (Region region : regions) {
double weight = wf.getWeight(region);
lst += weight * weight;
}
return Math.sqrt(lst);
}
private static double getAvgRegionDensity(List<Region> regions,
WeightPrecomputation wf) {
double totalDensity = 0;
for (Region region : regions) {
double weight = wf.getWeight(region);
int halfPerimeter = region.getHalfPerimeter();
double density = weight / halfPerimeter;
totalDensity += density;
}
return totalDensity / regions.size();
}
public static class RegionXComparator implements Comparator<Region> {
@Override
public int compare(Region region1, Region region2) {
int x11 = region1.get_x1();
int x21 = region2.get_x1();
if (x11 > x21)
return -1;
if (x11 == x21)
return 0;
return 1;
}
}
public static class RegionYComparator implements Comparator<Region> {
@Override
public int compare(Region region1, Region region2) {
int y11 = region1.get_y1();
int y21 = region2.get_y1();
if (y11 > y21)
return -1;
if (y11 == y21)
return 0;
return 1;
}
}
public static class RegionWeightComparator implements Comparator<Region> {
private WeightPrecomputation _wf;
public RegionWeightComparator(WeightPrecomputation wf) {
_wf = wf;
}
@Override
public int compare(Region region1, Region region2) {
double w1 = _wf.getWeight(region1);
double w2 = _wf.getWeight(region2);
if (w1 > w2)
return -1;
if (w1 == w1)
return 0;
return 1;
}
}
@Override
public String getShortName() {
return "mcmc";
}
private static class Goodness {
// direct
private JoinMatrix _joinMatrix;
private List<Region> _regions;
// derived
private double _maxWeight;
private double _avgWeight;
private double _lst;
private double _avgRegionDensity; // derived from weight and perimeter
public Goodness(JoinMatrix joinMatrix, List<Region> regions,
WeightPrecomputation wf) {
_joinMatrix = joinMatrix;
_regions = regions;
// compute others
Region maxRegion = Collections.max(_regions,
new RegionWeightComparator(wf));
_maxWeight = wf.getWeight(maxRegion);
_avgWeight = getAvgWeight(_regions, wf);
_lst = getLstWeight(_regions, wf);
_avgRegionDensity = getAvgRegionDensity(_regions, wf);
}
/*
* bigger than 1 means that 'this' is better than 'other'
*/
public double compare(Goodness other) {
if (_maxWeight != other._maxWeight) {
return 1 / (_maxWeight / other._maxWeight);
} else if (_lst != other._lst) {
return 1 / (_lst / other._lst);
} else {
return 1 / (_avgRegionDensity / other._avgRegionDensity);
}
}
}
private static class SpatialRegions {
List<Region> _regions;
List<Region> _sortedRegionsX;
List<Region> _sortedRegionsY;
public SpatialRegions(List<Region> regions) {
_regions = new ArrayList<Region>();
_regions.addAll(regions);
_sortedRegionsX = new ArrayList<Region>();
_sortedRegionsX.addAll(regions);
_sortedRegionsY = new ArrayList<Region>();
_sortedRegionsY.addAll(regions);
Collections.sort(_sortedRegionsX, new RegionXComparator());
Collections.sort(_sortedRegionsY, new RegionYComparator());
}
/*
* TODO find existing intersection by going through sorted lists
*/
public boolean isIntersect(Region region) {
return false;
}
}
public static class TwoBoolean {
private boolean _first;
private boolean _second;
public TwoBoolean(boolean first, boolean second) {
_first = first;
_second = second;
}
public boolean getFirst() {
return _first;
}
public boolean getSecond() {
return _second;
}
}
}