starwing / amoeba

a Cassowary constraint solving algorithm implements in pure C.
MIT License
179 stars 24 forks source link

Is there a way of telling when a variable is underconstrained? #6

Closed hfossli closed 4 days ago

hfossli commented 7 years ago

I would like to ask the solver wether a variable is underconstrained or not. Maybe I can try to add a constraint for a variable and see if I get an error? Is there better ways?

starwing commented 7 years ago

use am_hasvariable()

but notice that if you add a variable to solver and remove it, am_hasvariable() still return true. because I don't know whether other constraint in solver use this variable.

hfossli commented 7 years ago

a >= 5 In this can hasvariable will be true, right, but the value is still underconstrained.

Another example a + b == 100 Both a and b are underconstrained

starwing commented 7 years ago

yes, in first case hasvariable(a) will return true, and in second case a and b all return true. if you create a variable, hasvariable() to it will return false, but after you add the constraint containing it to solver, hasvariable() will return true. but after you remove all constraints containing this variable from solver, hasvariable() will still return true.

hfossli commented 7 years ago

So it sounds like am_hasvariable() is not a viable solution...

starwing commented 7 years ago

but I don't have other idea to do that. this API is designed to do this, but to track variable need more cost. do you really need this function? I will think to implement if you really want it.

hfossli commented 7 years ago

Think about it :)

The designers in my workplace would love such a feature. Our designers write json like this

      "title.left == width / 2 - title.width / 2 - 10",
      "title.height == title.intrinsicHeight",
      "title.width == title.intrinsicWidth",
      "title.top == category.bottom",

Sometimes they forget to constraint all four variables (top, left, width, height) either explicitly or implicitly (using e.g. right, bottom). In a case like this the designer can get confused if he forgets one constraint

      "title.left == width / 2 - title.width / 2 - 10",
      "title.height == title.intrinsicHeight",
      "title.width == title.intrinsicWidth",

Having a warning telling him 'title' is not constrained for variable 'top' could be very useful.

hfossli commented 7 years ago
am_isunderconstrained(a)

should return TRUE

a >= 1

should return TRUE

a >= 1
a <= 10

should return FALSE

a >= 1
a <= 10
a == 5 !strong

should return FALSE

a == 5

should return TRUE

(nothing)

If you are going to look at this I can write tests for you. :)

starwing commented 7 years ago

why a >= 1 return TRUE but a >= 1 && a <= 10 return FALSE?

hfossli commented 7 years ago

I think I am not communicating clearly. Statement is above code (not under)

starwing commented 7 years ago

So, why (nothing) returns TRUE and a == 5 returns FALSE?

hfossli commented 7 years ago

Sorry, I'll try to communicate more clearly.

I'm thinking of underconstrained variables as variables that have a multiple correct answers.


a >= 1

In this case a can be any value above 1 and is thus underconstrained. We don't know if it really should be 1, 2, 3, 4, 5, 6... or any other value above 1, right?


a == 1

In this case a can only be 1 and is thus not underconstrained.


a >= 1
a <= 10

In this case a can be any value above 1 and below 10 and is thus underconstrained. We don't know if it really should be 1, 2, 3, 4, 5, 6, 7, 8 or 9 or any other value in between like 5.12315.


a == b
b >= 5

In this case a can be any value above 5 and is thus underconstrained.


b == c
c == d
d == 5

In this case a can be any value and is thus underconstrained because there's no constraints for a. b, c and d on the other is not underconstrained.


Let me know if I can explain better.

starwing commented 7 years ago

So, if a can be any value, then it's underconstrainted, but if it can only be the exactly one value, so it's not underconstrainted, right?

and, what if: a == 30 weak a >= 50 strong

and then a = 50, is a underconstrainted?

hfossli commented 7 years ago

So, if a can be any value, then it's underconstrainted, but if it can only be the exactly one value, so it's not underconstrainted, right?

Yep, that's what I'm thinking

and then a = 50, is a underconstrainted?

No, it is not underconstrained. It is sufficiently constrained to be 50 because a == 30 weak pulls it to the lowest possible value while satisfying constraint a >= 50 strong

hfossli commented 7 years ago

You posed a really good question. I thought maybe I could write something like this

    public func isUnderconstrained(_ variable: Variable) -> Bool {
        do {
            let currentValue: Double = try value(variable)
            let dummyConstraint: Constraint = variable == currentValue
            try add(dummyConstraint)
            try remove(dummyConstraint)
            return true
        } catch let error {
            return false
        }
    }

(😬 this will probably be a little bit expensive?)

Which makes this test pass

    func testUnderconstrained() {
        let solver = Solver(debug: true)
        do {
            let v = Variable()
            XCTAssertTrue(solver.isUnderconstrained(v))
            let constraint = v == 5
            try solver.add(constraint)
            XCTAssertFalse(solver.isUnderconstrained(v))
            try solver.remove(constraint)
            XCTAssertTrue(solver.isUnderconstrained(v))
        } catch let error as AmoebaError {
            switch error {
            case .unbound:
                break
            default:
                XCTFail("Didn't expect \(error)")
                break
            }
        } catch let error {
            XCTFail("Didn't expect \(error)")
        }
    }

But that doesn't work when using constraints which isn't using required strength.

    func testUnderconstrained2() {
        let solver = Solver(debug: true)
        do {
            let v = Variable()
            XCTAssertTrue(solver.isUnderconstrained(v))
            let constraint = (v == 5).strong()
            try solver.add(constraint)
            XCTAssertFalse(solver.isUnderconstrained(v)) // <<< fails
            try solver.remove(constraint)
            XCTAssertTrue(solver.isUnderconstrained(v))
        } catch let error as AmoebaError {
            switch error {
            case .unbound:
                break
            default:
                XCTFail("Didn't expect \(error)")
                break
            }
        } catch let error {
            XCTFail("Didn't expect \(error)")
        }
    }
dumblob commented 7 years ago

I find the question about underconstraint (its meaning and its influence on the workflow of designers) quite important. Any more thoughts on this?

starwing commented 7 years ago

I need rethought this thing, because now I changed the API it export. Can you give me some insight about how you will use this function?

hfossli commented 7 years ago

It is only useful at the time of writing constraints. Give the designers feedback on which variables that is not being constrained sufficiently to have a deterministic behavior.

Example

A designer places a textbox on screen. The textbox is placed given the constraints

left == 0
top == 0
height == automatic_height

But then nothing shows up. The designer scratches his head because he is not aware that it is missing a constraint for the width.

He should be given a cue (on demand) about how to sufficiently constrain this text box.

He could write

right == 200

Or

width == 200

in order to be sufficiently constrained. Now the textbox is visible.

Having some API to tell our designers of his mistake is what we are asking for – ~as opposed to fixing/adding constraints automatically~

starwing commented 7 years ago

But if you don't add width to amoeba, it can't know whether it need under constraint. It's somewhat "business related" in this case.

dumblob commented 7 years ago

For me it's for the same reason as @hfossli described. I understand, that it's not inherently needed for Amoeba, but we might treat this as an optional extension to the Amoeba functionality or even API. So what I'm asking is a built-in support for the possibility to create such an extension.

The extension might be also in a form of a flag indicating how the rule evaluation should proceed - we might call it somehow like "a stronger requirement on rules".

hfossli commented 7 years ago

I think I have an idea. It won't be efficient computation wise, but it will probably do the job. I'll try to post next week

starwing commented 7 years ago

@dumblob this is acceptable I think. can you give some examples or definitions for the plugin interface?

dumblob commented 7 years ago

I've thought about it, but everything I came up with was not good enough. I think we should wait the few days for @hfossli and his new idea and proceed with the discussion afterwards. Should something worth mentioning come to my mind in the mean time, I'll post it here.

hfossli commented 7 years ago

I think my new idea might be flawed after all... :( maybe @kongtomorrow has some ideas?

hfossli commented 7 years ago

My suggestion is to improve one of my earlier suggestions in this thread. Introduce a new function AM_API bool am_isunderconstrained (am_Variable *var);

It could do the following:

  1. Check if the variable is known to the solver and return true if not
  2. Try to add a weak constraint with a high positive value and return false if value didn't change
  3. Try to add a weak constraint with a high negative value and return false if value didn't change
  4. Repeat step 2 with medium strength constraint
  5. Repeat step 3 with medium strength constraint
  6. Repeat step 2 with strong strength constraint
  7. Repeat step 3 with strong strength constraint
  8. Repeat step 2 with required strength constraint
  9. Repeat step 3 with required strength constraint
  10. return true

I'm sure there are edge cases that won't be covered. It might be quite expensive, but it should only be done at the time of writing constraints (oppsoed to consuming).

dumblob commented 6 years ago

@hfossli did you succeed to implement your last proposal? Anything I could take a look at and try it to see what are the caveats of this logic?

Any comments @kongtomorrow?

hfossli commented 6 years ago

I ran out of time and that feature was put in backlog unfortunately. I think I made it work the way I described it. I guess it messes up stays and edit variables though.