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

Swift wrapper #5

Open
hfossli opened this issue Nov 19, 2016 · 27 comments
Open

Swift wrapper #5

hfossli opened this issue Nov 19, 2016 · 27 comments

Comments

@hfossli
Copy link

hfossli commented Nov 19, 2016

I am looking into creating a wrapper around this api in Swift. I'm facing some difficult choices.

All variables are created using a solver and the solvers am_Allocf. This makes it hard to create an object oriented model as they are tied to the solver pretty closely. This is seen throughout the API as well. Example:

AM_API int  am_add    (am_Constraint *cons)

To me I now think it is not feasible to create an object oriented swift model around Amoeba unless variables, terms and constraints can be created independently from the solver.

Methods like

AM_API int  am_add    (am_Constraint *cons)

would have to be rewritten to

AM_API int  am_add    (am_Constraint *cons, am_Solver *solver)

This is not a request to change anything. I'm just thinking loudly :) amoeba.lua is not a wrapper, but the whole implementation written in Lua if I understand correctly?

@starwing
Copy link
Owner

starwing commented Nov 19, 2016

I'm writing a C++11 wrapper, too. And I also find this issue.

the amoeba.lua is a pure Lua implement, but lua_amoeba.c is real a Lua binding for C implement of amoeba. In this binding, I still need a solver to create variable/constraint.

this API is intend to solve the pure C memory management issue. all objects are under the same memory pool. so a solver is likely a arena. you could allocate any object you want in this arena, and after am_delsolver(), all objects are freed.

So, in my C++11 solution, I'm trying to construct constraint in pure C++ way, recording all variable and terms it used, and when add to solver, it will be convert into amoeba object. In this way, you could create object apart from solver itself.

Another way is always using the amoeba object, something like this:

Solver solver;
Variable xl(solver), xm(solver), xr(solver);

solver.add((xl + 10 < xr) / amoeba::Required);

notice that you could add a solver pointer into variable, so creating a constraint object is easy. this is the way of the Lua binding.

I don't know which is better, amoeba is a pure C library, means I must manage all memory it use. So I could not create objects part from it (or you had call am_del** on any object you created, just like other C libraries).

We could work out some better solution, any feedback are welcome :-)

@starwing
Copy link
Owner

A idea, if I add some routines to copy/assignment data across solver, maybe we could make a static solver for allocate temporary objects, and copy it into the solver it will be added.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

So, in my C++11 solution, I'm trying to construct constraint in pure C++ way, recording all variable and terms it used, and when add to solver, it will be convert into amoeba object. In this way, you could create object apart from solver itself.

Yep, trying that out now. Seems promising.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

Can you explain int am_mergeconstraint(am_Constraint *cons, am_Constraint *other, double multiplier)? Does it just add all terms from another constraint?

What happens when a constraint already is added to the solver and you change the constraint? Example: add a new term after the constraint has been added.

@starwing
Copy link
Owner

  1. yes, it merge two constraint from same solver.
    merge(a > 10, a > 20) == 2*a > 30
    if you treat constraint as expression, merge means add two expression.
  2. it will return AM_FAILED, after constraint added to solver, all changes to it will fail and return failure.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

  1. I like it. This is different from Kiwi, but I like it!
  2. Brilliant!

@starwing
Copy link
Owner

constraint is just like expression, e.g. x >= 10 means x - 10 >= 0, so constraint only have two state (kiwi has three state, >=, <= and ==)

for example, if you want spec a + b <= 10 + c, it become this:

newconstant() ==> 0.0 >= 0.0
addterm(a, 1.0) ==> a >= 0.0
addterm(b, 1.0) ==> a + b >= 0.0
setrelation(<=) ==> -a -b >= 0.0
addconstant(10.0) ==> -a -b + 10.0 >= 0.0
addconstant(c, 1.0) ==> -a -b + 10.0 + c >= 0.0

notice that if you set relation to <= or ==, all terms before this will be negative, and following terms are positive.
and if you set relation to >=, all terms before this remaining position, and following added terms will be negative when you add.

so, the constraint always in form term >= 0 or term == 0, this makes merge constraint possible.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

Thanks for that thorough explanation! Very useful!

I am making progress here. This is f***ing amazing! This is done using only pointers and no wrappers (except for the solver)

        do {
            let solver = AMSolver() // solver is a swift solver class
            let x = solver.createVariable() // x is a `am_Variable *`
            let c = solver.createConstraint(strength: Strength.required) // c is a `am_Constraint *`
            try c.setRelation(.eq)
            try c.addTerm(variable: x, multiplier: 1.0)
            try c.addConstant(-10)
            try c.addToSolver()
            print("value of x: \(x.value)")
        } catch let error {
            print("Error \(error)")
        }

Outputs

value of x: 10.0

I also tested creating wrappers around variable, term and constraint

        do {
            let solver = Solver() // solver is a swift `Solver` class
            let x = Variable() // x is a swift `Variable` class
            let c = Constraint() // c is a swift `Constraint` class
            c.relation = .eq
            c.strength = Strength.required
            c.add(term: Term(variable: x, multiplier: 1.0))
            c.add(constant: -10)
            try solver.add(constraint: c)
            print("value of x: \(solver.value(x))")
        } catch let error {
            print("Error \(error)")
        }

Outputs

value of x: 10.0

I will check more closely wether I need this object oriented approach or if I kind just extend the pointers in Swift. I kind of like the first alternative. It is the least memory intensive, but I have a couple of questions. What happens if someone uses c or `x´ after the solver has been deleted?

I'm impressed how easy it is to use this c api's like this from Swift.

@starwing
Copy link
Owner

you can't.

the solver is just like a arena, all object allocated by it. after delete solver, all object relate with it (variables, constraints, etc) are all freed, if you still have these pointer, they will become wild pointer.

so in Lua/C++11 wrapper, I make a weak-referenced mapping between object and solver, if delete the solver, I will clear all pointers in object.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

I see! But what if I call am_usevariable() and friends? Still wild pointers?

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

notice that if you set relation to <= or ==, all terms before this will be negative, and following terms are positive.
and if you set relation to >=, all terms before this remaining position, and following added terms will be negative when you add.

This logic will need to be duplicated/added to a pure swift constraint I guess. Very useful that you took the time to explain it.

@starwing
Copy link
Owner

yes, even if you call usevariable.

And you needn't duplicate this logic, even needn't do e.g. Merge the same variable. I just recorded all expressions using a vector, and leave things when add constraints into solver.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

Good idea. Can I see the preliminary c++ code?

@starwing
Copy link
Owner

Can swift override operators? If it can't,you just need bind the current interface, and if it can, you could record the expressions, yet. Because operators always create new object, amoeba objects are cheap, but swift objects may cheaper.

@hfossli
Copy link
Author

hfossli commented Nov 19, 2016

Yep, I'm creating operator overloads as we speak :)

@starwing
Copy link
Owner

I'm still working on it(and changing API when necessary), after finished I will upload it. And if you need new functionality I can add them for you. :-)

@starwing
Copy link
Owner

You could do some benchmarks to determine whether swift or amoeba object cheaper, and deciding how to implement operator overrides

@hfossli
Copy link
Author

hfossli commented Nov 20, 2016

I'm currently at this point

let solver = Solver()
let v1 = Variable()
let v2 = Variable()
try solver.add(v1 / 2 == v2 * 3)
try solver.add(v1 + v1 - v1 == 8)
print("value of v1: \(solver.value(v1))")
print("value of v2: \(solver.value(v2))")
value of v1: -8.0
value of v2: -1.33333333333333

http://i.giphy.com/l3E6qj3wFoUiXbxuw.gif

In native swift classes I have now Solver, Variable, Term, Expression, Constraint. I had to create Expression in order to compile time enforce that it isn't possible to say a == b <= 3 etc.

@starwing
Copy link
Owner

starwing commented Nov 20, 2016

the correct way to avoid a == b <= 3 is using typing:

Expression = Variable
Expression = constant
Expression = Expression + Expression
Expression = Expression - Expresxsion
Expression = Expression * constant
Expression = constant * Expression
Expression = Expression / constant
Constraint = Expression >= Expression
Constraint = Expression <= Expression
Constraint = Expression == Expression
Constraint = Constraint / constant       (set strength for constraint)

this is how kiwi and my wrapper done.

@hfossli
Copy link
Author

hfossli commented Nov 20, 2016

Yep, that's what Rhea did as well.
søn. 20. nov. 2016 kl. 14.11 skrev Xavier Wang notifications@github.com:

the correct way to avoid a == b <= 3 is using typing:

Expression = Variable
Expression = Expression + Expression
Expression = Expression - Expresxsion
Expression = Expression * constant
Expression = Expression / constant
Constraint = Expression >= Expression
Constraint = Expression <= Expression
Constraint = Expression == Expression

this is how kiwi and my wrapper done.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#5 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ADe7602Bsb8Va7TB9aMKGxS87oL9M8Uzks5rAEbkgaJpZM4K3P9q
.

@hfossli
Copy link
Author

hfossli commented Dec 2, 2016

Currently

  • you have the core implementation in c
  • you have a lua wrapper
  • you have a c++ wrapper
  • I am writing a swift wrapper

Should we keep all in one repo or should it be in separate? I'm in favor of keeping them all together if the API is similar. My swift wrapper may support string parsing etc as well, but I don't know if that's too much.

Any thoughts on this?

@starwing
Copy link
Owner

starwing commented Dec 7, 2016

Yes, I prefer together, too. should we build separate folders for different bindings? pull requests welcome :-)

@hfossli
Copy link
Author

hfossli commented Dec 8, 2016

Sounds good. I'm packing quite a lot into the swift wrapper. So I think I will divide my code as following

  • amoeba-swift (just a swift wrapper)
  • ameoba-swift-parsing (string to constraints parsing, support for conditionals, ++)

It may be necessary to place a amoeba.podspec file on root and maybe also several additional files. If that's a deal breaker then I fully understand.

@starwing
Copy link
Owner

yes, that seems good.

@hfossli
Copy link
Author

hfossli commented Dec 12, 2016

How's the c++ project going btw?

@starwing
Copy link
Owner

because C++ project only one header, so I plan to put it direct into this project :-)

I have not much time for the end of year :-( so I will put the source later, as soon as I have time, sorry.

@hfossli
Copy link
Author

hfossli commented Dec 14, 2016

I see. How big is that file (line count)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants