potassco / potassco/constraint-handler
Fallbackk faster than constraint handler native version
@AbdallahS is already working on this.
Since Sep 11, 2026.
- Dominant language
- Python
- Stars
- 3
- Forks
- 0
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 20
Description
Problem
Translating round(value, ndigits) becomes very expensive when value is a nonlinear expression over several variables.
There are three cases:
- Native arithmetic expands the round operation and repeats the nonlinear operand.
operation(python("round"), ...)hides only the final call. Its argument is still grounded.statement_python(...)hides the complete expression.
Reproducer
clingo native.lp tests/correctness/boilerplate.lp --stats=2
clingo python_operation.lp tests/correctness/boilerplate.lp --stats=2
clingo fallback.lp tests/correctness/boilerplate.lp --stats=2
Encoding 1: native round expansion
The native version repeats the nonlinear operand inside the round branches.
% Issue 414: native round expansion with the nonlinear operand repeated.
variable_declare("input_a","input_a",fromFacts).
variable_declare(execution_input(calc,"input_a"),execution_input(calc,"input_a"),fromFacts).
variable_domain("input_a",val(float,float("600.0"))).
variable_domain("input_a",val(float,float("602.0"))).
variable_domain("input_a",val(float,float("604.0"))).
variable_domain(execution_input(calc,"input_a"),variable("input_a")).
variable_declare("input_b","input_b",fromFacts).
variable_declare(execution_input(calc,"input_b"),execution_input(calc,"input_b"),fromFacts).
variable_domain("input_b",val(float,float("100.0"))).
variable_domain("input_b",val(float,float("102.0"))).
variable_domain("input_b",val(float,float("104.0"))).
variable_domain(execution_input(calc,"input_b"),variable("input_b")).
variable_declare("input_c","input_c",fromFacts).
variable_declare(execution_input(calc,"input_c"),execution_input(calc,"input_c"),fromFacts).
variable_domain("input_c",val(float,float("120.0"))).
variable_domain("input_c",val(float,float("122.0"))).
variable_domain("input_c",val(float,float("124.0"))).
variable_domain(execution_input(calc,"input_c"),variable("input_c")).
execution_declare(calc,calc,assign("result",RESULT),("input_a",("input_b",("input_c",()))),("result",())) :-
SIDE_B = operation(add,(operation(float_div,(variable("input_a"),(val(int,2),()))),(operation(float_div,(variable("input_b"),(val(int,2),()))),()))),
SIDE_C = operation(add,(operation(float_div,(variable("input_a"),(val(int,2),()))),(operation(float_div,(variable("input_c"),(val(int,2),()))),()))),
SIDE_A = operation(add,(operation(float_div,(variable("input_b"),(val(int,2),()))),(operation(float_div,(variable("input_c"),(val(int,2),()))),(val(int,280),())))),
NUMERATOR = operation(sub,(operation(add,(operation(pow,(SIDE_B,(val(int,2),()))),(operation(pow,(SIDE_C,(val(int,2),()))),()))),(operation(pow,(SIDE_A,(val(int,2),()))),()))),
DENOMINATOR = operation(mult,(val(int,2),(operation(mult,(SIDE_B,(SIDE_C,()))),()))),
ANGLE_PART = operation(acos,(operation(float_div,(NUMERATOR,(DENOMINATOR,()))),())),
ANGLE = operation(sub,(val(float,float("360.0")),(operation(mult,(ANGLE_PART,(operation(float_div,(val(float,float("180.0")),(val(float,float("3.141592653589793")),()))),()))),()))),
SCALE = operation(pow,(val(float,float("10.0")),(val(int,3),()))),
A = ANGLE_PART,
SCALED = operation(mult,(ANGLE,(SCALE,()))),
ROUND_FLOOR = operation(floor,(operation(add,(SCALED,(val(float,float("0.5")),()))),())),
ROUND_CEIL = operation(ceil,(operation(sub,(SCALED,(val(float,float("0.5")),()))),())),
ROUND_CHOICE = operation(ite,(operation(geq,(A,(val(float,float("0.0")),()))),(ROUND_FLOOR,(ROUND_CEIL,())))),
POSITIVE = operation(float_div,(ROUND_CHOICE,(SCALE,()))),
NEGATIVE_INPUT = operation(float_div,(ANGLE,(SCALE,()))),
NEGATIVE_SHIFTED = operation(sub,(NEGATIVE_INPUT,(val(float,float("0.5")),()))),
NEGATIVE = operation(mult,(operation(ceil,(NEGATIVE_SHIFTED,())),(SCALE,()))),
RESULT = operation(ite,(operation(geq,(val(int,3),(val(int,0),()))),(POSITIVE,(NEGATIVE,())))).
execution_run(calc,calc).
Encoding 2: Python operation around the nonlinear operand
The Python operation hides only the final round call; its nonlinear argument is still grounded.
% Issue 414: only the final round call is delegated to Python.
variable_declare("input_a","input_a",fromFacts).
variable_declare(execution_input(calc,"input_a"),execution_input(calc,"input_a"),fromFacts).
variable_domain("input_a",val(float,float("600.0"))).
variable_domain("input_a",val(float,float("602.0"))).
variable_domain("input_a",val(float,float("604.0"))).
variable_domain(execution_input(calc,"input_a"),variable("input_a")).
variable_declare("input_b","input_b",fromFacts).
variable_declare(execution_input(calc,"input_b"),execution_input(calc,"input_b"),fromFacts).
variable_domain("input_b",val(float,float("100.0"))).
variable_domain("input_b",val(float,float("102.0"))).
variable_domain("input_b",val(float,float("104.0"))).
variable_domain(execution_input(calc,"input_b"),variable("input_b")).
variable_declare("input_c","input_c",fromFacts).
variable_declare(execution_input(calc,"input_c"),execution_input(calc,"input_c"),fromFacts).
variable_domain("input_c",val(float,float("120.0"))).
variable_domain("input_c",val(float,float("122.0"))).
variable_domain("input_c",val(float,float("124.0"))).
variable_domain(execution_input(calc,"input_c"),variable("input_c")).
execution_declare(calc,calc,assign("result",RESULT),("input_a",("input_b",("input_c",()))),("result",())) :-
SIDE_B = operation(add,(operation(float_div,(variable("input_a"),(val(int,2),()))),(operation(float_div,(variable("input_b"),(val(int,2),()))),()))),
SIDE_C = operation(add,(operation(float_div,(variable("input_a"),(val(int,2),()))),(operation(float_div,(variable("input_c"),(val(int,2),()))),()))),
SIDE_A = operation(add,(operation(float_div,(variable("input_b"),(val(int,2),()))),(operation(float_div,(variable("input_c"),(val(int,2),()))),(val(int,280),())))),
NUMERATOR = operation(sub,(operation(add,(operation(pow,(SIDE_B,(val(int,2),()))),(operation(pow,(SIDE_C,(val(int,2),()))),()))),(operation(pow,(SIDE_A,(val(int,2),()))),()))),
DENOMINATOR = operation(mult,(val(int,2),(operation(mult,(SIDE_B,(SIDE_C,()))),()))),
ANGLE_PART = operation(acos,(operation(float_div,(NUMERATOR,(DENOMINATOR,()))),())),
ANGLE = operation(sub,(val(float,float("360.0")),(operation(mult,(ANGLE_PART,(operation(float_div,(val(float,float("180.0")),(val(float,float("3.141592653589793")),()))),()))),()))),
RESULT = operation(python("round"),(ANGLE,(val(int,3),()))).
execution_run(calc,calc).
Encoding 3: complete Python fallback
The complete fallback hides the full calculation from grounding.
% Issue 414: evaluate the complete calculation in Python.
variable_declare("input_a","input_a",fromFacts).
variable_declare(execution_input(calc,"input_a"),execution_input(calc,"input_a"),fromFacts).
variable_domain("input_a",val(float,float("600.0"))).
variable_domain("input_a",val(float,float("602.0"))).
variable_domain("input_a",val(float,float("604.0"))).
variable_domain(execution_input(calc,"input_a"),variable("input_a")).
variable_declare("input_b","input_b",fromFacts).
variable_declare(execution_input(calc,"input_b"),execution_input(calc,"input_b"),fromFacts).
variable_domain("input_b",val(float,float("100.0"))).
variable_domain("input_b",val(float,float("102.0"))).
variable_domain("input_b",val(float,float("104.0"))).
variable_domain(execution_input(calc,"input_b"),variable("input_b")).
variable_declare("input_c","input_c",fromFacts).
variable_declare(execution_input(calc,"input_c"),execution_input(calc,"input_c"),fromFacts).
variable_domain("input_c",val(float,float("120.0"))).
variable_domain("input_c",val(float,float("122.0"))).
variable_domain("input_c",val(float,float("124.0"))).
variable_domain(execution_input(calc,"input_c"),variable("input_c")).
execution_declare(
calc,
calc,
statement_python("import math\nside_b = input_a / 2 + input_b / 2\nside_c = input_a / 2 + input_c / 2\nside_a = input_b / 2 + input_c / 2 + 280\nangle = math.acos((side_b**2 + side_c**2 - side_a**2) / (2*side_b*side_c))\nresult = round(360 - math.degrees(angle), 3)"),
("input_a",("input_b",("input_c",()))),
("result",())
).
execution_run(calc,calc).
Result
The corrected encodings all produce the same 27 public result atoms. The measurements below were generated in the constraint-handler Conda environment using the commands above.
| Representation | Ground lines | Rules | Atoms | Solve time |
|---|---|---|---|---|
| Native expansion | 314,562 | 314,586 | 191,125 | 4.833s |
| Python operation | 177,227 | 177,251 | 109,695 | 2.632s |
| Complete fallback | 4,152 | 4,176 | 3,499 | 0.153s |
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.