Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to get the absolute value of an expression? #163

Closed
gsingh93 opened this issue May 30, 2023 · 10 comments
Closed

Is it possible to get the absolute value of an expression? #163

gsingh93 opened this issue May 30, 2023 · 10 comments

Comments

@gsingh93
Copy link

I saw here that absolute values are not linear constraints and so can't directly be added: #35 (comment)

I just wanted to follow up to see if there was any workaround for this, or if there are suggestions for other ways to deal with the problem I'm facing.

I have an Arrow object in my layout that will point from one object to another object. Specifically, it starts at xy coordinates (expr1, expr2) and ends at (expr3, expr4). I would like to set the width of this object to abs(expr3 - expr1) and the height to abs(expr4 - expr2). The absolute value is needed here because it shouldn't matter in what direction the arrow is going, the width will be the same. And I don't know which of the expressions will be greater than the other at this point, so I can't reorder the subtraction to always make it positive.

Am I out of luck here or is there some other way to express this constraint with a linear constraint solver?

@gsingh93
Copy link
Author

gsingh93 commented May 30, 2023

Ah, as I wrote this, I thought maybe I can add all of these constraints (width == expr3 - expr1, width == expr1 - expr3, height == expr4 - expr2, height == expr2 - expr4), but make them all some strength other than "required" (which I've been using for everything else, which is why this idea didn't initially occur to me). Then with a required width >= 0 and height >= 0 constraint, I think this will do what I want.

I'll test it out and close this issue if it works.

@gsingh93
Copy link
Author

It worked, sorry for the noise.

@gsingh93 gsingh93 reopened this Jun 7, 2023
@gsingh93
Copy link
Author

gsingh93 commented Jun 7, 2023

There is a problem with the idea mentioned above. I was under the impression that the algorithm would always return a result that minimizes the number of broken constraints (weighted by strength). That is, if there are two "strong" constraints where one can be satisfied but not both, the solution will have at least one of those constraints satisfied. But it seems like this is not the case.

Here's an example:

from kiwisolver import Solver, Variable

x = Variable("x")

s = Solver()
s.addConstraint(x >= 0)
s.addConstraint((x == -10) | "strong")
s.addConstraint((x == 10) | "strong")

s.updateVariables()
print(x.value())

This prints 0.0, which violates both strong constraints, despite the fact that 10.0 would have only violated one strong constraint.

Similar to #164, it seems like order of constraints matters. For example:

from kiwisolver import Solver, Variable

x = Variable("x")

s = Solver()
s.addConstraint(x >= 0)
s.addConstraint((x == 10) | "strong")
s.addConstraint((x == -10) | "strong")

s.updateVariables()
print(x.value())

This prints out 10.0, but in a real application it won't be clear what order the constraints should be added in. So I'm stuck on my original question about absolute value. Maybe there's some way to pick custom strengths to get this to work, but I'm not sure what those strengths would be.

@sccolbert
Copy link
Member

sccolbert commented Jun 7, 2023 via email

@Qix-
Copy link
Contributor

Qix- commented Jun 7, 2023

Just for a quick reference, here are the defaults:

kiwi/kiwi/strength.h

Lines 18 to 34 in 26051dd

inline double create( double a, double b, double c, double w = 1.0 )
{
double result = 0.0;
result += std::max( 0.0, std::min( 1000.0, a * w ) ) * 1000000.0;
result += std::max( 0.0, std::min( 1000.0, b * w ) ) * 1000.0;
result += std::max( 0.0, std::min( 1000.0, c * w ) );
return result;
}
const double required = create( 1000.0, 1000.0, 1000.0 );
const double strong = create( 1.0, 0.0, 0.0 );
const double medium = create( 0.0, 1.0, 0.0 );
const double weak = create( 0.0, 0.0, 1.0 );

@gsingh93
Copy link
Author

gsingh93 commented Jun 7, 2023

This is actually the correct solution, because both “strong” constraints
have the same strength. So the solver has correctly minimized the error in
the system.

Sorry, I didn't follow this. Both strong constraints have the same strength. But both are violated. Why is this the minimal error when an additional strong constraint could be satisfied?

@sccolbert
Copy link
Member

sccolbert commented Jun 7, 2023 via email

@gsingh93
Copy link
Author

gsingh93 commented Jun 7, 2023

In your example, when one ‘strong’ constraint is solved, the opposite is broken by ‘twice as much’.

Are you referring to the second example? If so, I think that makes sense. Are you saying that when the x == 10 constraint is added it is satisfied, and then when the x == -10 constraint is added, the solver does not want to break the already satisfied constraint?

This wouldn't apply in the first example though, as when the x == -10 constraint is added, no strong constraints are satisfied. When the x == 10 constraint is added next, no constraints would be broken if the value of x was set to 10, and this additional constraint would be satisfied. This is what I'm confused about.

To understand this better I tried to dump the state of the Solver, but I don't really understand it:

from kiwisolver import Solver, Variable

x = Variable("x")

s = Solver()
print("Adding constraint: x >= 0")
s.addConstraint(x >= 0)
print(s.dumps())

print("Adding constraint: x == -10")
s.addConstraint((x == -10) | "strong")
print(s.dumps())

print("Adding constraint: x == 10")
s.addConstraint((x == 10) | "strong")
print(s.dumps())

s.updateVariables()
print(x.value())

Output:

Adding constraint: x >= 0
Objective
---------


Tableau
-------
v1 |  + 1 * s2

Infeasible
----------

Variables
---------
x = v1

Edit Variables
--------------

Constraints
-----------
1 * x + -0 >= 0  | strength = 1.001e+09



Adding constraint: x == -10
Objective
---------
 + 1e+06 * s2 + 2e+06 * e4

Tableau
-------
v1 |  + 1 * s2
e3 |  + 1 * s2 + 1 * e4

Infeasible
----------

Variables
---------
x = v1

Edit Variables
--------------

Constraints
-----------
1 * x + -0 >= 0  | strength = 1.001e+09
1 * x + 10 == 0  | strength = 1e+06



Adding constraint: x == 10
Objective
---------
 + 2e+06 * e4 + 2e+06 * e5

Tableau
-------
v1 |  + 1 * s2
e3 |  + 1 * s2 + 1 * e4
e6 |  + -1 * s2 + 1 * e5

Infeasible
----------

Variables
---------
x = v1

Edit Variables
--------------

Constraints
-----------
1 * x + -0 >= 0  | strength = 1.001e+09
1 * x + 10 == 0  | strength = 1e+06
1 * x + -10 == 0  | strength = 1e+06



0.0

I did some reading Simplex to understand the tableau, and then skimmed the Cassowary paper, but how the Objective and Tableau ends up with these values is still unclear to me. From this output, it is possible for me to manually compute the error of different solutions to better understand what's going on?

If you want one constraint to be more strong than another, you need to use numerical weights instead of string symbolic weights.

But back to the original problem, it's not that I know that I want one constraint to be stronger, it's that I have two constraints that are opposites and I know that only one will be satisfied, but I don't know which one. How would numerical constraints be any different than this (which again doesn't work because I won't know which one to make "weak"):

s.addConstraint(x >= 0)
s.addConstraint((x == -10) | "weak")
s.addConstraint((x == 10) | "strong")

With this x is 10.0, but I had prior knowledge that that constraint was the one that would be satisfied, which obviously won't be the case in real applications.

@sccolbert
Copy link
Member

sccolbert commented Jun 7, 2023 via email

@gsingh93
Copy link
Author

gsingh93 commented Jun 7, 2023

If you have a preference for positive values, try adding a weak constraint to some very large value.

This worked, thanks 🙂 I added the s.addConstraint((x == 1000000) | "weak") constraint after the other constraints. This changed the objective/tableau from:

Objective
---------
 + 2e+06 * e4 + 2e+06 * e5

Tableau
-------
v1 |  + 1 * s2
e3 |  + 1 * s2 + 1 * e4
e6 |  + -1 * s2 + 1 * e5

to

Objective
---------
 + 2e+06 * e4 + 2e+06 * e5 + 1 * e6 + 2 * e7

Tableau
-------
v1 |  + 1 * e5 + -1 * e6
s2 |  + 1 * e5 + -1 * e6
e3 |  + 1 * e4 + 1 * e5 + -1 * e6
e8 |  + -1 * e5 + 1 * e6 + 1 * e7

Again, I don't really get these equations yet, but I'll close this issue and keep investigating on my own.

@gsingh93 gsingh93 closed this as completed Jun 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants