7
7
from pyhf import events , get_backend
8
8
from pyhf .parameters import ParamViewer
9
9
10
+ import numpy as np
11
+
10
12
log = logging .getLogger (__name__ )
11
13
12
14
__all__ = ["add_custom_modifier" ]
@@ -35,81 +37,83 @@ class BaseBuilder:
35
37
...
36
38
37
39
38
- def _allocate_new_param (
39
- p : dict [str , Sequence [float ]]
40
- ) -> dict [str , str | bool | int | Sequence [float ]]:
41
- return {
42
- "paramset_type" : "unconstrained" ,
43
- "n_parameters" : 1 ,
44
- "is_shared" : True ,
45
- "inits" : p ["inits" ],
46
- "bounds" : p ["bounds" ],
47
- "is_scalar" : True ,
48
- "fixed" : False ,
49
- }
50
-
51
-
52
- def make_func (expression : str , deps : list [str ]) -> Callable [[Sequence [float ]], Any ]:
53
- def func (d : Sequence [float ]) -> Any :
54
- return ne .evaluate (expression , local_dict = dict (zip (deps , d )))
55
-
56
- return func
40
+ def add (funcname , par_names , newparams , input_set = None , namespace = None ):
41
+ namespace = namespace or {}
57
42
43
+ def make_func (
44
+ expression : str , namespace = namespace
45
+ ) -> Callable [[Sequence [float ]], Any ]:
46
+ def func (deps : Sequence [float ]) -> Any :
47
+ if expression in namespace :
48
+ parvals = dict (zip (par_names , deps ))
49
+ return namespace [expression ](parvals )()
50
+ return ne .evaluate (
51
+ expression , local_dict = dict (zip (par_names , deps ), ** namespace )
52
+ )
58
53
59
- def make_builder (
60
- func_name : str , deps : list [str ], new_params : dict [str , dict [str , Sequence [float ]]]
61
- ) -> BaseBuilder :
62
- class _builder (BaseBuilder ):
63
- is_shared = False
54
+ return func
55
+
56
+ def _allocate_new_param (p ):
57
+ param_dict = {
58
+ 'paramset_type' : p ['paramset_type' ]
59
+ if 'paramset_type' in p .keys ()
60
+ else 'unconstrained' ,
61
+ 'n_parameters' : 1 ,
62
+ 'is_shared' : True ,
63
+ 'inits' : p ['inits' ],
64
+ 'bounds' : p ['bounds' ],
65
+ 'is_scalar' : True ,
66
+ 'fixed' : False ,
67
+ 'auxdata' : p ['auxdata' ] if 'auxdata' in p .keys () else (0.0 ,),
68
+ }
69
+ return param_dict
70
+
71
+ class _builder :
72
+ is_shared = True
64
73
65
74
def __init__ (self , config ):
66
- self .builder_data = {" funcs" : {}}
75
+ self .builder_data = {' funcs' : {}}
67
76
self .config = config
77
+ self .required_parsets = {}
68
78
69
79
def collect (self , thismod , nom ):
70
- maskval = bool ( thismod )
80
+ maskval = True if thismod else False
71
81
mask = [maskval ] * len (nom )
72
- return {" mask" : mask }
82
+ return {' mask' : mask }
73
83
74
- def append (self , key , channel , sample , thismod , defined_sample ):
84
+ def append (self , key , channel , sample , thismod , defined_samp ):
75
85
self .builder_data .setdefault (key , {}).setdefault (sample , {}).setdefault (
76
- " data" , {" mask" : []}
86
+ ' data' , {' mask' : []}
77
87
)
78
88
nom = (
79
- defined_sample [ " data" ]
80
- if defined_sample
89
+ defined_samp [ ' data' ]
90
+ if defined_samp
81
91
else [0.0 ] * self .config .channel_nbins [channel ]
82
92
)
83
- mod_data = self .collect (thismod , nom )
84
- self .builder_data [key ][sample ][" data" ][ " mask" ] += mod_data [ " mask" ]
93
+ moddata = self .collect (thismod , nom )
94
+ self .builder_data [key ][sample ][' data' ][ ' mask' ] += moddata [ ' mask' ]
85
95
if thismod :
86
- if thismod [" name" ] != func_name :
87
- self .builder_data [" funcs" ].setdefault (
88
- thismod [" name" ], thismod [" data" ][ " expr" ]
96
+ if thismod [' name' ] != funcname :
97
+ self .builder_data [' funcs' ].setdefault (
98
+ thismod [' name' ], thismod [' data' ][ ' expr' ]
89
99
)
90
100
self .required_parsets = {
91
- k : [_allocate_new_param (v )] for k , v in new_params .items ()
101
+ k : [_allocate_new_param (v )] for k , v in newparams .items ()
92
102
}
93
103
94
104
def finalize (self ):
95
105
return self .builder_data
96
106
97
- return _builder
98
-
99
-
100
- def make_applier (
101
- func_name : str , deps : list [str ], new_params : dict [str , dict [str , Sequence [float ]]]
102
- ) -> BaseApplier :
103
- class _applier (BaseApplier ):
104
- name = func_name
105
- op_code = "multiplication"
107
+ class _applier :
108
+ name = funcname
109
+ op_code = 'addition'
106
110
107
111
def __init__ (self , modifiers , pdfconfig , builder_data , batch_size = None ):
108
- self .funcs = [make_func (v , deps ) for v in builder_data [" funcs" ].values ()]
112
+ self .funcs = [make_func (f ) for f in builder_data [' funcs' ].values ()]
109
113
110
114
self .batch_size = batch_size
111
- pars_for_applier = deps
112
- _mod_names = [f" { mtype } /{ m } " for m , mtype in modifiers ]
115
+ pars_for_applier = par_names
116
+ _modnames = [f' { mtype } /{ m } ' for m , mtype in modifiers ]
113
117
114
118
parfield_shape = (
115
119
(self .batch_size , pdfconfig .npars )
@@ -119,25 +123,25 @@ def __init__(self, modifiers, pdfconfig, builder_data, batch_size=None):
119
123
self .param_viewer = ParamViewer (
120
124
parfield_shape , pdfconfig .par_map , pars_for_applier
121
125
)
122
- self ._custom_mod_mask = [
123
- [[builder_data [mod_name ][s ][" data" ][ " mask" ]] for s in pdfconfig .samples ]
124
- for mod_name in _mod_names
126
+ self ._custommod_mask = [
127
+ [[builder_data [modname ][s ][' data' ][ ' mask' ]] for s in pdfconfig .samples ]
128
+ for modname in _modnames
125
129
]
126
130
self ._precompute ()
127
- events .subscribe (" tensorlib_changed" )(self ._precompute )
131
+ events .subscribe (' tensorlib_changed' )(self ._precompute )
128
132
129
133
def _precompute (self ):
130
134
tensorlib , _ = get_backend ()
131
135
if not self .param_viewer .index_selection :
132
136
return
133
- self .custom_mod_mask = tensorlib .tile (
134
- tensorlib .astensor (self ._custom_mod_mask ),
137
+ self .custommod_mask = tensorlib .tile (
138
+ tensorlib .astensor (self ._custommod_mask ),
135
139
(1 , 1 , self .batch_size or 1 , 1 ),
136
140
)
137
- self .custom_mod_mask_bool = tensorlib .astensor (
138
- self .custom_mod_mask , dtype = "bool"
141
+ self .custommod_mask_bool = tensorlib .astensor (
142
+ self .custommod_mask , dtype = "bool"
139
143
)
140
- self .custom_mod_default = tensorlib .ones (self .custom_mod_mask .shape )
144
+ self .custommod_default = tensorlib .zeros (self .custommod_mask .shape )
141
145
142
146
def apply (self , pars ):
143
147
"""
@@ -147,100 +151,17 @@ def apply(self, pars):
147
151
if not self .param_viewer .index_selection :
148
152
return
149
153
tensorlib , _ = get_backend ()
150
- if self .batch_size is None :
151
- deps = self .param_viewer .get (pars )
152
- results = tensorlib .astensor ([f (deps ) for f in self .funcs ])
153
- results = tensorlib .einsum (
154
- "msab,m->msab" , self .custom_mod_mask , results
155
- )
156
- else :
157
- deps = self .param_viewer .get (pars )
158
- results = tensorlib .astensor ([f (deps ) for f in self .funcs ])
159
- results = tensorlib .einsum (
160
- "msab,ma->msab" , self .custom_mod_mask , results
161
- )
154
+ deps = self .param_viewer .get (pars )
155
+ out = tensorlib .astensor ([f (deps ) for f in self .funcs ])
156
+ results = np .zeros_like (self .custommod_mask )
157
+ np .place (results , self .custommod_mask , out )
162
158
results = tensorlib .where (
163
- self .custom_mod_mask_bool , results , self .custom_mod_default
159
+ self .custommod_mask_bool , results , self .custommod_default
164
160
)
165
161
return results
166
162
167
- return _applier
168
-
169
-
170
- def add_custom_modifier (
171
- func_name : str , deps : list [str ], new_params : dict [str , dict [str , Sequence [float ]]]
172
- ) -> dict [str , tuple [BaseBuilder , BaseApplier ]]:
173
- r"""
174
- Add a custom modifier type with the modifier data defined through a custom
175
- numexpr string expression.
176
-
177
- Example:
178
-
179
- >>> import pyhf
180
- >>> import pyhf.experimental.modifiers
181
- >>> pyhf.set_backend("numpy")
182
- >>> new_params = {
183
- ... "m1": {"inits": (1.0,), "bounds": ((-5.0, 5.0),)},
184
- ... "m2": {"inits": (1.0,), "bounds": ((-5.0, 5.0),)},
185
- ... }
186
- >>> expanded_pyhf = pyhf.experimental.modifiers.add_custom_modifier(
187
- ... "custom", ["m1", "m2"], new_params
188
- ... )
189
- >>> model = pyhf.Model(
190
- ... {
191
- ... "channels": [
192
- ... {
193
- ... "name": "singlechannel",
194
- ... "samples": [
195
- ... {
196
- ... "name": "signal",
197
- ... "data": [10, 20],
198
- ... "modifiers": [
199
- ... {
200
- ... "name": "f2",
201
- ... "type": "custom",
202
- ... "data": {"expr": "m1"},
203
- ... },
204
- ... ],
205
- ... },
206
- ... {
207
- ... "name": "background",
208
- ... "data": [100, 150],
209
- ... "modifiers": [
210
- ... {
211
- ... "name": "f1",
212
- ... "type": "custom",
213
- ... "data": {"expr": "m1+(m2**2)"},
214
- ... },
215
- ... ],
216
- ... },
217
- ... ],
218
- ... }
219
- ... ]
220
- ... },
221
- ... modifier_set=expanded_pyhf,
222
- ... poi_name="m1",
223
- ... validate=False,
224
- ... batch_size=1,
225
- ... )
226
- >>> model.config.modifiers
227
- [('f1', 'custom'), ('f2', 'custom')]
228
-
229
- Args:
230
- func_name (:obj:`str`): The name of the custom modifier type.
231
- deps (:obj:`list`): The names of the new parameters of the modifier
232
- function.
233
- new_params (:obj:`dict`): The new parameters.
234
-
235
- Returns:
236
- :obj:`dict`: The updated ``pyhf.modifiers.histfactory_set`` with the added
237
- custom modifier type.
238
-
239
- .. versionadded:: 0.8.0
240
- """
241
- _builder = make_builder (func_name , deps , new_params )
242
- _applier = make_applier (func_name , deps , new_params )
243
-
244
163
modifier_set = {_applier .name : (_builder , _applier )}
245
- modifier_set .update (** pyhf .modifiers .histfactory_set )
164
+ modifier_set .update (
165
+ ** (input_set if input_set is not None else pyhf .modifiers .histfactory_set )
166
+ )
246
167
return modifier_set
0 commit comments