"""
Penalties that can be applied to the hyperopt loss
Penalties in this module usually take as signature the positional arguments:
pdf_model: :py:class:`n3fit.backends.keras_backend.MetaModel`
model taking a ``(1, xgrid_size, 1)`` array as input
and returning a ``(1, xgrid_size, 14, replicas)`` pdf.
stopping_object: :py:class:`n3fit.stopping.Stopping`
object holding the information about the validation model
and the stopping parameters
although not all penalties use both.
And return a float to be added to the hyperscan loss.
New penalties can be added directly in this module.
The name in the runcard must match the name used in this module.
"""
import numpy as np
from n3fit.vpinterface import N3PDF, integrability_numbers
from validphys import fitveto
[docs]
def saturation(pdf_model=None, n=100, min_x=1e-6, max_x=1e-4, flavors=None, **_kwargs):
"""Checks the pdf models for saturation at small x
by checking the slope from ``min_x`` to ``max_x``.
Sum the saturation loss of all pdf models
Parameters
----------
n: int
Number of point to evaluate the saturation
min_x: float
Initial point for checking the slope
max_x: float
Final point for checking the slope
flavors: list(int)
indices of the flavors to inspect
Returns
-------
NDArray
array of saturation penalties for each replica
Example
-------
>>> from n3fit.hyper_optimization.penalties import saturation
>>> from n3fit.model_gen import pdfNN_layer_generator
>>> fake_fl = [{'fl' : i, 'largex' : [0,1], 'smallx': [1,2]} for i in ['u', 'ubar', 'd', 'dbar', 'c', 'g', 's', 'sbar']]
>>> pdf_model = pdfNN_layer_generator(nodes=[8], activations=['linear'], seed=0, flav_info=fake_fl, fitbasis="FLAVOUR")
>>> isinstance(saturation(pdf_model, 5), float)
True
"""
if flavors is None:
flavors = [1, 2]
x = np.logspace(np.log10(min_x), np.log10(max_x), n)
extra_loss = 0.0
x_input = np.expand_dims(x, axis=[0, -1])
y = pdf_model.predict({"pdf_input": x_input})
xpdf = y[0, :, :, flavors] # this is now of shape (flavors, replicas, xgrid)
x = np.expand_dims(x, axis=[0, 1])
delta_logx = np.diff(np.log10(x), axis=2)
delta_xpdf = np.diff(xpdf, axis=2)
slope = delta_xpdf / delta_logx
pen = abs(np.mean(slope, axis=2)) + np.std(slope, axis=2)
# sum over flavors
# Add a small offset to avoid ZeroDivisionError
extra_loss += np.sum(1.0 / (1e-7 + pen), axis=0)
return extra_loss
[docs]
def patience(stopping_object, alpha: float = 1e-4, **_kwargs):
"""
Adds a penalty for fits that have finished too soon, which
means the number of epochs or its patience is not optimal.
The penalty is proportional to the validation loss and will be 0
when the best epoch is exactly at max_epoch - patience
The ``alpha`` factor is chosen so that at 10k epochs distance
the penalty is 2.7 * val_loss
Parameters
----------
alpha: float
dumping factor for the exponent
Returns
-------
NDArray
patience penalty for each replica
Example
-------
>>> from n3fit.hyper_optimization.penalties import patience
>>> from types import SimpleNamespace
>>> fake_stopping = SimpleNamespace(e_best_chi2=1000, stopping_patience=500, total_epochs=5000, vl_loss=2.42)
>>> patience(fake_stopping, alpha=1e-4)
3.434143467595683
"""
epoch_best = np.array(stopping_object.e_best_chi2)
patience = stopping_object.stopping_patience
max_epochs = stopping_object.total_epochs
diff = abs(max_epochs - patience - epoch_best)
vl_loss = np.array(stopping_object.vl_chi2)
return vl_loss * np.exp(alpha * diff)
[docs]
def integrability(pdf_model=None, **_kwargs):
"""Adds a penalty proportional to the value of the integrability integration
It adds a 0-penalty when the value of the integrability is equal or less than the value
of the threshold defined in validphys::fitveto
The penalty increases exponentially with the growth of the integrability number
Returns
-------
NDArray
array of integrability penalties for each replica
Example
-------
>>> from n3fit.hyper_optimization.penalties import integrability
>>> from n3fit.model_gen import pdfNN_layer_generator
>>> fake_fl = [{'fl' : i, 'largex' : [0,1], 'smallx': [1,2]} for i in ['u', 'ubar', 'd', 'dbar', 'c', 'g', 's', 'sbar']]
>>> pdf_model = pdfNN_layer_generator(nodes=[8], activations=['linear'], seed=0, flav_info=fake_fl, fitbasis="FLAVOUR")
>>> isinstance(integrability(pdf_model), float)
True
"""
pdf_instance = N3PDF(pdf_model.split_replicas())
integ_values = integrability_numbers(pdf_instance)
# set components under the threshold to 0
integ_values[integ_values <= fitveto.INTEG_THRESHOLD] = 0.0
# sum over flavours
integ_overflow = np.sum(integ_values, axis=-1) # -1 rather than 1 so it works with 1 replica
# Limit components to 50 to avoid overflow
if isinstance(integ_overflow, np.ndarray):
# Case: multi-replica scenario
integ_overflow[integ_overflow > 50.0] = 50.0
elif isinstance(integ_overflow, (float, np.float64)):
# Case: single replica scenario
integ_overflow = min(integ_overflow, 50.0)
return np.exp(integ_overflow) - 1.0