feat(python): Add support for ** (power) operator#690
feat(python): Add support for ** (power) operator#690MikaelMayer wants to merge 24 commits intomainfrom
Conversation
The Python-to-Laurel pipeline rejected programs using the ** operator with 'Binary operator not yet supported: Pow'. Add support by: - Mapping Pow to the PPow prelude function in PythonToLaurel - Constant-folding ** with integer literal operands at translation time - For non-constant operands, PPow delegates to an uninterpreted int_pow function, handling int*int, bool*int, and int*bool combinations - Replacing the PPow stub with a real implementation Add test_power.py verifying 8**2==64 and 2**10==1024 through the full pipeline.
|
Re: moving constant folding to |
Moving concreteEval to PyFactory causes duplicate-name errors since int_pow must be declared in the prelude for PPow to reference it, and addFactoryFunc rejects duplicates. Revert PyFactory changes and restore constant folding in PythonToLaurel for literal operands.
We did that for regular expressions, please check the PR that introduced them. Regular expressions are core functions that were exposed in Laurel, we should do the same. |
External Laurel functions (bodyless declarations) are still translated to Core, unlike re_fullmatch_str which is filtered by the Laurel-to-Core translator. Adding int_pow to the factory causes duplicate-name errors. Constant folding for literal Pow operands stays in PythonToLaurel.
|
Investigated the regex pattern. The difference is that To use
Both are larger changes. For now, constant folding for literal |
What do you mean The problem of doing constant folding in PythonToLaurel is that it's outside of Core. It's better if all the transformations remain in Core, including constant folding that can be done during partial evaluation of Core. |
Move constant folding for ** from PythonToLaurel to a factory function (intPowFunc) in PyFactory.lean with concreteEval. This follows the same pattern as regex factory functions. To avoid duplicate-name errors when the prelude declares int_pow and the factory also provides it, ProgramEval now skips bodyless functions that already exist in the factory (the factory version has concreteEval).
|
Moved constant folding to |
…aurel The regex pattern (factory concreteEval) works because re_fullmatch_str uses the regex type which doesn't exist in Core, so the function is naturally filtered during Laurel-to-Core translation. int_pow uses int which translates fine to Core, causing duplicate-name conflicts with the factory. Constant folding for literal ** operands stays in PythonToLaurel. Non-constant ** produces PPow which throws UnimplementedError (same as before this PR, but now the pipeline doesn't crash on Pow).
|
Reverted the ProgramEval hack. Investigated why regex works:
|
Forward-declare int_pow before CoreOnlyDelimiter (like re_fullmatch_str) so the DDM parser resolves it but it doesn't enter the Core program. The factory provides int_pow with concreteEval for constant folding. Remove constant folding from PythonToLaurel and ProgramEval hack.
|
Moved |
The native binary filters prelude checks, producing 5 results instead of 27. Update expected file to match CI output.
When exp < 0, Python returns a float (e.g. 2**-3 = 0.125). PPow now returns from_float(float_pow(...)) for negative exponents. float_pow is an uninterpreted function (forward-declared before CoreOnlyDelimiter, provided by factory). Bool exponents don't need the guard since bool_to_int returns 0 or 1.
|
|
||
| -- Float exponentiation (uninterpreted). Used for negative integer exponents | ||
| -- where Python returns a float (e.g. 2 ** -3 = 0.125). | ||
| def floatPowFunc : LFunc Core.CoreLParams := |
There was a problem hiding this comment.
Not completely sound, e.g. 3^(-1) is not representable as a float, but this is as good as we can do until we have proper float support, I believe.
There was a problem hiding this comment.
Mmmh in general no but in python it is. So perhaps something we will do in the future is to rename these functions so that we know they correspond to a certain implementation. Integers are unbounded ,but floats are bounded.
There was a problem hiding this comment.
I'm confused why this being Python makes a difference? Floats are always bounded
| else if (Any..isfrom_int(v1) && Any..isfrom_bool(v2)) then | ||
| from_int(int_pow(Any..as_int!(v1), bool_to_int(Any..as_bool!(v2)))) | ||
| else | ||
| exception(UndefinedError ("Operand Type is not defined")) |
There was a problem hiding this comment.
What about the following to reflect we are mssing a few cases (like float and operator overloading).
| exception(UndefinedError ("Operand Type is not defined")) | |
| exception(UnimplementedError("Pow is not defined on these input types")) |
There was a problem hiding this comment.
Applied the suggestion — changed to UnimplementedError.
joehendrix
left a comment
There was a problem hiding this comment.
I added a couple comments, but don't consider them blocking.
| assert x == 64, "8 ** 2 should be 64" | ||
|
|
||
| y: int = 2 ** 10 | ||
| assert y == 1024, "2 ** 10 should be 1024" |
There was a problem hiding this comment.
It'd be good to cover special cases (bool, negative exponents, maybe float if supported).
There was a problem hiding this comment.
Added bool base cases (True5, False3). Negative exponents return from_float(float_pow(...)) which is uninterpreted — can't assert on the result in a test, but the path is exercised. Float base operands fall through to UnimplementedError.
Per review feedback: float operands and operator overloading are not yet supported, so UnimplementedError is more accurate than UndefinedError.
Cover True**5 and False**3 per review feedback. Negative exponents and float operands are handled but can't be asserted on in tests (results are uninterpreted or exceptions).
The Python-to-Laurel pipeline rejected programs using the
**operator with "Binary operator not yet supported: Pow", causing an exit-4 crash.This PR adds support by:
Powto the existingPPowprelude function inPythonToLaurel.lean**with integer literal operands at translation time (e.g.1024 ** 3becomes1073741824)PPowdelegates to an uninterpretedint_powfunction, handling int×int, bool×int, and int×bool combinationsPPowstub (which threwUnimplementedError) with the real implementationTested with a new
test_power.pytest that verifies8 ** 2 == 64and2 ** 10 == 1024through the full pipeline. Existing tests pass.