local kiwi = {} local ffi = require("ffi") local ckiwi do local cpath, err = package.searchpath("ckiwi", package.cpath) if cpath == nil then error("kiwi dynamic library 'ckiwi' not found\n" .. err) end ckiwi = ffi.load(cpath) end ffi.cdef([[ enum KiwiErrKind { KiwiErrNone, KiwiErrUnsatisfiableConstraint = 1, KiwiErrUnknownConstraint, KiwiErrDuplicateConstraint, KiwiErrUnknownEditVariable, KiwiErrDuplicateEditVariable, KiwiErrBadRequiredStrength, KiwiErrInternalSolverError, KiwiErrAlloc, KiwiErrNullObject, KiwiErrUnknown, }; enum KiwiRelOp { LE, GE, EQ }; typedef struct KiwiVarRefType* KiwiVarRef; typedef struct KiwiConstraintRefType* KiwiConstraintRef; typedef struct KiwiTerm { KiwiVarRef var; double coefficient; } KiwiTerm; typedef struct KiwiExpression { double constant; int term_count; KiwiTerm terms_[?]; } KiwiExpression; typedef struct KiwiErr { enum KiwiErrKind kind; const char* message; bool must_free; } KiwiErr; typedef struct KiwiSolver KiwiSolver; KiwiVarRef kiwi_var_new(const char* name); void kiwi_var_del(KiwiVarRef var); KiwiVarRef kiwi_var_clone(KiwiVarRef var); const char* kiwi_var_name(KiwiVarRef var); void kiwi_var_set_name(KiwiVarRef var, const char* name); double kiwi_var_value(KiwiVarRef var); void kiwi_var_set_value(KiwiVarRef var, double value); int kiwi_var_eq(KiwiVarRef var, KiwiVarRef other); void kiwi_expression_del_vars(KiwiExpression* expr); KiwiConstraintRef kiwi_constraint_new( const KiwiExpression* lhs, const KiwiExpression* rhs, enum KiwiRelOp op, double strength ); void kiwi_constraint_del(KiwiConstraintRef constraint); KiwiConstraintRef kiwi_constraint_clone(KiwiConstraintRef constraint); double kiwi_constraint_strength(KiwiConstraintRef constraint); enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint); bool kiwi_constraint_violated(KiwiConstraintRef constraint); int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size); KiwiSolver* kiwi_solver_new(); void kiwi_solver_del(KiwiSolver* sp); const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* sp, KiwiConstraintRef constraint); bool kiwi_solver_has_constraint(const KiwiSolver* sp, KiwiConstraintRef constraint); const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* sp, KiwiVarRef var, double strength); const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* sp, KiwiVarRef var); bool kiwi_solver_has_edit_var(const KiwiSolver* sp, KiwiVarRef var); const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* sp, KiwiVarRef var, double value); void kiwi_solver_update_vars(KiwiSolver* sp); void kiwi_solver_reset(KiwiSolver* sp); void kiwi_solver_dump(const KiwiSolver* sp); char* kiwi_solver_dumps(const KiwiSolver* sp); void free(void *); ]]) local strformat = string.format local ffi_gc, ffi_istype, ffi_new, ffi_string = ffi.gc, ffi.istype, ffi.new, ffi.string local concat = table.concat local has_table_new, new_tab = pcall(require, "table.new") if not has_table_new or type(new_tab) ~= "function" then new_tab = function() return {} end end ---@alias kiwi.ErrKind ---| '"KiwiErrNone"' # No error. ---| '"KiwiErrUnsatisfiableConstraint"' # The given constraint is required and cannot be satisfied. ---| '"KiwiErrUnknownConstraint"' # The given constraint has not been added to the solver. ---| '"KiwiErrDuplicateConstraint"' # The given constraint has already been added to the solver. ---| '"KiwiErrUnknownEditVariable"' # The given edit variable has not been added to the solver. ---| '"KiwiErrDuplicateEditVariable"' # The given edit variable has already been added to the solver. ---| '"KiwiErrBadRequiredStrength"' # The given strength is >= required. ---| '"KiwiErrInternalSolverError"' # An internal solver error occurred. ---| '"KiwiErrAlloc"' # A memory allocation error occurred. ---| '"KiwiErrNullObject"' # A method was invoked on a null or empty object. ---| '"KiwiErrUnknown"' # An unknown error occurred. kiwi.ErrKind = ffi.typeof("enum KiwiErrKind") --[[@as kiwi.ErrKind]] ---@alias kiwi.RelOp ---| '"LE"' # <= (less than or equal) ---| '"GE"' # >= (greater than or equal) ---| '"EQ"' # == (equal) kiwi.RelOp = ffi.typeof("enum KiwiRelOp") kiwi.Strength = { REQUIRED = 1001001000.0, STRONG = 1000000.0, MEDIUM = 1000.0, WEAK = 1.0, } --- Create a custom constraint strength. ---@param a number: Scale factor 1e6 ---@param b number: Scale factor 1e3 ---@param c number: Scale factor 1 ---@param w? number: Weight ---@return number ---@nodiscard function kiwi.Strength.create(a, b, c, w) local function clamp(n) return math.max(0, math.min(1000, n)) end w = w or 1.0 return clamp(a * w) * 1000000.0 + clamp(b * w) * 1000.0 + clamp(c * w) end local Var = ffi.typeof("struct KiwiVarRefType") --[[@as kiwi.Var]] kiwi.Var = Var local Term = ffi.typeof("struct KiwiTerm") --[[@as kiwi.Term]] kiwi.Term = Term local Expression = ffi.typeof("struct KiwiExpression") --[[@as kiwi.Expression]] kiwi.Expression = Expression local Constraint = ffi.typeof("struct KiwiConstraintRefType") --[[@as kiwi.Constraint]] kiwi.Constraint = Constraint ---@param expr kiwi.Expression ---@param var kiwi.Var ---@param coeff number? ---@nodiscard local function add_expr_term(expr, var, coeff) local ret = ffi_gc(ffi_new(Expression, expr.term_count + 1), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] for i = 0, expr.term_count - 1 do local st = expr.terms_[i] --[[@as kiwi.Term]] local dt = ret.terms_[i] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(st.var) dt.coefficient = st.coefficient end local dt = ret.terms_[expr.term_count] dt.var = ckiwi.kiwi_var_clone(var) dt.coefficient = coeff or 1.0 ret.constant = expr.constant ret.term_count = expr.term_count + 1 return ret end ---@param constant number ---@param var kiwi.Var ---@param coeff number? ---@nodiscard local function new_expr_one_temp(constant, var, coeff) local ret = ffi_new(Expression, 1) --[[@as kiwi.Expression]] local dt = ret.terms_[0] dt.var = ckiwi.kiwi_var_clone(var) dt.coefficient = coeff or 1.0 ret.constant = constant ret.term_count = 1 return ret end ---@param constant number ---@param var kiwi.Var ---@param coeff number? ---@nodiscard local function new_expr_one(constant, var, coeff) return ffi_gc(new_expr_one_temp(constant, var, coeff), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] end ---@param constant number ---@param var1 kiwi.Var ---@param var2 kiwi.Var ---@param coeff1 number? ---@param coeff2 number? ---@nodiscard local function new_expr_pair(constant, var1, var2, coeff1, coeff2) local ret = ffi_gc(ffi_new(Expression, 2), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] local dt = ret.terms_[0] dt.var = ckiwi.kiwi_var_clone(var1) dt.coefficient = coeff1 or 1.0 dt = ret.terms_[1] dt.var = ckiwi.kiwi_var_clone(var2) dt.coefficient = coeff2 or 1.0 ret.constant = constant ret.term_count = 2 return ret end local Strength = kiwi.Strength local REQUIRED = Strength.REQUIRED ---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param op kiwi.RelOp ---@param strength? number ---@nodiscard local function rel(lhs, rhs, op, strength) local function to_expr(o) if ffi_istype(Expression, o) then return o --[[@as kiwi.Expression]] elseif type(o) == "number" then if o == 0 then return nil end local ret = ffi_new(Expression, 0) --[[@as kiwi.Expression]] ret.constant = o ret.term_count = 0 return ret end local var local coeff = 1.0 if ffi_istype(Var, o) then var = o --[[@as kiwi.Var]] elseif ffi_istype(Term, o) then var = o.var coeff = o.coefficient else error("Expected Expression|Term|Var|number, got " .. type(o) .. " instead") end return new_expr_one_temp(0.0, var, coeff) end return ffi_gc( ckiwi.kiwi_constraint_new(to_expr(lhs), to_expr(rhs), op, strength or REQUIRED), ckiwi.kiwi_constraint_del ) --[[@as kiwi.Constraint]] end --- Define a constraint with expressions as `a <= b`. ---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param strength? number ---@nodiscard function kiwi.le(lhs, rhs, strength) return rel(lhs, rhs, "LE", strength) end --- Define a constraint with expressions as `a >= b`. ---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param strength? number ---@nodiscard function kiwi.ge(lhs, rhs, strength) return rel(lhs, rhs, "GE", strength) end --- Define a constraint with expressions as `a == b`. ---@param lhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param rhs kiwi.Expression|kiwi.Term|kiwi.Var|number ---@param strength? number ---@nodiscard function kiwi.eq(lhs, rhs, strength) return rel(lhs, rhs, "EQ", strength) end do --- Variables are the values the constraint solver calculates. ---@class kiwi.Var: ffi.cdata* ---@overload fun(name: string): kiwi.Var ---@operator mul(number): kiwi.Term ---@operator div(number): kiwi.Term ---@operator unm: kiwi.Term ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression local Var_cls = { le = kiwi.le, ge = kiwi.ge, eq = kiwi.eq, --- Change the name of the variable. ---@type fun(self: kiwi.Var, name: string) set_name = ckiwi.kiwi_var_set_name, --- Get the current value of the variable. ---@type fun(self: kiwi.Var): number value = ckiwi.kiwi_var_value, --- Set the value of the variable. ---@type fun(self: kiwi.Var, value: number) set = ckiwi.kiwi_var_set_value, } --- Get the name of the variable. ---@return string ---@nodiscard function Var_cls:name() return ffi_string(ckiwi.kiwi_var_name(self)) end --- Create a term from this variable. ---@param coefficient number? ---@return kiwi.Term ---@nodiscard function Var_cls:toterm(coefficient) return Term(self, coefficient) end --- Create a term from this variable. ---@param coefficient number? ---@param constant number? ---@return kiwi.Expression ---@nodiscard function Var_cls:toexpr(coefficient, constant) return new_expr_one(constant or 0.0, self, coefficient) end local Var_mt = { __index = Var_cls, } function Var_mt:__new(name) return ffi_gc(ckiwi.kiwi_var_new(name), ckiwi.kiwi_var_del) end function Var_mt.__mul(a, b) if type(a) == "number" then return Term(b, a) elseif type(b) == "number" then return Term(a, b) end error("Invalid var *") end function Var_mt.__div(a, b) assert(type(b) == "number", "Invalid var /") return Term(a, 1.0 / b) end function Var_mt:__unm() return Term(self, -1.0) end function Var_mt.__add(a, b) if ffi_istype(Var, b) then if type(a) == "number" then return new_expr_one(a, b) else return new_expr_pair(0.0, a, b) end elseif ffi_istype(Term, b) then return new_expr_pair(0.0, b.var, a, b.coefficient) elseif ffi_istype(Expression, b) then return add_expr_term(b, a) elseif type(b) == "number" then return new_expr_one(b, a) end error("Invalid var +") end function Var_mt.__sub(a, b) return Var_mt.__add(a, -b) end function Var_mt:__tostring() return self:name() .. "(" .. self:value() .. ")" end ffi.metatype(Var, Var_mt) end do --- Terms are the components of an expression. --- Each term is a variable multiplied by a constant coefficient (default 1.0). ---@class kiwi.Term: ffi.cdata* ---@overload fun(var: kiwi.Var, coefficient: number?): kiwi.Term ---@field var kiwi.Var ---@field coefficient number ---@operator mul(number): kiwi.Term ---@operator div(number): kiwi.Term ---@operator unm: kiwi.Term ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression local Term_cls = { le = kiwi.le, ge = kiwi.ge, eq = kiwi.eq, } ---@return number ---@nodiscard function Term_cls:value() return self.coefficient * self.var:value() end --- Create an expression from this term. ---@param constant number? ---@return kiwi.Expression function Term_cls:toexpr(constant) return new_expr_one(constant or 0.0, self.var, self.coefficient) end local Term_mt = { __index = Term_cls } function Term_mt.__new(T, var, coefficient) return ffi_gc(ffi_new(T, ckiwi.kiwi_var_clone(var), coefficient or 1.0), function(term) ckiwi.kiwi_var_del(term.var) end) end function Term_mt.__mul(a, b) if type(b) == "number" then return Term(a.var, a.coefficient * b) elseif type(a) == "number" then return Term(b.var, b.coefficient * a) end error("Invalid term *") end function Term_mt.__div(a, b) assert(type(b) == "number", "Invalid term /") return Term(a.var, a.coefficient / b) end function Term_mt:__unm() return Term(self.var, -self.coefficient) end function Term_mt.__add(a, b) if ffi_istype(Var, b) then return new_expr_pair(0.0, a.var, b, a.coefficient) elseif ffi_istype(Term, b) then if type(a) == "number" then return new_expr_one(a, b.var, b.coefficient) else return new_expr_pair(0.0, a.var, b.var, a.coefficient, b.coefficient) end elseif ffi_istype(Expression, b) then return add_expr_term(b, a.var, a.coefficient) elseif type(b) == "number" then return new_expr_one(b, a.var, a.coefficient) end error("Invalid term + op") end function Term_mt.__sub(a, b) return Term_mt.__add(a, -b) end function Term_mt:__tostring() return tostring(self.coefficient) .. " " .. self.var:name() end ffi.metatype(Term, Term_mt) end do --- Expressions are a sum of terms with an added constant. ---@class kiwi.Expression: ffi.cdata* ---@overload fun(terms: kiwi.Term[], constant: number?): kiwi.Expression ---@field constant number ---@field package term_count number ---@field package terms_ ffi.cdata* ---@operator mul(number): kiwi.Expression ---@operator div(number): kiwi.Expression ---@operator unm: kiwi.Expression ---@operator add(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression ---@operator sub(kiwi.Expression|kiwi.Term|kiwi.Var|number): kiwi.Expression local Expression_cls = { le = kiwi.le, ge = kiwi.ge, eq = kiwi.eq, } ---@param expr kiwi.Expression ---@param constant number ---@nodiscard local function mul_expr_constant(expr, constant) local ret = ffi_gc(ffi_new(Expression, expr.term_count), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] for i = 0, expr.term_count - 1 do local st = expr.terms_[i] --[[@as kiwi.Term]] local dt = ret.terms_[i] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(st.var) dt.coefficient = st.coefficient * constant end ret.constant = expr.constant * constant ret.term_count = expr.term_count return ret end ---@param a kiwi.Expression ---@param b kiwi.Expression ---@nodiscard local function add_expr_expr(a, b) local a_count = a.term_count local b_count = b.term_count local ret = ffi_gc(ffi_new(Expression, a_count + b_count), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] for i = 0, a_count - 1 do local dt = ret.terms_[i] --[[@as kiwi.Term]] local st = a.terms_[i] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(st.var) dt.coefficient = st.coefficient end for i = 0, b_count - 1 do local dt = ret.terms_[a_count + i] --[[@as kiwi.Term]] local st = b.terms_[i] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(st.var) dt.coefficient = st.coefficient end ret.constant = a.constant + b.constant ret.term_count = a_count + b_count return ret end ---@param expr kiwi.Expression ---@param constant number ---@nodiscard local function new_expr_constant(expr, constant) local ret = ffi_gc(ffi_new(Expression, expr.term_count), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] for i = 0, expr.term_count - 1 do local dt = ret.terms_[i] --[[@as kiwi.Term]] local st = expr.terms_[i] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(st.var) dt.coefficient = st.coefficient end ret.constant = constant ret.term_count = expr.term_count return ret end ---@return number ---@nodiscard function Expression_cls:value() local sum = self.constant for i = 0, self.term_count - 1 do sum = sum + self.terms_[i]:value() end return sum end ---@return kiwi.Term[] ---@nodiscard function Expression_cls:terms() local terms = new_tab(self.term_count, 0) for i = 0, self.term_count - 1 do local t = self.terms_[i] --[[@as kiwi.Term]] terms[i + 1] = Term(t.var, t.coefficient) end return terms end ---@return kiwi.Expression ---@nodiscard function Expression_cls:copy() return new_expr_constant(self, self.constant) end local Expression_mt = { __index = Expression_cls, } function Expression_mt.__new(T, terms, constant) local e = ffi_gc(ffi_new(T, #terms), ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] for i, t in ipairs(terms) do local dt = e.terms_[i - 1] --[[@as kiwi.Term]] dt.var = ckiwi.kiwi_var_clone(t.var) dt.coefficient = t.coefficient end e.constant = constant or 0.0 e.term_count = #terms return e end function Expression_mt.__mul(a, b) if type(a) == "number" then return mul_expr_constant(b, a) elseif type(b) == "number" then return mul_expr_constant(a, b) end error("Invalid expr *") end function Expression_mt.__div(a, b) assert(type(b) == "number", "Invalid expr /") return mul_expr_constant(a, 1.0 / b) end function Expression_mt:__unm() return mul_expr_constant(self, -1.0) end function Expression_mt.__add(a, b) if ffi_istype(Var, b) then return add_expr_term(a, b) elseif ffi_istype(Expression, b) then if type(a) == "number" then return new_expr_constant(b, a + b.constant) else return add_expr_expr(a, b) end elseif ffi_istype(Term, b) then return add_expr_term(a, b.var, b.coefficient) elseif type(b) == "number" then return new_expr_constant(a, a.constant + b) end error("Invalid expr +") end function Expression_mt.__sub(a, b) return Expression_mt.__add(a, -b) end function Expression_mt:__tostring() local tab = new_tab(self.term_count + 1, 0) for i = 0, self.term_count - 1 do tab[i + 1] = tostring(self.terms_[i]) end tab[self.term_count + 1] = self.constant return concat(tab, " + ") end ffi.metatype(Expression, Expression_mt) end do --- A constraint is a linear inequality or equality with associated strength. --- Constraints can be built with arbitrary left and right hand expressions. But --- ultimately they all have the form `expression [op] 0`. ---@class kiwi.Constraint: ffi.cdata* ---@overload fun(lhs: kiwi.Expression, rhs: kiwi.Expression?, op: kiwi.RelOp?, strength: number?): kiwi.Constraint local Constraint_cls = { --- The strength of the constraint. ---@type fun(self: kiwi.Constraint): number strength = ckiwi.kiwi_constraint_strength, --- The relational operator of the constraint. ---@type fun(self: kiwi.Constraint): kiwi.RelOp op = ckiwi.kiwi_constraint_op, --- Whether the constraint is violated in the current solution. ---@type fun(self: kiwi.Constraint): boolean violated = ckiwi.kiwi_constraint_violated, } --- The reduced expression defining the constraint. ---@return kiwi.Expression ---@nodiscard function Constraint_cls:expression() local SZ = 8 local expr = ffi_new(Expression, SZ) --[[@as kiwi.Expression]] local n = ckiwi.kiwi_constraint_expression(self, expr, SZ) if n > SZ then expr = ffi_new(Expression, n) --[[@as kiwi.Expression]] n = ckiwi.kiwi_constraint_expression(self, expr, n) end return ffi_gc(expr, ckiwi.kiwi_expression_del_vars) --[[@as kiwi.Expression]] end local Constraint_mt = { __index = Constraint_cls, } function Constraint_mt:__new(lhs, rhs, op, strength) return ffi_gc( ckiwi.kiwi_constraint_new(lhs, rhs, op or "EQ", strength or REQUIRED), ckiwi.kiwi_constraint_del ) end function Constraint_mt:__tostring() local ops = { [0] = "<=", ">=", "==" } local strengths = { [Strength.REQUIRED] = "required", [Strength.STRONG] = "strong", [Strength.MEDIUM] = "medium", [Strength.WEAK] = "weak", } local strength = self:strength() local strength_str = strengths[strength] or tostring(strength) local op = ops[tonumber(self:op())] return strformat("%s %s 0 | %s", tostring(self:expression()), op, strength_str) end ffi.metatype(Constraint, Constraint_mt) end do local constraints = {} kiwi.constraints = constraints --- Create a constraint between a pair of variables with ratio. --- The constraint is of the form `left [op|==] coeff right + [constant|0.0]`. ---@param left kiwi.Var ---@param coeff number right side term coefficient ---@param right kiwi.Var ---@param constant number? constant (default 0.0) ---@param op kiwi.RelOp? relational operator (default "EQ") ---@param strength number? strength (default REQUIRED) ---@return kiwi.Constraint ---@nodiscard function constraints.pair_ratio(left, coeff, right, constant, op, strength) assert(ffi_istype(Var, left) and ffi_istype(Var, right)) local lhs = ffi_new(Expression, 2) --[[@as kiwi.Expression]] local dt = lhs.terms_[0] dt.var = left dt.coefficient = 1.0 dt = lhs.terms_[1] dt.var = right dt.coefficient = -coeff lhs.constant = -(constant or 0.0) lhs.term_count = 2 return ffi_gc( ckiwi.kiwi_constraint_new(lhs, nil, op or "EQ", strength or REQUIRED), ckiwi.kiwi_constraint_del ) --[[@as kiwi.Constraint]] end local pair_ratio = constraints.pair_ratio --- Create a constraint between a pair of variables with ratio. --- The constraint is of the form `left [op|==] right + [constant|0.0]`. ---@param left kiwi.Var ---@param right kiwi.Var ---@param constant number? constant (default 0.0) ---@param op kiwi.RelOp? relational operator (default "EQ") ---@param strength number? strength (default REQUIRED) ---@return kiwi.Constraint ---@nodiscard function constraints.pair(left, right, constant, op, strength) return pair_ratio(left, 1.0, right, constant, op, strength) end --- Create a single term constraint --- The constraint is of the form `var [op|==] [constant|0.0]`. ---@param var kiwi.Var ---@param constant number? constant (default 0.0) ---@param op kiwi.RelOp? relational operator (default "EQ") ---@param strength number? strength (default REQUIRED) ---@return kiwi.Constraint ---@nodiscard function constraints.single(var, constant, op, strength) assert(ffi_istype(Var, var)) return ffi_gc( ckiwi.kiwi_constraint_new( new_expr_one_temp(-(constant or 0.0), var, 1.0), nil, op or "EQ", strength or REQUIRED ), ckiwi.kiwi_constraint_del ) --[[@as kiwi.Constraint]] end end do local C = ffi.C ---@class kiwi.KiwiErr: ffi.cdata* ---@field package kind kiwi.ErrKind ---@field package message ffi.cdata* ---@field package must_free boolean ---@overload fun(): kiwi.KiwiErr local KiwiErr = ffi.typeof("struct KiwiErr") --[[@as kiwi.KiwiErr]] local Error_mt = { __tostring = function(self) return strformat("%s: (%s, %s)", self.message, self.solver, self.item) end, } ---@param kind kiwi.ErrKind ---@param message string ---@param solver kiwi.Solver ---@param item any local function new_error(kind, message, solver, item) ---@class kiwi.Error ---@field kind kiwi.ErrKind ---@field message string ---@field solver kiwi.Solver? ---@field item any? return setmetatable({ kind = kind, message = message, solver = solver, item = item, }, Error_mt) end ---@param f fun(solver: kiwi.Solver, item: any, ...): kiwi.KiwiErr? ---@param solver kiwi.Solver ---@param item any local function try_solver(f, solver, item, ...) local err = f(solver, item, ...) if err ~= nil then local kind = err.kind local message = err.message ~= nil and ffi_string(err.message) or "" if err.must_free then C.free(err) end error(new_error(kind, message, solver, item)) end end ---@class kiwi.Solver: ffi.cdata* ---@overload fun(): kiwi.Solver local Solver_cls = { --- Test whether a constraint is in the solver. ---@type fun(self: kiwi.Solver, constraint: kiwi.Constraint): boolean has_constraint = ckiwi.kiwi_solver_has_constraint, --- Test whether an edit variable has been added to the solver. ---@type fun(self: kiwi.Solver, var: kiwi.Var): boolean has_edit_var = ckiwi.kiwi_solver_has_edit_var, --- Update the values of the external solver variables. ---@type fun(self: kiwi.Solver) update_vars = ckiwi.kiwi_solver_update_vars, --- Reset the solver to the empty starting conditions. --- --- This method resets the internal solver state to the empty starting --- condition, as if no constraints or edit variables have been added. --- This can be faster than deleting the solver and creating a new one --- when the entire system must change, since it can avoid unecessary --- heap (de)allocations. ---@type fun(self: kiwi.Solver) reset = ckiwi.kiwi_solver_reset, --- Dump a representation of the solver to stdout. ---@type fun(self: kiwi.Solver) dump = ckiwi.kiwi_solver_dump, } --- Adds a constraint to the solver. --- Raises --- KiwiErrDuplicateConstraint: The given constraint has already been added to the solver. --- KiwiErrUnsatisfiableConstraint: The given constraint is required and cannot be satisfied. ---@param constraint kiwi.Constraint function Solver_cls:add_constraint(constraint) try_solver(ckiwi.kiwi_solver_add_constraint, self, constraint) end --- Removes a constraint from the solver. --- Raises --- KiwiErrUnknownConstraint: The given constraint has not been added to the solver. ---@param constraint kiwi.Constraint function Solver_cls:remove_constraint(constraint) try_solver(ckiwi.kiwi_solver_remove_constraint, self, constraint) end --- Adds an edit variable to the solver. --- --- This method should be called before the `suggestValue` method is --- used to supply a suggested value for the given edit variable. --- Raises --- KiwiErrDuplicateEditVariable: The given edit variable has already been added to the solver. --- KiwiErrBadRequiredStrength: The given strength is >= required. ---@param var kiwi.Var the variable to add as an edit variable ---@param strength number the strength of the edit variable (must be less than `Strength.REQUIRED`) function Solver_cls:add_edit_var(var, strength) try_solver(ckiwi.kiwi_solver_add_edit_var, self, var, strength) end --- Remove an edit variable from the solver. --- Raises --- KiwiErrUnknownEditVariable: The given edit variable has not been added to the solver ---@param var kiwi.Var the edit variable to remove function Solver_cls:remove_edit_var(var) try_solver(ckiwi.kiwi_solver_remove_edit_var, self, var) end --- Suggest a value for the given edit variable. --- This method should be used after an edit variable has been added to the solver in order --- to suggest the value for that variable. After all suggestions have been made, --- the `update_vars` methods can be used to update the values of the external solver variables. --- Raises --- KiwiErrUnknownEditVariable: The given edit variable has not been added to the solver. ---@param var kiwi.Var the edit variable to suggest a value for ---@param value number the suggested value function Solver_cls:suggest_value(var, value) try_solver(ckiwi.kiwi_solver_suggest_value, self, var, value) end --- Dump a representation of the solver to a string. ---@return string ---@nodiscard function Solver_cls:dumps() local cs = ckiwi.kiwi_solver_dumps(self) local s = ffi_string(cs) C.free(cs) return s end kiwi.Solver = ffi.metatype("struct KiwiSolver", { __index = Solver_cls, __new = function(_) return ffi_gc(ckiwi.kiwi_solver_new(), ckiwi.kiwi_solver_del) end, }) --[[@as kiwi.Solver]] end return kiwi