/*
* 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.metric.v2;
import org.deidentifier.arx.ARXConfiguration;
import org.deidentifier.arx.ARXCostBenefitConfiguration;
import org.deidentifier.arx.DataDefinition;
import org.deidentifier.arx.certificate.elements.ElementData;
import org.deidentifier.arx.framework.check.distribution.DistributionAggregateFunction;
import org.deidentifier.arx.framework.check.groupify.HashGroupify;
import org.deidentifier.arx.framework.check.groupify.HashGroupifyEntry;
import org.deidentifier.arx.framework.data.Data;
import org.deidentifier.arx.framework.data.DataManager;
import org.deidentifier.arx.framework.data.GeneralizationHierarchy;
import org.deidentifier.arx.framework.lattice.Transformation;
import org.deidentifier.arx.metric.InformationLossWithBound;
import org.deidentifier.arx.metric.MetricConfiguration;
import org.deidentifier.arx.risk.RiskModelCostBenefit;
/**
* This class implements a model which maximizes publisher benefit according to the model proposed in:<br>
* A Game Theoretic Framework for Analyzing Re-Identification Risk.
* Zhiyu Wan, Yevgeniy Vorobeychik, Weiyi Xia, Ellen Wright Clayton,
* Murat Kantarcioglu, Ranjit Ganta, Raymond Heatherly, Bradley A. Malin
* PLOS|ONE. 2015.
*
* @author Fabian Prasser
*/
public class MetricSDNMPublisherPayout extends AbstractMetricSingleDimensional {
/** SUID. */
private static final long serialVersionUID = 5729454129866471107L;
/** Parameter strings */
private static final String PUBLISHER_PAYOUT = "Publisher payout";
/** Parameter strings */
private static final String MAXIMAL_PAYOUT = "Theoretical maximum";
/** Configuration for the Stackelberg game */
private ARXCostBenefitConfiguration config;
/** Domain shares for each dimension. */
private DomainShare[] shares;
/** Maximal information loss */
private double maxIL;
/** Risk model */
private RiskModelCostBenefit modelRisk;
/** Journalist attacker model */
private boolean journalistAttackerModel;
/** Maximal payout */
private QualityMetadata<Double> maximalPayout;
/**
* Creates a new instance. Default constructor which treats all transformation methods equally.
* @param journalistAttackerModel If set to true, the journalist attacker model will be assumed,
* the prosecutor model will be assumed, otherwise
*/
public MetricSDNMPublisherPayout(boolean journalistAttackerModel) {
this(journalistAttackerModel, 0.5d);
}
/**
* Creates a new instance
* @param journalistAttackerModel If set to true, the journalist attacker model will be assumed,
* the prosecutor model will be assumed, otherwise
* @param gsFactor A factor [0,1] weighting generalization and suppression.
* The default value is 0.5, which means that generalization
* and suppression will be treated equally. A factor of 0
* will favor suppression, and a factor of 1 will favor
* generalization. The values in between can be used for
* balancing both methods.
*/
public MetricSDNMPublisherPayout(boolean journalistAttackerModel, double gsFactor) {
super(false, false, false, gsFactor);
this.journalistAttackerModel = journalistAttackerModel;
}
@Override
public ILSingleDimensional createMaxInformationLoss() {
Double rows = getNumTuples();
if (rows == null) {
throw new IllegalStateException("Metric must be initialized first");
} else {
return new ILSingleDimensional(rows * this.config.getPublisherBenefit());
}
}
@Override
public ILSingleDimensional createMinInformationLoss() {
return new ILSingleDimensional(0d);
}
/**
* Returns the configuration of this metric.
*
* @return
*/
public MetricConfiguration getConfiguration() {
return new MetricConfiguration(false,
super.getGeneralizationSuppressionFactor(), // gs-factor
false,
0.0d,
this.getAggregateFunction());
}
/**
* Returns the cost/benefit configuration
*/
public ARXCostBenefitConfiguration getCostBenefitConfiguration() {
return this.config;
}
@Override
public String getName() {
return "Publisher benefit";
}
@Override
public boolean isAbleToHandleMicroaggregation() {
return true;
}
@Override
public boolean isGSFactorSupported() {
return true;
}
/**
* Returns whether the journalist attacker model is being assumed.
* @return
*/
public boolean isJournalistAttackerModel() {
return this.journalistAttackerModel;
}
/**
* Returns whether the prosecutor attacker model is being assumed.
* @return
*/
public boolean isProsecutorAttackerModel() {
return !this.journalistAttackerModel;
}
@Override
public ElementData render(ARXConfiguration config) {
ElementData result = new ElementData("Publisher payout");
result.addProperty("Monotonic", this.isMonotonic(config.getMaxOutliers()));
result.addProperty("Generalization factor", this.getGeneralizationFactor());
result.addProperty("Suppression factor", this.getSuppressionFactor());
result.addProperty("Attacker model", (journalistAttackerModel ? "Journalist" : "Prosecutor"));
if (this.config != null) {
result.addProperty("Adversary cost", this.config.getAdversaryCost());
result.addProperty("Adversary gain", this.config.getAdversaryGain());
result.addProperty("Publisher loss", this.config.getPublisherLoss());
result.addProperty("Publisher benefit", this.config.getPublisherBenefit());
}
return result;
}
@Override
public String toString() {
String result = "PublisherBenefit (" + (journalistAttackerModel ? "Journalist" : "Prosecutor");
if (config == null) {
result += ")";
} else {
result += ", Benefit=" + config.getPublisherBenefit() + ")";
}
return result;
}
/**
* Returns the success probability. If the game is configured to use journalist risk,
* but no population table is available, we silently default to the prosecutor model.
* @param entry
* @return
*/
private double getSuccessProbability(HashGroupifyEntry entry) {
return !journalistAttackerModel || entry.pcount == 0 ? 1d / entry.count : 1d / entry.pcount;
}
@Override
protected ILSingleDimensionalWithBound getInformationLossInternal(Transformation transformation, HashGroupify groupify) {
// Prepare
double real = 0;
double bound = 0;
double gFactor = super.getGeneralizationFactor();
double sFactor = super.getSuppressionFactor();
HashGroupifyEntry entry = groupify.getFirstEquivalenceClass();
double maxPayout = this.config.getPublisherBenefit();
double payout = 0d;
DistributionAggregateFunction[] microaggregationFunctions = super.getMicroaggregationFunctions();
int microaggregationStartIndex = super.getMicroaggregationStartIndex();
// Compute
while (entry != null) {
if (entry.count > 0) {
double adversarySuccessProbability = this.getSuccessProbability(entry);
double informationLoss = MetricSDNMEntropyBasedInformationLoss.getEntropyBasedInformationLoss(transformation,
entry,
shares,
microaggregationFunctions,
microaggregationStartIndex,
maxIL);
double realPayout = modelRisk.getExpectedPublisherPayout(informationLoss, adversarySuccessProbability);
double boundPayout = modelRisk.getExpectedPublisherPayout(informationLoss, 0d);
real += !entry.isNotOutlier ? (sFactor * entry.count * maxPayout) :
(gFactor * entry.count * (maxPayout - realPayout));
bound += gFactor * entry.count * (maxPayout - boundPayout);
payout += !entry.isNotOutlier ? 0d : entry.count * realPayout;
}
entry = entry.nextOrdered;
}
// Return
ILSingleDimensionalWithBound result = super.createInformationLoss(real, bound);
result.getInformationLoss().addMetadata(new QualityMetadata<Double>(PUBLISHER_PAYOUT, payout));
result.getInformationLoss().addMetadata(maximalPayout);
return result;
}
@Override
protected InformationLossWithBound<ILSingleDimensional> getInformationLossInternal(Transformation transformation, HashGroupifyEntry entry) {
// Prepare
double gFactor = super.getGeneralizationFactor();
double sFactor = super.getSuppressionFactor();
DistributionAggregateFunction[] microaggregationFunctions = super.getMicroaggregationFunctions();
int microaggregationStartIndex = super.getMicroaggregationStartIndex();
// Compute
double adversarySuccessProbability = this.getSuccessProbability(entry);
double informationLoss = MetricSDNMEntropyBasedInformationLoss.getEntropyBasedInformationLoss(transformation,
entry,
shares,
microaggregationFunctions,
microaggregationStartIndex,
maxIL);
double maxPayout = this.config.getPublisherBenefit();
double realPayout = modelRisk.getExpectedPublisherPayout(informationLoss, adversarySuccessProbability);
double boundPayout = modelRisk.getExpectedPublisherPayout(informationLoss, 0d);
double real = !entry.isNotOutlier ? (sFactor * entry.count * maxPayout) :
(gFactor * entry.count * (maxPayout - realPayout));
double bound = gFactor * entry.count * (maxPayout - boundPayout);
// Return
return super.createInformationLoss(real, bound);
}
@Override
protected ILSingleDimensional getLowerBoundInternal(Transformation transformation) {
return null;
}
@Override
protected ILSingleDimensional getLowerBoundInternal(Transformation transformation,
HashGroupify groupify) {
// Compute
double bound = 0;
double gFactor = super.getGeneralizationFactor();
double maxPayout = this.config.getPublisherBenefit();
HashGroupifyEntry entry = groupify.getFirstEquivalenceClass();
while (entry != null) {
if (entry.count > 0) {
double informationLoss = MetricSDNMEntropyBasedInformationLoss.getEntropyBasedInformationLoss(transformation, entry, shares, null, 0, maxIL);
double boundPayout = modelRisk.getExpectedPublisherPayout(informationLoss, 0d);
bound += gFactor * entry.count * (maxPayout - boundPayout);
}
entry = entry.nextOrdered;
}
// Return
return new ILSingleDimensional(bound);
}
@Override
protected void initializeInternal(final DataManager manager,
final DataDefinition definition,
final Data input,
final GeneralizationHierarchy[] hierarchies,
final ARXConfiguration config) {
super.initializeInternal(manager, definition, input, hierarchies, config);
// Compute domain shares
this.shares = manager.getDomainShares();
this.config = config.getCostBenefitConfiguration();
this.modelRisk = new RiskModelCostBenefit(this.config);
this.maximalPayout = new QualityMetadata<Double>(MAXIMAL_PAYOUT, super.getNumRecords(config, input) * this.config.getPublisherBenefit());
// Calculate MaxIL
this.maxIL = MetricSDNMEntropyBasedInformationLoss.getMaximalEntropyBasedInformationLoss(this.shares, super.getMicroaggregationDomainSizes());
}
}