Open hfossli opened 7 years ago
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 :-)
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.
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.
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.
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.
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.
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.
I see! But what if I call am_usevariable() and friends? Still wild pointers?
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.
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.
Good idea. Can I see the preliminary c++ code?
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.
Yep, I'm creating operator overloads as we speak :)
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. :-)
You could do some benchmarks to determine whether swift or amoeba object cheaper, and deciding how to implement operator overrides
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
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.
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.
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 https://github.com/starwing/amoeba/issues/5#issuecomment-261777532, or mute the thread https://github.com/notifications/unsubscribe-auth/ADe7602Bsb8Va7TB9aMKGxS87oL9M8Uzks5rAEbkgaJpZM4K3P9q .
Currently
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?
Yes, I prefer together, too. should we build separate folders for different bindings? pull requests welcome :-)
Sounds good. I'm packing quite a lot into the swift wrapper. So I think I will divide my code as following
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.
yes, that seems good.
How's the c++ project going btw?
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.
I see. How big is that file (line count)?
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:
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
would have to be rewritten to
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?