pyibis-ami 7.2.1__py3-none-any.whl → 7.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyibis_ami-7.2.1.dist-info → pyibis_ami-7.2.3.dist-info}/METADATA +134 -134
- pyibis_ami-7.2.3.dist-info/RECORD +27 -0
- {pyibis_ami-7.2.1.dist-info → pyibis_ami-7.2.3.dist-info}/WHEEL +1 -1
- {pyibis_ami-7.2.1.dist-info → pyibis_ami-7.2.3.dist-info}/licenses/LICENSE +8 -8
- pyibisami/IBIS_AMI_Checker.ipynb +1693 -1693
- pyibisami/IBIS_AMI_Tester.ipynb +1457 -1457
- pyibisami/__init__.py +22 -22
- pyibisami/__main__.py +7 -7
- pyibisami/ami/config.py +297 -297
- pyibisami/ami/generic.ami.em +20 -20
- pyibisami/ami/generic.ibs.em +139 -139
- pyibisami/ami/model.py +631 -596
- pyibisami/ami/parameter.py +324 -316
- pyibisami/ami/parser.py +680 -674
- pyibisami/ami/reserved_parameter_names.py +84 -84
- pyibisami/common.py +42 -42
- pyibisami/ibis/file.py +325 -325
- pyibisami/ibis/model.py +399 -399
- pyibisami/ibis/parser.py +525 -525
- pyibisami/tools/run_notebook.py +144 -144
- pyibisami/tools/run_tests.py +273 -273
- pyibisami/tools/test_results.xsl +42 -42
- pyibis_ami-7.2.1.dist-info/RECORD +0 -27
- {pyibis_ami-7.2.1.dist-info → pyibis_ami-7.2.3.dist-info}/entry_points.txt +0 -0
- {pyibis_ami-7.2.1.dist-info → pyibis_ami-7.2.3.dist-info}/top_level.txt +0 -0
pyibisami/ami/parser.py
CHANGED
@@ -1,674 +1,680 @@
|
|
1
|
-
"""IBIS-AMI parameter parsing and configuration utilities.
|
2
|
-
|
3
|
-
Original author: David Banas <capn.freako@gmail.com>
|
4
|
-
|
5
|
-
Original date: December 17, 2016
|
6
|
-
|
7
|
-
Copyright (c) 2019 David Banas; all rights reserved World wide.
|
8
|
-
"""
|
9
|
-
|
10
|
-
from ctypes import c_double
|
11
|
-
import re
|
12
|
-
from typing import Any, Callable, NewType, Optional, TypeAlias
|
13
|
-
|
14
|
-
import numpy as np
|
15
|
-
from numpy.typing import NDArray
|
16
|
-
from parsec import ParseError, generate, many, regex, string
|
17
|
-
from traits.api import Bool, Enum, HasTraits, Range, Trait, TraitType
|
18
|
-
from traitsui.api import Group, HGroup, Item, VGroup, View
|
19
|
-
from traitsui.menu import ModalButtons
|
20
|
-
|
21
|
-
from .model import AMIModelInitializer
|
22
|
-
from .parameter import AMIParamError, AMIParameter
|
23
|
-
from .reserved_parameter_names import AmiReservedParameterName, RESERVED_PARAM_NAMES
|
24
|
-
|
25
|
-
# New types and aliases.
|
26
|
-
# Parameters = NewType('Parameters', dict[str, AMIParameter] | dict[str, 'Parameters'])
|
27
|
-
# ParamValues = NewType('ParamValues', dict[str, list[Any]] | dict[str, 'ParamValues'])
|
28
|
-
# See: https://stackoverflow.com/questions/70894567/using-mypy-newtype-with-type-aliases-or-protocols
|
29
|
-
ParamName = NewType("ParamName", str)
|
30
|
-
ParamValue: TypeAlias = int | float | str | list["ParamValue"]
|
31
|
-
Parameters: TypeAlias = dict[ParamName, "AMIParameter | 'Parameters'"]
|
32
|
-
ParamValues: TypeAlias = dict[ParamName, "ParamValue | 'ParamValues'"]
|
33
|
-
|
34
|
-
AmiName = NewType("AmiName", str)
|
35
|
-
AmiAtom: TypeAlias = bool | int | float | str
|
36
|
-
AmiExpr: TypeAlias = "AmiAtom | 'AmiNode'"
|
37
|
-
AmiNode: TypeAlias = tuple[AmiName, list[AmiExpr]]
|
38
|
-
AmiNodeParser: TypeAlias = Callable[[str], AmiNode]
|
39
|
-
AmiParser: TypeAlias = Callable[[str], tuple[AmiName, list[AmiNode]]] # Atoms may not exist at the root level.
|
40
|
-
|
41
|
-
ParseErrMsg = NewType("ParseErrMsg", str)
|
42
|
-
AmiRootName = NewType("AmiRootName", str)
|
43
|
-
ReservedParamDict: TypeAlias = dict[AmiReservedParameterName, AMIParameter]
|
44
|
-
ModelSpecificDict: TypeAlias = dict[ParamName, "AMIParameter | 'ModelSpecificDict'"]
|
45
|
-
|
46
|
-
__all__ = [
|
47
|
-
"ParamName", "ParamValue", "Parameters", "ParamValues",
|
48
|
-
"AmiName", "AmiAtom", "AmiExpr", "AmiNode", "AmiNodeParser", "AmiParser",
|
49
|
-
"ami_parse", "AMIParamConfigurator"]
|
50
|
-
|
51
|
-
#####
|
52
|
-
# AMI parameter configurator.
|
53
|
-
#####
|
54
|
-
|
55
|
-
|
56
|
-
class AMIParamConfigurator(HasTraits):
|
57
|
-
"""Customizable IBIS-AMI model parameter configurator.
|
58
|
-
|
59
|
-
This class can be configured to present a customized GUI to the user
|
60
|
-
for configuring a particular IBIS-AMI model.
|
61
|
-
|
62
|
-
The intended use model is as follows:
|
63
|
-
|
64
|
-
1. Instantiate this class only once per IBIS-AMI model invocation.
|
65
|
-
When instantiating, provide the unprocessed contents of the AMI
|
66
|
-
file, as a single string. This class will take care of getting
|
67
|
-
that string parsed properly, and report any errors or warnings
|
68
|
-
it encounters, in its ``ami_parsing_errors`` property.
|
69
|
-
|
70
|
-
2. When you want to let the user change the AMI parameter
|
71
|
-
configuration, call the ``open_gui`` member function.
|
72
|
-
(Or, just call the instance as if it were a function.)
|
73
|
-
The instance will then present a GUI to the user,
|
74
|
-
allowing him to modify the values of any *In* or *InOut* parameters.
|
75
|
-
The resultant AMI parameter dictionary, suitable for passing
|
76
|
-
into the ``ami_params`` parameter of the ``AMIModelInitializer``
|
77
|
-
constructor, can be accessed, via the instance's
|
78
|
-
``input_ami_params`` property. The latest user selections will be
|
79
|
-
remembered, as long as the instance remains in scope.
|
80
|
-
|
81
|
-
The entire AMI parameter definition dictionary, which should *not* be
|
82
|
-
passed to the ``AMIModelInitializer`` constructor, is available in the
|
83
|
-
instance's ``ami_param_defs`` property.
|
84
|
-
|
85
|
-
Any errors or warnings encountered while parsing are available, in
|
86
|
-
the ``ami_parsing_errors`` property.
|
87
|
-
"""
|
88
|
-
|
89
|
-
def __init__(self, ami_file_contents_str: str) -> None:
|
90
|
-
"""
|
91
|
-
Args:
|
92
|
-
ami_file_contents_str: The unprocessed contents of the AMI file, as a single string.
|
93
|
-
"""
|
94
|
-
|
95
|
-
# Super-class initialization is ABSOLUTELY NECESSARY, in order
|
96
|
-
# to get all the Traits/UI machinery setup correctly.
|
97
|
-
super().__init__()
|
98
|
-
|
99
|
-
# Parse the AMI file contents, storing any errors or warnings, and customize the view accordingly.
|
100
|
-
err_str,
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
self.
|
123
|
-
|
124
|
-
|
125
|
-
self.
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
self.
|
132
|
-
|
133
|
-
def
|
134
|
-
"
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
view.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
@property
|
204
|
-
def
|
205
|
-
"""
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
"""
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
self
|
230
|
-
params:
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
#
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
)
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
if not
|
548
|
-
_err_str += "ERROR: Reserved
|
549
|
-
|
550
|
-
if not
|
551
|
-
_err_str += "
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
""
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
)
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
1
|
+
"""IBIS-AMI parameter parsing and configuration utilities.
|
2
|
+
|
3
|
+
Original author: David Banas <capn.freako@gmail.com>
|
4
|
+
|
5
|
+
Original date: December 17, 2016
|
6
|
+
|
7
|
+
Copyright (c) 2019 David Banas; all rights reserved World wide.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from ctypes import c_double
|
11
|
+
import re
|
12
|
+
from typing import Any, Callable, NewType, Optional, TypeAlias
|
13
|
+
|
14
|
+
import numpy as np
|
15
|
+
from numpy.typing import NDArray
|
16
|
+
from parsec import ParseError, generate, many, regex, string
|
17
|
+
from traits.api import Bool, Enum, HasTraits, Range, Trait, TraitType
|
18
|
+
from traitsui.api import Group, HGroup, Item, VGroup, View
|
19
|
+
from traitsui.menu import ModalButtons
|
20
|
+
|
21
|
+
from .model import AMIModelInitializer
|
22
|
+
from .parameter import AMIParamError, AMIParameter
|
23
|
+
from .reserved_parameter_names import AmiReservedParameterName, RESERVED_PARAM_NAMES
|
24
|
+
|
25
|
+
# New types and aliases.
|
26
|
+
# Parameters = NewType('Parameters', dict[str, AMIParameter] | dict[str, 'Parameters'])
|
27
|
+
# ParamValues = NewType('ParamValues', dict[str, list[Any]] | dict[str, 'ParamValues'])
|
28
|
+
# See: https://stackoverflow.com/questions/70894567/using-mypy-newtype-with-type-aliases-or-protocols
|
29
|
+
ParamName = NewType("ParamName", str)
|
30
|
+
ParamValue: TypeAlias = int | float | str | list["ParamValue"]
|
31
|
+
Parameters: TypeAlias = dict[ParamName, "AMIParameter | 'Parameters'"]
|
32
|
+
ParamValues: TypeAlias = dict[ParamName, "ParamValue | 'ParamValues'"]
|
33
|
+
|
34
|
+
AmiName = NewType("AmiName", str)
|
35
|
+
AmiAtom: TypeAlias = bool | int | float | str
|
36
|
+
AmiExpr: TypeAlias = "AmiAtom | 'AmiNode'"
|
37
|
+
AmiNode: TypeAlias = tuple[AmiName, list[AmiExpr]]
|
38
|
+
AmiNodeParser: TypeAlias = Callable[[str], AmiNode]
|
39
|
+
AmiParser: TypeAlias = Callable[[str], tuple[AmiName, list[AmiNode]]] # Atoms may not exist at the root level.
|
40
|
+
|
41
|
+
ParseErrMsg = NewType("ParseErrMsg", str)
|
42
|
+
AmiRootName = NewType("AmiRootName", str)
|
43
|
+
ReservedParamDict: TypeAlias = dict[AmiReservedParameterName, AMIParameter]
|
44
|
+
ModelSpecificDict: TypeAlias = dict[ParamName, "AMIParameter | 'ModelSpecificDict'"]
|
45
|
+
|
46
|
+
__all__ = [
|
47
|
+
"ParamName", "ParamValue", "Parameters", "ParamValues",
|
48
|
+
"AmiName", "AmiAtom", "AmiExpr", "AmiNode", "AmiNodeParser", "AmiParser",
|
49
|
+
"ami_parse", "AMIParamConfigurator"]
|
50
|
+
|
51
|
+
#####
|
52
|
+
# AMI parameter configurator.
|
53
|
+
#####
|
54
|
+
|
55
|
+
|
56
|
+
class AMIParamConfigurator(HasTraits):
|
57
|
+
"""Customizable IBIS-AMI model parameter configurator.
|
58
|
+
|
59
|
+
This class can be configured to present a customized GUI to the user
|
60
|
+
for configuring a particular IBIS-AMI model.
|
61
|
+
|
62
|
+
The intended use model is as follows:
|
63
|
+
|
64
|
+
1. Instantiate this class only once per IBIS-AMI model invocation.
|
65
|
+
When instantiating, provide the unprocessed contents of the AMI
|
66
|
+
file, as a single string. This class will take care of getting
|
67
|
+
that string parsed properly, and report any errors or warnings
|
68
|
+
it encounters, in its ``ami_parsing_errors`` property.
|
69
|
+
|
70
|
+
2. When you want to let the user change the AMI parameter
|
71
|
+
configuration, call the ``open_gui`` member function.
|
72
|
+
(Or, just call the instance as if it were a function.)
|
73
|
+
The instance will then present a GUI to the user,
|
74
|
+
allowing him to modify the values of any *In* or *InOut* parameters.
|
75
|
+
The resultant AMI parameter dictionary, suitable for passing
|
76
|
+
into the ``ami_params`` parameter of the ``AMIModelInitializer``
|
77
|
+
constructor, can be accessed, via the instance's
|
78
|
+
``input_ami_params`` property. The latest user selections will be
|
79
|
+
remembered, as long as the instance remains in scope.
|
80
|
+
|
81
|
+
The entire AMI parameter definition dictionary, which should *not* be
|
82
|
+
passed to the ``AMIModelInitializer`` constructor, is available in the
|
83
|
+
instance's ``ami_param_defs`` property.
|
84
|
+
|
85
|
+
Any errors or warnings encountered while parsing are available, in
|
86
|
+
the ``ami_parsing_errors`` property.
|
87
|
+
"""
|
88
|
+
|
89
|
+
def __init__(self, ami_file_contents_str: str) -> None:
|
90
|
+
"""
|
91
|
+
Args:
|
92
|
+
ami_file_contents_str: The unprocessed contents of the AMI file, as a single string.
|
93
|
+
"""
|
94
|
+
|
95
|
+
# Super-class initialization is ABSOLUTELY NECESSARY, in order
|
96
|
+
# to get all the Traits/UI machinery setup correctly.
|
97
|
+
super().__init__()
|
98
|
+
|
99
|
+
# Parse the AMI file contents, storing any errors or warnings, and customize the view accordingly.
|
100
|
+
(err_str,
|
101
|
+
root_name,
|
102
|
+
description,
|
103
|
+
reserved_param_dict,
|
104
|
+
model_specific_dict) = parse_ami_file_contents(ami_file_contents_str)
|
105
|
+
if not reserved_param_dict:
|
106
|
+
raise ValueError(
|
107
|
+
"\n".join([
|
108
|
+
"No 'Reserved_Parameters' section found!",
|
109
|
+
err_str
|
110
|
+
]))
|
111
|
+
if not model_specific_dict:
|
112
|
+
raise ValueError(
|
113
|
+
"\n".join([
|
114
|
+
"No 'Model_Specific' section found!",
|
115
|
+
err_str
|
116
|
+
]))
|
117
|
+
gui_items, new_traits = make_gui(model_specific_dict)
|
118
|
+
trait_names = []
|
119
|
+
for trait in new_traits:
|
120
|
+
self.add_trait(trait[0], trait[1])
|
121
|
+
trait_names.append(trait[0])
|
122
|
+
self._param_trait_names = trait_names
|
123
|
+
self._root_name = root_name
|
124
|
+
self._ami_parsing_errors = err_str
|
125
|
+
self._content = gui_items
|
126
|
+
self._reserved_param_dict = reserved_param_dict
|
127
|
+
self._model_specific_dict = model_specific_dict
|
128
|
+
self._description = description
|
129
|
+
|
130
|
+
def __call__(self):
|
131
|
+
self.open_gui()
|
132
|
+
|
133
|
+
def open_gui(self):
|
134
|
+
"""Present a customized GUI to the user, for parameter
|
135
|
+
customization."""
|
136
|
+
# self.configure_traits(kind='modal') # Waiting for Enthought/Traits PR1841 to be accepted.
|
137
|
+
self.configure_traits()
|
138
|
+
|
139
|
+
def default_traits_view(self):
|
140
|
+
"Default Traits/UI view definition."
|
141
|
+
view = View(
|
142
|
+
resizable=False,
|
143
|
+
buttons=ModalButtons,
|
144
|
+
title=f"{self._root_name} AMI Parameter Configurator",
|
145
|
+
)
|
146
|
+
view.set_content(self._content)
|
147
|
+
return view
|
148
|
+
|
149
|
+
def fetch_param(self, branch_names):
|
150
|
+
"""Returns the parameter found by traversing 'branch_names' or None if
|
151
|
+
not found.
|
152
|
+
|
153
|
+
Note: 'branch_names' should *not* begin with 'root_name'.
|
154
|
+
"""
|
155
|
+
param_dict = self.ami_param_defs
|
156
|
+
while branch_names:
|
157
|
+
branch_name = branch_names.pop(0)
|
158
|
+
if branch_name in param_dict:
|
159
|
+
param_dict = param_dict[branch_name]
|
160
|
+
else:
|
161
|
+
return None
|
162
|
+
if isinstance(param_dict, AMIParameter):
|
163
|
+
return param_dict
|
164
|
+
return None
|
165
|
+
|
166
|
+
def fetch_param_val(self, branch_names):
|
167
|
+
"""Returns the value of the parameter found by traversing
|
168
|
+
'branch_names' or None if not found.
|
169
|
+
|
170
|
+
Note: 'branch_names' should *not* begin with 'root_name'.
|
171
|
+
"""
|
172
|
+
_param = self.fetch_param(branch_names)
|
173
|
+
if _param:
|
174
|
+
return _param.pvalue
|
175
|
+
return None
|
176
|
+
|
177
|
+
def set_param_val(self, branch_names, new_val):
|
178
|
+
"""Sets the value of the parameter found by traversing 'branch_names'
|
179
|
+
or raises an exception if not found.
|
180
|
+
|
181
|
+
Note: 'branch_names' should *not* begin with 'root_name'.
|
182
|
+
Note: Be careful! There is no checking done here!
|
183
|
+
"""
|
184
|
+
|
185
|
+
param_dict = self.ami_param_defs
|
186
|
+
while branch_names:
|
187
|
+
branch_name = branch_names.pop(0)
|
188
|
+
if branch_name in param_dict:
|
189
|
+
param_dict = param_dict[branch_name]
|
190
|
+
else:
|
191
|
+
raise ValueError(
|
192
|
+
f"Failed parameter tree search looking for: {branch_name}; available keys: {param_dict.keys()}"
|
193
|
+
)
|
194
|
+
if isinstance(param_dict, AMIParameter):
|
195
|
+
param_dict.pvalue = new_val
|
196
|
+
try:
|
197
|
+
eval(f"self.set({branch_name}_={new_val})") # pylint: disable=eval-used
|
198
|
+
except Exception: # pylint: disable=broad-exception-caught
|
199
|
+
eval(f"self.set({branch_name}={new_val})") # pylint: disable=eval-used
|
200
|
+
else:
|
201
|
+
raise TypeError(f"{param_dict} is not of type: AMIParameter!")
|
202
|
+
|
203
|
+
@property
|
204
|
+
def ami_parsing_errors(self):
|
205
|
+
"""Any errors or warnings encountered, while parsing the AMI parameter
|
206
|
+
definition file contents."""
|
207
|
+
return self._ami_parsing_errors
|
208
|
+
|
209
|
+
@property
|
210
|
+
def ami_param_defs(self) -> dict[str, ReservedParamDict | ModelSpecificDict]:
|
211
|
+
"""The entire AMI parameter definition dictionary.
|
212
|
+
|
213
|
+
Should *not* be passed to ``AMIModelInitializer`` constructor!
|
214
|
+
"""
|
215
|
+
return {"Reserved_Parameters": self._reserved_param_dict,
|
216
|
+
"Model_Specific": self._model_specific_dict}
|
217
|
+
|
218
|
+
@property
|
219
|
+
def input_ami_params(self) -> ParamValues:
|
220
|
+
"""
|
221
|
+
The dictionary of *Model Specific* AMI parameters of type 'In' or
|
222
|
+
'InOut', along with their user selected values.
|
223
|
+
|
224
|
+
Should be passed to ``AMIModelInitializer`` constructor.
|
225
|
+
"""
|
226
|
+
|
227
|
+
res: ParamValues = {}
|
228
|
+
res[ParamName("root_name")] = str(self._root_name)
|
229
|
+
params = self._model_specific_dict
|
230
|
+
for pname in params:
|
231
|
+
res.update(self.input_ami_param(params, pname))
|
232
|
+
return res
|
233
|
+
|
234
|
+
def input_ami_param(
|
235
|
+
self,
|
236
|
+
params: Parameters,
|
237
|
+
pname: ParamName,
|
238
|
+
prefix: str = ""
|
239
|
+
) -> ParamValues:
|
240
|
+
"""
|
241
|
+
Retrieve one AMI parameter value, or dictionary of subparameter values,
|
242
|
+
from the given parameter definition dictionary.
|
243
|
+
|
244
|
+
Args:
|
245
|
+
params: The parameter definition dictionary.
|
246
|
+
pname: The simple name of the parameter of interest, used by the IBIS-AMI model.
|
247
|
+
|
248
|
+
Keyword Args:
|
249
|
+
prefix: The current working parameter name prefix.
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
A dictionary of parameter values indexed by non-prefixed parameter names.
|
253
|
+
|
254
|
+
Notes:
|
255
|
+
1. The "prefix" referred to above refers to a string encoding of the
|
256
|
+
hierarchy above a particular trait. We need this hierarchy for the
|
257
|
+
sake of the ``Traits/UI`` machinery, which addresses traits by name
|
258
|
+
alone. However, the IBIS-AMI model is not expecting it. So, we have
|
259
|
+
to strip it off, before sending the result here into ``AMI_Init()``.
|
260
|
+
"""
|
261
|
+
|
262
|
+
res = {}
|
263
|
+
tname = prefix + pname # This is the fully hierarchical trait name, used by the Traits/UI machinery.
|
264
|
+
param = params[pname]
|
265
|
+
if isinstance(param, AMIParameter):
|
266
|
+
if tname in self._param_trait_names: # If model specific and of type In or InOut...
|
267
|
+
# See the docs on the *HasTraits* class, if this is confusing.
|
268
|
+
# Querry for a mapped trait, first, by trying to get '<trait_name>_'. (Note the underscore.)
|
269
|
+
try:
|
270
|
+
res[pname] = self.trait_get(tname + "_")[tname + "_"]
|
271
|
+
# If we get an exception, we have an ordinary (i.e. - not mapped) trait.
|
272
|
+
except Exception: # pylint: disable=broad-exception-caught
|
273
|
+
res[pname] = self.trait_get(tname)[tname]
|
274
|
+
elif isinstance(param, dict): # We received a dictionary of subparameters, in 'param'.
|
275
|
+
subs: ParamValues = {}
|
276
|
+
for sname in param:
|
277
|
+
subs.update(self.input_ami_param(param, sname, prefix=pname + "_")) # type: ignore
|
278
|
+
res[pname] = subs
|
279
|
+
return res
|
280
|
+
|
281
|
+
@property
|
282
|
+
def info_ami_params(self):
|
283
|
+
"Dictionary of *Reserved* AMI parameter values."
|
284
|
+
return self._reserved_param_dict
|
285
|
+
|
286
|
+
def get_init(
|
287
|
+
self,
|
288
|
+
bit_time: float,
|
289
|
+
sample_interval: float,
|
290
|
+
channel_response: NDArray[np.longdouble],
|
291
|
+
ami_params: Optional[dict[str, Any]] = None
|
292
|
+
) -> AMIModelInitializer:
|
293
|
+
"""
|
294
|
+
Get a model initializer, configured by the user if necessary.
|
295
|
+
"""
|
296
|
+
|
297
|
+
row_size = len(channel_response)
|
298
|
+
if ami_params:
|
299
|
+
initializer = AMIModelInitializer(
|
300
|
+
ami_params,
|
301
|
+
info_params=self.info_ami_params,
|
302
|
+
bit_time=c_double(bit_time),
|
303
|
+
row_size=row_size,
|
304
|
+
sample_interval=c_double(sample_interval)
|
305
|
+
)
|
306
|
+
else:
|
307
|
+
# This call will invoke a GUI applet for the user to interact with,
|
308
|
+
# to configure the AMI parameter values.
|
309
|
+
self()
|
310
|
+
initializer = AMIModelInitializer(
|
311
|
+
self.input_ami_params,
|
312
|
+
info_params=self.info_ami_params,
|
313
|
+
bit_time=c_double(bit_time),
|
314
|
+
row_size=row_size,
|
315
|
+
sample_interval=c_double(sample_interval)
|
316
|
+
)
|
317
|
+
|
318
|
+
# Don't try to pack this into the parentheses above!
|
319
|
+
initializer.channel_response = channel_response
|
320
|
+
return initializer
|
321
|
+
|
322
|
+
|
323
|
+
#####
|
324
|
+
# AMI file parser.
|
325
|
+
#####
|
326
|
+
|
327
|
+
# ignore cases.
|
328
|
+
whitespace = regex(r"\s+", re.MULTILINE)
|
329
|
+
comment = regex(r"\|.*")
|
330
|
+
ignore = many(whitespace | comment)
|
331
|
+
|
332
|
+
|
333
|
+
def lexeme(p):
|
334
|
+
"""Lexer for words."""
|
335
|
+
return p << ignore # skip all ignored characters.
|
336
|
+
|
337
|
+
|
338
|
+
def int2tap(x):
|
339
|
+
"""Convert integer to tap position."""
|
340
|
+
x = x.strip()
|
341
|
+
if x[0] == "-":
|
342
|
+
res = "pre" + x[1:]
|
343
|
+
else:
|
344
|
+
res = "post" + x
|
345
|
+
return res
|
346
|
+
|
347
|
+
|
348
|
+
lparen = lexeme(string("("))
|
349
|
+
rparen = lexeme(string(")"))
|
350
|
+
number = lexeme(regex(r"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?"))
|
351
|
+
integ = lexeme(regex(r"[-+]?[0-9]+"))
|
352
|
+
nat = lexeme(regex(r"[0-9]+"))
|
353
|
+
tap_ix = (integ << whitespace).parsecmap(int2tap)
|
354
|
+
symbol = lexeme(regex(r"[0-9a-zA-Z_][^\s()]*"))
|
355
|
+
true = lexeme(string("True")).result(True)
|
356
|
+
false = lexeme(string("False")).result(False)
|
357
|
+
ami_string = lexeme(regex(r'"[^"]*"'))
|
358
|
+
|
359
|
+
atom = number | symbol | ami_string | (true | false)
|
360
|
+
node_name = tap_ix ^ symbol # `tap_ix` is new and gives the tap position; negative positions are allowed.
|
361
|
+
|
362
|
+
|
363
|
+
@generate("AMI node")
|
364
|
+
def node():
|
365
|
+
"Parse AMI node."
|
366
|
+
yield lparen
|
367
|
+
label = yield node_name
|
368
|
+
values = yield many(expr)
|
369
|
+
yield rparen
|
370
|
+
return (label, values)
|
371
|
+
|
372
|
+
|
373
|
+
@generate("AMI file")
|
374
|
+
def root():
|
375
|
+
"Parse AMI file."
|
376
|
+
yield lparen
|
377
|
+
label = yield node_name
|
378
|
+
values = yield many(node)
|
379
|
+
yield rparen
|
380
|
+
return (label, values)
|
381
|
+
|
382
|
+
|
383
|
+
expr = atom | node
|
384
|
+
ami = ignore >> root
|
385
|
+
ami_parse: AmiParser = ami.parse
|
386
|
+
|
387
|
+
|
388
|
+
def proc_branch(branch):
|
389
|
+
"""Process a branch in a AMI parameter definition tree.
|
390
|
+
|
391
|
+
That is, build a dictionary from a pair containing:
|
392
|
+
- a parameter name, and
|
393
|
+
- a list of either:
|
394
|
+
- parameter definition tags, or
|
395
|
+
- subparameters.
|
396
|
+
|
397
|
+
We distinguish between the two possible kinds of payloads, by
|
398
|
+
peaking at the names of the first two items in the list and noting
|
399
|
+
whether they are keys of 'AMIParameter._param_def_tag_procs'.
|
400
|
+
We have to do this twice, due to the dual use of the 'Description'
|
401
|
+
tag and the fact that we have no guarantee of any particular
|
402
|
+
ordering of subparameter branch items.
|
403
|
+
|
404
|
+
Args:
|
405
|
+
p (str, list): A pair, as described above.
|
406
|
+
|
407
|
+
Returns:
|
408
|
+
(str, dict): A pair containing:
|
409
|
+
err_str: String containing any errors or warnings encountered,
|
410
|
+
while building the parameter dictionary.
|
411
|
+
param_dict: Resultant parameter dictionary.
|
412
|
+
"""
|
413
|
+
results = ("", {}) # Empty Results
|
414
|
+
if len(branch) != 2:
|
415
|
+
if not branch:
|
416
|
+
err_str = "ERROR: Empty branch provided to proc_branch()!\n"
|
417
|
+
else:
|
418
|
+
err_str = f"ERROR: Malformed item: {branch[0]}\n"
|
419
|
+
results = (err_str, {})
|
420
|
+
|
421
|
+
param_name = branch[0]
|
422
|
+
param_tags = branch[1]
|
423
|
+
|
424
|
+
if not param_tags:
|
425
|
+
err_str = f"ERROR: No tags/subparameters provided for parameter, '{param_name}'\n"
|
426
|
+
results = (err_str, {})
|
427
|
+
|
428
|
+
try:
|
429
|
+
if (
|
430
|
+
(len(param_tags) > 1)
|
431
|
+
and ( # noqa: W503
|
432
|
+
param_tags[0][0] in AMIParameter._param_def_tag_procs # pylint: disable=protected-access # noqa: W503
|
433
|
+
)
|
434
|
+
and ( # noqa: W503
|
435
|
+
param_tags[1][0] in AMIParameter._param_def_tag_procs # pylint: disable=protected-access # noqa: W503
|
436
|
+
)
|
437
|
+
):
|
438
|
+
try:
|
439
|
+
results = ("", {param_name: AMIParameter(param_name, param_tags)})
|
440
|
+
except AMIParamError as err:
|
441
|
+
results = (str(err), {})
|
442
|
+
elif param_name == "Description":
|
443
|
+
results = ("", {"description": param_tags[0].strip('"')})
|
444
|
+
else:
|
445
|
+
err_str = ""
|
446
|
+
param_dict = {}
|
447
|
+
param_dict[param_name] = {}
|
448
|
+
for param_tag in param_tags:
|
449
|
+
temp_str, temp_dict = proc_branch(param_tag)
|
450
|
+
param_dict[param_name].update(temp_dict)
|
451
|
+
if temp_str:
|
452
|
+
err_str = (
|
453
|
+
f"Error returned by recursive call, while processing parameter, '{param_name}':\n{temp_str}"
|
454
|
+
)
|
455
|
+
results = (err_str, param_dict)
|
456
|
+
|
457
|
+
results = (err_str, param_dict)
|
458
|
+
except Exception: # pylint: disable=broad-exception-caught
|
459
|
+
print(f"Error processing branch:\n{param_tags}")
|
460
|
+
return results
|
461
|
+
|
462
|
+
|
463
|
+
def parse_ami_file_contents( # pylint: disable=too-many-locals,too-many-branches
|
464
|
+
file_contents: str
|
465
|
+
) -> tuple[ParseErrMsg, AmiRootName, str, ReservedParamDict, ModelSpecificDict]:
|
466
|
+
"""
|
467
|
+
Parse the contents of an IBIS-AMI *parameter definition* (i.e. - `*.ami`) file.
|
468
|
+
|
469
|
+
Args:
|
470
|
+
file_contents: The contents of the file, as a single string.
|
471
|
+
|
472
|
+
Example:
|
473
|
+
::
|
474
|
+
|
475
|
+
with open(<ami_file_name>) as ami_file:
|
476
|
+
file_contents = ami_file.read()
|
477
|
+
(err_str, root_name, reserved_param_dict, model_specific_param_dict) = parse_ami_file_contents(file_contents)
|
478
|
+
|
479
|
+
Returns:
|
480
|
+
A tuple containing
|
481
|
+
|
482
|
+
1. Any error message generated by the parser. (empty on success)
|
483
|
+
|
484
|
+
2. AMI file "root" name.
|
485
|
+
|
486
|
+
3. *Reserved Parameters* dictionary. (empty on failure)
|
487
|
+
|
488
|
+
- The keys of the *Reserved Parameters* dictionary are
|
489
|
+
limited to those called out in the IBIS-AMI specification.
|
490
|
+
|
491
|
+
- The values of the *Reserved Parameters* dictionary
|
492
|
+
must be instances of class ``AMIParameter``.
|
493
|
+
|
494
|
+
4. *Model Specific Parameters* dictionary. (empty on failure)
|
495
|
+
|
496
|
+
- The keys of the *Model Specific Parameters* dictionary can be anything.
|
497
|
+
|
498
|
+
- The values of the *Model Specific Parameters* dictionary
|
499
|
+
may be either: an instance of class ``AMIParameter``, or a nested sub-dictionary.
|
500
|
+
"""
|
501
|
+
try:
|
502
|
+
res = ami_parse(file_contents)
|
503
|
+
except ParseError as pe:
|
504
|
+
err_str = ParseErrMsg(f"Expected {pe.expected} at {pe.loc()} in:\n{pe.text[pe.index:]}")
|
505
|
+
return err_str, AmiRootName(""), "", {}, {}
|
506
|
+
|
507
|
+
err_str, param_dict = proc_branch(res)
|
508
|
+
if err_str:
|
509
|
+
return (err_str, AmiRootName(""), "", {}, {})
|
510
|
+
if len(param_dict.keys()) != 1:
|
511
|
+
raise ValueError(f"Malformed AMI parameter S-exp has top-level keys: {param_dict.keys()}!")
|
512
|
+
|
513
|
+
reserved_found = False
|
514
|
+
init_returns_impulse_found = False
|
515
|
+
getwave_exists_found = False
|
516
|
+
model_spec_found = False
|
517
|
+
root_name, params = list(param_dict.items())[0]
|
518
|
+
description = ""
|
519
|
+
reserved_params_dict = {}
|
520
|
+
model_specific_dict = {}
|
521
|
+
_err_str = ""
|
522
|
+
for label in list(params.keys()):
|
523
|
+
tmp_params = params[label]
|
524
|
+
if label == "Reserved_Parameters":
|
525
|
+
reserved_found = True
|
526
|
+
for param_name in list(tmp_params.keys()):
|
527
|
+
if param_name not in RESERVED_PARAM_NAMES:
|
528
|
+
_err_str += f"WARNING: Unrecognized reserved parameter name, '{param_name}', found in parameter definition string!\n"
|
529
|
+
continue
|
530
|
+
param = tmp_params[param_name]
|
531
|
+
if param.pname == "AMI_Version":
|
532
|
+
if param.pusage != "Info" or param.ptype != "String":
|
533
|
+
_err_str += "WARNING: Malformed 'AMI_Version' parameter.\n"
|
534
|
+
elif param.pname == "Init_Returns_Impulse":
|
535
|
+
init_returns_impulse_found = True
|
536
|
+
elif param.pname == "GetWave_Exists":
|
537
|
+
getwave_exists_found = True
|
538
|
+
reserved_params_dict = tmp_params
|
539
|
+
elif label == "Model_Specific":
|
540
|
+
model_spec_found = True
|
541
|
+
model_specific_dict = tmp_params
|
542
|
+
elif label == "description":
|
543
|
+
description = str(tmp_params)
|
544
|
+
else:
|
545
|
+
_err_str += f"WARNING: Unrecognized group with label, '{label}', found in parameter definition string!\n"
|
546
|
+
|
547
|
+
if not reserved_found:
|
548
|
+
_err_str += "ERROR: Reserved parameters section not found! It is required."
|
549
|
+
|
550
|
+
if not init_returns_impulse_found:
|
551
|
+
_err_str += "ERROR: Reserved parameter, 'Init_Returns_Impulse', not found! It is required."
|
552
|
+
|
553
|
+
if not getwave_exists_found:
|
554
|
+
_err_str += "ERROR: Reserved parameter, 'GetWave_Exists', not found! It is required."
|
555
|
+
|
556
|
+
if not model_spec_found:
|
557
|
+
_err_str += "WARNING: Model specific parameters section not found!"
|
558
|
+
|
559
|
+
return (ParseErrMsg(_err_str), root_name, description, reserved_params_dict, model_specific_dict)
|
560
|
+
|
561
|
+
|
562
|
+
# Legacy client code support:
|
563
|
+
def parse_ami_param_defs(file_contents: str) -> tuple[ParseErrMsg, dict[str, Any]]:
|
564
|
+
"The legacy version of ``parse_ami_file_contents()``."
|
565
|
+
err_msg, root_name, description, reserved_params_dict, model_specific_dict = parse_ami_file_contents(file_contents)
|
566
|
+
return (err_msg, {root_name: {"description": description,
|
567
|
+
"Reserved_Parameters": reserved_params_dict,
|
568
|
+
"Model_Specific": model_specific_dict}})
|
569
|
+
|
570
|
+
|
571
|
+
def make_gui(params: ModelSpecificDict) -> tuple[Group, list[TraitType]]:
|
572
|
+
"""
|
573
|
+
Builds top-level ``Group`` and list of ``Trait`` s from AMI parameter dictionary.
|
574
|
+
|
575
|
+
Args:
|
576
|
+
params: Dictionary of AMI parameters to be configured.
|
577
|
+
|
578
|
+
Returns:
|
579
|
+
A pair consisting of:
|
580
|
+
|
581
|
+
- the top-level ``Group`` for the ``View``, and
|
582
|
+
- a list of new ``Trait`` s created.
|
583
|
+
|
584
|
+
Notes:
|
585
|
+
1. The dictionary passed through ``params`` may have sub-dictionaries.
|
586
|
+
The window layout will reflect this nesting.
|
587
|
+
"""
|
588
|
+
|
589
|
+
gui_items: list[Item | Group] = []
|
590
|
+
new_traits: list[tuple[str, TraitType]] = []
|
591
|
+
pnames = list(params.keys())
|
592
|
+
pnames.sort()
|
593
|
+
for pname in pnames:
|
594
|
+
gui_item, new_trait = make_gui_items(pname, params[pname])
|
595
|
+
gui_items.extend(gui_item)
|
596
|
+
new_traits.extend(new_trait)
|
597
|
+
|
598
|
+
return (HGroup(*gui_items), new_traits)
|
599
|
+
|
600
|
+
|
601
|
+
def make_gui_items( # pylint: disable=too-many-locals,too-many-branches
|
602
|
+
pname: str,
|
603
|
+
param: AMIParameter | Parameters
|
604
|
+
) -> tuple[list[Item | Group], list[tuple[str, TraitType]]]:
|
605
|
+
"""
|
606
|
+
Builds list of GUI items and list of traits from AMI parameter or dictionary.
|
607
|
+
|
608
|
+
Args:
|
609
|
+
pname: Parameter or sub-group name.
|
610
|
+
param: AMI parameter or dictionary of AMI parameters to be configured.
|
611
|
+
|
612
|
+
Returns:
|
613
|
+
A pair consisting of:
|
614
|
+
|
615
|
+
- the list of GUI items for the ``View``, and
|
616
|
+
- the list of new ``Trait`` s created.
|
617
|
+
|
618
|
+
Notes:
|
619
|
+
1. A dictionary passed through ``param`` may have sub-dictionaries.
|
620
|
+
These will be converted into sub- ``Group`` s in the returned list of GUI items.
|
621
|
+
"""
|
622
|
+
|
623
|
+
if isinstance(param, AMIParameter): # pylint: disable=no-else-return
|
624
|
+
pusage = param.pusage
|
625
|
+
if pusage not in ("In", "InOut"):
|
626
|
+
return ([], [])
|
627
|
+
|
628
|
+
if param.ptype == "Boolean":
|
629
|
+
return ([Item(pname, tooltip=param.pdescription)], [(pname, Bool(param.pvalue))])
|
630
|
+
|
631
|
+
pformat = param.pformat
|
632
|
+
match pformat:
|
633
|
+
case "Value": # Value
|
634
|
+
the_trait = Trait(param.pvalue)
|
635
|
+
case "Range":
|
636
|
+
the_trait = Range(param.pmin, param.pmax, param.pvalue)
|
637
|
+
case "List":
|
638
|
+
list_tips = param.plist_tip
|
639
|
+
default = param.pdefault
|
640
|
+
if list_tips:
|
641
|
+
tmp_dict: dict[str, Any] = {}
|
642
|
+
tmp_dict.update(list(zip(list_tips, param.pvalue)))
|
643
|
+
val = list(tmp_dict.keys())[0]
|
644
|
+
if default:
|
645
|
+
for tip in tmp_dict.items():
|
646
|
+
if tip[1] == default:
|
647
|
+
val = tip[0]
|
648
|
+
break
|
649
|
+
the_trait = Trait(val, tmp_dict)
|
650
|
+
else:
|
651
|
+
val = default if default else param.pvalue[0]
|
652
|
+
the_trait = Enum([val] + param.pvalue)
|
653
|
+
case _:
|
654
|
+
raise ValueError(f"Unrecognized AMI parameter format: {pformat}!")
|
655
|
+
if the_trait.metadata:
|
656
|
+
the_trait.metadata.update({"transient": False}) # Required to support modal dialogs.
|
657
|
+
else:
|
658
|
+
the_trait.metadata = {"transient": False}
|
659
|
+
return ([Item(name=pname, label=pname.split("_")[-1], tooltip=param.pdescription)], [(pname, the_trait)])
|
660
|
+
|
661
|
+
else: # subparameter branch
|
662
|
+
gui_items: list[Item | Group] = []
|
663
|
+
new_traits: list[tuple[str, TraitType]] = []
|
664
|
+
subparam_names = list(param.keys())
|
665
|
+
subparam_names.sort()
|
666
|
+
group_desc = None
|
667
|
+
|
668
|
+
# Build GUI items for this branch.
|
669
|
+
for subparam_name in subparam_names:
|
670
|
+
if subparam_name == "description":
|
671
|
+
group_desc = param[subparam_name]
|
672
|
+
else:
|
673
|
+
tmp_items, tmp_traits = make_gui_items(pname + "_" + subparam_name, param[subparam_name])
|
674
|
+
gui_items.extend(tmp_items)
|
675
|
+
new_traits.extend(tmp_traits)
|
676
|
+
|
677
|
+
if group_desc:
|
678
|
+
gui_items = [Item(label=group_desc)] + gui_items
|
679
|
+
|
680
|
+
return ([VGroup(*gui_items, label=pname.split("_")[-1], show_border=True)], new_traits)
|