latexify_py icon indicating copy to clipboard operation
latexify_py copied to clipboard

Multiplication needs extra processing to reorder coefficient and variable

Open Casper-Guo opened this issue 3 years ago • 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.

https://github.com/google/latexify_py/blob/3b1b05bc5d6d9811799327dc268001e94bf68a28/src/latexify/codegen/function_codegen.py#L473-L479

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}$

Casper-Guo avatar Nov 16 '22 04:11 Casper-Guo

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.

odashi avatar Nov 16 '22 04:11 odashi

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.

ZibingZhang avatar Nov 20 '22 00:11 ZibingZhang

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)

odashi avatar Nov 20 '22 04:11 odashi

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.

Casper-Guo avatar Dec 04 '22 21:12 Casper-Guo

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.

ZibingZhang avatar Dec 04 '22 22:12 ZibingZhang

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

Casper-Guo avatar Dec 04 '22 23:12 Casper-Guo

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)

odashi avatar Dec 05 '22 02:12 odashi