Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 148 additions & 148 deletions edg/abstract_parts/AbstractPowerConverters.py

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions edg/abstract_parts/test_switching_converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest

from .AbstractPowerConverters import BuckConverterPowerPath, BoostConverterPowerPath
from ..core import Range


class BuckConverterCalculationTest(unittest.TestCase):
def test_buck_converter(self):
values_ref = BuckConverterPowerPath.calculate_parameters(
Range.exact(5), Range.exact(2.5), Range.exact(100e3), Range.exact(1),
Range.exact(0.1), 0.01, 0.001,
efficiency=Range.exact(1)
)
self.assertEqual(values_ref.dutycycle, Range.exact(0.5))
# validated against https://www.omnicalculator.com/physics/buck-converter
self.assertEqual(values_ref.inductance, Range.exact(125e-6))

# test that component values are calculated for worst-case conversion
values = BuckConverterPowerPath.calculate_parameters(
Range(4, 5), Range(2.5, 4), Range.exact(100e3), Range.exact(1),
Range.exact(0.1), 0.01, 0.001,
efficiency=Range.exact(1)
)
self.assertEqual(values_ref.inductance, values.inductance)
self.assertEqual(values_ref.input_capacitance, values.input_capacitance)
self.assertEqual(values_ref.output_capacitance, values.output_capacitance)

def test_buck_converter_example(self):
# using the example from https://passive-components.eu/buck-converter-design-and-calculation/
values = BuckConverterPowerPath.calculate_parameters(
Range.exact(12 + 0.4), Range.exact(3.3 + 0.4), Range.exact(500e3), Range.exact(1),
Range.exact(0.35), 1, 0.0165,
efficiency=Range.exact(1)
)
self.assertAlmostEqual(values.dutycycle.upper, 0.298, places=3)
self.assertAlmostEqual(values.inductance.upper, 14.8e-6, places=7)

# the example uses a ripple current of 0.346 for the rest of the calculations
values = BuckConverterPowerPath.calculate_parameters(
Range.exact(12 + 0.4), Range.exact(3.3 + 0.4), Range.exact(500e3), Range.exact(1),
Range.exact(0.346), 1, 0.0165,
efficiency=Range.exact(1)
)
self.assertAlmostEqual(values.inductor_peak_currents.upper, 1.173, places=3)
self.assertAlmostEqual(values.output_capacitance.lower, 5.24e-6, places=7)

def test_boost_converter(self):
values_ref = BoostConverterPowerPath.calculate_parameters(
Range.exact(5), Range.exact(10), Range.exact(100e3), Range.exact(1),
Range.exact(0.1), 0.01, 0.001,
efficiency=Range.exact(1)
)
self.assertEqual(values_ref.dutycycle, Range.exact(0.5))
# validated against https://www.omnicalculator.com/physics/boost-converter
self.assertEqual(values_ref.inductance, Range.exact(250e-6))

# test that component values are calculated for worst-case conversion
values = BoostConverterPowerPath.calculate_parameters(
Range(5, 8), Range(7, 10), Range.exact(100e3), Range.exact(1),
Range.exact(0.1), 0.01, 0.001,
efficiency=Range.exact(1)
)
self.assertEqual(values_ref.inductance, values.inductance)
self.assertEqual(values_ref.input_capacitance, values.input_capacitance)
self.assertEqual(values_ref.output_capacitance, values.output_capacitance)

def test_boost_converter_example(self):
# using the example from https://passive-components.eu/boost-converter-design-and-calculation/
# 0.4342A ripple current from .35 factor in example converted in output current terms
values = BoostConverterPowerPath.calculate_parameters(
Range.exact(5), Range.exact(12 + 0.4), Range.exact(500e3), Range.exact(0.5),
Range.exact(0.4342), 1, 1,
efficiency=Range.exact(1)
)
self.assertAlmostEqual(values.dutycycle.upper, 0.597, places=3)
self.assertAlmostEqual(values.inductance.upper, 13.75e-6, places=7)

# the example continues with a normalized inductance of 15uH
values = BoostConverterPowerPath.calculate_parameters(
Range.exact(5), Range.exact(12 + 0.4), Range.exact(500e3), Range.exact(0.5),
Range.exact(.4342*13.75/15), 0.01, 0.06,
efficiency=Range.exact(1)
)
self.assertAlmostEqual(values.dutycycle.upper, 0.597, places=3)
self.assertAlmostEqual(values.inductance.upper, 15.0e-6, places=7)
self.assertAlmostEqual(values.inductor_peak_currents.upper, 1.44, places=2)
# the example calculation output is wrong, this is the correct result of the formula
self.assertAlmostEqual(values.output_capacitance.lower, 9.95e-6, places=7)
81 changes: 48 additions & 33 deletions edg/core/ConstraintExpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ def then_else(self, then_val: IteType, else_val: IteType) -> IteType:
class NumLikeExpr(ConstraintExpr[WrappedType, NumLikeCastable], Generic[WrappedType, NumLikeCastable]):
"""Trait for numeric-like expressions, providing common arithmetic operations"""

_CASTABLE_TYPES: Tuple[Type[NumLikeCastable], ...] # NumLikeCastable for use in ininstance(), excluding self-cls

@classmethod
@abstractmethod
def _to_expr_type(cls: Type[NumLikeSelfType],
Expand Down Expand Up @@ -209,28 +211,44 @@ def __mul_inv__(self: NumLikeSelfType) -> NumLikeSelfType:
return self._create_unary_op(self, NumericOp.invert)

def __add__(self: NumLikeSelfType, rhs: NumLikeCastable) -> NumLikeSelfType:
return self._create_binary_op(self, self._to_expr_type(rhs), NumericOp.add)
if isinstance(rhs, self._CASTABLE_TYPES) or isinstance(rhs, self.__class__):
return self._create_binary_op(self, self._to_expr_type(rhs), NumericOp.add)
return NotImplemented

def __radd__(self: NumLikeSelfType, lhs: NumLikeCastable) -> NumLikeSelfType:
return self._create_binary_op(self._to_expr_type(lhs), self, NumericOp.add)
if isinstance(lhs, self._CASTABLE_TYPES) or isinstance(lhs, self.__class__):
return self._create_binary_op(self._to_expr_type(lhs), self, NumericOp.add)
return NotImplemented

def __sub__(self: NumLikeSelfType, rhs: NumLikeCastable) -> NumLikeSelfType:
return self.__add__(self._to_expr_type(rhs).__neg__())
if isinstance(rhs, self._CASTABLE_TYPES) or isinstance(rhs, self.__class__):
return self.__add__(self._to_expr_type(rhs).__neg__())
return NotImplemented

def __rsub__(self: NumLikeSelfType, lhs: NumLikeCastable) -> NumLikeSelfType:
return self.__neg__().__radd__(self._to_expr_type(lhs))
if isinstance(lhs, self._CASTABLE_TYPES) or isinstance(lhs, self.__class__):
return self.__neg__().__radd__(self._to_expr_type(lhs))
return NotImplemented

def __mul__(self: NumLikeSelfType, rhs: NumLikeCastable) -> NumLikeSelfType:
return self._create_binary_op(self, self._to_expr_type(rhs), NumericOp.mul)
if isinstance(rhs, self._CASTABLE_TYPES) or isinstance(rhs, self.__class__):
return self._create_binary_op(self, self._to_expr_type(rhs), NumericOp.mul)
return NotImplemented

def __rmul__(self: NumLikeSelfType, lhs: NumLikeCastable) -> NumLikeSelfType:
return self._create_binary_op(self._to_expr_type(lhs), self, NumericOp.mul)
if isinstance(lhs, self._CASTABLE_TYPES) or isinstance(lhs, self.__class__):
return self._create_binary_op(self._to_expr_type(lhs), self, NumericOp.mul)
return NotImplemented

def __truediv__(self: NumLikeSelfType, rhs: NumLikeCastable) -> NumLikeSelfType:
return self.__mul__(self._to_expr_type(rhs).__mul_inv__())
if isinstance(rhs, self._CASTABLE_TYPES) or isinstance(rhs, self.__class__):
return self.__mul__(self._to_expr_type(rhs).__mul_inv__())
return NotImplemented

def __rtruediv__(self: NumLikeSelfType, lhs: NumLikeCastable) -> NumLikeSelfType:
return self.__mul_inv__().__mul__(self._to_expr_type(lhs))
if isinstance(lhs, self._CASTABLE_TYPES) or isinstance(lhs, self.__class__):
return self.__mul_inv__().__mul__(self._to_expr_type(lhs))
return NotImplemented

@classmethod
def _create_bool_op(cls,
Expand All @@ -245,23 +263,35 @@ def _create_bool_op(cls,
return BoolExpr()._bind(BinaryOpBinding(lhs, rhs, op))

def __ne__(self: NumLikeSelfType, other: NumLikeCastable) -> BoolExpr: #type: ignore
return self._create_bool_op(self, self._to_expr_type(other), EqOp.ne)
if isinstance(other, self._CASTABLE_TYPES) or isinstance(other, self.__class__):
return self._create_bool_op(self, self._to_expr_type(other), EqOp.ne)
return NotImplemented

def __gt__(self: NumLikeSelfType, other: NumLikeCastable) -> BoolExpr: #type: ignore
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.gt)
if isinstance(other, self._CASTABLE_TYPES) or isinstance(other, self.__class__):
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.gt)
return NotImplemented

def __ge__(self: NumLikeSelfType, other: NumLikeCastable) -> BoolExpr: #type: ignore
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.ge)
if isinstance(other, self._CASTABLE_TYPES) or isinstance(other, self.__class__):
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.ge)
return NotImplemented

def __lt__(self: NumLikeSelfType, other: NumLikeCastable) -> BoolExpr: #type: ignore
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.lt)
if isinstance(other, self._CASTABLE_TYPES) or isinstance(other, self.__class__):
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.lt)
return NotImplemented

def __le__(self: NumLikeSelfType, other: NumLikeCastable) -> BoolExpr: #type: ignore
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.le)
if isinstance(other, self._CASTABLE_TYPES) or isinstance(other, self.__class__):
return self._create_bool_op(self, self._to_expr_type(other), OrdOp.le)
return NotImplemented


IntLike = Union['IntExpr', int]
class IntExpr(NumLikeExpr[int, IntLike]):
_CASTABLE_TYPES = (int, )

@classmethod
def _to_expr_type(cls, input: IntLike) -> IntExpr:
if isinstance(input, IntExpr):
Expand All @@ -287,6 +317,8 @@ def _from_lit(cls, pb: edgir.ValueLit) -> int:
FloatLit = Union[float, int]
FloatLike = Union['FloatExpr', float, int]
class FloatExpr(NumLikeExpr[float, Union[FloatLike, IntExpr]]):
_CASTABLE_TYPES = (float, int)

@classmethod
def _to_expr_type(cls, input: Union[FloatLike, IntExpr]) -> FloatExpr:
if isinstance(input, FloatExpr):
Expand Down Expand Up @@ -320,6 +352,9 @@ def max(self, other: FloatLike) -> FloatExpr:

RangeLike = Union['RangeExpr', Range, Tuple[FloatLike, FloatLike]]
class RangeExpr(NumLikeExpr[Range, Union[RangeLike, FloatLike, IntExpr]]):
# mypy doesn't like the unbounded tuple
_CASTABLE_TYPES = (float, int, FloatExpr, IntExpr, Range, tuple) # type: ignore

# Some range literals for defaults
POSITIVE: Range = Range.from_lower(0.0)
NEGATIVE: Range = Range.from_upper(0.0)
Expand Down Expand Up @@ -424,26 +459,6 @@ def _create_range_float_binary_op(cls,
assert lhs._is_bound() and rhs._is_bound()
return lhs._new_bind(BinaryOpBinding(lhs, rhs, op))

# special option to allow range * float
def __mul__(self, rhs: Union[RangeLike, FloatLike, IntLike]) -> RangeExpr:
if isinstance(rhs, (int, float)): # TODO clean up w/ literal to expr pass, then type based on that
rhs_cast: Union[FloatExpr, IntExpr, RangeExpr] = FloatExpr._to_expr_type(rhs)
elif isinstance(rhs, (FloatExpr, IntExpr)):
rhs_cast = rhs
else:
rhs_cast = self._to_expr_type(rhs) # type: ignore
return self._create_range_float_binary_op(self, rhs_cast, NumericOp.mul)

# special option to allow range / float
def __truediv__(self, rhs: Union[RangeLike, FloatLike, IntLike]) -> RangeExpr:
if isinstance(rhs, (int, float)): # TODO clean up w/ literal to expr pass, then type based on that
rhs_cast: Union[FloatExpr, IntExpr, RangeExpr] = FloatExpr._to_expr_type(rhs)
elif isinstance(rhs, (FloatExpr, IntExpr)):
rhs_cast = rhs
else:
rhs_cast = self._to_expr_type(rhs) # type: ignore
return self * rhs_cast.__mul_inv__()

def shrink_multiply(self, contributing: RangeLike) -> RangeExpr:
"""RangeExpr version of Range.shrink_multiply.
See docs for Range.shrink_multiply."""
Expand Down
9 changes: 9 additions & 0 deletions edg/core/Range.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,18 @@ def __contains__(self, item: Union['Range', float]) -> bool:
else:
return NotImplemented

def hull(self, other: 'Range') -> 'Range':
return Range(min(self.lower, other.lower), max(self.upper, other.upper))

def intersects(self, other: 'Range') -> bool:
return (self.upper >= other.lower) and (self.lower <= other.upper)

def intersect(self, other: 'Range') -> 'Range':
# TODO make behavior more consistent w/ compiler and returning empty that props as a unit
if not self.intersects(other):
raise ValueError("cannot intersect ranges that do not intersect")
return Range(max(self.lower, other.lower), min(self.upper, other.upper))

def __add__(self, other: Union['Range', float]) -> 'Range':
if isinstance(other, Range):
return Range(self.lower + other.lower, self.upper + other.upper)
Expand Down
18 changes: 17 additions & 1 deletion edg/core/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_ops(self) -> None:

self.assertEqual(Range(1, 5).center(), 3)

def test_intersect(self) -> None:
def test_intersects(self) -> None:
self.assertTrue(Range(-1, 2).intersects(Range(2, 3)))
self.assertTrue(Range(-1, 2).intersects(Range(0, 3)))
self.assertTrue(Range(-1, 2).intersects(Range(-2, -1)))
Expand All @@ -56,6 +56,22 @@ def test_intersect(self) -> None:
self.assertFalse(Range(-1, 2).intersects(Range(3, 4)))
self.assertFalse(Range(-1, 2).intersects(Range(-3, -2)))

def test_intersect(self):
self.assertEqual(Range(-1, 2).intersect(Range(2, 3)), Range(2, 2))
self.assertEqual(Range(-1, 2).intersect(Range(0, 3)), Range(0, 2))
self.assertEqual(Range(-1, 2).intersect(Range(-2, -1)), Range(-1, -1))
self.assertEqual(Range(-1, 2).intersect(Range(-2, 0)), Range(-1, 0))
self.assertEqual(Range(-1, 2).intersect(Range(0, 1)), Range(0, 1))
with self.assertRaises(ValueError):
Range(-1, 2).intersect(Range(3, 4))

def test_hull(self):
self.assertEqual(Range(-1, 2).hull(Range(2, 3)), Range(-1, 3))
self.assertEqual(Range(-1, 2).hull(Range(0, 3)), Range(-1, 3))
self.assertEqual(Range(-1, 2).hull(Range(-2, -1)), Range(-2, 2))
self.assertEqual(Range(-1, 2).hull(Range(-2, 0)), Range(-2, 2))
self.assertEqual(Range(-1, 2).hull(Range(0, 1)), Range(-1, 2))

def test_shrink_property(self) -> None:
range1 = Range(10, 20)
self.assertEqual(range1.shrink_multiply(1/range1), Range(1, 1))
Expand Down
2 changes: 1 addition & 1 deletion examples/Fcml/Fcml.net
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@
(value "4.1A 4.7uH ±30% 23.4mΩ SMD,8x8x4.2mm Power Inductors ROHS")
(footprint "Inductor_SMD:L_Taiyo-Yuden_NR-80xx")
(property (name "Sheetname") (value "power_path"))
(property (name "Sheetfile") (value "edg.abstract_parts.AbstractPowerConverters.BuckConverterPowerPath"))
(property (name "Sheetfile") (value "examples.test_fcml.FcmlPowerPath"))
(property (name "edg_path") (value "conv.power_path.inductor"))
(property (name "edg_short_path") (value "conv.power_path.inductor"))
(property (name "edg_refdes") (value "L2"))
Expand Down
2 changes: 1 addition & 1 deletion examples/Fcml/Fcml.ref.net
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@
(value "4.1A 4.7uH ±30% 23.4mΩ SMD,8x8x4.2mm Power Inductors ROHS")
(footprint "Inductor_SMD:L_Taiyo-Yuden_NR-80xx")
(property (name "Sheetname") (value "power_path"))
(property (name "Sheetfile") (value "edg.abstract_parts.AbstractPowerConverters.BuckConverterPowerPath"))
(property (name "Sheetfile") (value "examples.test_fcml.FcmlPowerPath"))
(property (name "edg_path") (value "conv.power_path.inductor"))
(property (name "edg_short_path") (value "conv.power_path.inductor"))
(property (name "edg_refdes") (value "L2"))
Expand Down
4 changes: 2 additions & 2 deletions examples/Simon/Simon.net
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,14 @@
(sheetpath (names "/pwr/fb/") (tstamps "/02b3015a/013000c9/"))
(tstamps "175b043f"))
(comp (ref "pwr.power_path.inductor")
(value "390mA 47uH ±10% 1210 Power Inductors ROHS")
(value "250mA 33uH ±10% 800mΩ 1210 Inductors (SMD) ROHS")
(footprint "Inductor_SMD:L_1210_3225Metric")
(property (name "Sheetname") (value "power_path"))
(property (name "Sheetfile") (value "edg.abstract_parts.AbstractPowerConverters.BoostConverterPowerPath"))
(property (name "edg_path") (value "pwr.power_path.inductor"))
(property (name "edg_short_path") (value "pwr.power_path.inductor"))
(property (name "edg_refdes") (value "L1"))
(property (name "edg_part") (value "BRL3225T470K (Taiyo Yuden)"))
(property (name "edg_part") (value "CMI322513J330KT (FH(Guangdong Fenghua Advanced Tech))"))
(sheetpath (names "/pwr/power_path/") (tstamps "/02b3015a/1786043a/"))
(tstamps "0f2b0369"))
(comp (ref "pwr.power_path.in_cap")
Expand Down
4 changes: 2 additions & 2 deletions examples/Simon/Simon.ref.net
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,14 @@
(sheetpath (names "/pwr/fb/") (tstamps "/02b3015a/013000c9/"))
(tstamps "175b043f"))
(comp (ref "L1")
(value "390mA 47uH ±10% 1210 Power Inductors ROHS")
(value "250mA 33uH ±10% 800mΩ 1210 Inductors (SMD) ROHS")
(footprint "Inductor_SMD:L_1210_3225Metric")
(property (name "Sheetname") (value "power_path"))
(property (name "Sheetfile") (value "edg.abstract_parts.AbstractPowerConverters.BoostConverterPowerPath"))
(property (name "edg_path") (value "pwr.power_path.inductor"))
(property (name "edg_short_path") (value "pwr.power_path.inductor"))
(property (name "edg_refdes") (value "L1"))
(property (name "edg_part") (value "BRL3225T470K (Taiyo Yuden)"))
(property (name "edg_part") (value "CMI322513J330KT (FH(Guangdong Fenghua Advanced Tech))"))
(sheetpath (names "/pwr/power_path/") (tstamps "/02b3015a/1786043a/"))
(tstamps "0f2b0369"))
(comp (ref "C4")
Expand Down
Loading