/*
* ARX: Powerful Data Anonymization
* Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors
*
* 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 org.deidentifier.arx.framework.lattice;
import org.deidentifier.arx.ARXConfiguration;
import org.deidentifier.arx.ARXConfiguration.Monotonicity;
import org.deidentifier.arx.ARXLattice;
import org.deidentifier.arx.ARXLattice.ARXNode;
import org.deidentifier.arx.ARXLattice.Anonymity;
import org.deidentifier.arx.metric.InformationLoss;
import com.carrotsearch.hppc.LongObjectOpenHashMap;
import de.linearbits.jhpl.JHPLIterator.LongIterator;
import de.linearbits.jhpl.Lattice;
import de.linearbits.jhpl.PredictiveProperty;
import de.linearbits.jhpl.PredictiveProperty.Direction;
/**
* A class representing the solution space
* @author Fabian Prasser
*/
public class SolutionSpace {
/** Information loss */
private LongObjectOpenHashMap<Object> data = new LongObjectOpenHashMap<Object>();
/** The backing JHPL lattice */
private final Lattice<Integer, Integer> lattice;
/** Information loss */
private LongObjectOpenHashMap<InformationLoss<?>> lowerBound = new LongObjectOpenHashMap<InformationLoss<?>>();
/** The offsets for indices */
private final int[] offsetIndices;
/** The offset the level */
private final int offsetLevel;
/** Potentially changing property */
private PredictiveProperty propertyAnonymous = new PredictiveProperty("Anonymous",
Direction.NONE);
/** Static property */
private final PredictiveProperty propertyChecked = new PredictiveProperty("Checked",
Direction.NONE);
/** Static property */
private final PredictiveProperty propertyForceSnapshot = new PredictiveProperty("Force snapshot",
Direction.NONE);
/** Static property */
private final PredictiveProperty propertyInsufficientUtility = new PredictiveProperty("Insufficient utility",
Direction.UP);
/** Static property */
private final PredictiveProperty propertyKAnonymous = new PredictiveProperty("K-Anonymous",
Direction.UP);
/** Potentially changing property */
private PredictiveProperty propertyNotAnonymous = new PredictiveProperty("Not anonymous",
Direction.NONE);
/** Static property */
private final PredictiveProperty propertyNotKAnonymous = new PredictiveProperty("Not k-anonymous",
Direction.DOWN);
/** Static property */
private final PredictiveProperty propertySuccessorsPruned = new PredictiveProperty("Successors pruned",
Direction.UP); // TODO: Was NONE?
/** Static property */
private final PredictiveProperty propertyVisited = new PredictiveProperty("Visited",
Direction.NONE);
/** Static property */
private final PredictiveProperty propertyExpanded = new PredictiveProperty("Expanded",
Direction.NONE);
/** Information loss */
private LongObjectOpenHashMap<InformationLoss<?>> utility = new LongObjectOpenHashMap<InformationLoss<?>>();
/**
* For de-serialization
* @param lattice
* @param config
*/
public SolutionSpace(ARXLattice lattice, ARXConfiguration config) {
this(lattice.getBottom().getTransformation(), lattice.getTop().getTransformation());
setMonotonicity(config);
for (ARXNode[] level : lattice.getLevels()) {
for (ARXNode node : level) {
int[] index = toJHPL(node.getTransformation());
int lvl = getLevel(index);
long id = this.lattice.space().toId(index);
if (node.getAnonymity() == Anonymity.ANONYMOUS) {
this.lattice.putProperty(index, lvl, this.getPropertyAnonymous());
} else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS) {
this.lattice.putProperty(index, lvl, this.getPropertyNotAnonymous());
}
if (node.isChecked()) {
this.lattice.putProperty(index, lvl, this.getPropertyChecked());
this.setInformationLoss(id, node.getHighestScore());
}
}
}
}
/**
* Creates a new solution space
* @param minLevels
* @param maxLevels
*/
public SolutionSpace(int[] minLevels, int[] maxLevels) {
// Create offsets
minLevels = reverse(minLevels);
maxLevels = reverse(maxLevels);
this.offsetIndices = minLevels.clone();
int lvl = 0; for (int i : offsetIndices) lvl+=i;
this.offsetLevel = lvl;
// Create lattice
Integer[][] elements = new Integer[minLevels.length][];
for (int i = 0; i < elements.length; i++) {
Integer[] element = new Integer[maxLevels[i] - minLevels[i] + 1];
int idx = 0;
for (int j = minLevels[i]; j <= maxLevels[i]; j++) {
element[idx++] = j;
}
elements[i] = element;
}
this.lattice = new Lattice<Integer, Integer>(elements);
}
/**
* Returns the bottom transformation
* @return
*/
public Transformation getBottom() {
return getTransformation(fromJHPL(lattice.nodes().getBottom()));
}
/**
* Returns the level of the given transformation
* @param transformation
* @return
*/
public int getLevel(int[] transformation) {
int level = 0;
for (int dimension : transformation) {
level += dimension;
}
return level;
}
/**
* Returns all materialized transformations
* @return
*/
public LongIterator getMaterializedTransformations() {
return lattice.listNodesAsIdentifiers();
}
/**
* Returns the multipliers
* @return
*/
public long[] getMultipliersForHighDimensionalData() {
long[] multiplier = lattice.nodes().getMultiplier();
long[] result = new long[multiplier.length];
for (int i = 0; i < result.length; i++) {
result[result.length - i - 1] = multiplier[i];
}
return result;
}
/**
* Returns the multipliers
* @return
*/
public int[] getMultipliersForLowDimensionalData() {
long[] multiplier = lattice.nodes().getMultiplier();
int[] result = new int[multiplier.length];
for (int i = 0; i < result.length; i++) {
result[result.length - i - 1] = (int) multiplier[i];
}
return result;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyAnonymous() {
return propertyAnonymous;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyChecked() {
return propertyChecked;
}
/**
* Property for expanded transformation
* @return
*/
public PredictiveProperty getPropertyExpanded() {
return this.propertyExpanded;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyForceSnapshot() {
return propertyForceSnapshot;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyInsufficientUtility() {
return propertyInsufficientUtility;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyKAnonymous() {
return propertyKAnonymous;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyNotAnonymous() {
return propertyNotAnonymous;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyNotKAnonymous() {
return propertyNotKAnonymous;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertySuccessorsPruned() {
return propertySuccessorsPruned;
}
/**
* Returns a property
* @return
*/
public PredictiveProperty getPropertyVisited() {
return propertyVisited;
}
/**
* Returns the overall number of transformations in the solution space
* @return
*/
public long getSize() {
return lattice.numNodes();
}
/**
* Returns the top-transformation
* @return
*/
public Transformation getTop() {
return getTransformation(fromJHPL(lattice.nodes().getTop()));
}
/**
* Returns a wrapper object with access to all properties about the transformation
* @param transformation
* @return
*/
public Transformation getTransformation(int[] transformation) {
return new Transformation(transformation, lattice, this);
}
/**
* Returns the transformation with the given identifier
* @param identifier
* @return
*/
public Transformation getTransformation(long identifier) {
int[] transformationJHPL = lattice.space().toIndex(identifier);
return new Transformation(transformationJHPL, identifier, lattice, this);
}
/**
* Returns the utility of the transformation with the given identifier
* @param identifier
* @return
*/
public InformationLoss<?> getUtility(long identifier) {
return utility.getOrDefault(identifier, null);
}
/**
* Returns whether a node has a given property
* @param transformation
* @param property
* @return
*/
public boolean hasProperty(int[] transformation, PredictiveProperty property) {
int[] index = toJHPL(transformation);
int level = getLevel(index);
return lattice.hasProperty(index, level, property);
}
/**
* Determines whether a direct parent-child relationship exists.
* @param parent
* @param child
* @return
*/
public boolean isDirectParentChild(int[] parent, int[] child) {
int diff = 0;
for (int i=0; i<parent.length; i++) {
if (parent[i] < child[i]) {
return false;
} else {
diff += parent[i] - child[i];
}
}
return diff == 1;
}
/**
* Determines whether a parent-child relationship exists, or both are equal
* @param parent
* @param child
* @return
*/
public boolean isParentChildOrEqual(int[] parent, int[] child) {
for (int i=0; i<parent.length; i++) {
if (parent[i] < child[i]) {
return false;
}
}
return true;
}
/**
* Makes the anonymity property predictable
* @param predictable
*/
public void setAnonymityPropertyPredictable(boolean predictable) {
if (predictable) {
propertyAnonymous = new PredictiveProperty("Anonymous", Direction.UP);
propertyNotAnonymous = new PredictiveProperty("Not anonymous", Direction.DOWN);
} else {
propertyAnonymous = new PredictiveProperty("Anonymous", Direction.NONE);
propertyNotAnonymous = new PredictiveProperty("Not anonymous", Direction.NONE);
}
}
/**
* Returns all transformations in the solution space
* @return
*/
public LongIterator unsafeGetAllTransformations() {
return lattice.unsafe().listAllNodesAsIdentifiers();
}
/**
* Returns *all* nodes on the given level. This is an unsafe operation that only performs well for "small" spaces.
* @param level
* @return
*/
public LongIterator unsafeGetLevel(int level) {
return lattice.unsafe().listAllNodesAsIdentifiers(toJHPL(level));
}
/**
* Reverses the given array
* @param input
* @return
*/
private int[] reverse(int[] input) {
int[] result = new int[input.length];
for (int i = 0; i < input.length; i++) {
result[i] = input[input.length - i - 1];
}
return result;
}
/**
* Sets the monotonicity of the anonymity property
* @param config
*/
private void setMonotonicity(ARXConfiguration config) {
setAnonymityPropertyPredictable(config.getMonotonicityOfPrivacy() == Monotonicity.FULL);
}
/**
* Internal method that adds the offset
* @param level
* @return
*/
protected int fromJHPL(int level) {
return level + offsetLevel;
}
/**
* Internal method that adds the offsets
* @param transformation
* @return
*/
protected int[] fromJHPL(int[] transformation) {
int[] result = new int[transformation.length];
for (int i=0; i<result.length; i++) {
result[i] = transformation[transformation.length - i - 1] + offsetIndices[transformation.length - i - 1];
}
return result;
}
/**
* Returns data
* @param id
* @return
*/
protected Object getData(long id) {
return data.getOrDefault(id, null);
}
/**
* Returns the information loss
* @param identifier
* @return
*/
protected InformationLoss<?> getInformationLoss(long identifier) {
return utility.getOrDefault(identifier, null);
}
/**
* Returns the lower bound
* @param identifier
* @return
*/
protected InformationLoss<?> getLowerBound(long identifier) {
return lowerBound.getOrDefault(identifier, null);
}
/**
* Sets data
* @param id
* @param object
*/
protected void setData(long id, Object object) {
data.put(id, object);
}
/**
* Sets the information loss
* @param identifier
* @param loss
*/
protected void setInformationLoss(long identifier, InformationLoss<?> loss) {
utility.put(identifier, loss);
}
/**
* Sets the lower bound
* @param identifier
* @param loss
*/
protected void setLowerBound(long identifier, InformationLoss<?> loss) {
lowerBound.put(identifier, loss);
}
/**
* Internal method that subtracts the offset
* @param level
* @return
*/
protected int toJHPL(int level) {
return level - offsetLevel;
}
/**
* Internal method that subtracts the offsets
* @param transformation
* @return
*/
protected int[] toJHPL(int[] transformation) {
int[] result = new int[transformation.length];
for (int i=0; i<result.length; i++) {
result[i]=transformation[transformation.length - i - 1] - offsetIndices[i];
}
return result;
}
}