google/latexify_py

Multiplication needs extra processing to reorder coefficient and variable

Casper-Guo opened this issue · 7 comments

Environment

If you used latexify in your own environment, fill at least the following items.
Feel free to add other items if you think they are useful.

  • OS: Windows 10
  • Python: 3.8
  • Package manager: pip 22.2.1
  • Latexify version: current dev branch

Description

The current implementation of visit_BinOp doesn't change the ordering of the operands. This is desirable or acceptable for most operations with multiplication being the exception. Numeric coefficients should always precede variable names. That is a*2 and 2*a should both produce 2a.

def visit_BinOp(self, node: ast.BinOp) -> str:
"""Visit a BinOp node."""
prec = _get_precedence(node)
rule = self._bin_op_rules[type(node.op)]
lhs = self._wrap_binop_operand(node.left, prec, rule.operand_left)
rhs = self._wrap_binop_operand(node.right, prec, rule.operand_right)
return f"{rule.latex_left}{lhs}{rule.latex_middle}{rhs}{rule.latex_right}"

Reproduction

@latexify.function
def solve(a, b, c):
    return (-b + math.sqrt(b**2 - 4*a*c)) / (a*2)

print(solve(1, 4, 3))
print(solve)
solve

produces

\mathrm{solve}(a, b, c) = \frac{-b + \sqrt{b^{{2}} - {4} a c}}{a {2}}

$\mathrm{solve}(a, b, c) = \frac{-b + \sqrt{b^{{2}} - {4} a c}}{a {2}}$

Expected behavior

The above should produce:

\mathrm{solve}(a, b, c) = \frac{-b + \sqrt{b^{{2}} - {4} a c}}{{2} a}

$\mathrm{solve}(a, b, c) = \frac{-b + \sqrt{b^{{2}} - {4} a c}}{{2} a}$

To be clear: this reordering should be applicable only when the targeted node represents the exact number.

The algorithm can be implemented as a new transformer I think. We should also provide a boolean option to switch the behavior.

What about in more complicated cases?

def solve(x):
    return 2 * x * 4

It's probably desirable to bubble constant numbers to the front of each product expression somehow, e.g. 2 * x * 4 + a * 3 -> 2 * 4 * x + 3 * a.

I think we need to handle #89 before this issue. Adding \cdot ( $\cdot$ ) should be the default behavior of the multiplication, and in several cases we can avoid the symbol. As long as inserting \cdot, all expressions should be fine without any modification of AST (but the appearance becomes redundant)

Now that #139 is done, the only times where \cdot shouldn't be used are between a number and a single-character variable or between two single-character variables. The ordering of these variables can be handled using default Python string ordering (e.g "2" < "a" = True)

What about in more complicated cases?

def solve(x):
    return 2 * x * 4

It's probably desirable to bubble constant numbers to the front of each product expression somehow, e.g. 2 * x * 4 + a * 3 -> 2 * 4 * x + 3 * a.

In such cases we can implement a simplification procedure similar to #115 and combine all the constant coefficients.

I'm not sure combining constant coeff is always desirable. It may be nice to keep separate for things like 4 * 3.14 / 3 r^3.

If the user wants them combined they can do it themselves, or maybe it can be introduced as a config option, defaulting to False.

Good point, it should be configurable

On a related note, operations with brackets, such as (3*a)*(2*b) also shouldn't be modified. If IIRC, this is the default behavior anyways

The ordering of these variables can be handled using default Python string ordering (e.g "2" < "a" = True)

Yeah that's true, but somewhat tricky. It looks it is desirable to check it in more declarative manner.

I'm not sure combining constant coeff is always desirable

It is generally good to keep the original syntax as-is. It would be good to make every option normally off if it is too intelligent (the good example is reduce_assignments: this is quite useful IMO, but may confuse users if it is enabled by default)