"""l-uncertainty"""
from qiflib.util.types import is_list, is_function, is_2d_list_matrix, is_2d_numpy_matrix
from qiflib.core.secrets import Secrets
from numpy import arange, zeros, array
from numpy import min as npmin
[docs]
class LUncertainty:
""":math:`\ell`-uncertainty. To create an instance of this class it is necessary
to have an instance of :py:class:`.Secrets` class and a loss function,
that can be a matrix or a pointer to a function. The matrix G must be
G :math:`w{\\times}n` where :math:`w` is the number of actions, :math:`n` is the
number of secrets and :code:`G[w][x]` is the adversary's loss when she takes the action
:code:`w` and the secret's value is :code:`x`. The function must have
two input parameters (action w and secret x, in this order) and outputs a real value.
Parameters
----------
secrets : core.Secrets
Set of secrets.
actions : list
Set of actions.
lfunction : list, numpy.ndarray, pointer to a function
A 2d matrix or a pointer to a loss function.
If the value is a matrix, its shape must match with the actions
and secrets sets size.
If the value is a pointer to a function, the function must have
2 input parameters (w,x), where w is the index of an element from
the set of actions and x is an index of an element from the set
of secrets.
Attributes
----------
secrets : core.Secrets
Secrets object.
actions : list
List of actions' labels.
num_actions : int
Number of actions.
matrix : numpy.ndarray
Loss function matrix. :code:`loss[w][x]` is the adversary's loss
when she takes the action of index :code:`w` (that has the
label :code:`actions[w]`) and the secret is the one from index
:code:`x` (and has the label :code:`secrets.labels[x]`).
"""
def __init__(self, secrets, actions, lfunction):
self._check_types(secrets, actions, lfunction)
self._check_sizes(secrets, actions, lfunction)
self.secrets = secrets
self.actions = actions
self.num_actions = len(self.actions)
self.matrix = None
self._set_loss_function_matrix(lfunction)
[docs]
def prior_uncertainty(self):
"""Prior uncertainty.
Returns
-------
prior_uncertainty : float
Prior uncertainty.
"""
return npmin(self.secrets.prior @ self.matrix.T)
[docs]
def posterior_uncertainty(self, hyper):
"""Posterior uncertainty.
Parameters
----------
hyper : core.Hyper
Hyper-distribution.
Returns
-------
posterior_uncertainty : float
Posterior uncertainty.
"""
if hyper.channel.secrets.num_secrets != self.secrets.num_secrets:
raise Exception('The number of secrets in the loss function is' +
'different from the one in the hyper-distribution.')
return npmin(self.matrix @ hyper.joint, axis=0).sum()
[docs]
def leakage(self, hyper):
"""Calculates the additive and multiplicative leakages.
Parameters
----------
hyper : core.hyper.Hyper
Hyper-distribution
Returns
-------
add_leakage, mult_leakage : (float, float)
Additive and multiplicative leakage
"""
prior_v = self.prior_uncertainty()
posterior_v = self.posterior_uncertainty(hyper)
add_leakage = posterior_v - prior_v
if prior_v == 0:
mult_leakage = 0
else:
mult_leakage = posterior_v/prior_v
return add_leakage, mult_leakage
def _check_types(self, secrets, actions, lfunction):
if type(secrets) != type(Secrets(['x1','x2'], [1,0])):
raise TypeError('The parameter \'secrets\' must be a core.Secrets object')
if not is_list(actions) and not is_numpy_array(actions):
raise TypeError('The parameter \'actions\' must be a list or a numpy.ndarray')
if not is_2d_list_matrix(lfunction) and not is_2d_numpy_matrix(lfunction) and not is_function(lfunction):
raise TypeError('The parameter \'lfunction\' must be a 2d matrix or a pointer to a function')
def _check_sizes(self, secrets, actions, lfunction):
if not is_function(lfunction) and len(actions) != len(lfunction):
raise Exception('The number of rows in the loss function matrix ' +
'must have the same number of actions in the ' +
'labels list')
if len(actions) < 1:
raise Exception('The set of actions must have at least one element')
if not is_function(lfunction):
for i in arange(len(actions)):
if len(lfunction[i]) < 0:
raise Exception('There is an empty row in the loss matrix')
if not is_function(lfunction) and secrets.num_secrets != len(lfunction[0]):
raise Exception('The number of columns in the loss function matrix ' +
'must be the same as the number of secrets')
def _build_loss_matrix(self, lfunction):
"""Given a loss function build the matrix G."""
self.matrix = zeros((self.num_actions, self.secrets.num_secrets))
for w in arange(self.num_actions):
for x in arange(self.secrets.num_secrets):
self.matrix[w][x] = lfunction(w,x)
def _check_loss_matrix(self, matrix):
"""Check if a loss function matrix is valid."""
if (self.num_actions != len(matrix) or
self.secrets.num_secrets != len(matrix[0])):
raise Exception('Loss function matrix shape does not match with ' +
'the set of secrets or the set of actions size.')
def _set_loss_function_matrix(self, lfunction):
try:
if is_function(lfunction):
# Generate the loss matrix for all actions and secrets
self._build_loss_matrix(lfunction)
else:
# Copy the loss matrix
self._check_loss_matrix(lfunction)
self.matrix = array(lfunction).copy()
except:
raise Exception(
'Invalid loss function. It must be a pointer to a function ' +
'that has 2 input parameters (action\'s index and ' +
'secret\'s index) or a 2d matrix.'
)