ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
Trigger
TrigMonitoring
TrigIDR4Monitoring
python
TrigIDR4MonFlags.py
Go to the documentation of this file.
1
36
37
_ALL_SIGS = [
"Electrons"
,
"Muons"
,
"Bjets"
]
38
_DATA_SIGS = [
"Taus"
]
39
_MC_SIGS = [
"Offline"
,
"MuonTruth"
,
"ElectronTruth"
,
"TauTruth"
,
"BjetTruth"
]
40
41
from
TrigIDR4Monitoring.NodeUtils
import
decodeFlags
42
43
def
TrigIDR4MonFlags( inflags, signatures=None, cfgFile=None) :
44
"""
45
Top-level driver for TrigIDR4Mon flag configuration.
46
47
Parameters
48
----------
49
iflags : Node root node tree from decode_flags
50
signatures : list or None
51
Signatures to configure; None runs all appropriate for input type.
52
Example: signatures=["Electrons", "Muons"]
53
cfgFile : str or None
54
Path to a JSON config file that overrides Python defaults.
55
"""
56
57
if
type
(inflags).__name__ ==
"Node"
: iflags = inflags
58
else
: iflags = decodeFlags( inflags )
59
60
flags = iflags.addNode(
"IDTPM2"
)
61
62
flags.useDecorations =
False
63
64
if
cfgFile
is
not
None
:
65
66
print
( f
"configuring from json file : {cfgFile}"
)
67
print
(
"---------------------------------------------"
)
68
69
loadJsonConfig
( flags, cfgFile, signatures )
70
71
else
:
72
73
print
(
"configuring from flags ..."
)
74
print
(
"--------------------------"
)
75
76
loadPythonConfig
( flags, signatures )
77
78
_printConfig
( flags, cfgFile )
79
80
return
iflags
81
82
83
# ---------------------------------------------------------------------------
84
# Common defaults applied to every signature node
85
# ---------------------------------------------------------------------------
86
87
def
_setCommonDefaults
( node ) :
88
89
node.SliceTag = f
"HLT/TRIDT/{node.name()}/Expert"
90
91
# eta-phi-z (DeltaRZSinTheta) matching as default per meeting notes
92
93
node.addNode(
"Matcher"
)
94
node.Matcher.MatcherType =
"DeltaR"
95
node.Matcher.DeltaR = 0.05
96
97
node.addNode(
"RefSelector"
)
98
node.addNode(
"TestSelector"
)
99
100
node.useDecorations =
False
101
102
node.RefSelector.addNode(
"InDetSelector"
)
103
104
node.RefSelector.InDetSelector.minNPixelHits = 2
105
node.RefSelector.InDetSelector.maxNPixelHoles = 1
106
node.RefSelector.InDetSelector.minNSctHits = 3
107
108
node.RefSelector.minPt = 1000.0
109
node.RefSelector.minEta = -2.5
110
node.RefSelector.maxEta = 2.5
111
112
node.RefSelector.maxD0 = 4.0
113
114
# offline selector for reference tracks, none for test tracks
115
node.RefSelector.UseOfflineTool =
True
116
node.TestSelector.UseOfflineTool =
False
117
118
119
# ---------------------------------------------------------------------------
120
# Per-signature flag setters
121
# ---------------------------------------------------------------------------
122
123
def
_setElectronFlags
( flags ) :
124
125
flags.Chains = [
126
"HLT_e.*idperf.*:key=HLT_IDTrack_Electron_FTF:roi=HLT_Roi_FastElectron"
,
127
"HLT_e.*idperf.*:key=HLT_IDTrack_Electron_IDTrig"
,
128
"HLT_e.*:key=HLT_IDTrack_Electron_IDTrig:extra=el_tag:te=0"
,
129
"HLT_e.*:key=HLT_IDTrack_Electron_IDTrig:extra=el_probe:te=1"
130
]
131
132
flags.Reference =
"Electrons:Tight"
133
134
flags.MinTagPT = 14000
135
flags.MinTagMass = 75
136
flags.MaxTagMass = 105
137
138
flags.RefSelector.minET = 14000
139
flags.RefSelector.minPt = 1000
140
flags.RefSelector.minETovpt = 0.8
141
142
143
def
_setMuonFlags
( flags ) :
144
145
flags.Chains = [
146
"HLT_mu.*_idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon"
,
147
"HLT_mu.*_idperf.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon"
,
148
"HLT_mu.*_mu14.*_idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=mu_tag:te=0"
,
149
"HLT_mu.*_mu14.*_idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=mu_probe:te=1"
,
150
"HLT_mu.*_mu14.*_idperf.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=mu1_tag:te=0"
,
151
"HLT_mu.*_mu14.*_idperf.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=mu1_probe:te=1"
152
]
153
154
flags.Reference =
"Muons:Medium"
155
156
flags.MinTagPT = 14000
157
flags.MinTagMass = 75
158
flags.MaxTagMass = 105
159
160
flags.RefSelector.minET = 14000
161
flags.RefSelector.minPt = 1000
162
flags.RefSelector.minETovpt = 0
163
164
165
def
_setTauFlags
( flags ) :
166
167
flags.Chains = [
168
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore"
,
169
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso"
,
170
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso"
,
171
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore:te=1"
,
172
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso:te=1"
,
173
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso:te=1"
,
174
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=tau1_tag:te=0"
,
175
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore:extra=tau1_probe:te=1"
,
176
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=tau0_tag:te=0"
,
177
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso:extra=tau0_probe:te=1"
,
178
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=tau_tag:te=0"
,
179
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso:extra=tau_probe:te=1"
180
]
181
182
flags.Reference =
"TauJets:Loose:1Prong"
183
flags.ReferenceTag =
"Muons:Medium"
184
185
flags.MinTagPT = 14000
186
flags.MinTagMass = 75
187
flags.MaxTagMass = 105
188
189
flags.RefSelector.minET = 14000
190
flags.RefSelector.minPt = 1000
191
flags.RefSelector.minETovpt = 0
192
193
194
def
_setBjetFlags
( flags ) :
195
196
flags.Chains = [
197
"HLT_j.*:key=HLT_IDTrack_JetSuper_FTF:roi=HLT_Roi_JetSuper:vtx=HLT_IDVertex_JetSuper"
,
198
"HLT_j.*:key=HLT_IDTrack_FS_FTF:roi=HLT_FSRoI:vtx=HLT_IDVertex_FS"
,
199
"HLT_j180.*dis.*:key=HLT_IDTrack_JetSuper_FTF:roi=HLT_Roi_JetSuper:vtx=HLT_IDVertex_JetSuper"
,
200
"HLT_j180.*dis.*:key=HLT_IDTrack_FS_FTF:roi=HLT_FSRoI:vtx=HLT_IDVertex_FS"
201
]
202
203
flags.Matcher.MatcherType =
"DeltaRZ"
204
flags.Matcher.DeltaEta = 0.05
205
flags.Matcher.DeltaPhi = 0.05
206
flags.Matcher.DeltaZed = 2
207
208
flags.Matcher.delete(
"DeltaR"
)
209
210
flags.Reference =
"InDetTrackParticles"
211
212
flags.RefSelector.minPt = 1000
213
flags.RefSelector.minETovpt = 0
214
215
216
217
218
def
_setMuonTruthFlags
( flags ) :
219
220
flags.Chains = [
221
"HLT_mu.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=mu_tag"
,
222
"HLT_mu.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=mu_probe"
,
223
"HLT_mu.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=mu1_tag"
,
224
"HLT_mu.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=mu1_probe"
225
]
226
227
flags.Reference =
"TruthParticles"
228
229
flags.MinTagPT = 14000
230
flags.MinTagMass = 75
231
flags.MaxTagMass = 105
232
233
flags.ReferencePdgIdTag = 13
234
235
flags.RefSelector.InDetSelector.minNPixelHits = -1
236
flags.RefSelector.InDetSelector.maxNPixelHoles = -1
237
flags.RefSelector.InDetSelector.minNSctHits = -1
238
239
flags.RefSelector.PdgId = 13
240
# flags.RefSelector.FromPdgId = 13
241
242
flags.RefSelector.minPt = 10000.0
243
flags.RefSelector.minEta = -2.5
244
flags.RefSelector.maxEta = 2.5
245
246
flags.RefSelector.maxD0 = 4.0
247
248
flags.RefSelector.UseOfflineTool =
False
249
flags.RefSelector.addNode(
"TruthSelector"
)
250
flags.TestSelector.UseOfflineTool =
False
251
252
253
def
_setElectronTruthFlags
( flags ) :
254
255
flags.Chains = [
256
"HLT_e.*idperf.*:key=HLT_IDTrack_Electron_FTF:roi=HLT_Roi_FastElectron"
,
257
"HLT_e.*idperf.*:key=HLT_IDTrack_Electron_IDTrig"
,
258
"HLT_e.*_idperf.*:key=HLT_IDTrack_Electron_IDTrig:extra=el_tag"
,
259
"HLT_e.*_idperf.*:key=HLT_IDTrack_Electron_IDTrig:extra=el_probe"
260
]
261
262
flags.Reference =
"TruthParticles"
263
flags.ReferencePdgIdTag = 11
264
265
flags.MinTagPT = 14000
266
flags.MinTagMass = 75
267
flags.MaxTagMass = 105
268
269
flags.RefSelector.InDetSelector.minNPixelHits = -1
270
flags.RefSelector.InDetSelector.maxNPixelHoles = -1
271
flags.RefSelector.InDetSelector.minNSctHits = -1
272
273
flags.RefSelector.PdgId = 11
274
# flags.RefSelector.FromPdgId = 11
275
276
flags.RefSelector.minPt = 10000.0
277
flags.RefSelector.minEta = -2.5
278
flags.RefSelector.maxEta = 2.5
279
280
flags.RefSelector.maxD0 = 4.0
281
282
flags.RefSelector.UseOfflineTool =
False
283
flags.RefSelector.addNode(
"TruthSelector"
)
284
flags.TestSelector.UseOfflineTool =
False
285
286
287
288
def
_setTauTruthFlags
( flags ) :
289
290
flags.Chains = [
291
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore"
,
292
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso"
,
293
"HLT_tau.*idperf.*tracktwoMVA_.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso"
,
294
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore:te=1"
,
295
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso:te=1"
,
296
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso:te=1"
,
297
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=tau1_tag:te=0"
,
298
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauCore_FTF:roi=HLT_Roi_TauCore:extra=tau1_probe:te=1"
,
299
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_FTF:roi=HLT_Roi_L2SAMuon:extra=tau0_tag:te=0"
,
300
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_TauIso_FTF:roi=HLT_Roi_TauIso:extra=tau0_probe:te=1"
,
301
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Muon_IDTrig:roi=HLT_Roi_L2SAMuon:extra=tau_tag:te=0"
,
302
"HLT_mu.*tau.*idperf.*:key=HLT_IDTrack_Tau_IDTrig:roi=HLT_Roi_TauIso:extra=tau_probe:te=1"
303
]
304
305
flags.Reference =
"TruthParticles"
306
flags.ReferencePdgIdTag = 11
307
308
flags.MinTagPT = 14000
309
flags.MinTagMass = 75
310
flags.MaxTagMass = 105
311
312
flags.RefSelector.InDetSelector.minNPixelHits = -1
313
flags.RefSelector.InDetSelector.maxNPixelHoles = -1
314
flags.RefSelector.InDetSelector.minNSctHits = -1
315
316
# flags.RefSelector.PdgId = 13
317
flags.RefSelector.FromPdgId = 15
318
319
flags.RefSelector.minPt = 10000.0
320
flags.RefSelector.minEta = -2.5
321
flags.RefSelector.maxEta = 2.5
322
323
flags.RefSelector.maxD0 = 4.0
324
325
flags.RefSelector.addNode(
"TruthSelector"
)
326
327
flags.RefSelector.UseOfflineTool =
False
328
flags.TestSelector.UseOfflineTool =
False
329
330
331
def
_setBjetTruthFlags
( flags ) :
332
333
flags.Chains = [
334
"HLT_j.*:key=HLT_IDTrack_JetSuper_FTF:roi=HLT_Roi_JetSuper:vtx=HLT_IDVertex_JetSuper"
,
335
"HLT_j.*:key=HLT_IDTrack_FS_FTF:roi=HLT_FSRoI:vtx=HLT_IDVertex_FS"
,
336
"HLT_j180.*dis.*:key=HLT_IDTrack_JetSuper_FTF:roi=HLT_Roi_JetSuper:vtx=HLT_IDVertex_JetSuper"
,
337
"HLT_j180.*dis.*:key=HLT_IDTrack_FS_FTF:roi=HLT_FSRoI:vtx=HLT_IDVertex_FS"
338
]
339
340
flags.Reference =
"TruthParticles"
341
342
flags.RefSelector.InDetSelector.minNPixelHits = -1
343
flags.RefSelector.InDetSelector.maxNPixelHoles = -1
344
flags.RefSelector.InDetSelector.minNSctHits = -1
345
346
flags.RefSelector.minPt = 1000.0
347
flags.RefSelector.minEta = -2.5
348
flags.RefSelector.maxEta = 2.5
349
350
flags.RefSelector.maxD0 = 4.0
351
352
flags.RefSelector.UseOfflineTool =
False
353
flags.RefSelector.addNode(
"TruthSelector"
)
354
flags.TestSelector.UseOfflineTool =
False
355
356
flags.RefSelector.minPt = 1000
357
flags.RefSelector.minETovpt = 0
358
359
360
def
_setOfflineFlags
( flags ) :
361
362
flags.SliceTag =
"Offline"
363
364
flags.Matcher.MatcherType =
"OfflineTruth"
365
flags.Matcher.Prob = 0.5
366
367
flags.Chains = [
"InDetTrackParticles"
]
368
369
flags.Reference =
"TruthParticles"
370
371
flags.RefSelector.UseOfflineTool =
False
372
flags.RefSelector.addNode(
"TruthSelector"
)
373
374
flags.RefSelector.minPt = 1000
375
flags.RefSelector.minETovpt = 0
376
377
378
_configs = {
379
"Electrons"
: _setElectronFlags,
380
"Muons"
: _setMuonFlags,
381
"Taus"
: _setTauFlags,
382
"Bjets"
: _setBjetFlags,
383
"ElectronTruth"
: _setElectronTruthFlags,
384
"MuonTruth"
: _setMuonTruthFlags,
385
"TauTruth"
: _setTauTruthFlags,
386
"BjetTruth"
: _setBjetTruthFlags,
387
"Offline"
: _setOfflineFlags,
388
}
389
390
391
# ---------------------------------------------------------------------------
392
# standard python flags configuration
393
# ---------------------------------------------------------------------------
394
395
def
loadPythonConfig
( flags, signatures=None ) :
396
397
all_signatures = list(_ALL_SIGS)
398
399
if
flags.Input.isMC : all_signatures += _MC_SIGS
400
else
: all_signatures += _DATA_SIGS
401
402
found =
False
403
404
if
signatures
is
not
None
:
405
for
signame
in
signatures :
406
if
signame
not
in
all_signatures :
print
( f
"WARNING: signature {signame} not available"
)
407
408
for
signame
in
all_signatures:
409
410
if
signatures
is
not
None
and
signame
not
in
signatures :
continue
411
412
found =
True
413
414
node = flags.addNode(signame)
415
416
_setCommonDefaults
(node)
417
fun = _configs.get(
node.name
())
418
if
fun
is
not
None
: fun(node)
419
420
if
found
is
False
:
raise
RuntimeError( f
"no matching signatures found for {signatures}"
)
421
422
423
# ---------------------------------------------------------------------------
424
# Optional JSON configuration
425
# ---------------------------------------------------------------------------
426
427
def
loadJsonConfig
( flags, filename, signatures=None ) :
428
"""
429
Optional: overlay a JSON config file onto existing flag defaults.
430
431
The JSON should be a dict keyed by signature name (e.g. "Electrons"),
432
with nested dicts for sub-nodes (e.g. "RefSelector") and scalar
433
values for flags. Any value set here overrides the Python defaults
434
set by TrigIDR4MonFlags().
435
436
Example JSON:
437
{
438
"Electrons": { "RefSelector": { "minPt": 2000 } },
439
"Muons": { "MatchingDeltaR": 0.03 }
440
}
441
"""
442
import
json
443
444
print
(
"loading json config ..."
)
445
446
from
TrigIDR4Monitoring.TrigIDR4MonConfig
import
file_exists
447
file_exists
( filename )
448
449
with
open(filename,
"r"
)
as
f:
450
data = json.load(f)
451
452
found =
False
453
454
matched = []
455
456
for
signame, sigdata
in
data.items():
457
if
signatures
is
None
or
signame
in
signatures :
458
found =
True
459
matched.append( signame )
460
flags.addNode(signame)
461
_applyDict
(flags.node(signame), sigdata)
462
463
if
found
is
False
:
raise
RuntimeError( f
"no matching signatures found for {signatures}"
)
464
465
if
signatures
is
not
None
:
466
for
signame
in
signatures :
467
if
signame
not
in
matched :
print
( f
"WARNING: signature {signame} not available"
)
468
469
470
471
def
_applyDict
( node, d ) :
472
"""Recursively apply a dict of values onto a Node."""
473
for
key, value
in
d.items():
474
if
isinstance(value, dict):
475
if
not
node.hasNode(key): node.addNode(key)
476
_applyDict
(
node.node
(key), value)
477
else
:
478
setattr(node, key, value)
479
480
481
482
483
484
# ---------------------------------------------------------------------------
485
# Configuration summary printout
486
# ---------------------------------------------------------------------------
487
488
def
_printConfig
(flags, cfgFile) :
489
""" Print a human-readable summary of the active flag configuration. """
490
491
nodes = list(flags.nodes())
492
493
print
()
494
print
(
" TrigIDR4MonFlags: configuration summary"
)
495
print
(
" ---------------------------------------"
)
496
497
if
cfgFile
is
None
:
print
(
" JSON config : none (using Python defaults)"
)
498
else
:
print
(f
" JSON config : {cfgFile}"
)
499
500
print
(f
" Signatures : {', '.join(n.name() for n in nodes)}"
501
f
" ({len(nodes)} active)"
)
502
503
print
(
""
)
504
505
for
node
in
nodes :
506
print
(f
" {node.name()}:"
)
507
for
key, value
in
node.Matcher.node_flags() :
print
(f
" Matcher : {key} {value}"
)
508
print
(f
" Reference : {node.Reference if node.has('Reference') else '?'}"
)
509
print
(f
" Patterns : {len(node.Chains) if node.has('Chains') else 0} pattern(s)"
)
510
print
()
print
void print(char *figname, TCanvas *c1)
Definition
TRTCalib_StrawStatusPlots.cxx:26
node::node
node(node *n=0, const std::string &d="", TObject *t=0)
Definition
node.h:32
node::name
void name(const std::string &n)
Definition
node.h:40
file_exists
bool file_exists(const std::string &file)
Definition
hcg.cxx:70
python.TrigIDR4MonFlags._setBjetFlags
_setBjetFlags(flags)
Definition
TrigIDR4MonFlags.py:194
python.TrigIDR4MonFlags._setElectronFlags
_setElectronFlags(flags)
Definition
TrigIDR4MonFlags.py:123
python.TrigIDR4MonFlags._setTauTruthFlags
_setTauTruthFlags(flags)
Definition
TrigIDR4MonFlags.py:288
python.TrigIDR4MonFlags._printConfig
_printConfig(flags, cfgFile)
Definition
TrigIDR4MonFlags.py:488
python.TrigIDR4MonFlags._setTauFlags
_setTauFlags(flags)
Definition
TrigIDR4MonFlags.py:165
python.TrigIDR4MonFlags._setMuonTruthFlags
_setMuonTruthFlags(flags)
Definition
TrigIDR4MonFlags.py:218
python.TrigIDR4MonFlags._applyDict
_applyDict(node, d)
Definition
TrigIDR4MonFlags.py:471
python.TrigIDR4MonFlags.loadJsonConfig
loadJsonConfig(flags, filename, signatures=None)
Definition
TrigIDR4MonFlags.py:427
python.TrigIDR4MonFlags._setOfflineFlags
_setOfflineFlags(flags)
Definition
TrigIDR4MonFlags.py:360
python.TrigIDR4MonFlags._setBjetTruthFlags
_setBjetTruthFlags(flags)
Definition
TrigIDR4MonFlags.py:331
python.TrigIDR4MonFlags._setElectronTruthFlags
_setElectronTruthFlags(flags)
Definition
TrigIDR4MonFlags.py:253
python.TrigIDR4MonFlags.loadPythonConfig
loadPythonConfig(flags, signatures=None)
Definition
TrigIDR4MonFlags.py:395
python.TrigIDR4MonFlags._setCommonDefaults
_setCommonDefaults(node)
Definition
TrigIDR4MonFlags.py:87
python.TrigIDR4MonFlags._setMuonFlags
_setMuonFlags(flags)
Definition
TrigIDR4MonFlags.py:143
type
Generated on
for ATLAS Offline Software by
1.17.0