Skip to content

Commit 5ba2b33

Browse files
committed
merge
2 parents 922e07c + e895c5f commit 5ba2b33

File tree

66 files changed

+1322
-757
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1322
-757
lines changed

edg/abstract_parts/AbstractInductor.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Dict, Optional, cast
22

33
from ..electronics_model import *
4-
from .PartsTable import PartsTableColumn, PartsTableRow
4+
from .PartsTable import PartsTableColumn, PartsTableRow, ExperimentalUserFnPartsTable
55
from .PartsTablePart import PartsTableSelector
66
from .Categories import *
77
from .StandardFootprint import StandardFootprint, HasStandardFootprint
@@ -94,7 +94,9 @@ def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:
9494
def __init__(self, inductance: RangeLike,
9595
current: RangeLike = RangeExpr.ZERO,
9696
frequency: RangeLike = RangeExpr.ZERO,
97-
resistance_dc: RangeLike = (0, 1)*Ohm # generic sane choice?
97+
resistance_dc: RangeLike = (0, 1)*Ohm, # generic sane choice?
98+
*,
99+
experimental_filter_fn: StringLike = ""
98100
) -> None:
99101
super().__init__()
100102

@@ -105,6 +107,7 @@ def __init__(self, inductance: RangeLike,
105107
self.current = self.ArgParameter(current) # defined as operating current range, non-directioned
106108
self.frequency = self.ArgParameter(frequency) # defined as operating frequency range
107109
self.resistance_dc = self.ArgParameter(resistance_dc)
110+
self.experimental_filter_fn = self.ArgParameter(experimental_filter_fn)
108111

109112
self.actual_inductance = self.Parameter(RangeExpr())
110113
self.actual_current_rating = self.Parameter(RangeExpr())
@@ -136,15 +139,23 @@ class TableInductor(PartsTableSelector, Inductor):
136139
@init_in_parent
137140
def __init__(self, *args, **kwargs) -> None:
138141
super().__init__(*args, **kwargs)
139-
self.generator_param(self.inductance, self.current, self.frequency, self.resistance_dc)
142+
self.generator_param(self.inductance, self.current, self.frequency, self.resistance_dc,
143+
self.experimental_filter_fn)
140144

141145
def _row_filter(self, row: PartsTableRow) -> bool:
142146
# TODO eliminate arbitrary DCR limit in favor of exposing max DCR to upper levels
147+
filter_fn_str = self.get(self.experimental_filter_fn)
148+
if filter_fn_str:
149+
filter_fn = ExperimentalUserFnPartsTable.deserialize_fn(filter_fn_str)
150+
else:
151+
filter_fn = None
152+
143153
return super()._row_filter(row) and \
144154
row[self.INDUCTANCE].fuzzy_in(self.get(self.inductance)) and \
145155
self.get(self.current).fuzzy_in(row[self.CURRENT_RATING]) and \
146156
row[self.DC_RESISTANCE].fuzzy_in(self.get(self.resistance_dc)) and \
147-
self.get(self.frequency).fuzzy_in(row[self.FREQUENCY_RATING])
157+
self.get(self.frequency).fuzzy_in(row[self.FREQUENCY_RATING]) and\
158+
(filter_fn is None or filter_fn(row))
148159

149160
def _row_generate(self, row: PartsTableRow) -> None:
150161
super()._row_generate(row)

edg/abstract_parts/AbstractPowerConverters.py

Lines changed: 342 additions & 231 deletions
Large diffs are not rendered by default.

edg/abstract_parts/DummyDevices.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ def __init__(self, forced_current_draw: RangeLike) -> None:
106106
), [Output])
107107

108108

109+
class ForcedVoltageCurrentLimit(DummyDevice, NetBlock):
110+
"""Forces some output current limit, which should be tighter than the input's actual current draw."""
111+
@init_in_parent
112+
def __init__(self, forced_current_limit: RangeLike) -> None:
113+
super().__init__()
114+
115+
self.pwr_in = self.Port(VoltageSink(
116+
current_draw=RangeExpr(),
117+
voltage_limits=RangeExpr.ALL
118+
), [Input])
119+
120+
self.pwr_out = self.Port(VoltageSource(
121+
voltage_out=self.pwr_in.link().voltage,
122+
current_limits=forced_current_limit
123+
), [Output])
124+
125+
self.assign(self.pwr_in.current_draw, self.pwr_out.link().current_drawn)
126+
127+
109128
class ForcedVoltage(DummyDevice, NetBlock):
110129
"""Forces some voltage on the output regardless of the input's actual voltage.
111130
Current draw is passed through unchanged."""

edg/abstract_parts/PartsTable.py

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import csv
44
import itertools
5-
import sys
65
from typing import TypeVar, Generic, Type, overload, Union, Callable, List, Dict, Any, KeysView, Optional, OrderedDict, \
7-
cast
6+
cast, Tuple, Sequence, Protocol
87

9-
if sys.version_info[1] < 8:
10-
from typing_extensions import Protocol
11-
else:
12-
from typing import Protocol
8+
from typing_extensions import ParamSpec
9+
10+
from ..core import Range
1311

1412

1513
# from https://stackoverflow.com/questions/47965083/comparable-types-with-mypy
@@ -152,3 +150,90 @@ def first(self, err="no elements in list") -> PartsTableRow:
152150
if not self.rows:
153151
raise IndexError(err)
154152
return self.rows[0]
153+
154+
155+
UserFnMetaParams = ParamSpec('UserFnMetaParams')
156+
UserFnType = TypeVar('UserFnType', bound=Callable, covariant=True)
157+
class UserFnSerialiable(Protocol[UserFnMetaParams, UserFnType]):
158+
"""A protocol that marks functions as usable in deserialize, that they have been registered."""
159+
_is_serializable: None # guard attribute
160+
161+
def __call__(self, *args: UserFnMetaParams.args, **kwargs: UserFnMetaParams.kwargs) -> UserFnType: ...
162+
__name__: str
163+
164+
165+
class ExperimentalUserFnPartsTable(PartsTable):
166+
"""A PartsTable that can take in a user-defined function for filtering and (possibly) other operations.
167+
These functions are serialized to a string by an internal name (cannot execute arbitrary code,
168+
bounded to defined functions in the codebase), and some arguments can be serialized with the name
169+
(think partial(...)).
170+
Functions must be pre-registered using the @ExperimentalUserFnPartsTable.user_fn(...) decorator,
171+
non-pre-registered functions will not be available.
172+
173+
This is intended to support searches on parts tables that are cross-coupled across multiple parameters,
174+
but still restricted to within on table (e.g., no cross-optimizing RC filters).
175+
176+
EXPERIMENTAL - subject to change without notice."""
177+
178+
_FN_SERIALIZATION_SEPARATOR = ";"
179+
180+
_user_fns: Dict[str, Tuple[Callable, Sequence[Type]]] = {} # name -> fn, [arg types]
181+
_fn_name_dict: Dict[Callable, str] = {}
182+
183+
@staticmethod
184+
def user_fn(param_types: Sequence[Type] = []) -> Callable[[Callable[UserFnMetaParams, UserFnType]],
185+
UserFnSerialiable[UserFnMetaParams, UserFnType]]:
186+
def decorator(fn: Callable[UserFnMetaParams, UserFnType]) -> UserFnSerialiable[UserFnMetaParams, UserFnType]:
187+
"""Decorator to register a user function that can be used in ExperimentalUserFnPartsTable."""
188+
if fn.__name__ in ExperimentalUserFnPartsTable._user_fns or fn in ExperimentalUserFnPartsTable._fn_name_dict:
189+
raise ValueError(f"Function {fn.__name__} already registered.")
190+
ExperimentalUserFnPartsTable._user_fns[fn.__name__] = (fn, param_types)
191+
ExperimentalUserFnPartsTable._fn_name_dict[fn] = fn.__name__
192+
return fn # type: ignore
193+
return decorator
194+
195+
@classmethod
196+
def serialize_fn(cls, fn: UserFnSerialiable[UserFnMetaParams, UserFnType],
197+
*args: UserFnMetaParams.args, **kwargs: UserFnMetaParams.kwargs) -> str:
198+
"""Serializes a user function to a string."""
199+
assert not kwargs, "kwargs not supported in serialization"
200+
if fn not in cls._fn_name_dict:
201+
raise ValueError(f"Function {fn} not registered.")
202+
fn_ctor, fn_argtypes = cls._user_fns[fn.__name__]
203+
def serialize_arg(tpe: Type, val: Any) -> str:
204+
assert isinstance(val, tpe), f"in serialize {val}, expected {tpe}, got {type(val)}"
205+
if tpe is bool:
206+
return str(val)
207+
elif tpe is int:
208+
return str(val)
209+
elif tpe is float:
210+
return str(val)
211+
elif tpe is Range:
212+
return f"({val.lower},{val.upper})"
213+
else:
214+
raise TypeError(f"cannot serialize type {tpe} in user function serialization")
215+
serialized_args = [serialize_arg(tpe, arg) for tpe, arg in zip(fn_argtypes, args)]
216+
return cls._FN_SERIALIZATION_SEPARATOR.join([fn.__name__] + serialized_args)
217+
218+
@classmethod
219+
def deserialize_fn(cls, serialized: str) -> Callable:
220+
"""Deserializes a user function from a string."""
221+
split = serialized.split(cls._FN_SERIALIZATION_SEPARATOR)
222+
if split[0] not in cls._user_fns:
223+
raise ValueError(f"Function {serialized} not registered.")
224+
fn_ctor, fn_argtypes = cls._user_fns[split[0]]
225+
assert len(split) == len(fn_argtypes) + 1
226+
def deserialize_arg(tpe: Type, val: str) -> Any:
227+
if tpe is bool:
228+
return val == 'True'
229+
elif tpe is int:
230+
return int(val)
231+
elif tpe is float:
232+
return float(val)
233+
elif tpe is Range:
234+
parts = val[1:-1].split(",")
235+
return Range(float(parts[0]), float(parts[1])) # type: ignore
236+
else:
237+
raise TypeError(f"cannot deserialize type {tpe} in user function serialization")
238+
deserialized_args = [deserialize_arg(tpe, arg) for tpe, arg in zip(fn_argtypes, split[1:])]
239+
return fn_ctor(*deserialized_args)

edg/abstract_parts/PowerCircuits.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def __init__(self, frequency: RangeLike, fet_rds: RangeLike = (0, 1)*Ohm,
4646
self.fet_rds = self.ArgParameter(fet_rds)
4747
self.gate_res = self.ArgParameter(gate_res)
4848

49+
self.actual_current_limits = self.Parameter(RangeExpr())
50+
4951
def contents(self):
5052
super().contents()
5153
self.driver = self.Block(HalfBridgeDriver(has_boot_diode=True))
@@ -94,6 +96,9 @@ def contents(self):
9496
self.out)
9597
self.connect(self.out.as_ground((0, 0)*Amp), self.driver.high_gnd) # TODO model driver current
9698

99+
self.assign(self.actual_current_limits, self.low_fet.actual_drain_current_rating.intersect(
100+
self.high_fet.actual_drain_current_rating))
101+
97102

98103
class FetHalfBridgeIndependent(FetHalfBridge, HalfBridgeIndependent):
99104
def contents(self):

edg/abstract_parts/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@
108108

109109
from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSink, \
110110
DummyAnalogSource, DummyAnalogSink
111-
from .DummyDevices import ForcedVoltageCurrentDraw, ForcedVoltage, ForcedVoltageCurrent, ForcedAnalogVoltage,\
112-
ForcedAnalogSignal, ForcedDigitalSinkCurrentDraw
111+
from .DummyDevices import ForcedVoltageCurrentDraw, ForcedVoltageCurrentLimit, ForcedVoltage, ForcedVoltageCurrent, \
112+
ForcedAnalogVoltage, ForcedAnalogSignal, ForcedDigitalSinkCurrentDraw
113113
from .MergedBlocks import MergedVoltageSource, MergedDigitalSource, MergedAnalogSource, MergedSpiController
114114

115115
from .Nonstrict3v3Compatible import Nonstrict3v3Compatible

edg/abstract_parts/test_parts_table.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,49 @@ def test_map(self) -> None:
7575

7676
def test_first(self) -> None:
7777
self.assertEqual(self.table.first().value, {'header1': '1', 'header2': 'foo', 'header3': '9'})
78+
79+
80+
class UserFnPartsTableTest(unittest.TestCase):
81+
@staticmethod
82+
@ExperimentalUserFnPartsTable.user_fn()
83+
def user_fn_false() -> Callable[[], bool]:
84+
def inner() -> bool:
85+
return False
86+
return inner
87+
88+
@staticmethod
89+
@ExperimentalUserFnPartsTable.user_fn([bool])
90+
def user_fn_bool_pass(meta_arg: bool) -> Callable[[], bool]:
91+
def inner() -> bool:
92+
return meta_arg
93+
return inner
94+
95+
@staticmethod
96+
@ExperimentalUserFnPartsTable.user_fn([float])
97+
def user_fn_float_pass(meta_arg: float) -> Callable[[], float]:
98+
def inner() -> float:
99+
return meta_arg
100+
return inner
101+
102+
@staticmethod
103+
def user_fn_unserialized() -> Callable[[], None]:
104+
def inner() -> None:
105+
return None
106+
return inner
107+
108+
def test_serialize_deserialize(self) -> None:
109+
self.assertEqual(ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_false), 'user_fn_false')
110+
self.assertEqual(ExperimentalUserFnPartsTable.deserialize_fn(
111+
ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_false))(), False)
112+
113+
self.assertEqual(ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_bool_pass, False),
114+
'user_fn_bool_pass;False')
115+
self.assertEqual(ExperimentalUserFnPartsTable.deserialize_fn(
116+
ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_bool_pass, False))(), False)
117+
self.assertEqual(ExperimentalUserFnPartsTable.deserialize_fn(
118+
ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_bool_pass, True))(), True)
119+
120+
self.assertEqual(ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_float_pass, 0.42),
121+
'user_fn_float_pass;0.42')
122+
self.assertEqual(ExperimentalUserFnPartsTable.deserialize_fn(
123+
ExperimentalUserFnPartsTable.serialize_fn(self.user_fn_float_pass, 0.42))(), 0.42)

0 commit comments

Comments
 (0)