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.
Files changed (65) hide show
  1. {pyibis_ami-9.2.0/src/pyibis_ami.egg-info → pyibis_ami-9.3.0}/PKG-INFO +1 -1
  2. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/pyproject.toml +8 -1
  3. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0/src/pyibis_ami.egg-info}/PKG-INFO +1 -1
  4. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/config.py +4 -5
  5. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/model.py +12 -12
  6. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/parameter.py +68 -1
  7. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/parser.py +155 -21
  8. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/reserved_parameter_names.py +1 -1
  9. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/file.py +29 -29
  10. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/model.py +5 -5
  11. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/parser.py +5 -4
  12. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_test_config.py +3 -3
  13. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_tests.py +13 -12
  14. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ami_tests_helpers.py +2 -2
  15. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/ibis_file_tests.py +3 -3
  16. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/test_defs.py +3 -2
  17. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/test_models.py +0 -0
  18. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/util.py +1 -1
  19. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/run_notebook.py +5 -7
  20. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/plot.py +4 -4
  21. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/reportlab.py +2 -2
  22. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/LICENSE +0 -0
  23. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/MANIFEST.in +0 -0
  24. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/README.md +0 -0
  25. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/ami.rst +0 -0
  26. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/conf.py +0 -0
  27. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/ibis.rst +0 -0
  28. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/index.rst +0 -0
  29. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/install.rst +0 -0
  30. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/modules.rst +0 -0
  31. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/docs/source/tools.rst +0 -0
  32. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/all_taps.run +0 -0
  33. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/bandwidth.run +0 -0
  34. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/dummy.em +0 -0
  35. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/examples.md +0 -0
  36. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/freq_resp.em +0 -0
  37. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/impulse.em +0 -0
  38. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/impulse_response_8ma.txt +0 -0
  39. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/mode.run +0 -0
  40. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/stratix4_ami_tx_runs/all_taps.run +0 -0
  41. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/stratix4_ami_tx_runs/test_results.xml +0 -0
  42. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/examples/test_results/test_results.xsl +0 -0
  43. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/setup.cfg +0 -0
  44. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/SOURCES.txt +0 -0
  45. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/dependency_links.txt +0 -0
  46. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/entry_points.txt +0 -0
  47. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/requires.txt +0 -0
  48. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibis_ami.egg-info/top_level.txt +0 -0
  49. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/IBIS_AMI_Checker.ipynb +0 -0
  50. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/IBIS_AMI_Tester.ipynb +0 -0
  51. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/__init__.py +0 -0
  52. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/__main__.py +0 -0
  53. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/__init__.py +0 -0
  54. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/generic.ami.em +0 -0
  55. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ami/generic.ibs.em +0 -0
  56. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/common.py +0 -0
  57. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/ibis/__init__.py +0 -0
  58. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/testing/__init__.py +0 -0
  59. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/__init__.py +0 -0
  60. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/run_tests.py +0 -0
  61. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/tools/test_results.xsl +0 -0
  62. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/ami.py +0 -0
  63. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/src/pyibisami/util/misc.py +0 -0
  64. {pyibis_ami-9.2.0 → pyibis_ami-9.3.0}/tests/test_ami_test_config.py +0 -0
  65. {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.2.0
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.2.0"
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.2.0
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.keys():
250
- desc, ami_defs, sim_defs, ref_fstr = test_defs[fname]
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 init_object.info_params["GetWave_Exists"].pvalue:
359
- if not self._amiGetWave:
360
- raise RuntimeError(
361
- "Reserved parameter `GetWave_Exists` is True, but I can't bind to `AMI_GetWave()`!"
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 not item[0] == "root_name":
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 err
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]]: # noqa: F405
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
- if "Ignore_Bits" in info_params:
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) # noqa: F405
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, Callable, NewType, Optional, TypeAlias
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["AmiNode"]
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(self, branch_names, new_val):
190
- """Sets the value of the parameter found by traversing 'branch_names'
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
- Note: 'branch_names' should *not* begin with 'root_name'.
194
- Note: Be careful! There is no checking done here!
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
- param_dict.pvalue = new_val
208
- try:
209
- eval(f"self.set({branch_name}_={new_val})") # pylint: disable=eval-used
210
- except Exception: # pylint: disable=broad-exception-caught
211
- eval(f"self.set({branch_name}={new_val})") # pylint: disable=eval-used
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=pname + "_")) # type: ignore
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 = list(param_dict.items())[0]
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
- # err_msg, root_name, description, reserved_params_dict, model_specific_dict = parse_ami_file_contents(file_contents)
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 = list(map(lambda n: n.split('_'), pnames))
654
- group_names = list(map(lambda xs: xs[0], pnames_split))
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 = list(tmp_dict.keys())[0]
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:
@@ -12,7 +12,7 @@ from dataclasses import dataclass
12
12
 
13
13
 
14
14
  @dataclass
15
- class AmiReservedParameterName():
15
+ class AmiReservedParameterName:
16
16
  "IBIS-AMI Reserved Parameter Name"
17
17
 
18
18
  pname: str
@@ -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 list(map(lambda pr: pr[0], self._model_selectors[mname]))
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__(self, ibis_file_name, debug=False, gui=True):
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
- self._model_dict = model_dict
203
- self._models = model_dict["models"]
204
- self._components = components
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 = model_dict["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
- self.add_trait("comp", Trait(list(components)[0], components)) # Doesn't need a custom mapper, because
210
- self.pins = self.get_pins() # the thing above it (file) can't change.
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(list(components)[0]) # Wasn't being called automatically.
231
- self._pin_changed(self.pins[0]) # Wasn't being called automatically.
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(list(self._pins)[0], self._pins))
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[name] if name in subDict else None
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 = (list(pd_zs)[0] + list(pu_zs)[0]) / 2
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 = list(gc_zs)[0] # Use typical value for Zin calc.
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 = list(pc_zs)[0]
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, Generator, Iterable, Optional, TypeAlias, TypeVar
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}: {str(le)}")
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}: {str(le)}")
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}: {str(err)}")
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
- f"params_out mismatch.\n"
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 Optional, Sequence
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] = [] # Any desired introductory text for report body.
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
- if not isinstance(value[0], Flowable):
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
- return
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 = list(map(lambda f: ibis_file_dir / Path(f), ami_files))
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 = True if pcfg.fetch_param_val(["Reserved_Parameters", "Ts4file"]) else False
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 model_name in ami_model_names:
140
- do_model(model_name)
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, Generator, NewType, Optional
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!"
@@ -36,7 +36,7 @@ def get_sweepers(
36
36
  return (
37
37
  mod.__doc__,
38
38
  [obj for name, obj in inspect.getmembers(mod, inspect.isclass) # type: ignore
39
- if issubclass(obj, TestSweep) and not obj == TestSweep]
39
+ if issubclass(obj, TestSweep) and obj != TestSweep]
40
40
  )
41
41
 
42
42
 
@@ -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 tmp_notebook:
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
- # import logging
13
- from typing import Generator, Optional, Sequence, TypeAlias
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, h, s, p, f, H = resps[OUT_RESP_INIT]
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, h, s, p, f, H = resps[OUT_RESP_GETW]
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