PyIBIS-AMI 9.2.0__tar.gz → 9.3.0__tar.gz
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-9.2.0/src/pyibis_ami.egg-info → pyibis_ami-9.3.0}/PKG-INFO +1 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/pyproject.toml +8 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0/src/pyibis_ami.egg-info}/PKG-INFO +1 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/config.py +4 -5
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/model.py +12 -12
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/parameter.py +68 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/parser.py +155 -21
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/reserved_parameter_names.py +1 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/file.py +29 -29
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/model.py +5 -5
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/parser.py +5 -4
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_test_config.py +3 -3
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_tests.py +13 -12
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_tests_helpers.py +2 -2
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ibis_file_tests.py +3 -3
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/test_defs.py +3 -2
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/test_models.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/util.py +1 -1
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/run_notebook.py +5 -7
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/plot.py +4 -4
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/reportlab.py +2 -2
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/LICENSE +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/MANIFEST.in +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/README.md +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/ami.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/conf.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/ibis.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/index.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/install.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/modules.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/tools.rst +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/all_taps.run +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/bandwidth.run +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/dummy.em +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/examples.md +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/freq_resp.em +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/impulse.em +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/impulse_response_8ma.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/mode.run +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/stratix4_ami_tx_runs/all_taps.run +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/stratix4_ami_tx_runs/test_results.xml +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/test_results/test_results.xsl +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/setup.cfg +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/SOURCES.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/dependency_links.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/entry_points.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/requires.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/top_level.txt +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/IBIS_AMI_Checker.ipynb +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/IBIS_AMI_Tester.ipynb +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/__init__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/__main__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/__init__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/generic.ami.em +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/generic.ibs.em +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/common.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/__init__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/__init__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/__init__.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/run_tests.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/test_results.xsl +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/ami.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/misc.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/tests/test_ami_test_config.py +0 -0
- {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/tests/test_run_tests.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyibis-ami
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.3.0
|
|
4
4
|
Summary: Facilitates working directly with IBIS-AMI DLLs from the Python command prompt.
|
|
5
5
|
Author-email: David Banas <capn.freako@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://pyibis-ami.readthedocs.io/en/latest/
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyibis-ami"
|
|
7
7
|
description = "Facilitates working directly with IBIS-AMI DLLs from the Python command prompt."
|
|
8
|
-
version = "9.
|
|
8
|
+
version = "9.3.0"
|
|
9
9
|
authors = [ {name = "David Banas", email = "capn.freako@gmail.com"}
|
|
10
10
|
]
|
|
11
11
|
readme = "README.md"
|
|
@@ -96,6 +96,13 @@ ignore = [
|
|
|
96
96
|
exclude = [
|
|
97
97
|
"*.ipynb"
|
|
98
98
|
]
|
|
99
|
+
lint.ignore = [
|
|
100
|
+
"UP045", # "Optional[X] -> "X | None"
|
|
101
|
+
"I001", # import formatting
|
|
102
|
+
"BLE001", # blind exceptions
|
|
103
|
+
"UP031", # format specifiers instead of '%'
|
|
104
|
+
"RUF022", # unsorted all
|
|
105
|
+
]
|
|
99
106
|
|
|
100
107
|
[[tool.mypy.overrides]]
|
|
101
108
|
module = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyibis-ami
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.3.0
|
|
4
4
|
Summary: Facilitates working directly with IBIS-AMI DLLs from the Python command prompt.
|
|
5
5
|
Author-email: David Banas <capn.freako@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://pyibis-ami.readthedocs.io/en/latest/
|
|
@@ -171,7 +171,7 @@ def mk_model(
|
|
|
171
171
|
"param_types": param_types,
|
|
172
172
|
"model_name": model_name,
|
|
173
173
|
"description": description,
|
|
174
|
-
"date": str(date.today()),
|
|
174
|
+
"date": str(date.today()), # noqa: DTZ011
|
|
175
175
|
},
|
|
176
176
|
)
|
|
177
177
|
try:
|
|
@@ -246,8 +246,8 @@ def mk_tests( # pylint: disable=too-many-locals
|
|
|
246
246
|
pname.mkdir(exist_ok=True)
|
|
247
247
|
pname = (pname / file_base_name).resolve()
|
|
248
248
|
pname.mkdir(exist_ok=True)
|
|
249
|
-
for fname in test_defs.
|
|
250
|
-
desc, ami_defs, sim_defs, ref_fstr =
|
|
249
|
+
for fname, value in test_defs.items():
|
|
250
|
+
desc, ami_defs, sim_defs, ref_fstr = value
|
|
251
251
|
ami_str, ami_dict = ami_defs
|
|
252
252
|
sim_str, sim_dict = sim_defs
|
|
253
253
|
with open((pname / fname).with_suffix(".run"), "w", encoding="utf-8") as f:
|
|
@@ -258,8 +258,7 @@ def mk_tests( # pylint: disable=too-many-locals
|
|
|
258
258
|
pdict.update(dict(sim_comb))
|
|
259
259
|
f.write(f"\n('{ami_str.format(pdict=pdict)}_{sim_str.format(pdict=pdict)}', \\\n")
|
|
260
260
|
f.write(f" ({{'root_name': '{file_base_name}', \\\n")
|
|
261
|
-
for k, v in ami_comb
|
|
262
|
-
f.write(f" '{k}': {v}, \\\n")
|
|
261
|
+
f.writelines(f" '{k}': {v}, \\\n" for k, v in ami_comb)
|
|
263
262
|
f.write(" }, \\\n")
|
|
264
263
|
if sim_comb:
|
|
265
264
|
head, *tail = sim_comb
|
|
@@ -355,11 +355,12 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
355
355
|
self._info_params = init_object.info_params # pylint: disable=attribute-defined-outside-init
|
|
356
356
|
|
|
357
357
|
# Check GetWave() consistency if possible.
|
|
358
|
-
if init_object.info_params and init_object.info_params["GetWave_Exists"] and
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
)
|
|
358
|
+
if (init_object.info_params and init_object.info_params["GetWave_Exists"] and
|
|
359
|
+
init_object.info_params["GetWave_Exists"].pvalue and
|
|
360
|
+
not self._amiGetWave): # noqa: E129
|
|
361
|
+
raise RuntimeError(
|
|
362
|
+
"Reserved parameter `GetWave_Exists` is True, but I can't bind to `AMI_GetWave()`!"
|
|
363
|
+
)
|
|
363
364
|
|
|
364
365
|
# Construct the AMI parameters string.
|
|
365
366
|
def sexpr(pname: str, pval: Any) -> str:
|
|
@@ -387,7 +388,7 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
387
388
|
self._root_name = root_name # pylint: disable=attribute-defined-outside-init
|
|
388
389
|
ami_params_in = f"({root_name} "
|
|
389
390
|
for item in list(init_object.ami_params.items()):
|
|
390
|
-
if
|
|
391
|
+
if item[0] != "root_name":
|
|
391
392
|
ami_params_in += sexpr(item[0], item[1])
|
|
392
393
|
ami_params_in += ")"
|
|
393
394
|
self._ami_params_in = ami_params_in.encode("utf-8") # pylint: disable=attribute-defined-outside-init
|
|
@@ -424,7 +425,7 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
424
425
|
print(f"&ami_params_out = {byref(self._ami_params_out)}")
|
|
425
426
|
print(f"&ami_mem_handle = {byref(self._ami_mem_handle)}") # type: ignore
|
|
426
427
|
print(f"&msg = {byref(self._msg)}")
|
|
427
|
-
raise
|
|
428
|
+
raise
|
|
428
429
|
|
|
429
430
|
# Initialize attributes used by getWave().
|
|
430
431
|
bit_time = init_object.bit_time
|
|
@@ -441,7 +442,7 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
441
442
|
)
|
|
442
443
|
self._getwave_step_response_out_params = None
|
|
443
444
|
|
|
444
|
-
def getWave(self, wave: Rvec, bits_per_call: int = 0) -> tuple[Rvec, Rvec, list[str]]:
|
|
445
|
+
def getWave(self, wave: Rvec, bits_per_call: int = 0) -> tuple[Rvec, Rvec, list[str]]:
|
|
445
446
|
"""
|
|
446
447
|
Performs time domain processing of input waveform, using the ``AMI_GetWave()`` function.
|
|
447
448
|
|
|
@@ -562,9 +563,8 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
562
563
|
ts = self.sample_interval
|
|
563
564
|
info_params = self._info_params
|
|
564
565
|
ignore_bits = 0
|
|
565
|
-
if info_params:
|
|
566
|
-
|
|
567
|
-
ignore_bits = info_params["Ignore_Bits"].pvalue
|
|
566
|
+
if info_params and "Ignore_Bits" in info_params:
|
|
567
|
+
ignore_bits = info_params["Ignore_Bits"].pvalue
|
|
568
568
|
|
|
569
569
|
# Capture/convert instance variables.
|
|
570
570
|
chnl_imp = np.array(self.channel_response[:self.row_size]) * ts # input (a.k.a. - "channel") impulse response (V/sample)
|
|
@@ -584,7 +584,7 @@ class AMIModel: # pylint: disable=too-many-instance-attributes
|
|
|
584
584
|
):
|
|
585
585
|
pass
|
|
586
586
|
else:
|
|
587
|
-
h_model = deconv_same(out_imp, chnl_imp)
|
|
587
|
+
h_model = deconv_same(out_imp, chnl_imp)
|
|
588
588
|
rslt[IMP_RESP_INIT] = np.roll(h_model, -len(h_model) // 2 + 3 * nspui)
|
|
589
589
|
|
|
590
590
|
h_init = np.roll(out_imp, pad_samps)
|
|
@@ -7,6 +7,10 @@ Original date: December 24, 2016
|
|
|
7
7
|
Copyright (c) 2019 David Banas; all rights reserved World wide.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from traits.api import Bool, Float, HasTraits, List, Str
|
|
13
|
+
|
|
10
14
|
#####
|
|
11
15
|
# AMI parameter
|
|
12
16
|
#####
|
|
@@ -179,7 +183,7 @@ class AMIParameter: # pylint: disable=too-many-instance-attributes,too-few-publ
|
|
|
179
183
|
# The idea is to allow this class to grow along with the IBIS
|
|
180
184
|
# standard, without having to change any of its boilerplate.
|
|
181
185
|
|
|
182
|
-
_param_def_tag_procs = {
|
|
186
|
+
_param_def_tag_procs: ClassVar = {
|
|
183
187
|
"Usage": _set_usage,
|
|
184
188
|
"Type": _set_type,
|
|
185
189
|
"Format": _set_format,
|
|
@@ -326,3 +330,66 @@ class AMIParameter: # pylint: disable=too-many-instance-attributes,too-few-publ
|
|
|
326
330
|
|
|
327
331
|
def __str__(self):
|
|
328
332
|
return f"{self.pvalue}"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
#####
|
|
336
|
+
# AMI parameter tuner
|
|
337
|
+
#####
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# pylint: disable=too-many-instance-attributes,too-few-public-methods
|
|
341
|
+
class AmiParamTuner(HasTraits):
|
|
342
|
+
"""Object used to populate the rows of a Tx/Rx AMI parameter tuning table.
|
|
343
|
+
|
|
344
|
+
Represents one *Model_Specific* 'In'/'InOut' AMI parameter, of 'Range'
|
|
345
|
+
format, offered to the user for inclusion in EQ optimization.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
name = Str("(noname)") # Display name (fully hierarchical, e.g. "tx_preset_coeffs_main").
|
|
349
|
+
branch_names = List(Str) # Hierarchical path for `fetch_param_val()`/`set_param_val()`.
|
|
350
|
+
enabled = Bool(False) # Will participate in EQ optimization when *True*.
|
|
351
|
+
min_val = Float(0.0) # Minimum allowed value during optimization.
|
|
352
|
+
max_val = Float(0.0) # Maximum allowed value during optimization.
|
|
353
|
+
step = Float(0.0) # Increment used during optimization.
|
|
354
|
+
value = Float(0.0) # Current value.
|
|
355
|
+
is_int = Bool(False) # Round swept values to the nearest integer when *True*.
|
|
356
|
+
|
|
357
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
358
|
+
def __init__(
|
|
359
|
+
self, name: str = "(noname)", branch_names: "list[str] | None" = None, enabled: bool = False,
|
|
360
|
+
min_val: float = 0.0, max_val: float = 0.0, step: float = 0.0, value: float = 0.0, is_int: bool = False
|
|
361
|
+
):
|
|
362
|
+
"""
|
|
363
|
+
Allows user to define properties, at instantiation.
|
|
364
|
+
|
|
365
|
+
Keyword Args:
|
|
366
|
+
name: Parameter name/label.
|
|
367
|
+
Default: "(noname)"
|
|
368
|
+
branch_names: Hierarchical path to the parameter, rooted at "Model_Specific".
|
|
369
|
+
Default: []
|
|
370
|
+
enabled: Will participate in EQ optimization when *True*.
|
|
371
|
+
Default: *False*
|
|
372
|
+
min_val: Minimum allowed value during optimization.
|
|
373
|
+
Default: 0.0
|
|
374
|
+
max_val: Maximum allowed value during optimization.
|
|
375
|
+
Default: 0.0
|
|
376
|
+
step: Increment used during optimization.
|
|
377
|
+
Default: 0.0
|
|
378
|
+
value: Current value.
|
|
379
|
+
Default: 0.0
|
|
380
|
+
is_int: Round swept values to the nearest integer when *True*.
|
|
381
|
+
Default: *False*
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
# Super-class initialization is ABSOLUTELY NECESSARY, in order
|
|
385
|
+
# to get all the Traits/UI machinery setup correctly.
|
|
386
|
+
super().__init__()
|
|
387
|
+
|
|
388
|
+
self.name = name
|
|
389
|
+
self.branch_names = branch_names or []
|
|
390
|
+
self.enabled = enabled
|
|
391
|
+
self.min_val = min_val
|
|
392
|
+
self.max_val = max_val
|
|
393
|
+
self.step = step
|
|
394
|
+
self.value = value
|
|
395
|
+
self.is_int = is_int
|
|
@@ -11,7 +11,8 @@ from __future__ import annotations
|
|
|
11
11
|
from ctypes import c_double
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
import re
|
|
14
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, NewType, Optional, TypeAlias
|
|
15
|
+
from collections.abc import Callable
|
|
15
16
|
|
|
16
17
|
import numpy as np
|
|
17
18
|
from numpy.typing import NDArray
|
|
@@ -21,7 +22,7 @@ from traitsui.api import Group, HGroup, Item, VGroup, View
|
|
|
21
22
|
from traitsui.menu import ModalButtons
|
|
22
23
|
|
|
23
24
|
from .model import AMIModelInitializer
|
|
24
|
-
from .parameter import AMIParamError, AMIParameter
|
|
25
|
+
from .parameter import AmiParamTuner, AMIParamError, AMIParameter
|
|
25
26
|
from .reserved_parameter_names import AmiReservedParameterName, RESERVED_PARAM_NAMES
|
|
26
27
|
|
|
27
28
|
# New types and aliases.
|
|
@@ -47,7 +48,7 @@ class AmiLeaf:
|
|
|
47
48
|
@dataclass(frozen=True)
|
|
48
49
|
class AmiBranch:
|
|
49
50
|
name: AmiName
|
|
50
|
-
children: list[
|
|
51
|
+
children: list[AmiNode]
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
AmiNode: TypeAlias = AmiLeaf | AmiBranch
|
|
@@ -60,7 +61,7 @@ AmiRootName = NewType("AmiRootName", str)
|
|
|
60
61
|
ReservedParamDict: TypeAlias = dict[AmiReservedParameterName, AMIParameter]
|
|
61
62
|
ModelSpecificDict: TypeAlias = dict[ParamName, "'AMIParameter' | 'ModelSpecificDict'"]
|
|
62
63
|
|
|
63
|
-
__all__ = [
|
|
64
|
+
__all__ = [ # ruff: ignore=RUF022
|
|
64
65
|
"ParamName", "ParamValue", "Parameters", "ParamValues",
|
|
65
66
|
"AmiName", "AmiAtom", "AmiNode", "AmiNodeParser", "AmiParser",
|
|
66
67
|
"ami_parse", "AMIParamConfigurator"]
|
|
@@ -186,32 +187,125 @@ class AMIParamConfigurator(HasTraits):
|
|
|
186
187
|
return _param.pvalue
|
|
187
188
|
return None
|
|
188
189
|
|
|
189
|
-
def set_param_val(
|
|
190
|
-
|
|
190
|
+
def set_param_val(
|
|
191
|
+
self: AMIParamConfigurator,
|
|
192
|
+
branch_names: list[str],
|
|
193
|
+
new_val: Any
|
|
194
|
+
) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Sets the value of the parameter found by traversing 'branch_names'
|
|
191
197
|
or raises an exception if not found.
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
Args:
|
|
200
|
+
branch_names: A sequence of node names, used to traverse the parameter tree.
|
|
201
|
+
new_val: The value to assign to the target parameter.
|
|
202
|
+
|
|
203
|
+
Notes:
|
|
204
|
+
1. ``branch_names`` should *not* begin with <root_name>.
|
|
205
|
+
2. Be careful! There is no checking done here!
|
|
195
206
|
"""
|
|
196
207
|
|
|
197
208
|
param_dict = self.ami_param_defs
|
|
209
|
+
tname_parts: list[str] = [] # Fully hierarchical trait name, minus the root key.
|
|
210
|
+
root_key: Optional[str] = None
|
|
198
211
|
while branch_names:
|
|
199
212
|
branch_name = branch_names.pop(0)
|
|
213
|
+
if root_key is None:
|
|
214
|
+
root_key = branch_name # "Reserved_Parameters" or "Model_Specific"; not part of the trait name.
|
|
215
|
+
else:
|
|
216
|
+
tname_parts.append(branch_name)
|
|
200
217
|
if branch_name in param_dict:
|
|
201
|
-
param_dict = param_dict[branch_name]
|
|
218
|
+
param_dict = param_dict[branch_name] # type: ignore
|
|
202
219
|
else:
|
|
203
220
|
raise ValueError(
|
|
204
221
|
f"Failed parameter tree search looking for: {branch_name}; available keys: {param_dict.keys()}"
|
|
205
222
|
)
|
|
206
223
|
if isinstance(param_dict, AMIParameter):
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
224
|
+
# Coerce to the parameter's declared type. Notably: a `Bool` trait rejects a
|
|
225
|
+
# float outright, and a vendor's AMI_Init() parser may choke on "1.0" where an
|
|
226
|
+
# Integer-typed value (e.g. a List-format mode selector) is expected.
|
|
227
|
+
if param_dict.ptype == "Boolean":
|
|
228
|
+
if not isinstance(new_val, bool):
|
|
229
|
+
if isinstance(new_val, str):
|
|
230
|
+
match new_val:
|
|
231
|
+
case "FALSE" | "False" | "false":
|
|
232
|
+
new_val = False
|
|
233
|
+
case _:
|
|
234
|
+
new_val = bool(new_val)
|
|
235
|
+
else:
|
|
236
|
+
new_val = bool(new_val)
|
|
237
|
+
elif param_dict.ptype in ("Integer", "Tap"):
|
|
238
|
+
new_val = int(new_val)
|
|
239
|
+
# `pvalue`, for 'List' format, holds the list of *legal* values, not the
|
|
240
|
+
# current one (that lives on the Trait) -- leave it alone, to avoid corrupting it.
|
|
241
|
+
if param_dict.pformat != "List":
|
|
242
|
+
param_dict.pvalue = new_val
|
|
243
|
+
tname = "_".join(tname_parts)
|
|
244
|
+
if tname:
|
|
245
|
+
try:
|
|
246
|
+
self.trait_set(**{f"{tname}_": new_val})
|
|
247
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
248
|
+
self.trait_set(**{tname: new_val})
|
|
212
249
|
else:
|
|
213
250
|
raise TypeError(f"{param_dict} is not of type: AMIParameter!")
|
|
214
251
|
|
|
252
|
+
@property
|
|
253
|
+
def tunable_params(self) -> list[tuple[list[str], AMIParameter]]:
|
|
254
|
+
"""Every *Model Specific* parameter of type 'In' or 'InOut' that is a
|
|
255
|
+
candidate for automated sweeping/optimization, paired with its fully
|
|
256
|
+
hierarchical branch name path. This includes:
|
|
257
|
+
|
|
258
|
+
- numeric 'Range'-format parameters (real min/max bounds),
|
|
259
|
+
- 'Boolean' parameters (True/False), and
|
|
260
|
+
- 'List'-format 'Integer' parameters whose legal values form a
|
|
261
|
+
contiguous range (e.g. a mode selector like `(List 0 1)`) --
|
|
262
|
+
this is deliberately conservative: a non-contiguous legal set
|
|
263
|
+
(e.g. `(List 6 12 18)`) can't be swept with a uniform step
|
|
264
|
+
without risking illegal intermediate values, so it's excluded.
|
|
265
|
+
|
|
266
|
+
The returned branch name paths are ready to pass directly into
|
|
267
|
+
``fetch_param_val()``/``set_param_val()`` (i.e. - they are rooted at
|
|
268
|
+
``"Model_Specific"``).
|
|
269
|
+
"""
|
|
270
|
+
return _walk_tunable_params(self._model_specific_dict, ["Model_Specific"])
|
|
271
|
+
|
|
272
|
+
def mk_tap_tuners(self) -> list[AmiParamTuner]:
|
|
273
|
+
"""Build a fresh list of `AmiParamTuner`s from this AMI model's tunable
|
|
274
|
+
Model_Specific parameters (see `tunable_params` for exactly which ones
|
|
275
|
+
qualify: Range-format, Boolean, and contiguous Integer/List "mode
|
|
276
|
+
selector" parameters)."""
|
|
277
|
+
tuners = []
|
|
278
|
+
for branch_names, param in self.tunable_params:
|
|
279
|
+
tname = "_".join(branch_names[1:]) # Drop the "Model_Specific" root.
|
|
280
|
+
if param.pformat == "Range":
|
|
281
|
+
pmin, pmax = float(param.pmin), float(param.pmax)
|
|
282
|
+
step = (pmax - pmin) / 10 or 1.0
|
|
283
|
+
is_int = param.ptype == "Integer"
|
|
284
|
+
value = float(param.pvalue)
|
|
285
|
+
elif param.pformat == "Value" and param.ptype == "Boolean":
|
|
286
|
+
pmin, pmax, step, is_int = 0.0, 1.0, 1.0, True
|
|
287
|
+
value = float(param.pvalue)
|
|
288
|
+
else: # List-format Integer "mode selector" (contiguous legal values).
|
|
289
|
+
vals = [float(v) for v in param.pvalue]
|
|
290
|
+
pmin, pmax, step, is_int = min(vals), max(vals), 1.0, True
|
|
291
|
+
# `pvalue`, for 'List' format, is the list of *legal* values, not the
|
|
292
|
+
# current one -- that lives on the Trait `make_gui_items()` registered.
|
|
293
|
+
try:
|
|
294
|
+
value = float(self.trait_get(tname + "_")[tname + "_"])
|
|
295
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
296
|
+
value = float(self.trait_get(tname)[tname])
|
|
297
|
+
tuners.append(AmiParamTuner(
|
|
298
|
+
name=tname,
|
|
299
|
+
branch_names=branch_names,
|
|
300
|
+
enabled=False,
|
|
301
|
+
min_val=pmin,
|
|
302
|
+
max_val=pmax,
|
|
303
|
+
step=step,
|
|
304
|
+
value=value,
|
|
305
|
+
is_int=is_int,
|
|
306
|
+
))
|
|
307
|
+
return tuners
|
|
308
|
+
|
|
215
309
|
@property
|
|
216
310
|
def ami_parsing_errors(self) -> list[str]:
|
|
217
311
|
"""Any errors or warnings encountered, while parsing the AMI parameter
|
|
@@ -286,7 +380,7 @@ class AMIParamConfigurator(HasTraits):
|
|
|
286
380
|
elif isinstance(param, dict): # We received a dictionary of subparameters, in 'param'.
|
|
287
381
|
subs: ParamValues = {}
|
|
288
382
|
for sname in param:
|
|
289
|
-
subs.update(self.input_ami_param(param, sname, prefix=
|
|
383
|
+
subs.update(self.input_ami_param(param, sname, prefix=tname + "_")) # type: ignore
|
|
290
384
|
res[pname] = subs
|
|
291
385
|
return res
|
|
292
386
|
|
|
@@ -332,6 +426,47 @@ class AMIParamConfigurator(HasTraits):
|
|
|
332
426
|
return initializer
|
|
333
427
|
|
|
334
428
|
|
|
429
|
+
def _is_sweepable(param: AMIParameter) -> bool:
|
|
430
|
+
"See `AMIParamConfigurator.tunable_params` for the exact criteria."
|
|
431
|
+
if param.pusage not in ("In", "InOut"):
|
|
432
|
+
return False
|
|
433
|
+
if param.pformat == "Range":
|
|
434
|
+
return True
|
|
435
|
+
if param.pformat == "Value" and param.ptype == "Boolean":
|
|
436
|
+
return True
|
|
437
|
+
if param.pformat == "List" and param.ptype == "Integer":
|
|
438
|
+
vals = sorted(int(v) for v in param.pvalue)
|
|
439
|
+
return vals == list(range(vals[0], vals[-1] + 1)) # Contiguous?
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _walk_tunable_params(
|
|
444
|
+
params: Parameters, prefix: list[str]
|
|
445
|
+
) -> list[tuple[list[str], AMIParameter]]:
|
|
446
|
+
"""Recursively collect every sweepable (see `_is_sweepable`) leaf of a
|
|
447
|
+
*Model Specific* parameter (sub)tree.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
params: The (sub)dictionary of AMI parameters to walk.
|
|
451
|
+
prefix: The branch name path leading to ``params``.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
A list of (branch name path, parameter) pairs, one per sweepable leaf.
|
|
455
|
+
"""
|
|
456
|
+
|
|
457
|
+
results: list[tuple[list[str], AMIParameter]] = []
|
|
458
|
+
for pname, param in params.items():
|
|
459
|
+
if pname == "description": # A branch's optional descriptive text, not a subparameter.
|
|
460
|
+
continue
|
|
461
|
+
path = prefix + [pname]
|
|
462
|
+
if isinstance(param, AMIParameter):
|
|
463
|
+
if _is_sweepable(param):
|
|
464
|
+
results.append((path, param))
|
|
465
|
+
else: # Subparameter branch.
|
|
466
|
+
results.extend(_walk_tunable_params(param, path))
|
|
467
|
+
return results
|
|
468
|
+
|
|
469
|
+
|
|
335
470
|
#####
|
|
336
471
|
# AMI file parser.
|
|
337
472
|
#####
|
|
@@ -558,7 +693,7 @@ def parse_ami_file_contents( # pylint: disable=too-many-locals,too-many-branche
|
|
|
558
693
|
init_returns_impulse_found = False
|
|
559
694
|
getwave_exists_found = False
|
|
560
695
|
model_spec_found = False
|
|
561
|
-
root_name, params =
|
|
696
|
+
root_name, params = next(iter(param_dict.items()))
|
|
562
697
|
description = ""
|
|
563
698
|
reserved_params_dict: dict[AmiReservedParameterName, AMIParameter] = {}
|
|
564
699
|
model_specific_dict: ModelSpecificDict = {}
|
|
@@ -606,8 +741,7 @@ def parse_ami_file_contents( # pylint: disable=too-many-locals,too-many-branche
|
|
|
606
741
|
# Legacy client code support:
|
|
607
742
|
def parse_ami_param_defs(file_contents: str) -> tuple[ParseErrMsg, dict[str, Any]]:
|
|
608
743
|
"The legacy version of ``parse_ami_file_contents()``."
|
|
609
|
-
|
|
610
|
-
errors, warnings, root_name, description, reserved_params_dict, model_specific_dict = parse_ami_file_contents(file_contents)
|
|
744
|
+
errors, _warnings, root_name, description, reserved_params_dict, model_specific_dict = parse_ami_file_contents(file_contents)
|
|
611
745
|
if errors:
|
|
612
746
|
err_msg = errors[0]
|
|
613
747
|
else:
|
|
@@ -650,8 +784,8 @@ def make_gui(params: ModelSpecificDict) -> tuple[Group, list[tuple[str, TraitTyp
|
|
|
650
784
|
# but no pre-existing enclosing group, in a ``VGroup``.
|
|
651
785
|
# - Generate grouped parameter names and list of unique group names.
|
|
652
786
|
pnames = list(params.keys())
|
|
653
|
-
pnames_split =
|
|
654
|
-
group_names =
|
|
787
|
+
pnames_split = [n.split('_') for n in pnames]
|
|
788
|
+
group_names = [xs[0] for xs in pnames_split]
|
|
655
789
|
grouped_pnames = list(zip(pnames, group_names))
|
|
656
790
|
# The following doesn't preserve the parameter ordering of the AMI file!
|
|
657
791
|
# for group_name in set(group_names): # Iterate over unique group names.
|
|
@@ -729,7 +863,7 @@ def make_gui_items( # pylint: disable=too-many-locals,too-many-branches
|
|
|
729
863
|
if list_tips:
|
|
730
864
|
tmp_dict: dict[str, Any] = {}
|
|
731
865
|
tmp_dict.update(list(zip(list_tips, param.pvalue)))
|
|
732
|
-
val =
|
|
866
|
+
val = next(iter(tmp_dict.keys()))
|
|
733
867
|
if default:
|
|
734
868
|
for tip in tmp_dict.items():
|
|
735
869
|
if tip[1] == default:
|
|
@@ -15,7 +15,7 @@ Copyright (c) 2019 by David Banas; All rights reserved World wide.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import platform
|
|
18
|
-
from datetime import datetime
|
|
18
|
+
from datetime import datetime, timedelta, timezone
|
|
19
19
|
from typing import Optional
|
|
20
20
|
|
|
21
21
|
from traits.api import (
|
|
@@ -70,16 +70,6 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
70
70
|
via the ``model_dict`` property.
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
|
-
_file_name: str = ""
|
|
74
|
-
_ibis_ver: float = 0.0
|
|
75
|
-
_file_rev: str = ""
|
|
76
|
-
_date: str = ""
|
|
77
|
-
_model_dict: dict[str, Any] = {}
|
|
78
|
-
_models: dict[str, Model] = {}
|
|
79
|
-
_model_selectors: dict[str, list[str]] = {}
|
|
80
|
-
_components: dict[str, Component] = {}
|
|
81
|
-
_log: str = ""
|
|
82
|
-
|
|
83
73
|
pin_ = Property(Any, depends_on=["pin"])
|
|
84
74
|
pin_rlcs = Property(Dict, depends_on=["pin"])
|
|
85
75
|
model = Property(Any, depends_on=["mod"])
|
|
@@ -118,7 +108,7 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
118
108
|
"""
|
|
119
109
|
|
|
120
110
|
if mname in self._model_selectors:
|
|
121
|
-
return
|
|
111
|
+
return [pr[0] for pr in self._model_selectors[mname]]
|
|
122
112
|
else:
|
|
123
113
|
if mname in self._models:
|
|
124
114
|
return [mname]
|
|
@@ -158,7 +148,12 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
158
148
|
|
|
159
149
|
return list(filter(pin_ok, list(pins)))
|
|
160
150
|
|
|
161
|
-
def __init__(
|
|
151
|
+
def __init__(
|
|
152
|
+
self: "IBISModel",
|
|
153
|
+
ibis_file_name: str,
|
|
154
|
+
debug: bool = False,
|
|
155
|
+
gui: bool = True
|
|
156
|
+
) -> None:
|
|
162
157
|
"""
|
|
163
158
|
Args:
|
|
164
159
|
ibis_file_name (str): The name of the IBIS file.
|
|
@@ -176,6 +171,7 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
176
171
|
|
|
177
172
|
self.debug = debug
|
|
178
173
|
self.GUI = gui
|
|
174
|
+
self._log: str = ""
|
|
179
175
|
if debug:
|
|
180
176
|
self.log("pyibisami.ibis_file.IBISModel initializing in debug mode...")
|
|
181
177
|
else:
|
|
@@ -185,10 +181,10 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
185
181
|
with open(ibis_file_name, "r", encoding="utf-8") as file:
|
|
186
182
|
ibis_file_contents_str = file.read()
|
|
187
183
|
err_str, model_dict = parse_ibis_file(ibis_file_contents_str, debug=debug)
|
|
188
|
-
self._file_name = model_dict.get("file_name", "(n/a)")
|
|
189
|
-
self._ibis_ver = model_dict.get("ibis_ver", "(n/a)")
|
|
190
|
-
self._file_rev = model_dict.get("file_rev", "(n/a)")
|
|
191
|
-
self._date = model_dict.get("date", "(n/a)")
|
|
184
|
+
self._file_name: str = model_dict.get("file_name", "(n/a)")
|
|
185
|
+
self._ibis_ver: float = model_dict.get("ibis_ver", "(n/a)")
|
|
186
|
+
self._file_rev: str = model_dict.get("file_rev", "(n/a)")
|
|
187
|
+
self._date: str = model_dict.get("date", "(n/a)")
|
|
192
188
|
self.log("IBIS parsing errors/warnings:\n" + err_str)
|
|
193
189
|
if "components" not in model_dict or not model_dict["components"]:
|
|
194
190
|
if debug:
|
|
@@ -199,22 +195,25 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
199
195
|
if debug:
|
|
200
196
|
print(f":\n{model_dict}", flush=True)
|
|
201
197
|
raise ValueError("This IBIS model has no models!")
|
|
202
|
-
|
|
203
|
-
self.
|
|
204
|
-
self.
|
|
198
|
+
|
|
199
|
+
self._model_dict: dict[str, Any] = model_dict
|
|
200
|
+
self._models: dict[str, Model] = model_dict["models"]
|
|
201
|
+
self._components: dict[str, Component] = components
|
|
202
|
+
self._model_selectors: dict[str, list[str]] = {}
|
|
205
203
|
if "model_selectors" in model_dict:
|
|
206
|
-
self._model_selectors
|
|
204
|
+
self._model_selectors.update(model_dict["model_selectors"])
|
|
207
205
|
|
|
208
206
|
# Add Traits for various attributes found in the IBIS file.
|
|
209
|
-
|
|
210
|
-
self.
|
|
207
|
+
# Doesn't need a custom mapper because the thing above it (file) can't change:
|
|
208
|
+
self.add_trait("comp", Trait(next(iter(components)), components))
|
|
209
|
+
self.pins = self.get_pins() # type: ignore
|
|
211
210
|
try:
|
|
212
|
-
self.add_trait("pin", Enum(self.pins[0], values="pins"))
|
|
211
|
+
self.add_trait("pin", Enum(self.pins[0], values="pins")) # type: ignore
|
|
213
212
|
except Exception as err:
|
|
214
213
|
raise ValueError(f"self.pins: {self.pins}") from err
|
|
215
214
|
(mname, _) = self.pin_
|
|
216
|
-
self.models = self.get_models(mname)
|
|
217
|
-
self.add_trait("mod", Enum(self.models[0], values="models"))
|
|
215
|
+
self.models = self.get_models(mname) # type: ignore
|
|
216
|
+
self.add_trait("mod", Enum(self.models[0], values="models")) # type: ignore
|
|
218
217
|
self.add_trait("ibis_ver", Float(model_dict["ibis_ver"]))
|
|
219
218
|
self.add_trait("file_name", String(model_dict["file_name"]))
|
|
220
219
|
self.add_trait("file_rev", String(model_dict["file_rev"]))
|
|
@@ -227,8 +226,9 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
227
226
|
self._os_type = platform.system() # These 2 are used, to choose
|
|
228
227
|
self._os_bits = platform.architecture()[0] # the correct AMI executable.
|
|
229
228
|
|
|
230
|
-
self._comp_changed(
|
|
231
|
-
|
|
229
|
+
self._comp_changed(next(iter(components))) # Wasn't being called automatically.
|
|
230
|
+
# Wasn't being called automatically:
|
|
231
|
+
self._pin_changed(self.pins[0]) # type: ignore
|
|
232
232
|
|
|
233
233
|
self.log("Done.")
|
|
234
234
|
|
|
@@ -266,7 +266,7 @@ class IBISModel(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
266
266
|
"""Log a message to the console and, optionally, to terminal and/or
|
|
267
267
|
pop-up dialog."""
|
|
268
268
|
_msg = msg.strip()
|
|
269
|
-
txt = f"\n[{datetime.now()}]: IBISModel: {_msg}\n"
|
|
269
|
+
txt = f"\n[{datetime.now(tz=timezone(timedelta(hours=0)))}]: IBISModel: {_msg}\n"
|
|
270
270
|
self._log += txt
|
|
271
271
|
if self.debug:
|
|
272
272
|
print(txt, flush=True)
|
|
@@ -43,7 +43,7 @@ class Component(HasTraits):
|
|
|
43
43
|
# Set up the GUI.
|
|
44
44
|
self.add_trait("manufacturer", String(self._mfr))
|
|
45
45
|
self.add_trait("package", String(self._pkg))
|
|
46
|
-
self.add_trait("_pin", Trait(
|
|
46
|
+
self.add_trait("_pin", Trait(next(iter(self._pins)), self._pins))
|
|
47
47
|
self._content = [
|
|
48
48
|
Group(
|
|
49
49
|
Item("manufacturer", label="Manufacturer", style="readonly"),
|
|
@@ -109,7 +109,7 @@ class Model(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
109
109
|
|
|
110
110
|
# Fetch available keyword/parameter definitions.
|
|
111
111
|
def maybe(name):
|
|
112
|
-
return subDict
|
|
112
|
+
return subDict.get(name, None)
|
|
113
113
|
|
|
114
114
|
self._mtype = maybe("model_type")
|
|
115
115
|
self._ccomp = maybe("c_comp")
|
|
@@ -164,7 +164,7 @@ class Model(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
164
164
|
pu_ityps = -np.array(pu_ityps) # Correct for current sense, for nicer plot.
|
|
165
165
|
pu_imins = -np.array(pu_imins)
|
|
166
166
|
pu_imaxs = -np.array(pu_imaxs)
|
|
167
|
-
self._zout = (
|
|
167
|
+
self._zout = (next(iter(pd_zs)) + next(iter(pu_zs))) / 2
|
|
168
168
|
plotdata.set_data("pd_vs", pd_vs)
|
|
169
169
|
plotdata.set_data("pd_ityps", pd_ityps)
|
|
170
170
|
plotdata.set_data("pd_imins", pd_imins)
|
|
@@ -204,7 +204,7 @@ class Model(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
204
204
|
|
|
205
205
|
if "gnd_clamp" in subDict:
|
|
206
206
|
gc_vs, gc_ityps, gc_imins, gc_imaxs, gc_zs = proc_iv(subDict["gnd_clamp"])
|
|
207
|
-
gc_z =
|
|
207
|
+
gc_z = next(iter(gc_zs)) # Use typical value for Zin calc.
|
|
208
208
|
plotdata.set_data("gc_vs", gc_vs)
|
|
209
209
|
plotdata.set_data("gc_ityps", gc_ityps)
|
|
210
210
|
plotdata.set_data("gc_imins", gc_imins)
|
|
@@ -214,7 +214,7 @@ class Model(HasTraits): # pylint: disable=too-many-instance-attributes
|
|
|
214
214
|
|
|
215
215
|
if "power_clamp" in subDict:
|
|
216
216
|
pc_vs, pc_ityps, pc_imins, pc_imaxs, pc_zs = proc_iv(subDict["power_clamp"])
|
|
217
|
-
pc_z =
|
|
217
|
+
pc_z = next(iter(pc_zs))
|
|
218
218
|
pc_vs = self._vrange[0] - np.array(pc_vs) # Correct for Vdd-relative pull-up voltages.
|
|
219
219
|
pc_ityps = -np.array(pc_ityps) # Correct for current sense, for nicer plot.
|
|
220
220
|
pc_imins = -np.array(pc_imins)
|
|
@@ -13,7 +13,8 @@ Copyright (c) 2019 by David Banas; All rights reserved World wide.
|
|
|
13
13
|
import re
|
|
14
14
|
|
|
15
15
|
from functools import reduce
|
|
16
|
-
from typing import Any,
|
|
16
|
+
from typing import Any, Optional, TypeAlias, TypeVar
|
|
17
|
+
from collections.abc import Generator, Iterable
|
|
17
18
|
|
|
18
19
|
from parsec import (
|
|
19
20
|
ParseError,
|
|
@@ -419,7 +420,7 @@ def model():
|
|
|
419
420
|
try:
|
|
420
421
|
theModel = Model(dict(res))
|
|
421
422
|
except LookupError as le:
|
|
422
|
-
return fail_with(f"[Model] {nm}: {
|
|
423
|
+
return fail_with(f"[Model] {nm}: {le!s}")
|
|
423
424
|
return {nm: theModel}
|
|
424
425
|
|
|
425
426
|
|
|
@@ -501,9 +502,9 @@ def comp():
|
|
|
501
502
|
try:
|
|
502
503
|
Component(dict(res))
|
|
503
504
|
except LookupError as le:
|
|
504
|
-
return fail_with(f"[Component] {nm}: {
|
|
505
|
+
return fail_with(f"[Component] {nm}: {le!s}")
|
|
505
506
|
except Exception as err: # pylint: disable=broad-exception-caught
|
|
506
|
-
return fail_with(f"[Component] {nm}: {
|
|
507
|
+
return fail_with(f"[Component] {nm}: {err!s}")
|
|
507
508
|
return {nm: Component(dict(res))}
|
|
508
509
|
|
|
509
510
|
|
|
@@ -145,11 +145,11 @@ def _compare_params_out(
|
|
|
145
145
|
actual_norm = _normalize_params_str(ami_model.ami_params_out)
|
|
146
146
|
golden_norm = _normalize_params_str(golden_str)
|
|
147
147
|
matched = (actual_norm == golden_norm)
|
|
148
|
-
msgs = [] if matched else [
|
|
149
|
-
|
|
148
|
+
msgs = [] if matched else [(
|
|
149
|
+
"params_out mismatch.\n"
|
|
150
150
|
f" Got: {actual_norm}\n"
|
|
151
151
|
f" Expected: {golden_norm}"
|
|
152
|
-
]
|
|
152
|
+
)]
|
|
153
153
|
return True, matched, msgs
|
|
154
154
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
155
155
|
return True, False, [f"params_out comparison failed: {exc}"]
|
|
@@ -11,9 +11,10 @@ Copyright (c) 2026 David Banas; All rights reserved World wide.
|
|
|
11
11
|
import platform
|
|
12
12
|
|
|
13
13
|
from abc import ABC
|
|
14
|
+
from collections.abc import Sequence
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from tempfile import NamedTemporaryFile
|
|
16
|
-
from typing import
|
|
17
|
+
from typing import ClassVar, Optional
|
|
17
18
|
|
|
18
19
|
import em
|
|
19
20
|
|
|
@@ -58,7 +59,7 @@ class AmiTester(ABC):
|
|
|
58
59
|
def pcfg(self):
|
|
59
60
|
return self._pcfg
|
|
60
61
|
|
|
61
|
-
_test_sweepers: list[TestSweeper]
|
|
62
|
+
_test_sweepers: list[TestSweeper]
|
|
62
63
|
|
|
63
64
|
@property
|
|
64
65
|
def test_sweepers(self):
|
|
@@ -96,7 +97,7 @@ class AmiTester(ABC):
|
|
|
96
97
|
def msg(self):
|
|
97
98
|
return self._msg
|
|
98
99
|
|
|
99
|
-
_preamble: list[Flowable]
|
|
100
|
+
_preamble: list[Flowable] # Any desired introductory text for report body.
|
|
100
101
|
|
|
101
102
|
@property
|
|
102
103
|
def preamble(self):
|
|
@@ -106,9 +107,8 @@ class AmiTester(ABC):
|
|
|
106
107
|
def preamble(self, value):
|
|
107
108
|
if not isinstance(value, list):
|
|
108
109
|
raise TypeError(f"New value for `preamble` needs type `list[Flowable]` not {type(value)}.")
|
|
109
|
-
if len(value) > 0:
|
|
110
|
-
|
|
111
|
-
raise TypeError(f"New value for `preamble` needs type `list[Flowable]` not `list[{type(value[0])}]`.")
|
|
110
|
+
if len(value) > 0 and not isinstance(value[0], Flowable):
|
|
111
|
+
raise TypeError(f"New value for `preamble` needs type `list[Flowable]` not `list[{type(value[0])}]`.")
|
|
112
112
|
self._preamble = value
|
|
113
113
|
|
|
114
114
|
@property
|
|
@@ -173,7 +173,8 @@ class AmiTester(ABC):
|
|
|
173
173
|
self._init_ok = True
|
|
174
174
|
self._getwave_ok = ami_model.has_getwave
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
# Set misc. defaults.
|
|
177
|
+
self._preamble = []
|
|
177
178
|
|
|
178
179
|
# Execution
|
|
179
180
|
def __call__(self):
|
|
@@ -218,7 +219,7 @@ class AmiTester(ABC):
|
|
|
218
219
|
class AmiTestLinearityChecker(AmiTester):
|
|
219
220
|
"Check ``AMI_Init()`` for linearity."
|
|
220
221
|
|
|
221
|
-
preamble = [
|
|
222
|
+
preamble: ClassVar = [
|
|
222
223
|
Paragraph(f"{fixed('AMI_Init()')} Linearity Check", H2),
|
|
223
224
|
Paragraph(f"Here, we check that the {fixed('AMI_Init()')} function is linear."),
|
|
224
225
|
spacer,
|
|
@@ -297,7 +298,7 @@ class AmiTestInitVsGetwave(AmiTester):
|
|
|
297
298
|
|
|
298
299
|
helper = AmiTestHelperInitVsGetwave(DEBUG)
|
|
299
300
|
|
|
300
|
-
preamble = [
|
|
301
|
+
preamble: ClassVar = [
|
|
301
302
|
Paragraph("Init() vs. GetWave()", H2),
|
|
302
303
|
Paragraph(
|
|
303
304
|
"Here, we check to see that the fundamental responses of the model are the same \
|
|
@@ -327,7 +328,7 @@ class AmiTestSamplesPerBit(AmiTester):
|
|
|
327
328
|
|
|
328
329
|
helper = AmiTestHelperSamplesPerBit()
|
|
329
330
|
|
|
330
|
-
preamble = [
|
|
331
|
+
preamble: ClassVar = [
|
|
331
332
|
Paragraph("Samples per Bit", H2),
|
|
332
333
|
Paragraph("Here, we test the model's sensitivity to the oversampling factor, \
|
|
333
334
|
i.e., number of samples per bit (or, symbol).", P),
|
|
@@ -420,7 +421,7 @@ def test_ami_model(
|
|
|
420
421
|
|
|
421
422
|
# Attempt to create the AMI model and its configurator.
|
|
422
423
|
ibis_file_dir = ibis_file.parent
|
|
423
|
-
dll_file, ami_file =
|
|
424
|
+
dll_file, ami_file = [ibis_file_dir / Path(f) for f in ami_files]
|
|
424
425
|
try:
|
|
425
426
|
ami_model = AMIModel(str(dll_file))
|
|
426
427
|
except Exception as err:
|
|
@@ -435,7 +436,7 @@ def test_ami_model(
|
|
|
435
436
|
has_getwave = pcfg.fetch_param_val(["Reserved_Parameters", "GetWave_Exists"]) or False
|
|
436
437
|
ignore_bits = pcfg.fetch_param_val(["Reserved_Parameters", "Ignore_Bits"]) or 0
|
|
437
438
|
returns_impulse = pcfg.fetch_param_val(["Reserved_Parameters", "Init_Returns_Impulse"]) or False
|
|
438
|
-
has_ts4 =
|
|
439
|
+
has_ts4 = bool(pcfg.fetch_param_val(["Reserved_Parameters", "Ts4file"]))
|
|
439
440
|
except Exception as err:
|
|
440
441
|
flowables.append(Paragraph(str(err), P))
|
|
441
442
|
flowables.append(Paragraph(f"Error loading AMI parameter file: {ami_file}!", P))
|
|
@@ -96,7 +96,7 @@ class AmiTestHelperInitVsGetwave(AmiTestHelper):
|
|
|
96
96
|
PLOT_LINESTYLE: "dashed"}))]
|
|
97
97
|
|
|
98
98
|
fig = plt.figure(figsize=(fig_x, fig_y))
|
|
99
|
-
top_fig, bottom_fig = fig.subfigures(2, 1)
|
|
99
|
+
top_fig, bottom_fig = fig.subfigures(2, 1) # type: ignore
|
|
100
100
|
top_fig.suptitle("Model Responses (Post-Adaptation)")
|
|
101
101
|
top_fig.subplots_adjust(left=.1, right=.9, wspace=.3)
|
|
102
102
|
bottom_fig.subplots_adjust(top=.7, bottom=.1)
|
|
@@ -120,7 +120,7 @@ class AmiTestHelperSamplesPerBit(AmiTestHelper):
|
|
|
120
120
|
model_responses = do_samples_per_bit(model, initializer, nbits)
|
|
121
121
|
|
|
122
122
|
fig = plt.figure(figsize=(fig_x, fig_y))
|
|
123
|
-
top_fig, bottom_fig = fig.subfigures(2, 1)
|
|
123
|
+
top_fig, bottom_fig = fig.subfigures(2, 1) # type: ignore
|
|
124
124
|
top_fig.suptitle("Model Responses (Post-Adaptation)")
|
|
125
125
|
top_fig.subplots_adjust(left=.1, right=.9, wspace=.3)
|
|
126
126
|
bottom_fig.subplots_adjust(top=.7, bottom=.1)
|
|
@@ -74,7 +74,7 @@ def golden_parser_results(ibis_file: Path) -> list[Flowable]:
|
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
76
|
flowables: list[Flowable] = [Paragraph("IBIS Golden Parser Results", H1)]
|
|
77
|
-
result = subprocess.run([IBIS_CHK_EXEC, str(ibis_file)], capture_output=True, text=True)
|
|
77
|
+
result = subprocess.run([IBIS_CHK_EXEC, str(ibis_file)], capture_output=True, text=True, check=False)
|
|
78
78
|
flowables.append(Paragraph(preformatted(result.stdout), styles['Code']))
|
|
79
79
|
flowables.append(Paragraph(preformatted(result.stderr), styles['Code']))
|
|
80
80
|
flowables.append(page_break)
|
|
@@ -136,7 +136,7 @@ def test_ami_models(
|
|
|
136
136
|
do_model(model_name)
|
|
137
137
|
return flowables
|
|
138
138
|
|
|
139
|
-
for
|
|
140
|
-
do_model(
|
|
139
|
+
for _model_name in ami_model_names:
|
|
140
|
+
do_model(_model_name)
|
|
141
141
|
|
|
142
142
|
return flowables
|
|
@@ -11,7 +11,8 @@ Copyright (C) 2026 David Banas; all rights reserved World wide.
|
|
|
11
11
|
from abc import abstractmethod
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, NewType, Optional
|
|
15
|
+
from collections.abc import Generator
|
|
15
16
|
|
|
16
17
|
import numpy as np
|
|
17
18
|
from scipy.interpolate import interp1d
|
|
@@ -101,7 +102,7 @@ def mk_default_test_sweep_file(
|
|
|
101
102
|
|
|
102
103
|
# Import the `*.ibs` file.
|
|
103
104
|
try:
|
|
104
|
-
ibis = IBISModel(ibis_file, debug=debug, gui=False)
|
|
105
|
+
ibis = IBISModel(str(ibis_file.resolve()), debug=debug, gui=False)
|
|
105
106
|
dName = ibis_file.parent
|
|
106
107
|
assert ibis.ami_file, RuntimeError(
|
|
107
108
|
"Missing AMI file definition in IBIS file!"
|
|
File without changes
|
|
@@ -41,7 +41,7 @@ def mk_dummy_run_file(ibis_file: Path, is_tx: bool, debug: bool) -> Path:
|
|
|
41
41
|
|
|
42
42
|
# Import the `*.ibs` file.
|
|
43
43
|
try:
|
|
44
|
-
ibis = IBISModel(ibis_file, debug=debug, gui=False)
|
|
44
|
+
ibis = IBISModel(str(ibis_file.resolve()), debug=debug, gui=False)
|
|
45
45
|
dName = ibis_file.parent
|
|
46
46
|
assert ibis.ami_file, RuntimeError(
|
|
47
47
|
"Missing AMI file definition in IBIS file!"
|
|
@@ -113,13 +113,9 @@ def run_notebook(
|
|
|
113
113
|
print("\nNOTE: Since you provided no parameter sweep information,")
|
|
114
114
|
print(f'a "dummy" sweep file has been created: {dummy_run_file_name}.')
|
|
115
115
|
print("You may use this file as a template for creating parameter sweep specifications.")
|
|
116
|
-
print(
|
|
116
|
+
print()
|
|
117
117
|
|
|
118
118
|
# Define temp. (i.e. - parameterized) notebook and output file locations.
|
|
119
|
-
tmp_notebook = NamedTemporaryFile(
|
|
120
|
-
suffix='.ipynb',
|
|
121
|
-
prefix=(notebook.stem + "_papermill"),
|
|
122
|
-
delete_on_close=False)
|
|
123
119
|
html_file = Path(out_dir.joinpath(ibis_file.name)).with_suffix(".html")
|
|
124
120
|
|
|
125
121
|
# Run the notebook.
|
|
@@ -132,7 +128,9 @@ def run_notebook(
|
|
|
132
128
|
extra_args = [tok for item in notebook_params.items()
|
|
133
129
|
for tok in ['-p', f'{item[0]}', f'{item[1]}']] # noqa: E127
|
|
134
130
|
|
|
135
|
-
with
|
|
131
|
+
with NamedTemporaryFile(suffix='.ipynb',
|
|
132
|
+
prefix=(notebook.stem + "_papermill"),
|
|
133
|
+
delete_on_close=False) as tmp_notebook:
|
|
136
134
|
subprocess.run(['papermill', str(notebook), tmp_notebook.name] + extra_args, check=True)
|
|
137
135
|
subprocess.run(
|
|
138
136
|
['jupyter', 'nbconvert', '--to', 'html', '--no-input', '--output', html_file, tmp_notebook.name],
|
|
@@ -9,8 +9,8 @@ Copyright (c) 2026 David Banas; All rights reserved World wide.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
|
|
13
|
-
from
|
|
12
|
+
from typing import Optional, TypeAlias
|
|
13
|
+
from collections.abc import Generator, Sequence
|
|
14
14
|
import warnings
|
|
15
15
|
|
|
16
16
|
from matplotlib import pyplot as plt
|
|
@@ -249,13 +249,13 @@ def plot_resps(
|
|
|
249
249
|
if OUT_RESP_INIT in resps:
|
|
250
250
|
if debug:
|
|
251
251
|
print("Plotting OUT_RESP_INIT...")
|
|
252
|
-
t,
|
|
252
|
+
t, _, s, p, f, H = resps[OUT_RESP_INIT]
|
|
253
253
|
left_ax.plot(t * 1e9, s, color=init_color, linestyle=init_linestyle,)
|
|
254
254
|
left_ax.plot(t * 1e9, p, label=lbl, color=init_color, linestyle=init_linestyle,)
|
|
255
255
|
right_ax.semilogx(f / 1e9, 20 * np.log10(np.abs(H)), label=lbl,
|
|
256
256
|
color=init_color, linestyle=init_linestyle,)
|
|
257
257
|
if OUT_RESP_GETW in resps:
|
|
258
|
-
t,
|
|
258
|
+
t, _, s, p, f, H = resps[OUT_RESP_GETW]
|
|
259
259
|
left_ax.plot(t * 1e9, s, color=getwave_color, linestyle=getwave_linestyle,)
|
|
260
260
|
left_ax.plot(t * 1e9, p, color=getwave_color, linestyle=getwave_linestyle,)
|
|
261
261
|
right_ax.semilogx(f / 1e9, 20 * np.log10(np.abs(H)),
|
|
@@ -10,7 +10,7 @@ Copyright (c) 2026 David Banas; All rights reserved World wide.
|
|
|
10
10
|
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
|
-
from datetime import datetime
|
|
13
|
+
from datetime import datetime, timezone, timedelta
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
import numpy as np
|
|
@@ -112,7 +112,7 @@ def title_page(ibis_file: Path) -> list[Flowable]:
|
|
|
112
112
|
|
|
113
113
|
flowables: list[Flowable] = [Spacer(1, 3 * inch)]
|
|
114
114
|
flowables.append(Paragraph(f"<em>PyIBIS-AMI</em> v{pyibisami.__version__} - IBIS-AMI Model Testing Report", title_style))
|
|
115
|
-
flowables.append(Paragraph(f"{bold('Date:')} {datetime.now()}", P))
|
|
115
|
+
flowables.append(Paragraph(f"{bold('Date:')} {datetime.now(tz=timezone(timedelta(hours=0)))}", P))
|
|
116
116
|
flowables.append(Paragraph(f"{bold('Tested:')} {ibis_file}", P))
|
|
117
117
|
flowables.append(Paragraph(f"{bold('Python:')} {sys.version}", P))
|
|
118
118
|
flowables.append(Paragraph(f"{bold('NumPy:')} {np.__version__}", P))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|