- Add modified BSD license
- Initial test runner github action
- Add is_{type} functions
- export kiwi.Error metatable such that it appears like a class.
- Allow Expression.new to take nil for terms
- Initial set of specs
433 lines
11 KiB
C++
433 lines
11 KiB
C++
#include "ckiwi.h"
|
|
|
|
#include <kiwi/kiwi.h>
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
using namespace kiwi;
|
|
|
|
namespace {
|
|
|
|
template<typename T, typename R, typename... Args>
|
|
inline R make_cref(Args&&... args) {
|
|
static_assert(
|
|
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
|
|
"to_cref: R too small for T"
|
|
);
|
|
static_assert(alignof(R) >= alignof(T), "to_cref: R alignment too small for T");
|
|
|
|
R cref;
|
|
new (&cref) T(std::forward<Args>(args)...);
|
|
return cref;
|
|
}
|
|
|
|
template<typename... Args>
|
|
inline decltype(auto) make_var_cref(Args&&... args) {
|
|
return make_cref<Variable, KiwiVarRef>(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<typename... Args>
|
|
inline decltype(auto) make_constraint_cref(Args&&... args) {
|
|
return make_cref<Constraint, KiwiConstraintRef>(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class T, class R>
|
|
class SharedRef {
|
|
private:
|
|
R& cref_;
|
|
|
|
public:
|
|
explicit SharedRef<T, R>(R& cref) : cref_(cref) {}
|
|
|
|
static_assert(
|
|
sizeof(R) >= sizeof(T), //NOLINT(bugprone-sizeof-expression)
|
|
"SharedRef<T,CS> CS too small for T"
|
|
);
|
|
|
|
R clone() const {
|
|
return make_cref<T, R>(cref());
|
|
}
|
|
|
|
void release() {
|
|
if (cref_) {
|
|
ptr()->~T();
|
|
cref_ = nullptr;
|
|
}
|
|
}
|
|
|
|
T* ptr() {
|
|
T* p;
|
|
void* s = &cref_;
|
|
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
|
|
return p;
|
|
}
|
|
|
|
const T* const_ptr() const {
|
|
const T* p;
|
|
const void* s = &cref_;
|
|
std::memcpy(&p, &s, sizeof p); //NOLINT(bugprone-sizeof-expression)
|
|
return p;
|
|
}
|
|
|
|
const T& cref() const {
|
|
return *const_ptr();
|
|
}
|
|
|
|
T* operator&() const {
|
|
return ptr();
|
|
}
|
|
|
|
T* operator->() {
|
|
return ptr();
|
|
}
|
|
|
|
const T* operator->() const {
|
|
return const_ptr();
|
|
}
|
|
|
|
operator const T&() const {
|
|
return cref();
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return cref_;
|
|
}
|
|
};
|
|
|
|
using ConstraintRef = SharedRef<Constraint, KiwiConstraintRef>;
|
|
using VariableRef = SharedRef<Variable, KiwiVarRef>;
|
|
using ConstVariableRef = const SharedRef<const Variable, const KiwiVarRef>;
|
|
|
|
const KiwiErr* new_error(const KiwiErr* base, const std::exception& ex) {
|
|
if (!std::strcmp(ex.what(), base->message))
|
|
return base;
|
|
|
|
const auto msg_n = std::strlen(ex.what()) + 1;
|
|
|
|
auto* mem = static_cast<char*>(std::malloc(sizeof(KiwiErr) + msg_n));
|
|
if (!mem) {
|
|
return base;
|
|
}
|
|
|
|
const auto* err = new (mem) KiwiErr {base->kind, mem + sizeof(KiwiErr), true};
|
|
std::memcpy(const_cast<char*>(err->message), ex.what(), msg_n);
|
|
return err;
|
|
}
|
|
|
|
static const constexpr KiwiErr kKiwiErrUnhandledCxxException {
|
|
KiwiErrUnknown,
|
|
"An unhandled C++ exception occurred."
|
|
};
|
|
|
|
static const constexpr KiwiErr kKiwiErrNullObjectArg0 {
|
|
KiwiErrNullObject,
|
|
"null object passed as argument #0 (self)"
|
|
};
|
|
|
|
static const constexpr KiwiErr kKiwiErrNullObjectArg1 {
|
|
KiwiErrNullObject,
|
|
"null object passed as argument #1"
|
|
};
|
|
|
|
template<typename F>
|
|
inline const KiwiErr* wrap_err(F&& f) {
|
|
try {
|
|
f();
|
|
} catch (const UnsatisfiableConstraint& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrUnsatisfiableConstraint,
|
|
"The constraint cannot be satisfied."
|
|
};
|
|
return &err;
|
|
} catch (const UnknownConstraint& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrUnknownConstraint,
|
|
"The constraint has not been added to the solver."
|
|
};
|
|
return &err;
|
|
|
|
} catch (const DuplicateConstraint& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrDuplicateConstraint,
|
|
"The constraint has already been added to the solver."
|
|
};
|
|
return &err;
|
|
|
|
} catch (const UnknownEditVariable& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrUnknownEditVariable,
|
|
"The edit variable has not been added to the solver."
|
|
};
|
|
return &err;
|
|
|
|
} catch (const DuplicateEditVariable& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrDuplicateEditVariable,
|
|
"The edit variable has already been added to the solver."
|
|
};
|
|
return &err;
|
|
|
|
} catch (const BadRequiredStrength& ex) {
|
|
static const constexpr KiwiErr err {
|
|
KiwiErrBadRequiredStrength,
|
|
"A required strength cannot be used in this context."
|
|
};
|
|
return &err;
|
|
|
|
} catch (const InternalSolverError& ex) {
|
|
static const constexpr KiwiErr base {
|
|
KiwiErrInternalSolverError,
|
|
"An internal solver error occurred."
|
|
};
|
|
return new_error(&base, ex);
|
|
} catch (std::bad_alloc&) {
|
|
static const constexpr KiwiErr err {KiwiErrAlloc, "A memory allocation failed."};
|
|
return &err;
|
|
} catch (const std::exception& ex) {
|
|
return new_error(&kKiwiErrUnhandledCxxException, ex);
|
|
} catch (...) {
|
|
return &kKiwiErrUnhandledCxxException;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template<typename P, typename R, typename F>
|
|
inline const KiwiErr* wrap_err(P ptr, F&& f) {
|
|
if (!ptr) {
|
|
return &kKiwiErrNullObjectArg0;
|
|
}
|
|
return wrap_err([&]() { f(ptr); });
|
|
}
|
|
|
|
template<typename P, typename R, typename F>
|
|
inline const KiwiErr* wrap_err(P ptr, R ref, F&& f) {
|
|
if (!ptr) {
|
|
return &kKiwiErrNullObjectArg0;
|
|
} else if (!ref) {
|
|
return &kKiwiErrNullObjectArg1;
|
|
}
|
|
return wrap_err([&]() { f(ptr, ref); });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
KiwiVarRef kiwi_var_new(const char* name) {
|
|
return make_var_cref(name ? name : "");
|
|
}
|
|
|
|
void kiwi_var_del(KiwiVarRef var) {
|
|
VariableRef(var).release();
|
|
}
|
|
|
|
KiwiVarRef kiwi_var_clone(KiwiVarRef var) {
|
|
return VariableRef(var).clone();
|
|
}
|
|
|
|
const char* kiwi_var_name(KiwiVarRef var) {
|
|
const VariableRef self(var);
|
|
return self ? self->name().c_str() : "(<null>)";
|
|
}
|
|
|
|
void kiwi_var_set_name(KiwiVarRef var, const char* name) {
|
|
VariableRef self(var);
|
|
if (self)
|
|
self->setName(name);
|
|
}
|
|
|
|
double kiwi_var_value(KiwiVarRef var) {
|
|
const VariableRef self(var);
|
|
return self ? self->value() : std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
|
|
void kiwi_var_set_value(KiwiVarRef var, double value) {
|
|
VariableRef self(var);
|
|
if (self)
|
|
self->setValue(value);
|
|
}
|
|
|
|
bool kiwi_var_eq(KiwiVarRef var, KiwiVarRef other) {
|
|
ConstVariableRef self(var); // const defect in upstream
|
|
const VariableRef other_ref(other);
|
|
|
|
return self && other_ref && self->equals(other_ref);
|
|
}
|
|
|
|
void kiwi_expression_del_vars(KiwiExpression* expr) {
|
|
if (!expr)
|
|
return;
|
|
|
|
for (auto* t = expr->terms; t != expr->terms + expr->term_count; ++t) {
|
|
VariableRef(t->var).release();
|
|
}
|
|
}
|
|
|
|
KiwiConstraintRef kiwi_constraint_new(
|
|
const KiwiExpression* lhs,
|
|
const KiwiExpression* rhs,
|
|
enum KiwiRelOp op,
|
|
double strength
|
|
) {
|
|
if (strength < 0.0) {
|
|
strength = kiwi::strength::required;
|
|
}
|
|
|
|
std::vector<Term> terms;
|
|
terms.reserve((lhs ? lhs->term_count : 0) + (rhs ? rhs->term_count : 0));
|
|
|
|
if (lhs) {
|
|
for (auto* t = lhs->terms; t != lhs->terms + lhs->term_count; ++t) {
|
|
ConstVariableRef var(t->var);
|
|
if (var)
|
|
terms.emplace_back(var.cref(), t->coefficient);
|
|
}
|
|
}
|
|
if (rhs) {
|
|
for (auto* t = rhs->terms; t != rhs->terms + rhs->term_count; ++t) {
|
|
ConstVariableRef var(t->var);
|
|
if (var)
|
|
terms.emplace_back(var.cref(), -t->coefficient);
|
|
}
|
|
}
|
|
|
|
return make_constraint_cref(
|
|
Expression(std::move(terms), (lhs ? lhs->constant : 0.0) - (rhs ? rhs->constant : 0.0)),
|
|
static_cast<RelationalOperator>(op),
|
|
strength
|
|
);
|
|
}
|
|
|
|
void kiwi_constraint_del(KiwiConstraintRef constraint) {
|
|
ConstraintRef(constraint).release();
|
|
}
|
|
|
|
KiwiConstraintRef kiwi_constraint_clone(KiwiConstraintRef constraint) {
|
|
return ConstraintRef(constraint).clone();
|
|
}
|
|
|
|
double kiwi_constraint_strength(KiwiConstraintRef constraint) {
|
|
const ConstraintRef self(constraint);
|
|
return self ? self->strength() : std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
|
|
enum KiwiRelOp kiwi_constraint_op(KiwiConstraintRef constraint) {
|
|
const ConstraintRef self(constraint);
|
|
return self ? static_cast<KiwiRelOp>(self->op()) : KiwiRelOp::KIWI_OP_EQ;
|
|
}
|
|
|
|
bool kiwi_constraint_violated(KiwiConstraintRef constraint) {
|
|
const ConstraintRef self(constraint);
|
|
return self ? self->violated() : 0;
|
|
}
|
|
|
|
int kiwi_constraint_expression(KiwiConstraintRef constraint, KiwiExpression* out, int out_size) {
|
|
const ConstraintRef self(constraint);
|
|
if (!self)
|
|
return 0;
|
|
const auto& expr = self->expression();
|
|
const auto& terms = expr.terms();
|
|
const int n_terms = terms.size();
|
|
if (!out || out_size < n_terms)
|
|
return n_terms;
|
|
|
|
auto* p = out->terms;
|
|
for (const auto& t : terms) {
|
|
*p = KiwiTerm {make_var_cref(t.variable()), t.coefficient()};
|
|
++p;
|
|
}
|
|
out->term_count = p - out->terms;
|
|
out->constant = expr.constant();
|
|
|
|
return n_terms;
|
|
}
|
|
|
|
struct KiwiSolver {
|
|
unsigned error_mask;
|
|
Solver solver;
|
|
};
|
|
|
|
KiwiSolver* kiwi_solver_new(unsigned error_mask) {
|
|
return new KiwiSolver {error_mask};
|
|
}
|
|
|
|
void kiwi_solver_del(KiwiSolver* s) {
|
|
if (s)
|
|
delete s;
|
|
}
|
|
|
|
const KiwiErr* kiwi_solver_add_constraint(KiwiSolver* s, KiwiConstraintRef constraint) {
|
|
return wrap_err(s, ConstraintRef(constraint), [](auto* s, const auto c) {
|
|
s->solver.addConstraint(c);
|
|
});
|
|
}
|
|
|
|
const KiwiErr* kiwi_solver_remove_constraint(KiwiSolver* s, KiwiConstraintRef constraint) {
|
|
return wrap_err(s, ConstraintRef(constraint), [](auto* s, const auto c) {
|
|
s->solver.removeConstraint(c);
|
|
});
|
|
}
|
|
|
|
bool kiwi_solver_has_constraint(const KiwiSolver* s, KiwiConstraintRef constraint) {
|
|
ConstraintRef c(constraint);
|
|
if (!s || !c)
|
|
return 0;
|
|
return s->solver.hasConstraint(c);
|
|
}
|
|
|
|
const KiwiErr* kiwi_solver_add_edit_var(KiwiSolver* s, KiwiVarRef var, double strength) {
|
|
return wrap_err(s, VariableRef(var), [strength](auto* s, const auto v) {
|
|
s->solver.addEditVariable(v, strength);
|
|
});
|
|
}
|
|
|
|
const KiwiErr* kiwi_solver_remove_edit_var(KiwiSolver* s, KiwiVarRef var) {
|
|
return wrap_err(s, VariableRef(var), [](auto* s, const auto v) {
|
|
s->solver.removeEditVariable(v);
|
|
});
|
|
}
|
|
|
|
bool kiwi_solver_has_edit_var(const KiwiSolver* s, KiwiVarRef var) {
|
|
VariableRef v(var);
|
|
if (!s || !v)
|
|
return 0;
|
|
return s->solver.hasEditVariable(v);
|
|
}
|
|
|
|
const KiwiErr* kiwi_solver_suggest_value(KiwiSolver* s, KiwiVarRef var, double value) {
|
|
return wrap_err(s, VariableRef(var), [value](auto* s, const auto v) {
|
|
s->solver.suggestValue(v, value);
|
|
});
|
|
}
|
|
|
|
void kiwi_solver_update_vars(KiwiSolver* s) {
|
|
if (s)
|
|
s->solver.updateVariables();
|
|
}
|
|
|
|
void kiwi_solver_reset(KiwiSolver* s) {
|
|
if (s)
|
|
s->solver.reset();
|
|
}
|
|
|
|
void kiwi_solver_dump(const KiwiSolver* s) {
|
|
if (s)
|
|
s->solver.dump();
|
|
}
|
|
|
|
char* kiwi_solver_dumps(const KiwiSolver* s) {
|
|
if (!s)
|
|
return nullptr;
|
|
|
|
const auto str = s->solver.dumps(); // upstream library defect
|
|
const auto buf_size = str.size() + 1;
|
|
auto* buf = static_cast<char*>(std::malloc(buf_size));
|
|
if (!buf)
|
|
return nullptr;
|
|
std::memcpy(buf, str.c_str(), str.size() + 1);
|
|
return buf;
|
|
}
|
|
|
|
} // extern "C"
|