potassco / potassco/constraint-handler

Fallbackk faster than constraint handler native version

Open
#414 4 comments 0 reactions 1 assignee View on GitHub

@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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.