From 37b6b8f8d26d6ef83d76cee1516398ee4fcc4fa4 Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 9 Nov 2023 14:30:12 +0100 Subject: Aperture macro expression simplification WIP --- gerbonara/aperture_macros/expression.py | 132 +++++++++++++++++++++++++++++--- gerbonara/aperture_macros/parse.py | 2 +- gerbonara/aperture_macros/primitive.py | 9 ++- 3 files changed, 127 insertions(+), 16 deletions(-) (limited to 'gerbonara') diff --git a/gerbonara/aperture_macros/expression.py b/gerbonara/aperture_macros/expression.py index 99d02eb..f096cf1 100644 --- a/gerbonara/aperture_macros/expression.py +++ b/gerbonara/aperture_macros/expression.py @@ -7,6 +7,7 @@ from dataclasses import dataclass import operator import re import ast +import math from ..utils import LengthUnit, MM, Inch, MILLIMETERS_PER_INCH @@ -61,7 +62,7 @@ class Expression: return expr(other) / self def __neg__(self): - return 0 - self + return NegatedExpression(self) def __pos__(self): return self @@ -155,9 +156,12 @@ class ConstantExpression(Expression): return float(self.value) def __eq__(self, other): - return type(self) == type(other) and self.value == other.value + try: + return math.isclose(self.value, float(other), abs_tol=1e-9) + except TypeError: + return False - def to_gerber(self, _unit=None): + def to_gerber(self, unit=None): return f'{self.value:.6f}'.rstrip('0').rstrip('.') @@ -174,9 +178,45 @@ class VariableExpression(Expression): return type(self) == type(other) and \ self.number == other.number - def to_gerber(self, _unit=None): + def to_gerber(self, unit=None): return f'${self.number}' +@dataclass(frozen=True, slots=True) +class NegatedExpression(Expression): + value: Expression + + def optimized(self, variable_binding={}): + match self.value.optimized(variable_binding): + # -(-x) == x + case NegatedExpression(inner_value): + return inner_value + # -(x) == -x + case ConstantExpression(inner_value): + return ConstantExpression(-inner_value) + # -(x-y) == y-x + case OperatorExpression(operator.sub, l, r): + return OperatorExpression(operator.sub, r, l) + # -(x [*/] y) == -x [*/] y + case OperatorExpression((operator.mul | operator.truediv) as op, ConstantExpression(l), r): + return OperatorExpression(op, ConstantExpression(-l), r) + # -(x [*/] y) == x [*/] -y + case OperatorExpression((operator.mul | operator.truediv) as op, l, ConstantExpression(r)): + return OperatorExpression(op, l, ConstantExpression(-r)) + + case x: + return NegatedExpression(x) + + def __eq__(self, other): + return type(self) == type(other) and \ + self.value == other.value + + def to_gerber(self, unit=None): + val_str = self.value.to_gerber(unit) + if isinstance(self.value, VariableExpression): + return f'-{val_str}' + else: + return f'-({val_str})' + @dataclass(frozen=True, slots=True) class OperatorExpression(Expression): @@ -198,15 +238,83 @@ class OperatorExpression(Expression): def optimized(self, variable_binding={}): l = self.l.optimized(variable_binding) r = self.r.optimized(variable_binding) + print(self.r, '->', r) - #if self.op in (operator.add, operator.mul): - # if id(r) < id(l): - # l, r = r, l - - if isinstance(l, ConstantExpression) and isinstance(r, ConstantExpression): - return ConstantExpression(self.op(float(l), float(r))) - - return OperatorExpression(self.op, l, r) + match (l, self.op, r): + case (ConstantExpression(), op, ConstantExpression()): + return ConstantExpression(self.op(float(l), float(r))) + + # Minimize operations with neutral elements and zeros + # 0 + x == x + case (0, operator.add, r): + return r + # x + 0 == x + case (l, operator.add, 0): + return l + # 0 * x == 0 + case (0, operator.mul, r): + return expr(0) + # x * 0 == 0 + case (l, operator.mul, 0): + return expr(0) + # x * 1 == x + case (l, operator.mul, 1): + return l + # 1 * x == x + case (1, operator.mul, r): + return r + # x * -1 == -x + case (l, operator.mul, -1): + rv = -l + # -1 * x == -x + case (-1, operator.mul, r): + rv = -r + # x - 0 == x + case (l, operator.sub, 0): + return l + # 0 - x == -x (unary minus) + case (0, operator.sub, r): + rv = -r + # x - x == 0 + case (l, operator.sub, r) if l == r: + return expr(0) + # x - -y == x + y + case (l, operator.sub, NegatedExpression(r)): + rv = (l + r) + # x / 1 == x + case (l, operator.truediv, 1): + return l + # x / -1 == -x + case (l, operator.truediv, -1): + rv = -l + # x / x == 1 + case (l, operator.truediv, r) if l == r: + return expr(1) + # -x [*/] -y == x [*/] y + case (NegatedExpression(l), (operator.truediv | operator.mul) as op, NegatedExpression(r)): + rv = op(l, r) + # -x + -y == -(x + y) + case (NegatedExpression(l), operator.add, NegatedExpression(r)): + rv = -(l+r) + # -x + y == y - x + case (NegatedExpression(l), operator.add, r): + rv = r-l + # x + x == 2 * x + case (l, operator.add, r) if l == r: + rv = 2*r + case ((l, op, OperatorExpression(operator.mul, ConstantExpression(cons), r)) | + (l, op, OperatorExpression(operator.mul, r, ConstantExpression(cons)))) \ + if l == r and op in (operator.add, operator.sub): + return op(1, cons) * r + case ((OperatorExpression(operator.mul, ConstantExpression(cons), r), op, l) | + (OperatorExpression(operator.mul, r, ConstantExpression(cons)), op, l)) \ + if l == r and op in (operator.add, operator.sub): + return op(cons, 1) * r + + case _: # default + return OperatorExpression(self.op, l, r) + + return expr(rv).optimized(variable_binding) def to_gerber(self, unit=None): lval = self.l.to_gerber(unit) diff --git a/gerbonara/aperture_macros/parse.py b/gerbonara/aperture_macros/parse.py index 45f8c41..1527bc1 100644 --- a/gerbonara/aperture_macros/parse.py +++ b/gerbonara/aperture_macros/parse.py @@ -30,7 +30,7 @@ def _map_expression(node): if type(node.op) == ast.UAdd: return _map_expression(node.operand) else: - return OperatorExpression(operator.sub, ConstantExpression(0), _map_expression(node.operand)) + return NegatedExpression(_map_expression(node.operand)) elif isinstance(node, ast.Name): return VariableExpression(int(node.id[3:])) # node.id has format var[0-9]+ diff --git a/gerbonara/aperture_macros/primitive.py b/gerbonara/aperture_macros/primitive.py index f575b0c..1372dfa 100644 --- a/gerbonara/aperture_macros/primitive.py +++ b/gerbonara/aperture_macros/primitive.py @@ -48,7 +48,7 @@ class Primitive: def to_gerber(self, unit=None): return f'{self.code},' + ','.join( - getattr(self, field.name).to_gerber(unit) for field in fields(self) if field.name != 'unit') + getattr(self, field.name).optimized().to_gerber(unit) for field in fields(self) if field.name != 'unit') def __str__(self): attrs = ','.join(str(getattr(self, name)).strip('<>') for name in type(self).__annotations__) @@ -253,6 +253,9 @@ class Outline(Primitive): object.__setattr__(self, 'exposure', expr(self.exposure)) if self.length.calculate() != len(self.coords)//2-1: + print(self.length, self.length.calculate(), len(self.coords)) + import pprint + pprint.pprint(self.coords) raise ValueError('length must exactly equal number of segments, which is the number of points minus one') if self.coords[-2:] != self.coords[:2]: @@ -289,8 +292,8 @@ class Outline(Primitive): rotation = ConstantExpression(0) - coords = ','.join(coord.to_gerber(unit) for coord in coords) - return f'{self.code},{self.exposure.to_gerber()},{len(self.coords)//2-1},{coords},{rotation.to_gerber()}' + coords = ','.join(coord.optimized().to_gerber(unit) for coord in coords) + return f'{self.code},{self.exposure.optimized().to_gerber()},{len(self.coords)//2-1},{coords},{rotation.to_gerber()}' def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None, polarity_dark=True): with self.Calculator(self, variable_binding, unit) as calc: -- cgit