/* * 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; import org.deidentifier.arx.ARXLattice.ARXNode; import org.deidentifier.arx.ARXLattice.Anonymity; import org.deidentifier.arx.metric.InformationLoss; import org.deidentifier.arx.metric.Metric; /** * A class that estimates information loss within a generalization lattice. Method:<br> * <br> * Minimum:<br> * - Anonymous & monotonic: max(push(min), optimum)<br> * - Anonymous & !monotonic: push(lower)<br> * - !Anonymous & monotonic: max(push(min), optimum)<br> * - !Anonymous & !monotonic: push(lower)<br> * <br> * Maximum:<br> * - Anonymous & monotonic: push(max)<br> * - Anonymous & !monotonic: metric.max<br> * - !Anonymous & monotonic: push(max)<br> * - !Anonymous & !monotonic: metric.max<br> * * @author Fabian Prasser * @author Florian Kohlmayer */ class UtilityEstimator { /** The lattice. */ private ARXLattice lattice; /** The metric. */ private Metric<?> metric; /** Additional fields. */ private InformationLoss<?>[] minimumAnonymous; /** Additional fields. */ private InformationLoss<?>[] minimumNonAnonymous; /** Additional fields. */ private InformationLoss<?>[] maximumAnonymous; /** Additional fields. */ private InformationLoss<?>[] maximumNonAnonymous; /** Additional fields. */ private InformationLoss<?>[] lowerBound; /** Monotonicity. */ private final boolean monotonicAnonymous; /** Monotonicity. */ private final boolean monotonicNonAnonymous; /** Maximum/minimum. */ private InformationLoss<?> globalMinimum; /** Maximum/minimum. */ private InformationLoss<?> globalMaximum; /** * Creates a new estimation process for a lattice. * * @param lattice * @param metric * @param monotonicAnonymous * @param monotonicNonAnonymous */ UtilityEstimator(ARXLattice lattice, Metric<?> metric, boolean monotonicAnonymous, boolean monotonicNonAnonymous) { // Init this.lattice = lattice; this.metric = metric; this.minimumAnonymous = new InformationLoss<?>[this.lattice.getSize()]; this.minimumNonAnonymous = new InformationLoss<?>[this.lattice.getSize()]; this.maximumAnonymous = new InformationLoss<?>[this.lattice.getSize()]; this.maximumNonAnonymous = new InformationLoss<?>[this.lattice.getSize()]; this.lowerBound = new InformationLoss<?>[this.lattice.getSize()]; this.monotonicAnonymous = monotonicAnonymous; this.monotonicNonAnonymous = monotonicNonAnonymous; // Make sure that all nodes have an identifier if (this.lattice.getBottom().getId() == null) { int id = 0; for (ARXNode[] level : this.lattice.getLevels()) { for (ARXNode node : level) { node.setId(id++); } } } } /** * Estimate maximum information loss. */ private void estimateMax() { // Prepare initializeTopDown(lattice.getTop()); setMaximum(lattice.getTop()); this.globalMaximum = lattice.getTop().getHighestScore(); // Pull ARXNode[][] levels = lattice.getLevels(); for (int i = levels.length-2; i >= 0; i--) { final ARXNode[] level = levels[i]; for (final ARXNode node : level) { pullTopDown(node); setMaximum(node); this.globalMaximum = max(this.globalMaximum, node.getHighestScore()); } } } /** * Estimate minimum information loss. */ private void estimateMin() { // Prepare initializeBottomUp(lattice.getBottom()); setMinimum(lattice.getBottom()); this.globalMinimum = lattice.getBottom().getLowestScore(); // Pull ARXNode[][] levels = lattice.getLevels(); for (int i = 1; i < levels.length; i++) { final ARXNode[] level = levels[i]; for (final ARXNode node : level) { pullBottomUp(node); setMinimum(node); this.globalMinimum = min(this.globalMinimum, node.getLowestScore()); } } } /** * Returns the value if != null, the default otherwise. * @param value * @param _default * @return */ private InformationLoss<?> getValueOrDefault(InformationLoss<?> value, InformationLoss<?> _default) { return value != null ? value : _default; } /** * Initializes the bottom node. * * @param node */ private void initializeBottomUp(ARXNode node) { int id = node.getId(); Anonymity nodeAnonymity = node.getAnonymity(); InformationLoss<?> nodeMin = node.getLowestScore(); InformationLoss<?> metricMin = metric.createInstanceOfLowestScore(); lowerBound[id] = getValueOrDefault(node.getLowerBound(), metricMin); if (nodeAnonymity == Anonymity.ANONYMOUS && monotonicAnonymous) { minimumAnonymous[id] = getValueOrDefault(nodeMin, metricMin); minimumNonAnonymous[id] = metricMin; } else if (nodeAnonymity == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { minimumNonAnonymous[id] = getValueOrDefault(nodeMin, metricMin); minimumAnonymous[id] = metricMin; } else { minimumAnonymous[id] = metricMin; minimumNonAnonymous[id] = metricMin; } } /** * Initializes the top node. * * @param node */ private void initializeTopDown(ARXNode node) { int id = node.getId(); Anonymity nodeAnonymity = node.getAnonymity(); InformationLoss<?> nodeMax = node.getHighestScore(); InformationLoss<?> metricMax = metric.createInstanceOfHighestScore(); if (nodeAnonymity == Anonymity.ANONYMOUS && monotonicAnonymous) { maximumAnonymous[id] = getValueOrDefault(nodeMax, metricMax); maximumNonAnonymous[id] = metricMax; } else if (nodeAnonymity == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { maximumNonAnonymous[id] = getValueOrDefault(nodeMax, metricMax); maximumAnonymous[id] = metricMax; } else { maximumAnonymous[id] = metricMax; maximumNonAnonymous[id] = metricMax; } } /** * Returns the max of both, handles null values. * * @param first * @param second * @return */ private InformationLoss<?> max(InformationLoss<?> first, InformationLoss<?> second) { return (first == null) ? second : (second == null) ? first : (first.compareTo(second) < 0) ? second : first; } /** * Returns the min of both, handles null values. * * @param first * @param second * @return */ private InformationLoss<?> min(InformationLoss<?> first, InformationLoss<?> second) { return (first == null) ? second : (second == null) ? first : (first.compareTo(second) < 0) ? first : second; } /** * Propagate bottom up. * * @param node */ private void pullBottomUp(ARXNode node) { int id = node.getId(); // Pull all values for (ARXNode pre : node.getPredecessors()) { int preId = pre.getId(); pullMax(minimumAnonymous, id, preId); pullMax(minimumNonAnonymous, id, preId); pullMax(lowerBound, id, preId); } // Lower bound can always be replaced if (node.getLowerBound() != null) { lowerBound[id] = max(lowerBound[id], node.getLowerBound()); } // Check if values can be replaced if (node.getLowestScore() != null) { if (node.getAnonymity() == Anonymity.ANONYMOUS && monotonicAnonymous) { minimumAnonymous[id] = max(minimumAnonymous[id], node.getLowestScore()); } else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { minimumNonAnonymous[id] = max(minimumNonAnonymous[id], node.getLowestScore()); } } } /** * Pulls a specific property and retains the value using a maximum aggregate function. * * @param array * @param target * @param source */ private void pullMax(InformationLoss<?>[] array, int target, int source) { array[target] = array[target] == null ? array[source] : max(array[target], array[source]); } /** * Pulls a specific property and retains the value using a minimum aggregate function. * * @param array * @param target * @param source */ private void pullMin(InformationLoss<?>[] array, int target, int source) { array[target] = array[target] == null ? array[source] : min(array[target], array[source]); } /** * Propagate top down. * * @param node */ private void pullTopDown(ARXNode node) { int id = node.getId(); // Pull all values for (ARXNode succ : node.getSuccessors()) { int succId = succ.getId(); pullMin(maximumAnonymous, id, succId); pullMin(maximumNonAnonymous, id, succId); } // Check if values can be replaced if (node.getHighestScore() != null) { if (node.getAnonymity() == Anonymity.ANONYMOUS && monotonicAnonymous) { maximumAnonymous[id] = min(maximumAnonymous[id], node.getHighestScore()); } else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { maximumNonAnonymous[id] = min(maximumNonAnonymous[id], node.getHighestScore()); } } } /** * Selects a maximum for the given node. * * @param node */ private void setMaximum(ARXNode node) { // If we already know everything, abort if (node.getLowestScore() != null && node.getHighestScore() != null && node.getLowestScore().compareTo(node.getHighestScore())==0){ return; } // Check if values can be replaced InformationLoss<?> minimalMaximum = null; if (node.getAnonymity() == Anonymity.ANONYMOUS && monotonicAnonymous) { minimalMaximum = min(node.getHighestScore(), maximumAnonymous[node.getId()]); } else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { minimalMaximum = min(node.getHighestScore(), maximumNonAnonymous[node.getId()]); } else { minimalMaximum = min(node.getHighestScore(), metric.createInstanceOfHighestScore()); } // Set node.access().setHighestScore(minimalMaximum); } /** * Selects a minimum for the given node. * * @param node */ private void setMinimum(ARXNode node) { // If we already know everything, abort if (node.getLowestScore() != null && node.getHighestScore() != null && node.getLowestScore().compareTo(node.getHighestScore())==0){ return; } // We can always use the lower bound InformationLoss<?> maximalMinimum = max(node.getLowestScore(), lowerBound[node.getId()]); // Check if values can be replaced if (node.getAnonymity() == Anonymity.ANONYMOUS) { // We can always use the optimum as a minimum for anonymous nodes maximalMinimum = max(maximalMinimum, lattice.getOptimum().getLowestScore()); if (monotonicAnonymous) { maximalMinimum = max(maximalMinimum, minimumAnonymous[node.getId()]); } } else if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS && monotonicNonAnonymous) { maximalMinimum = max(maximalMinimum, minimumNonAnonymous[node.getId()]); } // Set node.access().setLowestScore(maximalMinimum); } /** * Implements the estimation process. */ void estimate() { estimateMin(); estimateMax(); } /** * @return the globalMaximum */ InformationLoss<?> getGlobalMaximum() { return globalMaximum; } /** * @return the globalMinimum */ InformationLoss<?> getGlobalMinimum() { return globalMinimum; } }