7.1.1. Defining parameters

7.1.1.1. Gaussian parameters

Since it is typically required to propagate the systematic uncertainty it is implied that each parameter is associated with some probability distribution. By default it is normal distribution with central value and sigma.

Let us look at the following example:

 1from gna.env import env
 2
 3# Make a variable for the global namespace
 4ns = env.globalns
 5
 6# Create a set of parameters
 7ns.defparameter('par_constrained', central=10.0, sigma=0.1, label='Constrained parameter (absolute)')
 8ns.defparameter('par_constrained_rel', central=10.0, relsigma=0.1, label='Constrained parameter (relative)')
 9ns.defparameter('par_fixed', central=1.0, fixed=True, label='Fixed parameter')
10ns.defparameter('par_free', central=1.0, free=True, label='Free parameter')
11ns.reqparameter('par_free',central=2.0, free=True, label='Redefined free parameter')
12
13# Print the list of the parameters to the terminal
14ns.printparameters(labels=True)

The parameters are created within namespaces via method defparameter(name,...). The main namespace is env.globalns. We create parameter par_constrained with central value of 1.0 and absolute uncertainty of 0.1. The optional argument label defines text, which will be printed in the parameter listing:

ns.defparameter('par_constrained', central=10.0, sigma=0.1, label='Constrained parameter (absolute)')

Instead of sigma one may use keyword relsigma in order to provide the relative uncertainty:

ns.defparameter('par_constrained_rel', central=10.0, relsigma=0.1, label='Constrained parameter (relative)')

In case the minimization with nuisance parameters is requested the uncertainty value is used to setup the nuisance term for the parameters.

The parameter may be fixed. In this case it will not be passed to the minimizer or derivative calculation.

ns.defparameter('par_fixed', central=1.0, fixed=True, label='Fixed parameter')

The parameter may be set free. In this case the nuisance parameter is not added to the likelihood function and the parameter value will be fitted without constraints.

ns.defparameter('par_free', central=1.0, free=True, label='Free parameter')

In addition to the defparameter(name,...) another method reqparameter(name,...) is defined. The latter one does not create a new parameter in case the parameter with the same name is already defined. Using reqparameter in the code enables the possibility to predefine the parameter before the code is executed. Thus the line

ns.reqparameter('par_free',central=2.0, free=True, label='Redefined free parameter')

will do nothing since the parameter par_free already exists.

Finally the parameter listing may be printed to the terminal:

ns.printparameters(labels=True)

The command produces the following (colored) output:

Variables in namespace '':
par_constrained      =         10 │          10±         0.1 [          1%] │ Constrained parameter (absolute)
par_constrained_rel  =         10 │          10±           1 [         10%] │ Constrained parameter (relative)
par_fixed            =          1 │                 [fixed]                 │ Fixed parameter
par_free             =          1 │           1±         inf [free]         │ Free parameter

The printout contains the following columns:

  1. Parameter name.

  2. Symbol =.

  3. Current value of the parameter.

  4. Symbol |.

  5. Central/default value of the parameter or the flag [fixed].

  6. Uncertainty of the parameter.

  7. (optional) Depending on the type and state of the parameter:

    • Relative uncertainty in case central value is not 0.

    • Current value of the angle in number of \(\pi\).

    • A flag [free] in case parameter is free.

  8. Symbol |.

  9. (optional) Parameter description.

Labels are printed only in case labels=True argument is specified.

Hint

It’s highly recommended to add the proper description (including units) to the parameters from the very beginning.

7.1.1.2. Nested namespaces

The environment has directory-like structure: a group of parameters may be defined as nested namespace (without depth limit). A nested namespace may be created by calling a namespace and passing new namespace name as an argument:

 1from gna.env import env
 2
 3# Make a variable for global namespace
 4ns = env.globalns
 5
 6# Create a parameter in the global namespace
 7ns.defparameter('norm', central=1.0, sigma=0.1, label='Normalization')
 8
 9# Create (or access) nested namespace 'eff'
10effns = ns('eff')
11
12# Create a couple of parameters in the nested namespace
13effns.defparameter('eff1', central=0.9, sigma=0.01, label='Efficiency #1')
14effns.defparameter('eff2', central=0.8, sigma=0.02, label='Efficiency #2')
15
16# Create a parameter in a nested namespace (from the global namespace)
17ns.defparameter('eff.eff3', central=0.95, sigma=0.015, label='Efficiency #3')
18
19# Create a couple of parameters in the third level namespace
20ns.defparameter('misc.pars.par1', central=10.0, sigma=1, label='Some parameter')
21ns.defparameter('misc.pars.par2', central=11.0, sigma=1, label='One more parameter')
22
23# Print the list of parameters
24ns.printparameters(labels=True)

Here the nested namespace eff is created via call:

effns = ns('eff')

The variable effns is then used just like env.globalns to create new variables:

effns.defparameter('eff1', central=0.9, sigma=0.01, label='Efficiency #1')
effns.defparameter('eff2', central=0.8, sigma=0.02, label='Efficiency #2')

There is no need to refer to the nested namespace directly. Alternatively the namespace name(s) may be added in front of variable name with . (period) used to split names:

ns.defparameter('eff.eff3', central=0.95, sigma=0.015, label='Efficiency #3')

Here eff.eff3 means variable eff3 in namespace eff. The following lines are used to demonstrate the ability to create the deeper set of the parameters:

ns.defparameter('misc.pars.par1', central=10.0, sigma=1, label='Some parameter')
ns.defparameter('misc.pars.par2', central=11.0, sigma=1, label='One more parameter')

The example produces the following output:

Variables in namespace '':
  norm                 =          1 │           1±         0.1 [         10%] │ Normalization
Variables in namespace 'eff':
  eff1                 =        0.9 │         0.9±        0.01 [    1.11111%] │ Efficiency #1
  eff2                 =        0.8 │         0.8±        0.02 [        2.5%] │ Efficiency #2
  eff3                 =       0.95 │        0.95±       0.015 [    1.57895%] │ Efficiency #3
Variables in namespace 'misc.pars':
  par1                 =         10 │          10±           1 [         10%] │ Some parameter
  par2                 =         11 │          11±           1 [    9.09091%] │ One more parameter

The nested namespaces and parameters are printed on the same level to keep more space for the details.

7.1.1.3. Changing the value of the parameters and working with angles

The following example introduces the uniform angle parameter and shows some examples of how to get and set value for it and normal parameters.

 1from gna.env import env
 2import numpy as np
 3
 4# Make a variable for global namespace
 5ns = env.globalns
 6
 7# Create a parameter in the global namespace
 8norm = ns.defparameter('norm', central=1.0, sigma=0.1, label='Normalization')
 9ns.defparameter('phase', central=np.pi*0.5, type='uniformangle', label='Phase angle')
10
11phase = ns['phase']
12
13# Print the list of parameters
14ns.printparameters(labels=True)
15print()
16
17# Change parameters 1
18norm.set(2.0)
19phase.set(6.28)
20ns.printparameters(labels=True)
21print()
22
23# Change parameters 2
24norm.setNormalValue(1.0)
25phase.set(-3.13)
26ns.printparameters(labels=True)
27print()
28
29# Get parameters' values
30norm_value = norm.value()
31phase_value = phase.value()
32print('Norm', norm_value)
33print('Phase', phase_value)

The uniform angle parameter may be defined by the defparameter()/reqparameter() methods by passing type=’uniformangle’ option:

ns.defparameter('phase', central=np.pi*0.5, type='uniformangle', label='Phase angle')

The main feature of uniform angle is that its value is converted to the limits of \((-\pi,\pi)\). This enables proper minimization with periodicity taken into account.

In the example we have defined two parameters:

Variables in namespace '':
  norm                 =          1 │           1±         0.1 [         10%] │ Normalization
  phase                =     1.5708 │      1.5708              [        0.5π] │  (-π, π)                    │ Phase angle

The parameter instances may be obtained by assigning the result of the defparameter()/reqparameter() call

norm = ns.defparameter('norm', central=1.0, sigma=0.1, label='Normalization')

or requested by the namespace field access:

phase = ns['phase']

Then the set(value) method may be used to change the current value of the parameter:

norm.set(2.0)
phase.set(6.28)

This result may be printed to the terminal:

Variables in namespace '':
  norm                 =          2 │           1±         0.1 [         10%] │ Normalization
  phase                =-0.00318531 │      1.5708              [-0.00101391π] │  (-π, π)                    │ Phase angle

Note, that the value slightly below than \(2\pi\) was converted to the value close to 0 for the angle.

There is an extra method that enables the user to change the value of the normally distributed variable in a normalized way, in other words the use may set the number of sigmas the value deviates from the central value with the following command:

norm.setNormalValue(1.0)

Setting the deviation by \(1\sigma\) produces the following result.

norm                 =        1.1 │           1±         0.1 [         10%] │ Normalization

Finally, the values of the variables may be read back by the value() method:

norm_value = norm.value()
phase_value = phase.value()
print('Norm', norm_value)
print('Phase', phase_value)

Here is the output of the code:

Norm 1.1
Phase -3.13