├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── amoeba.h ├── amoeba.lua ├── enaml_like_benchmark.cpp ├── lua_amoeba.c ├── nanobench.h ├── test.c └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exp 3 | *.ilk 4 | *.lib 5 | *.pdb 6 | *.exe 7 | 8 | *.so 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: 4 | - gcc 5 | 6 | before_install: 7 | - pip install --user urllib3[secure] cpp-coveralls 8 | 9 | # Work around https://github.com/eddyxu/cpp-coveralls/issues/108 by manually 10 | # installing the pyOpenSSL module and injecting it into urllib3 as per 11 | # https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 12 | - sed -i -e '/^import sys$/a import urllib3.contrib.pyopenssl\nurllib3.contrib.pyopenssl.inject_into_urllib3()' `which coveralls` 13 | 14 | 15 | install: 16 | - gcc -shared -Wall -O3 -Wextra -pedantic -std=c89 -xc amoeba.h -o amoeba.so 17 | - gcc -Wall -fprofile-arcs -ftest-coverage -O0 -Wextra -pedantic -std=c89 test.c -o test 18 | 19 | script: 20 | - ./test 21 | 22 | after_success: 23 | - coveralls 24 | 25 | notifications: 26 | email: 27 | on_success: change 28 | on_failure: always 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xavier Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amoeba -- the constraint solving algorithm in pure C 2 | 3 | [![Build Status](https://travis-ci.org/starwing/amoeba.svg?branch=master)](https://travis-ci.org/starwing/amoeba) 4 | [![Coverage Status](https://coveralls.io/repos/github/starwing/amoeba/badge.svg?branch=master)](https://coveralls.io/github/starwing/amoeba?branch=master) 5 | 6 | Amoeba is a pure C implement of Cassowary algorithm. 7 | Amoeba use Clean C, which is the cross set of ANSI C89 and C++, like 8 | the Lua language. 9 | 10 | Amoeba is a single-file library, for more single-file library, see the 11 | stb project [here][1]. 12 | 13 | Amoeba largely impressed by [kiwi][2], the C++ implement of Cassowary 14 | algorithm, and the algorithm [paper][3]. 15 | 16 | Amoeba ships a hand written Lua binding. 17 | 18 | Amoeba has the same license with the [Lua language][4]. 19 | 20 | [1]: https://github.com/nothings/stb 21 | [2]: https://github.com/nucleic/kiwi 22 | [3]: http://constraints.cs.washington.edu/solvers/uist97.html 23 | [4]: https://www.lua.org/license.html 24 | 25 | ## How To Use 26 | 27 | This libary export a constraint solver interface, to solve a constraint problem, you should use it in steps: 28 | 29 | - create a new solver: `am_newsolver()` 30 | - create some variables: `am_newvariable()` 31 | - create some constraints that may use variables: `am_newconstraint()` 32 | - make constraints by construct equation using: 33 | - `am_addterm()` add a $a \times variable$ term to constraint equation items 34 | - `am_setrelation()` to specify the equal/greater/less sign in center of equation 35 | - `am_addconstant()` to add a number without variable 36 | - `am_setstrength()` to specify the priority of this constraint in all constraints 37 | - after make up a constraint, you could add it into solver by `am_add()` 38 | - and you can read out the result of each variable with `am_value()` 39 | - or you can manually specify a new value to variable with `am_sugguest()` 40 | - after done, use `am_delsolver()` to free al memory 41 | 42 | below is a tiny example to demonstrate the steps: 43 | 44 | ```c 45 | #define AM_IMPLEMENTATION // include implementations of library 46 | #include "amoeba.h" // and interface 47 | 48 | int main(void) 49 | { 50 | // first, create a solver: 51 | am_Solver *S = am_newsolver(NULL, NULL); 52 | 53 | // create some variable: 54 | am_Var *l = am_newvariable(S); 55 | am_Var *m = am_newvariable(S); 56 | am_Var *r = am_newvariable(S); 57 | 58 | // create the constraint: 59 | am_Constraint *c1 = am_newconstraint(S, AM_REQUIRED); 60 | am_Constraint *c2 = am_newconstraint(S, AM_REQUIRED); 61 | 62 | // c1: m is in middle of l and r: 63 | // i.e. m = (l + r) / 2, or 2*m = l + r 64 | am_addterm(c1, m, 2.f); 65 | am_setrelation(c1, AM_EQUAL); 66 | am_addterm(c1, l, 1.f); 67 | am_addterm(c1, r, 1.f); 68 | // apply c1 69 | am_add(c1); 70 | 71 | // c2: r - l >= 100 72 | am_addterm(c2, r, 1.f); 73 | am_addterm(c2, l, -1.f); 74 | am_setrelation(c2, AM_GREATEQUAL); 75 | am_addconstant(c2, 100.f); 76 | // apply c2 77 | am_add(c2); 78 | 79 | // now we set variable l to 20 80 | am_suggest(l, 20.f); 81 | 82 | // and see the value of m and r: 83 | am_updatevars(S); 84 | 85 | // r should by 20 + 100 == 120: 86 | assert(am_value(r) == 120.f); 87 | 88 | // and m should in middle of l and r: 89 | assert(am_value(m) == 70.f); 90 | 91 | // done with solver 92 | am_delsolver(S); 93 | return 0; 94 | } 95 | ``` 96 | 97 | 98 | 99 | ## Reference 100 | 101 | All functions below that returns `int` may return error codes: 102 | 103 | - `AM_OK`: the operations success. 104 | - `AM_FAILED`: the operation fail 105 | - `AM_UNSATISFIED`: can not add specific constraints into solver 106 | - `AM_UNBOUND`: add specific constraints failed because variables in constraints unbound 107 | 108 | Routines: 109 | 110 | - `am_Solver *am_newsolver(am_Allocf *allocf, void *ud);` 111 | 112 | create a new solver with custom memory alloculator, pass NULL for use default ones. 113 | 114 | - `void am_resetsolver(am_Solver *solver, int clear_constraints);` 115 | 116 | remove all variable suggests from solver. 117 | 118 | if `clear_constraints` is nonzero, also remove and delete all constraints from solver. 119 | 120 | - `void am_delsolver(am_Solver *solver);` 121 | 122 | delete a solver and frees all memory it used, after that, all variables/constraints created from this solver are all freed. 123 | 124 | - `void am_updatevars(am_Solver *solver);` 125 | 126 | refresh variables' value into it's constrainted value, you could use `am_autoupdate()` to avoid call this routine every time on changing constraints in solver. 127 | 128 | - `void am_autoupdate(am_Solver *solver, int auto_update);` 129 | 130 | set auto update flags, if set, all variable will auto update to its' latest value after any changes to solver. 131 | 132 | - `int am_hasedit(am_Var *var);` 133 | 134 | check whether a variable has suggested value in solver. 135 | 136 | - `int am_hasconstraint(am_Constraint *cons);` 137 | 138 | check whether a constraint has been added into solver. 139 | 140 | - `int am_add(am_Constraint *cons);` 141 | 142 | add constraint into solver it's created from. 143 | 144 | - `void am_remove(am_Constraint *cons);` 145 | 146 | remove added constraint. 147 | 148 | - `int am_addedit(am_Var *var, am_Num strength);` 149 | 150 | prepare to change the value of variables or the `strength` value if the variable is in edit now. 151 | 152 | - `void am_suggest(am_Var *var, am_Num value);` 153 | 154 | actually change the value of the variable `var`, after changed, other variable may changed due to the constraints in solver. if you do not want change the strength of suggest (default is `AM_MEDIUM`), you may call this routine directly. 155 | 156 | - `void am_deledit(am_Var *var);` 157 | 158 | cancel the modify of variable, the value will restore to the referred value according the solver. 159 | 160 | - `am_Var *am_newvariable(am_Solver *solver);` 161 | 162 | create a new variable. variable is reference counting since it may used in serveral constraints, 163 | 164 | so if you want store it in multiple place, call `am_usevariable()` before. 165 | 166 | - `void am_usevariable(am_Var *var);` 167 | 168 | add the reference counting of a variable to avoid it been freed. 169 | 170 | - `void am_delvariable(am_Var *var);` 171 | 172 | sub the reference counting of a variable, and free it when the count down to 0. 173 | 174 | - `int am_variableid(am_Var *var);` 175 | 176 | return a unqiue id (within solver) of the variable `var`. 177 | 178 | - `am_Num am_value(am_Var *var);` 179 | 180 | fetch the current value of variable `var`, note that if auto update not set and `am_updatevars()` not called, the value may not the latest values that inferred by solver. 181 | 182 | - `am_Constraint *am_newconstraint(am_Solver *solver, am_Num strength);` 183 | 184 | create a new constraints. 185 | 186 | - `am_Constraint *am_cloneconstraint(am_Constraint *other, am_Num strength);` 187 | 188 | make a new constraints from existing one, with new `strength` 189 | 190 | - `void am_resetconstraint(am_Constraint *cons);` 191 | 192 | remove all terms and variables from the constraint. 193 | 194 | - `void am_delconstraint(am_Constraint *cons);` 195 | 196 | frees the constraint. if it's added into solver, remove it first. 197 | 198 | - `int am_addterm(am_Constraint *cons, am_Var *var, am_Num multiplier);` 199 | 200 | add a term into constraint, e.g. a constraint like $2*m = x + y$, the terms are $2*m$, $1*x$ and $1*y$. 201 | 202 | so makeup this constraint you could: 203 | 204 | ```c 205 | am_addterm(c, m, 2.0); // 2*m 206 | am_setrelation(c, AM_EQUAL); // = 207 | am_addterm(c, x, 1.0); // x 208 | am_addterm(c, y, 1.0); // y 209 | ``` 210 | 211 | - `int am_setrelation(am_Constraint *cons, int relation);` 212 | 213 | set the relations of constraint, could be one of these: 214 | 215 | - `AM_EQUAL` 216 | - `AM_GREATEQUAL` 217 | - `AM_LESSEQUAL` 218 | 219 | the terms added before `am_setrelation()` become the left hand terms of constraints, and the terms adds after call will become the right hand terms of constraints. 220 | 221 | - `int am_addconstant(am_Constraint *cons, am_Num constant);` 222 | 223 | add a constant without variable into constraint as a term. 224 | 225 | - `int am_setstrength(am_Constraint *cons, am_Num strength);` 226 | 227 | set the strength of a constraint. 228 | 229 | - `am_mergeconstraint(am_Constraint *cons, const am_Constraint *other, am_Num multiplier);` 230 | 231 | merge other constraints into `cons`, with a multiplier multiples with `other`. 232 | 233 | -------------------------------------------------------------------------------- /amoeba.h: -------------------------------------------------------------------------------- 1 | #ifndef amoeba_h 2 | #define amoeba_h 3 | 4 | #ifndef AM_NS_BEGIN 5 | # ifdef __cplusplus 6 | # define AM_NS_BEGIN extern "C" { 7 | # define AM_NS_END } 8 | # else 9 | # define AM_NS_BEGIN 10 | # define AM_NS_END 11 | # endif 12 | #endif /* AM_NS_BEGIN */ 13 | 14 | #ifndef AM_STATIC 15 | # ifdef __GNUC__ 16 | # define AM_STATIC static __attribute((unused)) 17 | # else 18 | # define AM_STATIC static 19 | # endif 20 | #endif 21 | 22 | #ifdef AM_STATIC_API 23 | # ifndef AM_IMPLEMENTATION 24 | # define AM_IMPLEMENTATION 25 | # endif 26 | # define AM_API AM_STATIC 27 | #endif 28 | 29 | #if !defined(AM_API) && defined(_WIN32) 30 | # ifdef AM_IMPLEMENTATION 31 | # define AM_API __declspec(dllexport) 32 | # else 33 | # define AM_API __declspec(dllimport) 34 | # endif 35 | #endif 36 | 37 | #ifndef AM_API 38 | # define AM_API extern 39 | #endif 40 | 41 | #define AM_OK (0) 42 | #define AM_FAILED (-1) 43 | #define AM_UNSATISFIED (-2) 44 | #define AM_UNBOUND (-3) 45 | 46 | #define AM_LESSEQUAL (1) 47 | #define AM_EQUAL (2) 48 | #define AM_GREATEQUAL (3) 49 | 50 | #define AM_REQUIRED ((am_Num)1000000000) 51 | #define AM_STRONG ((am_Num)1000000) 52 | #define AM_MEDIUM ((am_Num)1000) 53 | #define AM_WEAK ((am_Num)1) 54 | 55 | #include 56 | 57 | AM_NS_BEGIN 58 | 59 | 60 | #ifdef AM_USE_FLOAT 61 | typedef float am_Num; 62 | #else 63 | typedef double am_Num; 64 | #endif 65 | 66 | typedef struct am_Solver am_Solver; 67 | typedef struct am_Var am_Var; 68 | typedef struct am_Constraint am_Constraint; 69 | 70 | typedef void *am_Allocf (void *ud, void *ptr, size_t nsize, size_t osize); 71 | 72 | AM_API am_Solver *am_newsolver (am_Allocf *allocf, void *ud); 73 | AM_API void am_resetsolver (am_Solver *solver, int clear_constraints); 74 | AM_API void am_delsolver (am_Solver *solver); 75 | 76 | AM_API void am_updatevars (am_Solver *solver); 77 | AM_API void am_autoupdate (am_Solver *solver, int auto_update); 78 | 79 | AM_API int am_hasedit (am_Var *var); 80 | AM_API int am_hasconstraint (am_Constraint *cons); 81 | 82 | AM_API int am_add (am_Constraint *cons); 83 | AM_API void am_remove (am_Constraint *cons); 84 | 85 | AM_API int am_addedit (am_Var *var, am_Num strength); 86 | AM_API void am_suggest (am_Var *var, am_Num value); 87 | AM_API void am_deledit (am_Var *var); 88 | 89 | AM_API am_Var *am_newvariable (am_Solver *solver); 90 | AM_API void am_usevariable (am_Var *var); 91 | AM_API void am_delvariable (am_Var *var); 92 | AM_API int am_variableid (am_Var *var); 93 | AM_API am_Num am_value (am_Var *var); 94 | 95 | AM_API am_Constraint *am_newconstraint (am_Solver *solver, am_Num strength); 96 | AM_API am_Constraint *am_cloneconstraint (am_Constraint *other, am_Num strength); 97 | 98 | AM_API void am_resetconstraint (am_Constraint *cons); 99 | AM_API void am_delconstraint (am_Constraint *cons); 100 | 101 | AM_API int am_addterm (am_Constraint *cons, am_Var *var, am_Num multiplier); 102 | AM_API int am_setrelation (am_Constraint *cons, int relation); 103 | AM_API int am_addconstant (am_Constraint *cons, am_Num constant); 104 | AM_API int am_setstrength (am_Constraint *cons, am_Num strength); 105 | 106 | AM_API int am_mergeconstraint (am_Constraint *cons, const am_Constraint *other, am_Num multiplier); 107 | 108 | 109 | AM_NS_END 110 | 111 | #endif /* amoeba_h */ 112 | 113 | 114 | #if defined(AM_IMPLEMENTATION) && !defined(am_implemented) 115 | #define am_implemented 116 | 117 | 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | 124 | #define AM_EXTERNAL (0) 125 | #define AM_SLACK (1) 126 | #define AM_ERROR (2) 127 | #define AM_DUMMY (3) 128 | 129 | #define am_isexternal(key) ((key).type == AM_EXTERNAL) 130 | #define am_isslack(key) ((key).type == AM_SLACK) 131 | #define am_iserror(key) ((key).type == AM_ERROR) 132 | #define am_isdummy(key) ((key).type == AM_DUMMY) 133 | #define am_ispivotable(key) (am_isslack(key) || am_iserror(key)) 134 | 135 | #define AM_POOLSIZE 4096 136 | #define AM_MIN_HASHSIZE 4 137 | #define AM_MAX_SIZET ((~(size_t)0)-100) 138 | 139 | #define AM_UNSIGNED_BITS (sizeof(unsigned)*CHAR_BIT) 140 | 141 | #ifdef AM_USE_FLOAT 142 | # define AM_NUM_MAX FLT_MAX 143 | # define AM_NUM_EPS 1e-4f 144 | #else 145 | # define AM_NUM_MAX DBL_MAX 146 | # define AM_NUM_EPS 1e-6 147 | #endif 148 | 149 | AM_NS_BEGIN 150 | 151 | 152 | typedef struct am_Symbol { 153 | unsigned id : AM_UNSIGNED_BITS - 2; 154 | unsigned type : 2; 155 | } am_Symbol; 156 | 157 | typedef struct am_MemPool { 158 | size_t size; 159 | void *freed; 160 | void *pages; 161 | } am_MemPool; 162 | 163 | typedef struct am_Entry { 164 | int next; 165 | am_Symbol key; 166 | } am_Entry; 167 | 168 | typedef struct am_Table { 169 | size_t size; 170 | size_t count; 171 | size_t entry_size; 172 | size_t lastfree; 173 | am_Entry *hash; 174 | } am_Table; 175 | 176 | typedef struct am_Iterator { 177 | const am_Table *t; 178 | const am_Entry *entry; 179 | } am_Iterator; 180 | 181 | typedef struct am_VarEntry { 182 | am_Entry entry; 183 | am_Var *var; 184 | } am_VarEntry; 185 | 186 | typedef struct am_ConsEntry { 187 | am_Entry entry; 188 | am_Constraint *constraint; 189 | } am_ConsEntry; 190 | 191 | typedef struct am_Term { 192 | am_Entry entry; 193 | am_Num multiplier; 194 | } am_Term; 195 | 196 | typedef struct am_Row { 197 | am_Entry entry; 198 | am_Symbol infeasible_next; 199 | am_Table terms; 200 | am_Num constant; 201 | } am_Row; 202 | 203 | struct am_Var { 204 | am_Symbol sym; 205 | unsigned refcount : AM_UNSIGNED_BITS - 1; 206 | unsigned dirty : 1; 207 | am_Var *next; 208 | am_Solver *solver; 209 | am_Constraint *constraint; 210 | am_Num edit_value; 211 | am_Num value; 212 | }; 213 | 214 | struct am_Constraint { 215 | am_Row expression; 216 | am_Symbol marker; 217 | am_Symbol other; 218 | int relation; 219 | am_Solver *solver; 220 | am_Num strength; 221 | }; 222 | 223 | struct am_Solver { 224 | am_Allocf *allocf; 225 | void *ud; 226 | am_Row objective; 227 | am_Table vars; /* symbol -> VarEntry */ 228 | am_Table constraints; /* symbol -> ConsEntry */ 229 | am_Table rows; /* symbol -> Row */ 230 | am_MemPool varpool; 231 | am_MemPool conspool; 232 | unsigned symbol_count; 233 | unsigned constraint_count; 234 | unsigned auto_update; 235 | am_Symbol infeasible_rows; 236 | am_Var *dirty_vars; 237 | }; 238 | 239 | 240 | /* utils */ 241 | 242 | static am_Symbol am_newsymbol(am_Solver *solver, int type); 243 | 244 | static int am_approx(am_Num a, am_Num b) 245 | { return a > b ? a - b < AM_NUM_EPS : b - a < AM_NUM_EPS; } 246 | 247 | static int am_nearzero(am_Num a) 248 | { return am_approx(a, 0.0f); } 249 | 250 | static am_Symbol am_null(void) 251 | { am_Symbol null = { 0, 0 }; return null; } 252 | 253 | static void am_initsymbol(am_Solver *solver, am_Symbol *sym, int type) 254 | { if (sym->id == 0) *sym = am_newsymbol(solver, type); } 255 | 256 | static void am_initpool(am_MemPool *pool, size_t size) { 257 | pool->size = size; 258 | pool->freed = pool->pages = NULL; 259 | assert(size > sizeof(void*) && size < AM_POOLSIZE/4); 260 | } 261 | 262 | static void am_freepool(am_Solver *solver, am_MemPool *pool) { 263 | const size_t offset = AM_POOLSIZE - sizeof(void*); 264 | while (pool->pages != NULL) { 265 | void *next = *(void**)((char*)pool->pages + offset); 266 | solver->allocf(solver->ud, pool->pages, 0, AM_POOLSIZE); 267 | pool->pages = next; 268 | } 269 | am_initpool(pool, pool->size); 270 | } 271 | 272 | static void *am_alloc(am_Solver *solver, am_MemPool *pool) { 273 | void *obj = pool->freed; 274 | if (obj == NULL) { 275 | const size_t offset = AM_POOLSIZE - sizeof(void*); 276 | void *end, *newpage = solver->allocf(solver->ud, NULL, AM_POOLSIZE, 0); 277 | *(void**)((char*)newpage + offset) = pool->pages; 278 | pool->pages = newpage; 279 | end = (char*)newpage + (offset/pool->size-1)*pool->size; 280 | while (end != newpage) { 281 | *(void**)end = pool->freed; 282 | pool->freed = (void**)end; 283 | end = (char*)end - pool->size; 284 | } 285 | return end; 286 | } 287 | pool->freed = *(void**)obj; 288 | return obj; 289 | } 290 | 291 | static void am_free(am_MemPool *pool, void *obj) { 292 | *(void**)obj = pool->freed; 293 | pool->freed = obj; 294 | } 295 | 296 | static am_Symbol am_newsymbol(am_Solver *solver, int type) { 297 | am_Symbol sym; 298 | unsigned id = ++solver->symbol_count; 299 | if (id > 0x3FFFFFFF) id = solver->symbol_count = 1; 300 | assert(type >= AM_EXTERNAL && type <= AM_DUMMY); 301 | sym.id = id; 302 | sym.type = type; 303 | return sym; 304 | } 305 | 306 | 307 | /* hash table */ 308 | 309 | #define am_key(entry) (((am_Entry*)(entry))->key) 310 | 311 | #define am_offset(lhs,rhs) ((int)((char*)(lhs) - (char*)(rhs))) 312 | #define am_index(h,i) ((am_Entry*)((char*)(h) + (i))) 313 | 314 | static am_Entry *am_newkey(am_Solver *solver, am_Table *t, am_Symbol key); 315 | 316 | static void am_delkey(am_Table *t, am_Entry *entry) 317 | { entry->key = am_null(), --t->count; } 318 | 319 | static void am_inittable(am_Table *t, size_t entry_size) 320 | { memset(t, 0, sizeof(*t)), t->entry_size = entry_size; } 321 | 322 | static am_Entry *am_mainposition(const am_Table *t, am_Symbol key) 323 | { return am_index(t->hash, (key.id & (t->size - 1))*t->entry_size); } 324 | 325 | static void am_resettable(am_Table *t) 326 | { t->count = 0; memset(t->hash, 0, t->lastfree = t->size * t->entry_size); } 327 | 328 | static size_t am_hashsize(am_Table *t, size_t len) { 329 | size_t newsize = AM_MIN_HASHSIZE; 330 | const size_t max_size = (AM_MAX_SIZET / 2) / t->entry_size; 331 | while (newsize < max_size && newsize < len) 332 | newsize <<= 1; 333 | assert((newsize & (newsize - 1)) == 0); 334 | return newsize < len ? 0 : newsize; 335 | } 336 | 337 | static void am_freetable(am_Solver *solver, am_Table *t) { 338 | size_t size = t->size*t->entry_size; 339 | if (size) solver->allocf(solver->ud, t->hash, 0, size); 340 | am_inittable(t, t->entry_size); 341 | } 342 | 343 | static size_t am_resizetable(am_Solver *solver, am_Table *t, size_t len) { 344 | size_t i, oldsize = t->size * t->entry_size; 345 | am_Table nt = *t; 346 | nt.size = am_hashsize(t, len); 347 | nt.lastfree = nt.size*nt.entry_size; 348 | nt.hash = (am_Entry*)solver->allocf(solver->ud, NULL, nt.lastfree, 0); 349 | memset(nt.hash, 0, nt.size*nt.entry_size); 350 | for (i = 0; i < oldsize; i += nt.entry_size) { 351 | am_Entry *e = am_index(t->hash, i); 352 | if (e->key.id != 0) { 353 | am_Entry *ne = am_newkey(solver, &nt, e->key); 354 | if (t->entry_size > sizeof(am_Entry)) 355 | memcpy(ne + 1, e + 1, t->entry_size-sizeof(am_Entry)); 356 | } 357 | } 358 | if (oldsize) solver->allocf(solver->ud, t->hash, 0, oldsize); 359 | *t = nt; 360 | return t->size; 361 | } 362 | 363 | static am_Entry *am_newkey(am_Solver *solver, am_Table *t, am_Symbol key) { 364 | if (t->size == 0) am_resizetable(solver, t, AM_MIN_HASHSIZE); 365 | for (;;) { 366 | am_Entry *mp = am_mainposition(t, key); 367 | if (mp->key.id != 0) { 368 | am_Entry *f = NULL, *othern; 369 | while (t->lastfree > 0) { 370 | am_Entry *e = am_index(t->hash, t->lastfree -= t->entry_size); 371 | if (e->key.id == 0 && e->next == 0) { f = e; break; } 372 | } 373 | if (!f) { am_resizetable(solver, t, t->count*2); continue; } 374 | assert(f->key.id == 0); 375 | othern = am_mainposition(t, mp->key); 376 | if (othern != mp) { 377 | am_Entry *next; 378 | while ((next = am_index(othern, othern->next)) != mp) 379 | othern = next; 380 | othern->next = am_offset(f, othern); 381 | memcpy(f, mp, t->entry_size); 382 | if (mp->next) f->next += am_offset(mp, f), mp->next = 0; 383 | } else { 384 | if (mp->next != 0) 385 | f->next = am_offset(mp, f) + mp->next; 386 | else 387 | assert(f->next == 0); 388 | mp->next = am_offset(f, mp), mp = f; 389 | } 390 | } 391 | mp->key = key; 392 | return mp; 393 | } 394 | } 395 | 396 | static const am_Entry *am_gettable(const am_Table *t, am_Symbol key) { 397 | const am_Entry *e; 398 | if (t->size == 0 || key.id == 0) return NULL; 399 | e = am_mainposition(t, key); 400 | for (; e->key.id != key.id; e = am_index(e, e->next)) 401 | if (e->next == 0) return NULL; 402 | return e; 403 | } 404 | 405 | static am_Entry *am_settable(am_Solver *solver, am_Table *t, am_Symbol key) { 406 | am_Entry *e; 407 | assert(key.id != 0 && am_gettable(t, key) == NULL); 408 | e = am_newkey(solver, t, key); 409 | ++t->count; 410 | return e; 411 | } 412 | 413 | static am_Iterator am_itertable(const am_Table *t) { 414 | am_Iterator it; 415 | it.t = t; 416 | it.entry = NULL; 417 | return it; 418 | } 419 | 420 | static const am_Entry *am_nextentry(am_Iterator *it) { 421 | const am_Table *t = it->t; 422 | const am_Entry *end = am_index(t->hash, t->size*t->entry_size); 423 | const am_Entry *e = it->entry; 424 | e = e ? am_index(e, t->entry_size) : t->hash; 425 | for (; e < end; e = am_index(e, t->entry_size)) 426 | if (e->key.id != 0) return it->entry = e; 427 | return it->entry = NULL; 428 | } 429 | 430 | /* expression (row) */ 431 | 432 | static int am_isconstant(am_Row *row) 433 | { return row->terms.count == 0; } 434 | 435 | static void am_freerow(am_Solver *solver, am_Row *row) 436 | { am_freetable(solver, &row->terms); } 437 | 438 | static void am_resetrow(am_Row *row) 439 | { row->constant = 0.0f; am_resettable(&row->terms); } 440 | 441 | static void am_initrow(am_Row *row) { 442 | am_key(row) = am_null(); 443 | row->infeasible_next = am_null(); 444 | row->constant = 0.0f; 445 | am_inittable(&row->terms, sizeof(am_Term)); 446 | } 447 | 448 | static void am_multiply(am_Row *row, am_Num multiplier) { 449 | am_Iterator it = am_itertable(&row->terms); 450 | row->constant *= multiplier; 451 | while (am_nextentry(&it)) 452 | ((am_Term*)it.entry)->multiplier *= multiplier; 453 | } 454 | 455 | static void am_addvar(am_Solver *solver, am_Row *row, am_Symbol sym, am_Num value) { 456 | am_Term *term; 457 | if (sym.id == 0 || am_nearzero(value)) return; 458 | term = (am_Term*)am_gettable(&row->terms, sym); 459 | if (term == NULL) { 460 | term = (am_Term*)am_settable(solver, &row->terms, sym); 461 | assert(term != NULL); 462 | term->multiplier = value; 463 | } else if (am_nearzero(term->multiplier += value)) 464 | am_delkey(&row->terms, &term->entry); 465 | } 466 | 467 | static void am_addrow(am_Solver *solver, am_Row *row, const am_Row *other, am_Num multiplier) { 468 | am_Iterator it = am_itertable(&other->terms); 469 | am_Term *term; 470 | row->constant += other->constant*multiplier; 471 | while ((term = (am_Term*)am_nextentry(&it))) 472 | am_addvar(solver, row, am_key(term), term->multiplier*multiplier); 473 | } 474 | 475 | static void am_solvefor(am_Solver *solver, am_Row *row, am_Symbol enter, am_Symbol leave) { 476 | am_Term *term = (am_Term*)am_gettable(&row->terms, enter); 477 | am_Num reciprocal = 1.0f / term->multiplier; 478 | assert(enter.id != leave.id && !am_nearzero(term->multiplier)); 479 | am_delkey(&row->terms, &term->entry); 480 | am_multiply(row, -reciprocal); 481 | if (leave.id != 0) am_addvar(solver, row, leave, reciprocal); 482 | } 483 | 484 | static void am_substitute(am_Solver *solver, am_Row *row, am_Symbol enter, const am_Row *other) { 485 | am_Term *term = (am_Term*)am_gettable(&row->terms, enter); 486 | if (!term) return; 487 | am_delkey(&row->terms, &term->entry); 488 | am_addrow(solver, row, other, term->multiplier); 489 | } 490 | 491 | /* variables & constraints */ 492 | 493 | AM_API int am_variableid(am_Var *var) { return var ? var->sym.id : -1; } 494 | AM_API am_Num am_value(am_Var *var) { return var ? var->value : 0.0f; } 495 | AM_API void am_usevariable(am_Var *var) { if (var) ++var->refcount; } 496 | 497 | static am_Var *am_sym2var(am_Solver *solver, am_Symbol sym) { 498 | am_VarEntry *ve = (am_VarEntry*)am_gettable(&solver->vars, sym); 499 | assert(ve != NULL); 500 | return ve->var; 501 | } 502 | 503 | AM_API am_Var *am_newvariable(am_Solver *solver) { 504 | am_Var *var = (am_Var*)am_alloc(solver, &solver->varpool); 505 | am_Symbol sym = am_newsymbol(solver, AM_EXTERNAL); 506 | am_VarEntry *ve = (am_VarEntry*)am_settable(solver, &solver->vars, sym); 507 | assert(ve != NULL); 508 | memset(var, 0, sizeof(am_Var)); 509 | var->sym = sym; 510 | var->refcount = 1; 511 | var->solver = solver; 512 | ve->var = var; 513 | return var; 514 | } 515 | 516 | AM_API void am_delvariable(am_Var *var) { 517 | if (var && --var->refcount == 0) { 518 | am_Solver *solver = var->solver; 519 | am_VarEntry *e = (am_VarEntry*)am_gettable(&solver->vars, var->sym); 520 | assert(!var->dirty && e != NULL); 521 | am_delkey(&solver->vars, &e->entry); 522 | am_remove(var->constraint); 523 | am_free(&solver->varpool, var); 524 | } 525 | } 526 | 527 | AM_API am_Constraint *am_newconstraint(am_Solver *solver, am_Num strength) { 528 | am_Constraint *cons = (am_Constraint*)am_alloc(solver, &solver->conspool); 529 | memset(cons, 0, sizeof(*cons)); 530 | cons->solver = solver; 531 | cons->strength = am_nearzero(strength) ? AM_REQUIRED : strength; 532 | am_initrow(&cons->expression); 533 | am_key(cons).id = ++solver->constraint_count; 534 | am_key(cons).type = AM_EXTERNAL; 535 | ((am_ConsEntry*)am_settable(solver, &solver->constraints, 536 | am_key(cons)))->constraint = cons; 537 | return cons; 538 | } 539 | 540 | AM_API void am_delconstraint(am_Constraint *cons) { 541 | am_Solver *solver = cons ? cons->solver : NULL; 542 | am_Iterator it; 543 | am_ConsEntry *ce; 544 | if (cons == NULL) return; 545 | am_remove(cons); 546 | ce = (am_ConsEntry*)am_gettable(&solver->constraints, am_key(cons)); 547 | assert(ce != NULL); 548 | am_delkey(&solver->constraints, &ce->entry); 549 | it = am_itertable(&cons->expression.terms); 550 | while (am_nextentry(&it)) 551 | am_delvariable(am_sym2var(solver, it.entry->key)); 552 | am_freerow(solver, &cons->expression); 553 | am_free(&solver->conspool, cons); 554 | } 555 | 556 | AM_API am_Constraint *am_cloneconstraint(am_Constraint *other, am_Num strength) { 557 | am_Constraint *cons; 558 | if (other == NULL) return NULL; 559 | cons = am_newconstraint(other->solver, 560 | am_nearzero(strength) ? other->strength : strength); 561 | am_mergeconstraint(cons, other, 1.0f); 562 | cons->relation = other->relation; 563 | return cons; 564 | } 565 | 566 | AM_API int am_mergeconstraint(am_Constraint *cons, const am_Constraint *other, am_Num multiplier) { 567 | am_Iterator it; 568 | if (cons == NULL || other == NULL || cons->marker.id != 0 569 | || cons->solver != other->solver) return AM_FAILED; 570 | if (cons->relation == AM_GREATEQUAL) multiplier = -multiplier; 571 | cons->expression.constant += other->expression.constant*multiplier; 572 | it = am_itertable(&other->expression.terms); 573 | while (am_nextentry(&it)) { 574 | am_Term *term = (am_Term*)it.entry; 575 | am_usevariable(am_sym2var(cons->solver, am_key(term))); 576 | am_addvar(cons->solver, &cons->expression, am_key(term), 577 | term->multiplier*multiplier); 578 | } 579 | return AM_OK; 580 | } 581 | 582 | AM_API void am_resetconstraint(am_Constraint *cons) { 583 | am_Iterator it; 584 | if (cons == NULL) return; 585 | am_remove(cons); 586 | cons->relation = 0; 587 | it = am_itertable(&cons->expression.terms); 588 | while (am_nextentry(&it)) 589 | am_delvariable(am_sym2var(cons->solver, it.entry->key)); 590 | am_resetrow(&cons->expression); 591 | } 592 | 593 | AM_API int am_addterm(am_Constraint *cons, am_Var *var, am_Num multiplier) { 594 | if (cons == NULL || var == NULL || cons->marker.id != 0 || 595 | cons->solver != var->solver) return AM_FAILED; 596 | assert(var->sym.id != 0); 597 | assert(var->solver == cons->solver); 598 | if (cons->relation == AM_GREATEQUAL) multiplier = -multiplier; 599 | am_addvar(cons->solver, &cons->expression, var->sym, multiplier); 600 | am_usevariable(var); 601 | return AM_OK; 602 | } 603 | 604 | AM_API int am_addconstant(am_Constraint *cons, am_Num constant) { 605 | if (cons == NULL || cons->marker.id != 0) return AM_FAILED; 606 | cons->expression.constant += 607 | cons->relation == AM_GREATEQUAL ? -constant : constant; 608 | return AM_OK; 609 | } 610 | 611 | AM_API int am_setrelation(am_Constraint *cons, int relation) { 612 | assert(relation >= AM_LESSEQUAL && relation <= AM_GREATEQUAL); 613 | if (cons == NULL || cons->marker.id != 0 || cons->relation != 0) 614 | return AM_FAILED; 615 | if (relation != AM_GREATEQUAL) am_multiply(&cons->expression, -1.0f); 616 | cons->relation = relation; 617 | return AM_OK; 618 | } 619 | 620 | /* Cassowary algorithm */ 621 | 622 | AM_API int am_hasedit(am_Var *var) 623 | { return var != NULL && var->constraint != NULL; } 624 | 625 | AM_API int am_hasconstraint(am_Constraint *cons) 626 | { return cons != NULL && cons->marker.id != 0; } 627 | 628 | AM_API void am_autoupdate(am_Solver *solver, int auto_update) 629 | { solver->auto_update = auto_update; } 630 | 631 | static void am_infeasible(am_Solver *solver, am_Row *row) { 632 | if (row->constant < 0.0f && !am_isdummy(row->infeasible_next)) { 633 | row->infeasible_next.id = solver->infeasible_rows.id; 634 | row->infeasible_next.type = AM_DUMMY; 635 | solver->infeasible_rows = am_key(row); 636 | } 637 | } 638 | 639 | static void am_markdirty(am_Solver *solver, am_Var *var) { 640 | if (var->dirty) return; 641 | var->next = solver->dirty_vars; 642 | solver->dirty_vars = var; 643 | var->dirty = 1; 644 | ++var->refcount; 645 | } 646 | 647 | static void am_substitute_rows(am_Solver *solver, am_Symbol var, am_Row *expr) { 648 | am_Iterator it = am_itertable(&solver->rows); 649 | while (am_nextentry(&it)) { 650 | am_Row *row = (am_Row*)it.entry; 651 | am_substitute(solver, row, var, expr); 652 | if (am_isexternal(am_key(row))) 653 | am_markdirty(solver, am_sym2var(solver, am_key(row))); 654 | else 655 | am_infeasible(solver, row); 656 | } 657 | am_substitute(solver, &solver->objective, var, expr); 658 | } 659 | 660 | static int am_takerow(am_Solver *solver, am_Symbol sym, am_Row *dst) { 661 | am_Row *row = (am_Row*)am_gettable(&solver->rows, sym); 662 | am_key(dst) = am_null(); 663 | if (row == NULL) return AM_FAILED; 664 | am_delkey(&solver->rows, &row->entry); 665 | dst->constant = row->constant; 666 | dst->terms = row->terms; 667 | return AM_OK; 668 | } 669 | 670 | static int am_putrow(am_Solver *solver, am_Symbol sym, const am_Row *src) { 671 | am_Row *row; 672 | assert(am_gettable(&solver->rows, sym) == NULL); 673 | row = (am_Row*)am_settable(solver, &solver->rows, sym); 674 | row->infeasible_next = am_null(); 675 | row->constant = src->constant; 676 | row->terms = src->terms; 677 | return AM_OK; 678 | } 679 | 680 | static void am_mergerow(am_Solver *solver, am_Row *row, am_Symbol var, am_Num multiplier) { 681 | am_Row *oldrow = (am_Row*)am_gettable(&solver->rows, var); 682 | if (oldrow) 683 | am_addrow(solver, row, oldrow, multiplier); 684 | else 685 | am_addvar(solver, row, var, multiplier); 686 | } 687 | 688 | static int am_optimize(am_Solver *solver, am_Row *objective) { 689 | for (;;) { 690 | am_Symbol enter = am_null(), leave = am_null(); 691 | am_Num r, min_ratio = AM_NUM_MAX; 692 | am_Iterator it = am_itertable(&objective->terms); 693 | am_Row tmp, *row; 694 | am_Term *term; 695 | 696 | assert(solver->infeasible_rows.id == 0); 697 | while ((term = (am_Term*)am_nextentry(&it))) 698 | if (!am_isdummy(am_key(term)) && term->multiplier < 0.0f) 699 | { enter = am_key(term); break; } 700 | if (enter.id == 0) return AM_OK; 701 | 702 | it = am_itertable(&solver->rows); 703 | while ((row = (am_Row*)am_nextentry(&it))) { 704 | if (am_isexternal(am_key(row))) continue; 705 | term = (am_Term*)am_gettable(&row->terms, enter); 706 | if (term == NULL || term->multiplier > 0.0f) continue; 707 | r = -row->constant / term->multiplier; 708 | if (r < min_ratio || (am_approx(r, min_ratio) 709 | && am_key(row).id < leave.id)) 710 | min_ratio = r, leave = am_key(row); 711 | } 712 | assert(leave.id != 0); 713 | if (leave.id == 0) return AM_FAILED; 714 | 715 | am_takerow(solver, leave, &tmp); 716 | am_solvefor(solver, &tmp, enter, leave); 717 | am_substitute_rows(solver, enter, &tmp); 718 | if (objective != &solver->objective) 719 | am_substitute(solver, objective, enter, &tmp); 720 | am_putrow(solver, enter, &tmp); 721 | } 722 | } 723 | 724 | static am_Row am_makerow(am_Solver *solver, am_Constraint *cons) { 725 | am_Iterator it = am_itertable(&cons->expression.terms); 726 | am_Row row; 727 | am_initrow(&row); 728 | row.constant = cons->expression.constant; 729 | while (am_nextentry(&it)) { 730 | am_Term *term = (am_Term*)it.entry; 731 | am_markdirty(solver, am_sym2var(solver, am_key(term))); 732 | am_mergerow(solver, &row, am_key(term), term->multiplier); 733 | } 734 | if (cons->relation != AM_EQUAL) { 735 | am_initsymbol(solver, &cons->marker, AM_SLACK); 736 | am_addvar(solver, &row, cons->marker, -1.0f); 737 | if (cons->strength < AM_REQUIRED) { 738 | am_initsymbol(solver, &cons->other, AM_ERROR); 739 | am_addvar(solver, &row, cons->other, 1.0f); 740 | am_addvar(solver, &solver->objective, cons->other, cons->strength); 741 | } 742 | } else if (cons->strength >= AM_REQUIRED) { 743 | am_initsymbol(solver, &cons->marker, AM_DUMMY); 744 | am_addvar(solver, &row, cons->marker, 1.0f); 745 | } else { 746 | am_initsymbol(solver, &cons->marker, AM_ERROR); 747 | am_initsymbol(solver, &cons->other, AM_ERROR); 748 | am_addvar(solver, &row, cons->marker, -1.0f); 749 | am_addvar(solver, &row, cons->other, 1.0f); 750 | am_addvar(solver, &solver->objective, cons->marker, cons->strength); 751 | am_addvar(solver, &solver->objective, cons->other, cons->strength); 752 | } 753 | if (row.constant < 0.0f) am_multiply(&row, -1.0f); 754 | return row; 755 | } 756 | 757 | static void am_remove_errors(am_Solver *solver, am_Constraint *cons) { 758 | if (am_iserror(cons->marker)) 759 | am_mergerow(solver, &solver->objective, cons->marker, -cons->strength); 760 | if (am_iserror(cons->other)) 761 | am_mergerow(solver, &solver->objective, cons->other, -cons->strength); 762 | if (am_isconstant(&solver->objective)) 763 | solver->objective.constant = 0.0f; 764 | cons->marker = cons->other = am_null(); 765 | } 766 | 767 | static int am_add_with_artificial(am_Solver *solver, am_Row *row, am_Constraint *cons) { 768 | am_Symbol a = am_newsymbol(solver, AM_SLACK); 769 | am_Iterator it; 770 | am_Row tmp; 771 | am_Term *term; 772 | int ret; 773 | --solver->symbol_count; /* artificial variable will be removed */ 774 | am_initrow(&tmp); 775 | am_addrow(solver, &tmp, row, 1.0f); 776 | am_putrow(solver, a, row); 777 | am_initrow(row); /* row is useless */ 778 | am_optimize(solver, &tmp); 779 | ret = am_nearzero(tmp.constant) ? AM_OK : AM_UNBOUND; 780 | am_freerow(solver, &tmp); 781 | if (am_takerow(solver, a, &tmp) == AM_OK) { 782 | am_Symbol enter = am_null(); 783 | if (am_isconstant(&tmp)) { am_freerow(solver, &tmp); return ret; } 784 | it = am_itertable(&tmp.terms); 785 | while ((term = (am_Term*)am_nextentry(&it))) 786 | if (am_ispivotable(am_key(term))) { enter = am_key(term); break; } 787 | if (enter.id == 0) { am_freerow(solver, &tmp); return AM_UNBOUND; } 788 | am_solvefor(solver, &tmp, enter, a); 789 | am_substitute_rows(solver, enter, &tmp); 790 | am_putrow(solver, enter, &tmp); 791 | } 792 | it = am_itertable(&solver->rows); 793 | while ((row = (am_Row*)am_nextentry(&it))) { 794 | term = (am_Term*)am_gettable(&row->terms, a); 795 | if (term) am_delkey(&row->terms, &term->entry); 796 | } 797 | term = (am_Term*)am_gettable(&solver->objective.terms, a); 798 | if (term) am_delkey(&solver->objective.terms, &term->entry); 799 | if (ret != AM_OK) am_remove(cons); 800 | return ret; 801 | } 802 | 803 | static int am_try_addrow(am_Solver *solver, am_Row *row, am_Constraint *cons) { 804 | am_Symbol subject = am_null(); 805 | am_Term *term; 806 | am_Iterator it = am_itertable(&row->terms); 807 | while ((term = (am_Term*)am_nextentry(&it))) 808 | if (am_isexternal(am_key(term))) { subject = am_key(term); break; } 809 | if (subject.id == 0 && am_ispivotable(cons->marker)) { 810 | am_Term *mterm = (am_Term*)am_gettable(&row->terms, cons->marker); 811 | if (mterm->multiplier < 0.0f) subject = cons->marker; 812 | } 813 | if (subject.id == 0 && am_ispivotable(cons->other)) { 814 | am_Term *oterm = (am_Term*)am_gettable(&row->terms, cons->other); 815 | if (oterm->multiplier < 0.0f) subject = cons->other; 816 | } 817 | if (subject.id == 0) { 818 | it = am_itertable(&row->terms); 819 | while ((term = (am_Term*)am_nextentry(&it))) 820 | if (!am_isdummy(am_key(term))) break; 821 | if (term == NULL) { 822 | if (am_nearzero(row->constant)) 823 | subject = cons->marker; 824 | else { 825 | am_freerow(solver, row); 826 | return AM_UNSATISFIED; 827 | } 828 | } 829 | } 830 | if (subject.id == 0) 831 | return am_add_with_artificial(solver, row, cons); 832 | am_solvefor(solver, row, subject, am_null()); 833 | am_substitute_rows(solver, subject, row); 834 | am_putrow(solver, subject, row); 835 | return AM_OK; 836 | } 837 | 838 | static am_Symbol am_get_leaving_row(am_Solver *solver, am_Symbol marker) { 839 | am_Symbol first = am_null(), second = am_null(), third = am_null(); 840 | am_Num r1 = AM_NUM_MAX, r2 = AM_NUM_MAX; 841 | am_Iterator it = am_itertable(&solver->rows); 842 | while (am_nextentry(&it)) { 843 | am_Row *row = (am_Row*)it.entry; 844 | am_Term *term = (am_Term*)am_gettable(&row->terms, marker); 845 | if (term == NULL) continue; 846 | if (am_isexternal(am_key(row))) 847 | third = am_key(row); 848 | else if (term->multiplier < 0.0f) { 849 | am_Num r = -row->constant / term->multiplier; 850 | if (r < r1) r1 = r, first = am_key(row); 851 | } else { 852 | am_Num r = row->constant / term->multiplier; 853 | if (r < r2) r2 = r, second = am_key(row); 854 | } 855 | } 856 | return first.id ? first : second.id ? second : third; 857 | } 858 | 859 | static void am_delta_edit_constant(am_Solver *solver, am_Num delta, am_Constraint *cons) { 860 | am_Iterator it; 861 | am_Row *row; 862 | if ((row = (am_Row*)am_gettable(&solver->rows, cons->marker)) != NULL) 863 | { row->constant -= delta; am_infeasible(solver, row); return; } 864 | if ((row = (am_Row*)am_gettable(&solver->rows, cons->other)) != NULL) 865 | { row->constant += delta; am_infeasible(solver, row); return; } 866 | it = am_itertable(&solver->rows); 867 | while ((row = (am_Row*)am_nextentry(&it))) { 868 | am_Term *term = (am_Term*)am_gettable(&row->terms, cons->marker); 869 | if (term == NULL) continue; 870 | row->constant += term->multiplier*delta; 871 | if (am_isexternal(am_key(row))) 872 | am_markdirty(solver, am_sym2var(solver, am_key(row))); 873 | else 874 | am_infeasible(solver, row); 875 | } 876 | } 877 | 878 | static void am_dual_optimize(am_Solver *solver) { 879 | while (solver->infeasible_rows.id != 0) { 880 | am_Symbol cur, enter = am_null(), leave; 881 | am_Term *objterm, *term; 882 | am_Num r, min_ratio = AM_NUM_MAX; 883 | am_Iterator it; 884 | am_Row tmp, *row = 885 | (am_Row*)am_gettable(&solver->rows, solver->infeasible_rows); 886 | assert(row != NULL); 887 | leave = am_key(row); 888 | solver->infeasible_rows = row->infeasible_next; 889 | row->infeasible_next = am_null(); 890 | if (am_nearzero(row->constant) || row->constant >= 0.0f) continue; 891 | it = am_itertable(&row->terms); 892 | while ((term = (am_Term*)am_nextentry(&it))) { 893 | if (am_isdummy(cur = am_key(term)) || term->multiplier <= 0.0f) 894 | continue; 895 | objterm = (am_Term*)am_gettable(&solver->objective.terms, cur); 896 | r = objterm ? objterm->multiplier / term->multiplier : 0.0f; 897 | if (min_ratio > r) min_ratio = r, enter = cur; 898 | } 899 | assert(enter.id != 0); 900 | am_takerow(solver, leave, &tmp); 901 | am_solvefor(solver, &tmp, enter, leave); 902 | am_substitute_rows(solver, enter, &tmp); 903 | am_putrow(solver, enter, &tmp); 904 | } 905 | } 906 | 907 | static void *am_default_allocf(void *ud, void *ptr, size_t nsize, size_t osize) { 908 | void *newptr; 909 | (void)ud, (void)osize; 910 | if (nsize == 0) { free(ptr); return NULL; } 911 | newptr = realloc(ptr, nsize); 912 | if (newptr == NULL) abort(); 913 | return newptr; 914 | } 915 | 916 | AM_API am_Solver *am_newsolver(am_Allocf *allocf, void *ud) { 917 | am_Solver *solver; 918 | if (allocf == NULL) allocf = am_default_allocf; 919 | if ((solver = (am_Solver*)allocf(ud, NULL, sizeof(am_Solver), 0)) == NULL) 920 | return NULL; 921 | memset(solver, 0, sizeof(*solver)); 922 | solver->allocf = allocf; 923 | solver->ud = ud; 924 | am_initrow(&solver->objective); 925 | am_inittable(&solver->vars, sizeof(am_VarEntry)); 926 | am_inittable(&solver->constraints, sizeof(am_ConsEntry)); 927 | am_inittable(&solver->rows, sizeof(am_Row)); 928 | am_initpool(&solver->varpool, sizeof(am_Var)); 929 | am_initpool(&solver->conspool, sizeof(am_Constraint)); 930 | return solver; 931 | } 932 | 933 | AM_API void am_delsolver(am_Solver *solver) { 934 | am_Iterator it = am_itertable(&solver->constraints); 935 | am_ConsEntry *ce; 936 | am_Row *row; 937 | while ((ce = (am_ConsEntry*)am_nextentry(&it))) 938 | am_freerow(solver, &ce->constraint->expression); 939 | it = am_itertable(&solver->rows); 940 | while ((row = (am_Row*)am_nextentry(&it))) 941 | am_freerow(solver, row); 942 | am_freerow(solver, &solver->objective); 943 | am_freetable(solver, &solver->vars); 944 | am_freetable(solver, &solver->constraints); 945 | am_freetable(solver, &solver->rows); 946 | am_freepool(solver, &solver->varpool); 947 | am_freepool(solver, &solver->conspool); 948 | solver->allocf(solver->ud, solver, 0, sizeof(*solver)); 949 | } 950 | 951 | AM_API void am_resetsolver(am_Solver *solver, int clear_constraints) { 952 | am_Iterator it = am_itertable(&solver->vars); 953 | if (!solver->auto_update) am_updatevars(solver); 954 | while (am_nextentry(&it)) { 955 | am_VarEntry *ve = (am_VarEntry*)it.entry; 956 | am_Constraint **cons = &ve->var->constraint; 957 | am_remove(*cons); 958 | *cons = NULL; 959 | } 960 | assert(am_nearzero(solver->objective.constant)); 961 | assert(solver->infeasible_rows.id == 0); 962 | assert(solver->dirty_vars == NULL); 963 | if (!clear_constraints) return; 964 | am_resetrow(&solver->objective); 965 | it = am_itertable(&solver->constraints); 966 | while (am_nextentry(&it)) { 967 | am_Constraint *cons = ((am_ConsEntry*)it.entry)->constraint; 968 | if (cons->marker.id != 0) 969 | cons->marker = cons->other = am_null(); 970 | } 971 | it = am_itertable(&solver->rows); 972 | while (am_nextentry(&it)) { 973 | am_delkey(&solver->rows, (am_Entry*)it.entry); 974 | am_freerow(solver, (am_Row*)it.entry); 975 | } 976 | } 977 | 978 | AM_API void am_updatevars(am_Solver *solver) { 979 | am_Var *var, *dead_vars = NULL; 980 | while (solver->dirty_vars != NULL) { 981 | var = solver->dirty_vars; 982 | solver->dirty_vars = var->next; 983 | var->dirty = 0; 984 | if (var->refcount == 1) 985 | var->next = dead_vars, dead_vars = var; 986 | else { 987 | am_Row *row = (am_Row*)am_gettable(&solver->rows, var->sym); 988 | var->value = row ? row->constant : 0.0f; 989 | --var->refcount; 990 | } 991 | } 992 | while (dead_vars != NULL) { 993 | var = dead_vars, dead_vars = var->next; 994 | am_delvariable(var); 995 | } 996 | } 997 | 998 | AM_API int am_add(am_Constraint *cons) { 999 | am_Solver *solver = cons ? cons->solver : NULL; 1000 | int ret, oldsym = solver ? solver->symbol_count : 0; 1001 | am_Row row; 1002 | if (solver == NULL || cons->marker.id != 0) return AM_FAILED; 1003 | row = am_makerow(solver, cons); 1004 | if ((ret = am_try_addrow(solver, &row, cons)) != AM_OK) { 1005 | am_remove_errors(solver, cons); 1006 | solver->symbol_count = oldsym; 1007 | } else { 1008 | am_optimize(solver, &solver->objective); 1009 | if (solver->auto_update) am_updatevars(solver); 1010 | } 1011 | assert(solver->infeasible_rows.id == 0); 1012 | return ret; 1013 | } 1014 | 1015 | AM_API void am_remove(am_Constraint *cons) { 1016 | am_Solver *solver; 1017 | am_Symbol marker; 1018 | am_Row tmp; 1019 | if (cons == NULL || cons->marker.id == 0) return; 1020 | solver = cons->solver, marker = cons->marker; 1021 | am_remove_errors(solver, cons); 1022 | if (am_takerow(solver, marker, &tmp) != AM_OK) { 1023 | am_Symbol leave = am_get_leaving_row(solver, marker); 1024 | assert(leave.id != 0); 1025 | am_takerow(solver, leave, &tmp); 1026 | am_solvefor(solver, &tmp, marker, leave); 1027 | am_substitute_rows(solver, marker, &tmp); 1028 | } 1029 | am_freerow(solver, &tmp); 1030 | am_optimize(solver, &solver->objective); 1031 | if (solver->auto_update) am_updatevars(solver); 1032 | } 1033 | 1034 | AM_API int am_setstrength(am_Constraint *cons, am_Num strength) { 1035 | if (cons == NULL) return AM_FAILED; 1036 | strength = am_nearzero(strength) ? AM_REQUIRED : strength; 1037 | if (cons->strength == strength) return AM_OK; 1038 | if (cons->strength >= AM_REQUIRED || strength >= AM_REQUIRED) 1039 | { am_remove(cons), cons->strength = strength; return am_add(cons); } 1040 | if (cons->marker.id != 0) { 1041 | am_Solver *solver = cons->solver; 1042 | am_Num diff = strength - cons->strength; 1043 | am_mergerow(solver, &solver->objective, cons->marker, diff); 1044 | am_mergerow(solver, &solver->objective, cons->other, diff); 1045 | am_optimize(solver, &solver->objective); 1046 | if (solver->auto_update) am_updatevars(solver); 1047 | } 1048 | cons->strength = strength; 1049 | return AM_OK; 1050 | } 1051 | 1052 | AM_API int am_addedit(am_Var *var, am_Num strength) { 1053 | am_Solver *solver = var ? var->solver : NULL; 1054 | am_Constraint *cons; 1055 | if (var == NULL) return AM_FAILED; 1056 | if (strength >= AM_STRONG) strength = AM_STRONG; 1057 | if (var->constraint) return am_setstrength(var->constraint, strength); 1058 | assert(var->sym.id != 0); 1059 | cons = am_newconstraint(solver, strength); 1060 | am_setrelation(cons, AM_EQUAL); 1061 | am_addterm(cons, var, 1.0f); /* var must have positive signture */ 1062 | am_addconstant(cons, -var->value); 1063 | if (am_add(cons) != AM_OK) assert(0); 1064 | var->constraint = cons; 1065 | var->edit_value = var->value; 1066 | return AM_OK; 1067 | } 1068 | 1069 | AM_API void am_deledit(am_Var *var) { 1070 | if (var == NULL || var->constraint == NULL) return; 1071 | am_delconstraint(var->constraint); 1072 | var->constraint = NULL; 1073 | var->edit_value = 0.0f; 1074 | } 1075 | 1076 | AM_API void am_suggest(am_Var *var, am_Num value) { 1077 | am_Solver *solver = var ? var->solver : NULL; 1078 | am_Num delta; 1079 | if (var == NULL) return; 1080 | if (var->constraint == NULL) { 1081 | am_addedit(var, AM_MEDIUM); 1082 | assert(var->constraint != NULL); 1083 | } 1084 | delta = value - var->edit_value; 1085 | var->edit_value = value; 1086 | am_delta_edit_constant(solver, delta, var->constraint); 1087 | am_dual_optimize(solver); 1088 | if (solver->auto_update) am_updatevars(solver); 1089 | } 1090 | 1091 | 1092 | AM_NS_END 1093 | 1094 | #endif /* AM_IMPLEMENTATION */ 1095 | 1096 | /* cc: flags+='-shared -O2 -DAM_IMPLEMENTATION -xc' 1097 | unixcc: output='amoeba.so' 1098 | win32cc: output='amoeba.dll' */ 1099 | 1100 | -------------------------------------------------------------------------------- /amoeba.lua: -------------------------------------------------------------------------------- 1 | 2 | local function meta(name, parent) 3 | local t = {} 4 | t.__name = name 5 | t.__index = t 6 | return setmetatable(t, parent) 7 | end 8 | 9 | local function approx(a, b) 10 | if a > b then return a - b < 1e-6 end 11 | return b - a < 1e-6 12 | end 13 | 14 | local function near_zero(n) 15 | return approx(n, 0.0) 16 | end 17 | 18 | local Variable, Expression, Constraint do 19 | 20 | Variable = meta "Variable" 21 | Expression = meta "Expression" 22 | Constraint = meta "Constraint" 23 | 24 | Constraint.REQUIRED = 1000000000.0 25 | Constraint.STRONG = 1000000.0 26 | Constraint.MEDIUM = 1000.0 27 | Constraint.WEAK = 1.0 28 | 29 | function Variable:__unm() return Expression.new(self, -1.0) end 30 | function Expression:__unm() return Expression.new(self):multiply(-1) end 31 | 32 | function Variable:__add(other) return Expression.new(self) + other end 33 | function Variable:__sub(other) return Expression.new(self) - other end 34 | function Variable:__mul(other) return Expression.new(self) * other end 35 | function Variable:__div(other) return Expression.new(self) / other end 36 | 37 | function Variable:le(other) return Expression.new(self):le(other) end 38 | function Variable:eq(other) return Expression.new(self):eq(other) end 39 | function Variable:ge(other) return Expression.new(self):ge(other) end 40 | 41 | function Expression:__add(other) return Expression.new(self):add(other) end 42 | function Expression:__sub(other) return Expression.new(self):add(-other) end 43 | function Expression:__mul(other) return Expression.new(self):multiply(other) end 44 | function Expression:__div(other) return Expression.new(self):multiply(1.0/other) end 45 | 46 | function Expression:le(other) return Constraint.new("<=", self, other) end 47 | function Expression:eq(other) return Constraint.new("==", self, other) end 48 | function Expression:ge(other) return Constraint.new(">=", self, other) end 49 | 50 | function Constraint:__call(...) return self:add(...) end 51 | 52 | function Variable.new(name, type, id) 53 | type = type or "external" 54 | assert(type == "external" or 55 | type == "slack" or 56 | type == "error" or 57 | type == "dummy", type) 58 | local self = { 59 | id = id, 60 | name = name, 61 | value = 0.0, 62 | type = type or "external", 63 | is_dummy = type == "dummy", 64 | is_slack = type == "slack", 65 | is_error = type == "error", 66 | is_external = type == "external", 67 | is_pivotable = type == "slack" or type == "error", 68 | is_restricted = type ~= "external", 69 | } 70 | return setmetatable(self, Variable) 71 | end 72 | 73 | function Variable:__tostring() 74 | return ("amoeba.Variable: %s = %g"):format(self.name, self.value) 75 | end 76 | 77 | function Expression.new(other, multiplier, constant) 78 | local self = setmetatable({}, Expression) 79 | return self:add(other, multiplier, constant) 80 | end 81 | 82 | function Expression:tostring() 83 | local t = { ("%g"):format(self.constant or 0.0) } 84 | for k, v in self:iter_vars() do 85 | t[#t+1] = v < 0.0 and ' - ' or ' + ' 86 | v = math.abs(v) 87 | if not approx(v, 1.0) then 88 | t[#t+1] = ("%g*"):format(v) 89 | end 90 | t[#t+1] = k.name 91 | end 92 | return table.concat(t) 93 | end 94 | 95 | function Expression:__tostring() 96 | return "Exp: "..self:tostring() 97 | end 98 | 99 | function Expression:add(other, multiplier, constant) 100 | if other == nil then return self end 101 | self.constant = (self.constant or 0.0) + (constant or 0.0) 102 | multiplier = multiplier or 1.0 103 | if tonumber(other) then 104 | self.constant = self.constant + other*multiplier 105 | return self 106 | end 107 | local mt = getmetatable(other) 108 | if mt == Variable then 109 | multiplier = (self[other] or 0.0) + multiplier 110 | self[other] = not near_zero(multiplier) and multiplier or nil 111 | elseif mt == Expression then 112 | for k, v in pairs(other) do 113 | multiplier = (self[k] or 0.0) + multiplier * v 114 | self[k] = not near_zero(multiplier) and multiplier or nil 115 | end 116 | self.constant = self.constant or 0.0 117 | else 118 | error("constant/variable/expression expected") 119 | end 120 | return self 121 | end 122 | 123 | function Expression:multiply(other) 124 | if tonumber(other) then 125 | for k, v in pairs(self) do 126 | self[k] = v * other 127 | end 128 | return self 129 | end 130 | local mt = getmetatable(other) 131 | if mt == Variable then 132 | return self:multiply(Expression.new(other)) 133 | elseif mt == Expression then 134 | if other:is_constant() then 135 | return self:multiply(other.constant) 136 | elseif self.constant then 137 | local constant = self.constant 138 | self.constant = 0.0 139 | return self:add(other):multiply(constant) 140 | end 141 | error("attempt to multiply two non-constant expression") 142 | else 143 | error("number/variable/constant expression expected") 144 | end 145 | end 146 | 147 | function Expression:choose_pivotable() 148 | for k in pairs(self) do 149 | if k.is_pivotable then 150 | return k 151 | end 152 | end 153 | end 154 | 155 | function Expression:is_constant() 156 | local f, state = self:iter_vars() 157 | return f(state) == nil 158 | end 159 | 160 | function Expression:solve_for(new, old) 161 | -- expr: old == a[n] *new + constant + a[i]* v[i]... 162 | -- => new == (1/a[n])*old - 1/a[n]*constant - (1/a[n])*a[i]*v[i]... 163 | local multiplier = assert(self[new]) 164 | assert(new ~= old and not near_zero(multiplier)) 165 | self[new] = nil 166 | local reciprocal = 1.0 / multiplier 167 | self:multiply(-reciprocal) 168 | if old then self[old] = reciprocal end 169 | return new 170 | end 171 | 172 | function Expression:substitute_out(var, expr) 173 | assert(var ~= "constant") 174 | local multiplier = self[var] 175 | if not multiplier then return end 176 | self[var] = nil 177 | self:add(expr, multiplier) 178 | end 179 | 180 | function Expression:iter_vars() 181 | return function(self1, k1) 182 | local k, v = next(self1, k1) 183 | if k == 'constant' then 184 | return next(self1, k1) 185 | end 186 | return k, v 187 | end, self 188 | end 189 | 190 | function Constraint.new(op, expr1, expr2, strength) 191 | local self = setmetatable({}, Constraint) 192 | if not op then 193 | self.expression = Expression.new() 194 | else 195 | self:relation(op) 196 | if self.op == '<=' then 197 | self.expression = Expression.new(expr2 or 0.0):add(expr1, -1.0) 198 | else 199 | self.expression = Expression.new(expr1 or 0.0):add(expr2, -1.0) 200 | end 201 | end 202 | return self:strength(strength or Constraint.REQUIRED) 203 | end 204 | 205 | function Constraint:__tostring() 206 | local repr = "amoeba.Constraint: ["..self.expression:tostring() 207 | if self.is_inequality then 208 | repr = repr .. " >= 0.0]" 209 | else 210 | repr = repr .. " == 0.0]" 211 | end 212 | return repr 213 | end 214 | 215 | function Constraint:add(other, multiplier, constant) 216 | if other == ">=" or other == "<=" or other == "==" then 217 | self:relation(other) 218 | else 219 | multiplier = multiplier or 1.0 220 | if self.op == '>=' then multiplier = -multiplier end 221 | self.expression:add(other, multiplier, constant) 222 | end 223 | return self 224 | end 225 | 226 | function Constraint:relation(op) 227 | assert(op == '==' or op == '<=' or op == '>=' or 228 | op == 'eq' or op == 'le' or op == 'ge', 229 | "op must be '==', '>=' or '<='") 230 | if op == 'eq' then op = '==' 231 | elseif op == 'le' then op = '<=' 232 | elseif op == 'ge' then op = '>=' end 233 | self.op = op 234 | if self.op ~= '==' then 235 | self.is_inequality = true 236 | end 237 | if self.op ~= '>=' and self.expression then 238 | self.expression:multiply(-1.0) 239 | end 240 | return self 241 | end 242 | 243 | function Constraint:strength(strength) 244 | if self.solver then 245 | self.solver:setstrength(self, strength) 246 | else 247 | self.weight = Constraint[strength] or tonumber(strength) or self.weight 248 | self.is_required = self.weight >= Constraint.REQUIRED 249 | end 250 | return self 251 | end 252 | 253 | function Constraint:clone(strength) 254 | local new = Constraint.new():strength(strength) 255 | new:add(self) 256 | new.op = self.op 257 | new.is_inequality = self.is_inequality 258 | return new 259 | end 260 | 261 | end 262 | 263 | local SimplexSolver = meta "SimplexSolver" do 264 | 265 | -- implements 266 | 267 | local function update_external_variables(self) 268 | for var in pairs(self.vars) do 269 | local row = self.rows[var] 270 | var.value = row and row.constant or 0.0 271 | end 272 | end 273 | 274 | local function substitute_out(self, var, expr) 275 | for k, row in pairs(self.rows) do 276 | row:substitute_out(var, expr) 277 | if k.is_restricted and row.constant < 0.0 then 278 | self.infeasible_rows[#self.infeasible_rows+1] = k 279 | end 280 | end 281 | self.objective:substitute_out(var, expr) 282 | end 283 | 284 | local function optimize(self, objective) 285 | objective = objective or self.objective 286 | while true do 287 | local entry, exit 288 | for var, multiplier in objective:iter_vars() do 289 | if not var.is_dummy and multiplier < 0.0 then 290 | entry = var 291 | break 292 | end 293 | end 294 | if not entry then return end 295 | 296 | local r 297 | local min_ratio = math.huge 298 | for var, row in pairs(self.rows) do 299 | local multiplier = row[entry] 300 | if multiplier and var.is_pivotable and multiplier < 0.0 then 301 | r = -row.constant / multiplier 302 | if r < min_ratio or (approx(r, min_ratio) and 303 | var.id < exit.id) then 304 | min_ratio, exit = r, var 305 | end 306 | end 307 | end 308 | assert(exit, "objective function is unbounded") 309 | 310 | -- do pivot 311 | local row = self.rows[exit] 312 | self.rows[exit] = nil 313 | row:solve_for(entry, exit) 314 | substitute_out(self, entry, row) 315 | if objective ~= self.objective then 316 | objective:substitute_out(entry, row) 317 | end 318 | self.rows[entry] = row 319 | end 320 | end 321 | 322 | local function make_variable(self, type) 323 | local id = self.last_varid 324 | self.last_varid = id + 1 325 | local prefix = type == "eplus" and "ep" or 326 | type == "eminus" and "em" or 327 | type == "dummy" and "d" or 328 | type == "artificial" and "a" or "s" 329 | if not type or type == "artificial" then 330 | type = "slack" 331 | elseif type == "eplus" or type == "eminus" then 332 | type = "error" 333 | end 334 | return Variable.new(prefix..id, type, id) 335 | end 336 | 337 | local function make_expression(self, cons) 338 | local expr = Expression.new(cons.expression.constant) 339 | local var1, var2 340 | for k, v in cons.expression:iter_vars() do 341 | if not k.id then 342 | k.id = self.last_varid 343 | self.last_varid = k.id + 1 344 | end 345 | if not self.vars[k] then 346 | self.vars[k] = true 347 | end 348 | expr:add(self.rows[k] or k, v) 349 | end 350 | if cons.is_inequality then 351 | var1 = make_variable(self) -- slack 352 | expr[var1] = -1.0 353 | if not cons.is_required then 354 | var2 = make_variable(self, "eminus") 355 | expr[var2] = 1.0 356 | self.objective[var2] = cons.weight 357 | end 358 | elseif cons.is_required then 359 | var1 = make_variable(self, 'dummy') 360 | expr[var1] = 1.0 361 | else 362 | var1 = make_variable(self, 'eplus') 363 | var2 = make_variable(self, 'eminus') 364 | expr[var1] = -1.0 365 | expr[var2] = 1.0 366 | self.objective[var1] = cons.weight 367 | self.objective[var2] = cons.weight 368 | end 369 | if expr.constant < 0.0 then expr:multiply(-1.0) end 370 | return expr, var1, var2 371 | end 372 | 373 | local function choose_subject(_, expr, var1, var2) 374 | for k in expr:iter_vars() do 375 | if k.is_external then return k end 376 | end 377 | if var1 and var1.is_pivotable then return var1 end 378 | if var2 and var2.is_pivotable then return var2 end 379 | for k in expr:iter_vars() do 380 | if not k.is_dummy then return nil end -- no luck 381 | end 382 | if not near_zero(expr.constant) then 383 | return nil, "unsatisfiable required constraint added" 384 | end 385 | return var1 386 | end 387 | 388 | local function add_with_artificial_variable(self, expr) 389 | local a = make_variable(self, 'artificial') 390 | self.last_varid = self.last_varid - 1 391 | 392 | self.rows[a] = expr 393 | optimize(self, expr) 394 | local row = self.rows[a] 395 | self.rows[a] = nil 396 | 397 | local success = near_zero(expr.constant) 398 | if row then 399 | if row:is_constant() then 400 | return success 401 | end 402 | local entering = row:choose_pivotable() 403 | if not entering then return false end 404 | 405 | row:solve_for(entering, a) 406 | self.rows[entering] = row 407 | end 408 | 409 | for _, r in pairs(self.rows) do r[a] = nil end 410 | self.objective[a] = nil 411 | return success 412 | end 413 | 414 | local function get_marker_leaving_row(self, marker) 415 | local r1, r2 = math.huge, math.huge 416 | local first, second, third 417 | for var, row in pairs(self.rows) do 418 | local multiplier = row[marker] 419 | if multiplier then 420 | if var.is_external then 421 | third = var 422 | elseif multiplier < 0.0 then 423 | local r = -row.constant / multiplier 424 | if r < r1 then r1 = r; first = var end 425 | else 426 | local r = row.constant / multiplier 427 | if r < r2 then r2 = r; second = var end 428 | end 429 | end 430 | end 431 | return first or second or third 432 | end 433 | 434 | local function delta_edit_constant(self, delta, var1, var2) 435 | local row = self.rows[var1] 436 | if row then 437 | row.constant = row.constant - delta 438 | if row.constant < 0.0 then 439 | self.infeasible_rows[#self.infeasible_rows+1] = var1 440 | end 441 | return 442 | end 443 | row = self.rows[var2] 444 | if row then 445 | row.constant = row.constant + delta 446 | if row.constant < 0.0 then 447 | self.infeasible_rows[#self.infeasible_rows+1] = var2 448 | end 449 | return 450 | end 451 | for var, r in pairs(self.rows) do 452 | r.constant = r.constant + (r[var1] or 0.0)*delta 453 | if var.is_restricted and r.constant < 0.0 then 454 | self.infeasible_rows[#self.infeasible_rows+1] = var 455 | end 456 | end 457 | end 458 | 459 | local function dual_optimize(self) 460 | while true do 461 | local count = #self.infeasible_rows 462 | if count == 0 then return end 463 | local exit = self.infeasible_rows[count] 464 | self.infeasible_rows[count] = nil 465 | 466 | local row = self.rows[exit] 467 | if row and row.constant < 0.0 then 468 | local entry 469 | local min_ratio = math.huge 470 | for var, multiplier in row:iter_vars() do 471 | if multiplier > 0.0 and not var.is_dummy then 472 | local r = (self.objective[var] or 0.0) / multiplier 473 | if r < min_ratio then 474 | min_ratio, entry = r, var 475 | end 476 | end 477 | end 478 | assert(entry, "dual optimize failed") 479 | 480 | -- pivot 481 | self.rows[exit] = nil 482 | row:solve_for(entry, exit) 483 | substitute_out(self, entry, row) 484 | self.rows[entry] = row 485 | end 486 | end 487 | end 488 | 489 | -- interface 490 | 491 | function SimplexSolver:hasvariable(var) return self.vars[var] end 492 | function SimplexSolver:hasconstraint(cons) return self.constraints[cons] end 493 | function SimplexSolver:hasedit(var) return self.edits[var] end 494 | function SimplexSolver.var(_, ...) return Variable.new(...) end 495 | function SimplexSolver.constraint(_, ...) return Constraint.new(...) end 496 | 497 | function SimplexSolver.new() 498 | local self = {} 499 | self.last_varid = 1 500 | 501 | self.vars = {} 502 | self.edits = {} 503 | self.constraints = {} 504 | 505 | self.objective = Expression.new() 506 | self.rows = {} 507 | self.infeasible_rows = {} 508 | 509 | return setmetatable(self, SimplexSolver) 510 | end 511 | 512 | function SimplexSolver:__tostring() 513 | local t = { "amoeba.Solver: {\n" } 514 | t[#t+1] = (" objective = %s\n"):format(self.objective:tostring()) 515 | if next(self.rows) then 516 | t[#t+1] = " rows:\n" 517 | local keys = {} 518 | for k in pairs(self.rows) do 519 | keys[#keys+1] = k 520 | end 521 | table.sort(keys, function(a, b) return a.id < b.id end) 522 | for idx, k in ipairs(keys) do local v = self.rows[k] 523 | t[#t+1] = (" %d. %s(%g) = %s\n"):format(idx, k.name, k.value, v:tostring()) 524 | end 525 | end 526 | if next(self.edits) then 527 | t[#t+1] = " edits:\n" 528 | local idx = 1 529 | for k, v in pairs(self.edits) do 530 | t[#t+1] = (" %d. %s = %s; info = { %s, %s, %g }\n"):format( 531 | idx, k.name, k.value, v.plus.name, v.minus.name, 532 | v.prev_constant) 533 | idx = idx + 1 534 | end 535 | end 536 | if #self.infeasible_rows ~= 0 then 537 | t[#t+1] = " infeasible_rows: {" 538 | for _, var in ipairs(self.infeasible_rows) do 539 | t[#t+1] = (" %s"):format(var.name) 540 | end 541 | t[#t+1] = " }\n" 542 | end 543 | if #self.vars ~= 0 then 544 | t[#t+1] = " vars: {" 545 | for var in pairs(self.vars) do 546 | t[#t+1] = (" %s"):format(var.name) 547 | end 548 | t[#t+1] = " }\n" 549 | end 550 | t[#t+1] = "}" 551 | return table.concat(t) 552 | end 553 | 554 | function SimplexSolver:addconstraint(cons, ...) 555 | if getmetatable(cons) ~= Constraint then 556 | cons = Constraint.new(cons, ...) 557 | end 558 | if self.constraints[cons] then return cons end 559 | local expr, var1, var2 = make_expression(self, cons) 560 | local subject, err = choose_subject(self, expr, var1, var2) 561 | if subject then 562 | expr:solve_for(subject) 563 | substitute_out(self, subject, expr) 564 | self.rows[subject] = expr 565 | elseif err then 566 | return nil, err 567 | elseif not add_with_artificial_variable(self, expr) then 568 | return nil, "constraint added may unbounded" 569 | end 570 | self.constraints[cons] = { 571 | marker = var1, 572 | other = var2, 573 | } 574 | cons.solver = self 575 | optimize(self) 576 | update_external_variables(self) 577 | return cons 578 | end 579 | 580 | function SimplexSolver:delconstraint(cons) 581 | local info = self.constraints[cons] 582 | if not info then return end 583 | self.constraints[cons] = nil 584 | 585 | if info.marker and info.marker.is_error then 586 | self.objective:add(self.rows[info.marker] or info.marker, -cons.weight) 587 | end 588 | if info.other and info.other.is_error then 589 | self.objective:add(self.rows[info.other] or info.other, -cons.weight) 590 | end 591 | if self.objective:is_constant() then 592 | self.objective.constant = 0.0 593 | end 594 | 595 | local row = self.rows[info.marker] 596 | if row then 597 | self.rows[info.marker] = nil 598 | else 599 | local var = assert(get_marker_leaving_row(self, info.marker), 600 | "failed to find leaving row") 601 | row = self.rows[var] 602 | self.rows[var] = nil 603 | row:solve_for(info.marker, var) 604 | substitute_out(self, info.marker, row) 605 | end 606 | cons.solver = nil 607 | optimize(self) 608 | update_external_variables(self) 609 | return cons 610 | end 611 | 612 | function SimplexSolver:addedit(var, strength) 613 | if self.edits[var] then return end 614 | strength = strength or Constraint.MEDIUM 615 | assert(strength < Constraint.REQUIRED, "attempt to edit a required var") 616 | local cons = Constraint.new("==", var, var.value, strength) 617 | assert(self:addconstraint(cons)) 618 | local info = self.constraints[cons] 619 | self.edits[var] = { 620 | constraint = cons, 621 | plus = info.marker, 622 | minus = info.other, 623 | prev_constant = var.value or 0.0, 624 | } 625 | return self 626 | end 627 | 628 | function SimplexSolver:deledit(var) 629 | local info = self.edits[var] 630 | if info then 631 | self:delconstraint(info.constraint) 632 | self.edits[var] = nil 633 | end 634 | end 635 | 636 | function SimplexSolver:suggest(var, value) 637 | local info = self.edits[var] 638 | if not info then self:addedit(var); info = self.edits[var] end 639 | local delta = value - info.prev_constant 640 | info.prev_constant = value 641 | delta_edit_constant(self, delta, info.plus, info.minus) 642 | dual_optimize(self) 643 | update_external_variables(self) 644 | end 645 | 646 | function SimplexSolver:setstrength(cons, strength) 647 | local info = self.constraints[cons] 648 | if not info then cons.weight = strength end 649 | assert(info.marker and info.marker.is_error, "attempt to change required strength") 650 | local multiplier = strength / cons.strength 651 | cons.weight = strength 652 | self.is_required = self.weight >= Constraint.REQUIRED 653 | if near_zero(diff) then return self end 654 | 655 | self.objective:add(self.rows[info.marker] or info.marker, multiplier) 656 | self.objective:add(self.rows[info.other] or info.other, multiplier) 657 | optimize(self) 658 | update_external_variables(self) 659 | return self 660 | end 661 | 662 | function SimplexSolver:resolve() 663 | dual_optimize(self) 664 | set_external_variables() 665 | reset_stay_constant(self) 666 | self.infeasible_rows = {} 667 | end 668 | 669 | function SimplexSolver:set_constant(cons, constant) 670 | local info = self.constraints[cons] 671 | if not info then return end 672 | local delta = info.prev_constant - constant 673 | info.prev_constant = constant 674 | 675 | if info.marker.is_slack or cons.is_required then 676 | for var, row in pairs(self.rows) do 677 | row:add((row[info.marker] or 0.0) * -delta) 678 | if var.is_restricted and row.constant < 0.0 then 679 | self.infeasible_rows[#self.infeasible_rows+1] = var 680 | end 681 | end 682 | else 683 | delta_edit_constant(self, delta, info.marker, info.other) 684 | end 685 | dual_optimize(self) 686 | update_external_variables(self) 687 | end 688 | 689 | end 690 | 691 | return SimplexSolver 692 | -------------------------------------------------------------------------------- /enaml_like_benchmark.cpp: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2020, Nucleic Development Team. 3 | | 4 | | Distributed under the terms of the Modified BSD License. 5 | | 6 | | The full license is in the file LICENSE, distributed with this software. 7 | |----------------------------------------------------------------------------*/ 8 | 9 | // Time updating an EditVariable in a set of constraints typical of enaml use. 10 | 11 | #define ANKERL_NANOBENCH_IMPLEMENT 12 | #include "nanobench.h" 13 | 14 | #define AM_IMPLEMENTATION 15 | #include "amoeba.h" 16 | 17 | void build_solver(am_Solver* S, am_Var* width, am_Var* height) 18 | { 19 | // Create custom strength 20 | am_Num mmedium = AM_MEDIUM * 1.25; 21 | am_Num smedium = AM_MEDIUM * 100; 22 | 23 | // Create the variable 24 | am_Var *left = am_newvariable(S); 25 | am_Var *top = am_newvariable(S); 26 | am_Var *contents_top = am_newvariable(S); 27 | am_Var *contents_bottom = am_newvariable(S); 28 | am_Var *contents_left = am_newvariable(S); 29 | am_Var *contents_right = am_newvariable(S); 30 | am_Var *midline = am_newvariable(S); 31 | am_Var *ctleft = am_newvariable(S); 32 | am_Var *ctheight = am_newvariable(S); 33 | am_Var *cttop = am_newvariable(S); 34 | am_Var *ctwidth = am_newvariable(S); 35 | am_Var *lb1left = am_newvariable(S); 36 | am_Var *lb1height = am_newvariable(S); 37 | am_Var *lb1top = am_newvariable(S); 38 | am_Var *lb1width = am_newvariable(S); 39 | am_Var *lb2left = am_newvariable(S); 40 | am_Var *lb2height = am_newvariable(S); 41 | am_Var *lb2top = am_newvariable(S); 42 | am_Var *lb2width = am_newvariable(S); 43 | am_Var *lb3left = am_newvariable(S); 44 | am_Var *lb3height = am_newvariable(S); 45 | am_Var *lb3top = am_newvariable(S); 46 | am_Var *lb3width = am_newvariable(S); 47 | am_Var *fl1left = am_newvariable(S); 48 | am_Var *fl1height = am_newvariable(S); 49 | am_Var *fl1top = am_newvariable(S); 50 | am_Var *fl1width = am_newvariable(S); 51 | am_Var *fl2left = am_newvariable(S); 52 | am_Var *fl2height = am_newvariable(S); 53 | am_Var *fl2top = am_newvariable(S); 54 | am_Var *fl2width = am_newvariable(S); 55 | am_Var *fl3left = am_newvariable(S); 56 | am_Var *fl3height = am_newvariable(S); 57 | am_Var *fl3top = am_newvariable(S); 58 | am_Var *fl3width = am_newvariable(S); 59 | 60 | // Add the edit variables 61 | am_addedit(width, AM_STRONG); 62 | am_addedit(height, AM_STRONG); 63 | 64 | 65 | // Add the constraints 66 | const 67 | struct Info { 68 | struct Item { 69 | am_Var *var; 70 | am_Num mul; 71 | } term[5]; 72 | am_Num constant; 73 | int relation; 74 | am_Num strength; 75 | } constraints[] = { 76 | { {{left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 77 | { {{height}}, 0, AM_EQUAL, AM_MEDIUM }, 78 | { {{top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 79 | { {{width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 80 | { {{height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 81 | { {{top,-1},{contents_top}}, -10, AM_EQUAL, AM_REQUIRED }, 82 | { {{lb3height}}, -16, AM_EQUAL, AM_STRONG }, 83 | { {{lb3height}}, -16, AM_GREATEQUAL, AM_STRONG }, 84 | { {{ctleft}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 85 | { {{cttop}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 86 | { {{ctwidth}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 87 | { {{ctheight}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 88 | { {{fl3left}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 89 | { {{ctheight}}, -24, AM_GREATEQUAL, smedium }, 90 | { {{ctwidth}}, -1.67772e+07, AM_LESSEQUAL, smedium }, 91 | { {{ctheight}}, -24, AM_LESSEQUAL, smedium }, 92 | { {{fl3top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 93 | { {{fl3width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 94 | { {{fl3height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 95 | { {{lb1width}}, -67, AM_EQUAL, AM_WEAK }, 96 | { {{lb2width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 97 | { {{lb2height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 98 | { {{fl2height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 99 | { {{lb3left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 100 | { {{fl2width}}, -125, AM_GREATEQUAL, AM_STRONG }, 101 | { {{fl2height}}, -21, AM_EQUAL, AM_STRONG }, 102 | { {{fl2height}}, -21, AM_GREATEQUAL, AM_STRONG }, 103 | { {{lb3top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 104 | { {{lb3width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 105 | { {{lb1left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 106 | { {{fl1width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 107 | { {{lb1width}}, -67, AM_GREATEQUAL, AM_STRONG }, 108 | { {{fl2left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 109 | { {{lb2width}}, -66, AM_EQUAL, AM_WEAK }, 110 | { {{lb2width}}, -66, AM_GREATEQUAL, AM_STRONG }, 111 | { {{lb2height}}, -16, AM_EQUAL, AM_STRONG }, 112 | { {{fl1height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 113 | { {{fl1top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 114 | { {{lb2top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 115 | { {{lb2top,-1},{lb3top},{lb2height,-1}}, -10, AM_EQUAL, mmedium }, 116 | { {{lb3top,-1},{lb3height,-1},{fl3top}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 117 | { {{lb3top,-1},{lb3height,-1},{fl3top}}, -10, AM_EQUAL, mmedium }, 118 | { {{contents_bottom},{fl3height,-1},{fl3top,-1}}, -0, AM_EQUAL, mmedium }, 119 | { {{fl1top},{contents_top,-1}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 120 | { {{fl1top},{contents_top,-1}}, 0, AM_EQUAL, mmedium }, 121 | { {{contents_bottom},{fl3height,-1},{fl3top,-1}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 122 | { {{left,-1},{width,-1},{contents_right}}, 10, AM_EQUAL, AM_REQUIRED }, 123 | { {{top,-1},{height,-1},{contents_bottom}}, 10, AM_EQUAL, AM_REQUIRED }, 124 | { {{left,-1},{contents_left}}, -10, AM_EQUAL, AM_REQUIRED }, 125 | { {{lb3left},{contents_left,-1}}, 0, AM_EQUAL, mmedium }, 126 | { {{fl1left},{midline,-1}}, 0, AM_EQUAL, AM_STRONG }, 127 | { {{fl2left},{midline,-1}}, 0, AM_EQUAL, AM_STRONG }, 128 | { {{ctleft},{midline,-1}}, 0, AM_EQUAL, AM_STRONG }, 129 | { {{fl1top},{fl1height,0.5},{lb1top,-1},{lb1height,-0.5}}, 0, AM_EQUAL, AM_STRONG }, 130 | { {{lb1left},{contents_left,-1}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 131 | { {{lb1left},{contents_left,-1}}, 0, AM_EQUAL, mmedium }, 132 | { {{lb1left,-1},{fl1left},{lb1width,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 133 | { {{lb1left,-1},{fl1left},{lb1width,-1}}, -10, AM_EQUAL, mmedium }, 134 | { {{fl1left,-1},{contents_right},{fl1width,-1}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 135 | { {{width}}, 0, AM_EQUAL, AM_MEDIUM }, 136 | { {{fl1top,-1},{fl2top},{fl1height,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 137 | { {{fl1top,-1},{fl2top},{fl1height,-1}}, -10, AM_EQUAL, mmedium }, 138 | { {{cttop},{fl2top,-1},{fl2height,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 139 | { {{ctheight,-1},{cttop,-1},{fl3top}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 140 | { {{contents_bottom},{fl3height,-1},{fl3top,-1}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 141 | { {{cttop},{fl2top,-1},{fl2height,-1}}, -10, AM_EQUAL, mmedium }, 142 | { {{fl1left,-1},{contents_right},{fl1width,-1}}, -0, AM_EQUAL, mmedium }, 143 | { {{lb2top,-1},{lb2height,-0.5},{fl2top},{fl2height,0.5}}, 0, AM_EQUAL, AM_STRONG }, 144 | { {{contents_left,-1},{lb2left}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 145 | { {{contents_left,-1},{lb2left}}, 0, AM_EQUAL, mmedium }, 146 | { {{fl2left},{lb2width,-1},{lb2left,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 147 | { {{ctheight,-1},{cttop,-1},{fl3top}}, -10, AM_EQUAL, mmedium }, 148 | { {{contents_bottom},{fl3height,-1},{fl3top,-1}}, -0, AM_EQUAL, mmedium }, 149 | { {{lb1top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 150 | { {{lb1width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 151 | { {{lb1height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 152 | { {{fl2left},{lb2width,-1},{lb2left,-1}}, -10, AM_EQUAL, mmedium }, 153 | { {{fl2left,-1},{fl2width,-1},{contents_right}}, -0, AM_EQUAL, mmedium }, 154 | { {{fl2left,-1},{fl2width,-1},{contents_right}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 155 | { {{lb3left},{contents_left,-1}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 156 | { {{lb1left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 157 | { {{ctheight,0.5},{cttop},{lb3top,-1},{lb3height,-0.5}}, 0, AM_EQUAL, AM_STRONG }, 158 | { {{ctleft},{lb3left,-1},{lb3width,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 159 | { {{ctwidth,-1},{ctleft,-1},{contents_right}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 160 | { {{ctleft},{lb3left,-1},{lb3width,-1}}, -10, AM_EQUAL, mmedium }, 161 | { {{fl3left},{contents_left,-1}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 162 | { {{fl3left},{contents_left,-1}}, 0, AM_EQUAL, mmedium }, 163 | { {{ctwidth,-1},{ctleft,-1},{contents_right}}, -0, AM_EQUAL, mmedium }, 164 | { {{fl3left,-1},{contents_right},{fl3width,-1}}, -0, AM_EQUAL, mmedium }, 165 | { {{contents_top,-1},{lb1top}}, 0, AM_GREATEQUAL, AM_REQUIRED }, 166 | { {{contents_top,-1},{lb1top}}, 0, AM_EQUAL, mmedium }, 167 | { {{fl3left,-1},{contents_right},{fl3width,-1}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 168 | { {{lb2top},{lb1top,-1},{lb1height,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 169 | { {{lb2top,-1},{lb3top},{lb2height,-1}}, -10, AM_GREATEQUAL, AM_REQUIRED }, 170 | { {{lb2top},{lb1top,-1},{lb1height,-1}}, -10, AM_EQUAL, mmedium }, 171 | { {{fl1height}}, -21, AM_EQUAL, AM_STRONG }, 172 | { {{fl1height}}, -21, AM_GREATEQUAL, AM_STRONG }, 173 | { {{lb2left}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 174 | { {{lb2height}}, -16, AM_GREATEQUAL, AM_STRONG }, 175 | { {{fl2top}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 176 | { {{fl2width}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 177 | { {{lb1height}}, -16, AM_GREATEQUAL, AM_STRONG }, 178 | { {{lb1height}}, -16, AM_EQUAL, AM_STRONG }, 179 | { {{fl3width}}, -125, AM_GREATEQUAL, AM_STRONG }, 180 | { {{fl3height}}, -21, AM_EQUAL, AM_STRONG }, 181 | { {{fl3height}}, -21, AM_GREATEQUAL, AM_STRONG }, 182 | { {{lb3height}}, -0, AM_GREATEQUAL, AM_REQUIRED }, 183 | { {{ctwidth}}, -119, AM_GREATEQUAL, smedium }, 184 | { {{lb3width}}, -24, AM_EQUAL, AM_WEAK }, 185 | { {{lb3width}}, -24, AM_GREATEQUAL, AM_STRONG }, 186 | { {{fl1width}}, -125, AM_GREATEQUAL, AM_STRONG }, 187 | }; 188 | 189 | for (const auto& constraint : constraints) { 190 | am_Constraint *c = am_newconstraint(S, constraint.strength); 191 | for (auto* p = constraint.term; p->var; ++p) { 192 | am_addterm(c, p->var, p->mul ? p->mul : 1); 193 | } 194 | am_addconstant(c, constraint.constant); 195 | am_setrelation(c, constraint.relation); 196 | int r = am_add(c); 197 | assert(r == AM_OK); 198 | } 199 | } 200 | 201 | int main() 202 | { 203 | ankerl::nanobench::Bench().minEpochIterations(100).run("building solver", [&] { 204 | am_Solver *S = am_newsolver(NULL, NULL); 205 | am_Var *width = am_newvariable(S); 206 | am_Var *height = am_newvariable(S); 207 | build_solver(S, width, height); 208 | ankerl::nanobench::doNotOptimizeAway(S); //< prevent the compiler to optimize away the S 209 | am_delsolver(S); 210 | }); 211 | 212 | struct Size 213 | { 214 | int width; 215 | int height; 216 | }; 217 | 218 | Size sizes[] = { 219 | { 400, 600 }, 220 | { 600, 400 }, 221 | { 800, 1200 }, 222 | { 1200, 800 }, 223 | { 400, 800 }, 224 | { 800, 400 } 225 | }; 226 | 227 | am_Solver *S = am_newsolver(NULL, NULL); 228 | am_Var *widthVar = am_newvariable(S); 229 | am_Var *heightVar = am_newvariable(S); 230 | build_solver(S, widthVar, heightVar); 231 | 232 | for (const Size& size : sizes) 233 | { 234 | am_Num width = size.width; 235 | am_Num height = size.height; 236 | 237 | ankerl::nanobench::Bench().minEpochIterations(100000).run("suggest value " + std::to_string(size.width) + "x" + std::to_string(size.height), [&] { 238 | am_suggest(widthVar, width); 239 | am_suggest(heightVar, height); 240 | am_updatevars(S); 241 | }); 242 | } 243 | 244 | am_delsolver(S); 245 | return 0; 246 | } 247 | 248 | // cc: flags+='-O2 -std=c++11' 249 | -------------------------------------------------------------------------------- /lua_amoeba.c: -------------------------------------------------------------------------------- 1 | #define LUA_LIB 2 | #include 3 | #include 4 | #include 5 | 6 | #define AM_STATIC_API 7 | #include "amoeba.h" 8 | 9 | 10 | #define AML_SOLVER_TYPE "amoeba.Solver" 11 | #define AML_VAR_TYPE "amoeba.Variable" 12 | #define AML_CONS_TYPE "amoeba.Constraint" 13 | 14 | enum aml_ItemType { AML_VAR, AML_CONS, AML_CONSTANT }; 15 | 16 | typedef struct aml_Solver { 17 | am_Solver *solver; 18 | int ref_vars; 19 | int ref_cons; 20 | } aml_Solver; 21 | 22 | typedef struct aml_Var { 23 | am_Var *var; 24 | aml_Solver *S; 25 | const char *name; 26 | } aml_Var; 27 | 28 | typedef struct aml_Cons { 29 | am_Constraint *cons; 30 | aml_Solver *S; 31 | } aml_Cons; 32 | 33 | typedef struct aml_Item { 34 | int type; 35 | am_Var *var; 36 | am_Constraint *cons; 37 | am_Num value; 38 | } aml_Item; 39 | 40 | 41 | /* utils */ 42 | 43 | static int aml_argferror(lua_State *L, int idx, const char *fmt, ...) { 44 | va_list l; 45 | va_start(l, fmt); 46 | lua_pushvfstring(L, fmt, l); 47 | va_end(l); 48 | return luaL_argerror(L, idx, lua_tostring(L, -1)); 49 | } 50 | 51 | static int aml_typeerror(lua_State *L, int idx, const char *tname) { 52 | return aml_argferror(L, idx, "%s expected, got %s", 53 | tname, luaL_typename(L, idx)); 54 | } 55 | 56 | static void aml_setweak(lua_State *L, const char *mode) { 57 | lua_createtable(L, 0, 1); 58 | lua_pushstring(L, mode); 59 | lua_setfield(L, -2, "__mode"); 60 | lua_setmetatable(L, -2); 61 | } 62 | 63 | static am_Var *aml_checkvar(lua_State *L, aml_Solver *S, int idx) { 64 | aml_Var *lvar = (aml_Var*)luaL_testudata(L, idx, AML_VAR_TYPE); 65 | const char *name; 66 | if (lvar != NULL) { 67 | if (lvar->var == NULL) luaL_argerror(L, idx, "invalid variable"); 68 | return lvar->var; 69 | } 70 | name = luaL_checkstring(L, idx); 71 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars); 72 | if (lua_getfield(L, -2, name) == LUA_TUSERDATA) { 73 | lua_remove(L, -2); 74 | return aml_checkvar(L, S, -1); 75 | } 76 | lua_pop(L, 2); 77 | aml_argferror(L, idx, "variable named '%s' not exists", 78 | lua_tostring(L, idx)); 79 | return NULL; 80 | } 81 | 82 | static aml_Cons *aml_newcons(lua_State *L, aml_Solver *S, am_Num strength) { 83 | aml_Cons *lcons = (aml_Cons*)lua_newuserdata(L, sizeof(aml_Cons)); 84 | lcons->cons = am_newconstraint(S->solver, strength); 85 | lcons->S = S; 86 | luaL_setmetatable(L, AML_CONS_TYPE); 87 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_cons); 88 | lua_pushvalue(L, -2); 89 | lua_rawsetp(L, -2, lcons); 90 | lua_pop(L, 1); 91 | return lcons; 92 | } 93 | 94 | static aml_Item aml_checkitem(lua_State *L, aml_Solver *S, int idx) { 95 | aml_Item item = { 0 }; 96 | aml_Cons *lcons; 97 | aml_Var *lvar; 98 | switch (lua_type(L, idx)) { 99 | case LUA_TSTRING: 100 | item.var = aml_checkvar(L, S, idx); 101 | item.type = AML_VAR; 102 | return item; 103 | case LUA_TNUMBER: 104 | item.value = lua_tonumber(L, idx); 105 | item.type = AML_CONSTANT; 106 | return item; 107 | case LUA_TUSERDATA: 108 | lcons = (aml_Cons*)luaL_testudata(L, idx, AML_CONS_TYPE); 109 | if (lcons) { 110 | if (lcons->cons == NULL) luaL_argerror(L, idx, "invalid constraint"); 111 | item.cons = lcons->cons; 112 | item.type = AML_CONS; 113 | return item; 114 | } 115 | lvar = luaL_testudata(L, idx, AML_VAR_TYPE); 116 | if (lvar) { 117 | if (lvar->var == NULL) luaL_argerror(L, idx, "invalid variable"); 118 | item.var = lvar->var; 119 | item.type = AML_VAR; 120 | return item; 121 | } 122 | /* FALLTHROUGHT */ 123 | default: 124 | aml_typeerror(L, idx, "number/string/variable/constraint"); 125 | } 126 | return item; 127 | } 128 | 129 | static aml_Solver *aml_checkitems(lua_State *L, int start, aml_Item *items) { 130 | aml_Var *lvar; 131 | aml_Cons *lcons; 132 | if ((lcons = (aml_Cons*)luaL_testudata(L, start, AML_CONS_TYPE)) != NULL) { 133 | items[0].type = AML_CONS, items[0].cons = lcons->cons; 134 | items[1] = aml_checkitem(L, lcons->S, start+1); 135 | return lcons->S; 136 | } 137 | if ((lcons = (aml_Cons*)luaL_testudata(L, start+1, AML_CONS_TYPE)) != NULL) { 138 | items[1].type = AML_CONS, items[1].cons = lcons->cons; 139 | items[0] = aml_checkitem(L, lcons->S, start); 140 | return lcons->S; 141 | } 142 | if ((lvar = (aml_Var*)luaL_testudata(L, start, AML_VAR_TYPE)) != NULL) { 143 | if (lvar->var == NULL) luaL_argerror(L, start, "invalid variable"); 144 | items[0].type = AML_VAR, items[0].var = lvar->var; 145 | items[1] = aml_checkitem(L, lvar->S, start+1); 146 | return lvar->S; 147 | } 148 | if ((lvar = (aml_Var*)luaL_testudata(L, start+1, AML_VAR_TYPE)) != NULL) { 149 | if (lvar->var == NULL) luaL_argerror(L, start+1, "invalid variable"); 150 | items[1].type = AML_VAR, items[1].var = lvar->var; 151 | items[0] = aml_checkitem(L, lvar->S, start); 152 | return lvar->S; 153 | } 154 | aml_typeerror(L, start, "variable/constraint"); 155 | return NULL; 156 | } 157 | 158 | static int aml_performitem(am_Constraint *cons, aml_Item *item, am_Num multiplier) { 159 | switch (item->type) { 160 | case AML_CONSTANT: return am_addconstant(cons, item->value*multiplier); break; 161 | case AML_VAR: return am_addterm(cons, item->var, multiplier); break; 162 | case AML_CONS: return am_mergeconstraint(cons, item->cons, multiplier); break; 163 | } 164 | return AM_FAILED; 165 | } 166 | 167 | static am_Num aml_checkstrength(lua_State *L, int idx, am_Num def) { 168 | int type = lua_type(L, idx); 169 | const char *s; 170 | switch (type) { 171 | case LUA_TSTRING: 172 | s = lua_tostring(L, idx); 173 | if (strcmp(s, "required") == 0) return AM_REQUIRED; 174 | if (strcmp(s, "strong") == 0) return AM_STRONG; 175 | if (strcmp(s, "medium") == 0) return AM_MEDIUM; 176 | if (strcmp(s, "weak") == 0) return AM_WEAK; 177 | aml_argferror(L, idx, "invalid strength value '%s'", s); 178 | break; 179 | case LUA_TNONE: 180 | case LUA_TNIL: return def; 181 | case LUA_TNUMBER: return lua_tonumber(L, idx); 182 | } 183 | aml_typeerror(L, idx, "number/string"); 184 | return 0.0f; 185 | } 186 | 187 | static int aml_checkrelation(lua_State *L, int idx) { 188 | const char *op = luaL_checkstring(L, idx); 189 | if (strcmp(op, "==") == 0) return AM_EQUAL; 190 | else if (strcmp(op, "<=") == 0) return AM_LESSEQUAL; 191 | else if (strcmp(op, ">=") == 0) return AM_GREATEQUAL; 192 | else if (strcmp(op, "eq") == 0) return AM_EQUAL; 193 | else if (strcmp(op, "le") == 0) return AM_LESSEQUAL; 194 | else if (strcmp(op, "ge") == 0) return AM_GREATEQUAL; 195 | return aml_argferror(L, 2, "invalid relation operator: '%s'", op); 196 | } 197 | 198 | static aml_Cons *aml_makecons(lua_State *L, aml_Solver *S, int start) { 199 | aml_Cons *lcons; 200 | int op = aml_checkrelation(L, start); 201 | am_Num strength = aml_checkstrength(L, start+3, AM_REQUIRED); 202 | aml_Item items[2]; 203 | aml_checkitems(L, start+1, items); 204 | lcons = aml_newcons(L, S, strength); 205 | aml_performitem(lcons->cons, &items[0], 1.0f); 206 | am_setrelation(lcons->cons, op); 207 | aml_performitem(lcons->cons, &items[1], 1.0f); 208 | return lcons; 209 | } 210 | 211 | static void aml_dumpkey(luaL_Buffer *B, int idx, am_Symbol sym) { 212 | lua_State *L = B->L; 213 | aml_Var *lvar; 214 | lua_rawgeti(L, idx, sym.id); 215 | lvar = (aml_Var*)luaL_testudata(L, -1, AML_VAR_TYPE); 216 | lua_pop(L, 1); 217 | if (lvar) luaL_addstring(B, lvar->name); 218 | else { 219 | int ch = 'v'; 220 | switch (sym.type) { 221 | case AM_EXTERNAL: ch = 'v'; break; 222 | case AM_SLACK: ch = 's'; break; 223 | case AM_ERROR: ch = 'e'; break; 224 | case AM_DUMMY: ch = 'd'; break; 225 | } 226 | lua_pushfstring(L, "%c%d", ch, sym.id); 227 | luaL_addvalue(B); 228 | } 229 | } 230 | 231 | static void aml_dumprow(luaL_Buffer *B, int idx, am_Row *row) { 232 | lua_State *L = B->L; 233 | am_Term *term = NULL; 234 | lua_pushfstring(L, "%f", row->constant); 235 | luaL_addvalue(B); 236 | while ((am_nextentry(&row->terms, (am_Entry**)&term))) { 237 | am_Num multiplier = term->multiplier; 238 | lua_pushfstring(L, " %c ", multiplier > 0.0f ? '+' : '-'); 239 | luaL_addvalue(B); 240 | if (multiplier < 0.0f) multiplier = -multiplier; 241 | if (!am_approx(multiplier, 1.0f)) { 242 | lua_pushfstring(L, "%f*", multiplier); 243 | luaL_addvalue(B); 244 | } 245 | aml_dumpkey(B, idx, am_key(term)); 246 | } 247 | } 248 | 249 | 250 | /* expression */ 251 | 252 | static int Lexpr_neg(lua_State *L) { 253 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 254 | aml_Cons *newcons = aml_newcons(L, lcons->S, AM_REQUIRED); 255 | am_mergeconstraint(newcons->cons, lcons->cons, -1.0f); 256 | return 1; 257 | } 258 | 259 | static int Lexpr_add(lua_State *L) { 260 | aml_Cons *lcons; 261 | aml_Item items[2]; 262 | aml_Solver *S = aml_checkitems(L, 1, items); 263 | lcons = aml_newcons(L, S, AM_REQUIRED); 264 | aml_performitem(lcons->cons, &items[0], 1.0f); 265 | aml_performitem(lcons->cons, &items[1], 1.0f); 266 | return 1; 267 | } 268 | 269 | static int Lexpr_sub(lua_State *L) { 270 | aml_Cons *lcons; 271 | aml_Item items[2]; 272 | aml_Solver *S = aml_checkitems(L, 1, items); 273 | lcons = aml_newcons(L, S, AM_REQUIRED); 274 | aml_performitem(lcons->cons, &items[0], 1.0f); 275 | aml_performitem(lcons->cons, &items[1], -1.0f); 276 | return 1; 277 | } 278 | 279 | static int Lexpr_mul(lua_State *L) { 280 | aml_Item items[2]; 281 | aml_Solver *S = aml_checkitems(L, 1, items); 282 | if (items[0].type == AML_CONSTANT) { 283 | aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED); 284 | aml_performitem(lcons->cons, &items[1], items[0].value); 285 | } 286 | else if (items[1].type == AML_CONSTANT) { 287 | aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED); 288 | aml_performitem(lcons->cons, &items[0], items[1].value); 289 | } 290 | else luaL_error(L, "attempt to multiply two expression"); 291 | return 1; 292 | } 293 | 294 | static int Lexpr_div(lua_State *L) { 295 | aml_Item items[2]; 296 | aml_Solver *S = aml_checkitems(L, 1, items); 297 | if (items[0].type == AML_CONSTANT) 298 | luaL_error(L, "attempt to divide a expression"); 299 | if (items[1].type == AML_CONSTANT) { 300 | aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED); 301 | aml_performitem(lcons->cons, &items[0], 1.0f/items[1].value); 302 | } 303 | else luaL_error(L, "attempt to divide two expression"); 304 | return 1; 305 | } 306 | 307 | static int Lexpr_cmp(lua_State *L, int op) { 308 | aml_Item items[2]; 309 | aml_Solver *S = aml_checkitems(L, 1, items); 310 | aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED); 311 | aml_performitem(lcons->cons, &items[0], 1.0f); 312 | am_setrelation(lcons->cons, op); 313 | aml_performitem(lcons->cons, &items[1], 1.0f); 314 | return 1; 315 | } 316 | 317 | static int Lexpr_le(lua_State *L) { return Lexpr_cmp(L, AM_LESSEQUAL); } 318 | static int Lexpr_eq(lua_State *L) { return Lexpr_cmp(L, AM_EQUAL); } 319 | static int Lexpr_ge(lua_State *L) { return Lexpr_cmp(L, AM_GREATEQUAL); } 320 | 321 | 322 | /* variable */ 323 | 324 | static int Lvar_new(lua_State *L) { 325 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 326 | aml_Var *lvar; 327 | int type = lua_type(L, 2); 328 | if (type != LUA_TNONE || type != LUA_TNIL) { 329 | if (type != LUA_TSTRING && type != LUA_TNUMBER) 330 | return aml_typeerror(L, 2, "number/string"); 331 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars); 332 | lua_pushvalue(L, 2); 333 | if (lua_rawget(L, -2) != LUA_TNIL) return 1; 334 | if (type == LUA_TNUMBER) 335 | aml_argferror(L, 2, "variable#%d not exists", lua_tointeger(L, 2)); 336 | lua_pop(L, 1); 337 | } 338 | lvar = (aml_Var*)lua_newuserdata(L, sizeof(aml_Var)); 339 | lvar->var = am_newvariable(S->solver); 340 | lvar->S = S; 341 | lvar->name = lua_tostring(L, 2); 342 | if (lvar->name == NULL) { 343 | lua_settop(L, 1); 344 | lua_pushfstring(L, "v%d", am_variableid(lvar->var)); 345 | lvar->name = lua_tostring(L, 2); 346 | } 347 | luaL_setmetatable(L, AML_VAR_TYPE); 348 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars); 349 | lua_pushvalue(L, -2); 350 | lua_setfield(L, -2, lvar->name); 351 | lua_pushvalue(L, -2); 352 | lua_rawseti(L, -2, am_variableid(lvar->var)); 353 | lua_pop(L, 1); 354 | return 1; 355 | } 356 | 357 | static int Lvar_delete(lua_State *L) { 358 | aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE); 359 | if (lvar->var == NULL) return 0; 360 | am_delvariable(lvar->var); 361 | lua_rawgeti(L, LUA_REGISTRYINDEX, lvar->S->ref_vars); 362 | lua_pushnil(L); 363 | lua_setfield(L, -2, lvar->name); 364 | lvar->var = NULL; 365 | lvar->name = NULL; 366 | return 0; 367 | } 368 | 369 | static int Lvar_value(lua_State *L) { 370 | aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE); 371 | if (lvar->var == NULL) luaL_argerror(L, 1, "invalid variable"); 372 | lua_pushnumber(L, am_value(lvar->var)); 373 | return 1; 374 | } 375 | 376 | static int Lvar_tostring(lua_State *L) { 377 | aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE); 378 | if (lvar->var) lua_pushfstring(L, AML_VAR_TYPE "(%p): %s = %f", 379 | lvar->var, lvar->name, am_value(lvar->var)); 380 | else lua_pushstring(L, AML_VAR_TYPE ": deleted"); 381 | return 1; 382 | } 383 | 384 | static void open_variable(lua_State *L) { 385 | luaL_Reg libs[] = { 386 | { "__neg", Lexpr_neg }, 387 | { "__add", Lexpr_add }, 388 | { "__sub", Lexpr_sub }, 389 | { "__mul", Lexpr_mul }, 390 | { "__div", Lexpr_div }, 391 | { "le", Lexpr_le }, 392 | { "eq", Lexpr_eq }, 393 | { "ge", Lexpr_ge }, 394 | { "__tostring", Lvar_tostring }, 395 | { "__gc", Lvar_delete }, 396 | #define ENTRY(name) { #name, Lvar_##name } 397 | ENTRY(new), 398 | ENTRY(delete), 399 | ENTRY(value), 400 | #undef ENTRY 401 | { NULL, NULL } 402 | }; 403 | if (luaL_newmetatable(L, AML_VAR_TYPE)) { 404 | luaL_setfuncs(L, libs, 0); 405 | lua_pushvalue(L, -1); 406 | lua_setfield(L, -2, "__index"); 407 | } 408 | } 409 | 410 | 411 | /* constraint */ 412 | 413 | static int Lcons_new(lua_State *L) { 414 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 415 | if (lua_gettop(L) >= 3) aml_makecons(L, S, 2); 416 | else aml_newcons(L, S, aml_checkstrength(L, 2, AM_REQUIRED)); 417 | return 1; 418 | } 419 | 420 | static int Lcons_delete(lua_State *L) { 421 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 422 | if (lcons->cons == NULL) return 0; 423 | am_delconstraint(lcons->cons); 424 | lcons->cons = NULL; 425 | lua_rawgeti(L, LUA_REGISTRYINDEX, lcons->S->ref_vars); 426 | lua_pushnil(L); 427 | lua_rawsetp(L, -2, lcons); 428 | return 0; 429 | } 430 | 431 | static int Lcons_reset(lua_State *L) { 432 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 433 | if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint"); 434 | am_resetconstraint(lcons->cons); 435 | lua_settop(L, 1); return 1; 436 | } 437 | 438 | static int Lcons_add(lua_State *L) { 439 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 440 | aml_Item item; 441 | int ret; 442 | if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint"); 443 | if (lua_type(L, 2) == LUA_TSTRING) { 444 | const char *s = lua_tostring(L, 2); 445 | if (s[0] == '<' || s[0] == '>' || s[0] == '=') { 446 | ret = am_setrelation(lcons->cons, aml_checkrelation(L, 2)); 447 | goto out; 448 | } 449 | } 450 | item = aml_checkitem(L, lcons->S, 2); 451 | ret = aml_performitem(lcons->cons, &item, 1.0f); 452 | out: 453 | if (ret != AM_OK) luaL_error(L, "constraint has been added to solver!"); 454 | lua_settop(L, 1); return 1; 455 | } 456 | 457 | static int Lcons_relation(lua_State *L) { 458 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 459 | int op = aml_checkrelation(L, 2); 460 | if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint"); 461 | if (am_setrelation(lcons->cons, op) != AM_OK) 462 | luaL_error(L, "constraint has been added to solver!"); 463 | lua_settop(L, 1); return 1; 464 | } 465 | 466 | static int Lcons_strength(lua_State *L) { 467 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 468 | am_Num strength = aml_checkstrength(L, 2, AM_REQUIRED); 469 | if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint"); 470 | if (am_setstrength(lcons->cons, strength) != AM_OK) 471 | luaL_error(L, "constraint has been added to solver!"); 472 | lua_settop(L, 1); return 1; 473 | } 474 | 475 | static int Lcons_tostring(lua_State *L) { 476 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE); 477 | luaL_Buffer B; 478 | if (lcons->cons == NULL) { 479 | lua_pushstring(L, AML_CONS_TYPE ": deleted"); 480 | return 1; 481 | } 482 | lua_settop(L, 1); 483 | lua_rawgeti(L, LUA_REGISTRYINDEX, lcons->S->ref_vars); 484 | luaL_buffinit(L, &B); 485 | lua_pushfstring(L, AML_CONS_TYPE "(%p): [", lcons->cons); 486 | luaL_addvalue(&B); 487 | aml_dumprow(&B, 2, &lcons->cons->expression); 488 | if (lcons->cons->relation == AM_EQUAL) 489 | luaL_addstring(&B, " == 0.0]"); 490 | else 491 | luaL_addstring(&B, " >= 0.0]"); 492 | if (lcons->cons->marker.id != 0) { 493 | luaL_addstring(&B, "(added:"); 494 | aml_dumpkey(&B, 2, lcons->cons->marker); 495 | luaL_addchar(&B, '-'); 496 | aml_dumpkey(&B, 2, lcons->cons->other); 497 | luaL_addchar(&B, ')'); 498 | } 499 | luaL_pushresult(&B); 500 | return 1; 501 | } 502 | 503 | static void open_constraint(lua_State *L) { 504 | luaL_Reg libs[] = { 505 | { "__call", Lcons_add }, 506 | { "__neg", Lexpr_neg }, 507 | { "__add", Lexpr_add }, 508 | { "__sub", Lexpr_sub }, 509 | { "__mul", Lexpr_mul }, 510 | { "__div", Lexpr_div }, 511 | { "le", Lexpr_le }, 512 | { "eq", Lexpr_eq }, 513 | { "ge", Lexpr_ge }, 514 | { "__bor", Lcons_strength }, 515 | { "__tostring", Lcons_tostring }, 516 | { "__gc", Lcons_delete }, 517 | #define ENTRY(name) { #name, Lcons_##name } 518 | ENTRY(new), 519 | ENTRY(delete), 520 | ENTRY(reset), 521 | ENTRY(add), 522 | ENTRY(relation), 523 | ENTRY(strength), 524 | #undef ENTRY 525 | { NULL, NULL } 526 | }; 527 | if (luaL_newmetatable(L, AML_CONS_TYPE)) { 528 | luaL_setfuncs(L, libs, 0); 529 | lua_pushvalue(L, -1); 530 | lua_setfield(L, -2, "__index"); 531 | } 532 | } 533 | 534 | 535 | /* solver */ 536 | 537 | static int Lnew(lua_State *L) { 538 | aml_Solver *S = lua_newuserdata(L, sizeof(aml_Solver)); 539 | if ((S->solver = am_newsolver(NULL, NULL)) == NULL) 540 | return 0; 541 | lua_createtable(L, 0, 4); aml_setweak(L, "v"); 542 | S->ref_vars = luaL_ref(L, LUA_REGISTRYINDEX); 543 | lua_createtable(L, 0, 4); aml_setweak(L, "v"); 544 | S->ref_cons = luaL_ref(L, LUA_REGISTRYINDEX); 545 | luaL_setmetatable(L, AML_SOLVER_TYPE); 546 | am_autoupdate(S->solver, lua_toboolean(L, 1)); 547 | return 1; 548 | } 549 | 550 | static int Ldelete(lua_State *L) { 551 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 552 | if (S->solver == NULL) return 0; 553 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars); 554 | lua_pushnil(L); 555 | while (lua_next(L, -2)) { 556 | aml_Var *lvar = (aml_Var*)luaL_testudata(L, -1, AML_VAR_TYPE); 557 | if (lvar && lvar->var) { 558 | am_delvariable(lvar->var); 559 | lvar->var = NULL; 560 | lvar->name = NULL; 561 | } 562 | lua_pop(L, 1); 563 | } 564 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_cons); 565 | lua_pushnil(L); 566 | while (lua_next(L, -2)) { 567 | aml_Cons *lcons = (aml_Cons*)luaL_testudata(L, -1, AML_CONS_TYPE); 568 | if (lcons && lcons->cons) { 569 | am_delconstraint(lcons->cons); 570 | lcons->cons = NULL; 571 | } 572 | lua_pop(L, 1); 573 | } 574 | luaL_unref(L, LUA_REGISTRYINDEX, S->ref_vars); 575 | luaL_unref(L, LUA_REGISTRYINDEX, S->ref_cons); 576 | am_delsolver(S->solver); 577 | S->solver = NULL; 578 | return 0; 579 | } 580 | 581 | static int Ltostring(lua_State *L) { 582 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 583 | luaL_Buffer B; 584 | lua_settop(L, 1); 585 | lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars); 586 | luaL_buffinit(L, &B); 587 | lua_pushfstring(L, AML_SOLVER_TYPE "(%p): {", S->solver); 588 | luaL_addvalue(&B); 589 | luaL_addstring(&B, "\n objective = "); 590 | aml_dumprow(&B, 2, &S->solver->objective); 591 | if (S->solver->rows.count != 0) { 592 | am_Row *row = NULL; 593 | int idx = 0; 594 | lua_pushfstring(L, "\n rows(%d):", S->solver->rows.count); 595 | luaL_addvalue(&B); 596 | while (am_nextentry(&S->solver->rows, (am_Entry**)&row)) { 597 | lua_pushfstring(L, "\n %d. ", ++idx); 598 | luaL_addvalue(&B); 599 | aml_dumpkey(&B, 2, am_key(row)); 600 | luaL_addstring(&B, " = "); 601 | aml_dumprow(&B, 2, row); 602 | } 603 | } 604 | if (S->solver->infeasible_rows.id != 0) { 605 | am_Row *row = (am_Row*)am_gettable(&S->solver->rows, 606 | S->solver->infeasible_rows); 607 | luaL_addstring(&B, "\n infeasible rows: "); 608 | aml_dumpkey(&B, 2, am_key(row)); 609 | while (row != NULL) { 610 | luaL_addstring(&B, ", "); 611 | aml_dumpkey(&B, 2, am_key(row)); 612 | row = (am_Row*)am_gettable(&S->solver->rows, row->infeasible_next); 613 | } 614 | } 615 | luaL_addstring(&B, "\n}"); 616 | luaL_pushresult(&B); 617 | return 1; 618 | } 619 | 620 | static int Lreset(lua_State *L) { 621 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 622 | int clear = lua_toboolean(L, 2); 623 | am_resetsolver(S->solver, clear); 624 | lua_settop(L, 1); return 1; 625 | } 626 | 627 | static int Lautoupdate(lua_State *L) { 628 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 629 | am_autoupdate(S->solver, lua_toboolean(L, 2)); 630 | return 0; 631 | } 632 | 633 | static int Laddconstraint(lua_State *L) { 634 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 635 | aml_Cons *lcons = (aml_Cons*)luaL_testudata(L, 2, AML_CONS_TYPE); 636 | int ret; 637 | if (lcons == NULL) lcons = aml_makecons(L, S, 2); 638 | if ((ret = am_add(lcons->cons)) == AM_OK) 639 | { lua_settop(L, 1); return 1; } 640 | switch (ret) { 641 | case AM_UNSATISFIED: luaL_argerror(L, 2, "constraint unsatisfied"); 642 | case AM_UNBOUND: luaL_argerror(L, 2, "constraint unbound"); 643 | } 644 | return 0; 645 | } 646 | 647 | static int Ldelconstraint(lua_State *L) { 648 | luaL_checkudata(L, 1, AML_SOLVER_TYPE); 649 | aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 2, AML_CONS_TYPE); 650 | am_remove(lcons->cons); 651 | lua_settop(L, 1); return 1; 652 | } 653 | 654 | static int Laddedit(lua_State *L) { 655 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 656 | am_Var *var = aml_checkvar(L, S, 2); 657 | am_Num strength = aml_checkstrength(L, 3, AM_MEDIUM); 658 | am_addedit(var, strength); 659 | lua_settop(L, 1); return 1; 660 | } 661 | 662 | static int Ldeledit(lua_State *L) { 663 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 664 | am_Var *var = aml_checkvar(L, S, 2); 665 | am_deledit(var); 666 | lua_settop(L, 1); return 1; 667 | } 668 | 669 | static int Lsuggest(lua_State *L) { 670 | aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE); 671 | am_Var *var = aml_checkvar(L, S, 2); 672 | am_Num value = (am_Num)luaL_checknumber(L, 3); 673 | am_suggest(var, value); 674 | lua_settop(L, 1); return 1; 675 | } 676 | 677 | LUALIB_API int luaopen_amoeba(lua_State *L) { 678 | luaL_Reg libs[] = { 679 | { "var", Lvar_new }, 680 | { "constraint", Lcons_new }, 681 | { "__tostring", Ltostring }, 682 | #define ENTRY(name) { #name, L##name } 683 | ENTRY(new), 684 | ENTRY(delete), 685 | ENTRY(autoupdate), 686 | ENTRY(reset), 687 | ENTRY(addconstraint), 688 | ENTRY(delconstraint), 689 | ENTRY(addedit), 690 | ENTRY(deledit), 691 | ENTRY(suggest), 692 | #undef ENTRY 693 | { NULL, NULL } 694 | }; 695 | open_variable(L); 696 | open_constraint(L); 697 | if (luaL_newmetatable(L, AML_SOLVER_TYPE)) { 698 | luaL_setfuncs(L, libs, 0); 699 | lua_pushvalue(L, -1); 700 | lua_setfield(L, -2, "__index"); 701 | } 702 | return 1; 703 | } 704 | 705 | /* maccc: flags+='-undefined dynamic_lookup -bundle -O2' output='amoeba.so' 706 | * win32cc: flags+='-DLUA_BUILD_AS_DLL -shared -O3' libs+='-llua54' output='amoeba.dll' */ 707 | 708 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #define AM_IMPLEMENTATION 2 | #include "amoeba.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static jmp_buf jbuf; 11 | static size_t allmem = 0; 12 | static size_t maxmem = 0; 13 | static void *END = NULL; 14 | 15 | static void *debug_allocf(void *ud, void *ptr, size_t ns, size_t os) { 16 | void *newptr = NULL; 17 | (void)ud; 18 | allmem += ns; 19 | allmem -= os; 20 | if (maxmem < allmem) maxmem = allmem; 21 | if (ns == 0) free(ptr); 22 | else { 23 | newptr = realloc(ptr, ns); 24 | if (newptr == NULL) longjmp(jbuf, 1); 25 | } 26 | #ifdef DEBUG_MEMORY 27 | printf("new(%p):\t+%d, old(%p):\t-%d\n", newptr, (int)ns, ptr, (int)os); 28 | #endif 29 | return newptr; 30 | } 31 | 32 | static void *null_allocf(void *ud, void *ptr, size_t ns, size_t os) 33 | { (void)ud, (void)ptr, (void)ns, (void)os; return NULL; } 34 | 35 | static void am_dumpkey(am_Symbol sym) { 36 | int ch = 'v'; 37 | switch (sym.type) { 38 | case AM_EXTERNAL: ch = 'v'; break; 39 | case AM_SLACK: ch = 's'; break; 40 | case AM_ERROR: ch = 'e'; break; 41 | case AM_DUMMY: ch = 'd'; break; 42 | } 43 | printf("%c%d", ch, (int)sym.id); 44 | } 45 | 46 | static void am_dumprow(am_Row *row) { 47 | am_Iterator it = am_itertable(&row->terms); 48 | am_Term *term; 49 | printf("%g", row->constant); 50 | while ((term = (am_Term*)am_nextentry(&it))) { 51 | am_Num multiplier = term->multiplier; 52 | printf(" %c ", multiplier > 0.0 ? '+' : '-'); 53 | if (multiplier < 0.0) multiplier = -multiplier; 54 | if (!am_approx(multiplier, 1.0f)) 55 | printf("%g*", multiplier); 56 | am_dumpkey(am_key(term)); 57 | } 58 | printf("\n"); 59 | } 60 | 61 | static void am_dumpsolver(am_Solver *solver) { 62 | am_Iterator it = am_itertable(&solver->rows); 63 | am_Row *row = NULL; 64 | int idx = 0; 65 | printf("-------------------------------\n"); 66 | printf("solver: "); 67 | am_dumprow(&solver->objective); 68 | printf("rows(%d):\n", (int)solver->rows.count); 69 | while ((row = (am_Row*)am_nextentry(&it))) { 70 | printf("%d. ", ++idx); 71 | am_dumpkey(am_key(row)); 72 | printf(" = "); 73 | am_dumprow(row); 74 | } 75 | printf("-------------------------------\n"); 76 | } 77 | 78 | static am_Constraint* new_constraint(am_Solver* in_solver, double in_strength, 79 | am_Var* in_term1, double in_factor1, int in_relation, 80 | double in_constant, ...) 81 | { 82 | int result; 83 | va_list argp; 84 | am_Constraint* c; 85 | assert(in_solver && in_term1); 86 | c = am_newconstraint(in_solver, (am_Num)in_strength); 87 | if(!c) return 0; 88 | am_addterm(c, in_term1, (am_Num)in_factor1); 89 | am_setrelation(c, in_relation); 90 | if(in_constant) am_addconstant(c, (am_Num)in_constant); 91 | va_start(argp, in_constant); 92 | while(1) { 93 | am_Var* va_term = va_arg(argp, am_Var*); 94 | double va_factor = va_arg(argp, double); 95 | if(va_term == 0) break; 96 | am_addterm(c, va_term, (am_Num)va_factor); 97 | } 98 | va_end(argp); 99 | result = am_add(c); 100 | assert(result == AM_OK); 101 | return c; 102 | } 103 | 104 | static void test_all(void) { 105 | am_Solver *solver; 106 | am_Var *xl; 107 | am_Var *xm; 108 | am_Var *xr; 109 | am_Var *xd; 110 | am_Constraint *c1, *c2, *c3, *c4, *c5, *c6; 111 | int ret = setjmp(jbuf); 112 | printf("\n\n==========\ntest all\n"); 113 | printf("ret = %d\n", ret); 114 | if (ret < 0) { perror("setjmp"); return; } 115 | else if (ret != 0) { printf("out of memory!\n"); return; } 116 | 117 | solver = am_newsolver(null_allocf, NULL); 118 | assert(solver == NULL); 119 | 120 | solver = am_newsolver(NULL, NULL); 121 | assert(solver != NULL); 122 | am_delsolver(solver); 123 | 124 | solver = am_newsolver(debug_allocf, NULL); 125 | xl = am_newvariable(solver); 126 | xm = am_newvariable(solver); 127 | xr = am_newvariable(solver); 128 | 129 | assert(am_variableid(NULL) == -1); 130 | assert(am_variableid(xl) == 1); 131 | assert(am_variableid(xm) == 2); 132 | assert(am_variableid(xr) == 3); 133 | assert(!am_hasedit(NULL)); 134 | assert(!am_hasedit(xl)); 135 | assert(!am_hasedit(xm)); 136 | assert(!am_hasedit(xr)); 137 | assert(!am_hasconstraint(NULL)); 138 | 139 | xd = am_newvariable(solver); 140 | am_delvariable(xd); 141 | 142 | assert(am_setrelation(NULL, AM_GREATEQUAL) == AM_FAILED); 143 | 144 | c1 = am_newconstraint(solver, AM_REQUIRED); 145 | am_addterm(c1, xl, 1.0); 146 | am_setrelation(c1, AM_GREATEQUAL); 147 | ret = am_add(c1); 148 | assert(ret == AM_OK); 149 | am_dumpsolver(solver); 150 | 151 | assert(am_setrelation(c1, AM_GREATEQUAL) == AM_FAILED); 152 | assert(am_setstrength(c1, AM_REQUIRED-10) == AM_OK); 153 | assert(am_setstrength(c1, AM_REQUIRED) == AM_OK); 154 | 155 | assert(am_hasconstraint(c1)); 156 | assert(!am_hasedit(xl)); 157 | 158 | c2 = am_newconstraint(solver, AM_REQUIRED); 159 | am_addterm(c2, xl, 1.0); 160 | am_setrelation(c2, AM_EQUAL); 161 | ret = am_add(c2); 162 | assert(ret == AM_OK); 163 | am_dumpsolver(solver); 164 | 165 | am_resetsolver(solver, 1); 166 | am_delconstraint(c1); 167 | am_delconstraint(c2); 168 | am_dumpsolver(solver); 169 | 170 | /* c1: 2*xm == xl + xr */ 171 | c1 = am_newconstraint(solver, AM_REQUIRED); 172 | am_addterm(c1, xm, 2.0); 173 | am_setrelation(c1, AM_EQUAL); 174 | am_addterm(c1, xl, 1.0); 175 | am_addterm(c1, xr, 1.0); 176 | ret = am_add(c1); 177 | assert(ret == AM_OK); 178 | am_dumpsolver(solver); 179 | 180 | /* c2: xl + 10 <= xr */ 181 | c2 = am_newconstraint(solver, AM_REQUIRED); 182 | am_addterm(c2, xl, 1.0); 183 | am_addconstant(c2, 10.0); 184 | am_setrelation(c2, AM_LESSEQUAL); 185 | am_addterm(c2, xr, 1.0); 186 | ret = am_add(c2); 187 | assert(ret == AM_OK); 188 | am_dumpsolver(solver); 189 | 190 | /* c3: xr <= 100 */ 191 | c3 = am_newconstraint(solver, AM_REQUIRED); 192 | am_addterm(c3, xr, 1.0); 193 | am_setrelation(c3, AM_LESSEQUAL); 194 | am_addconstant(c3, 100.0); 195 | ret = am_add(c3); 196 | assert(ret == AM_OK); 197 | am_dumpsolver(solver); 198 | 199 | /* c4: xl >= 0 */ 200 | c4 = am_newconstraint(solver, AM_REQUIRED); 201 | am_addterm(c4, xl, 1.0); 202 | am_setrelation(c4, AM_GREATEQUAL); 203 | am_addconstant(c4, 0.0); 204 | ret = am_add(c4); 205 | assert(ret == AM_OK); 206 | am_dumpsolver(solver); 207 | 208 | c5 = am_cloneconstraint(c4, AM_REQUIRED); 209 | ret = am_add(c5); 210 | assert(ret == AM_OK); 211 | am_dumpsolver(solver); 212 | am_remove(c5); 213 | 214 | c5 = am_newconstraint(solver, AM_REQUIRED); 215 | am_addterm(c5, xl, 1.0); 216 | am_setrelation(c5, AM_EQUAL); 217 | am_addconstant(c5, 0.0); 218 | ret = am_add(c5); 219 | assert(ret == AM_OK); 220 | 221 | c6 = am_cloneconstraint(c4, AM_REQUIRED); 222 | ret = am_add(c6); 223 | assert(ret == AM_OK); 224 | am_dumpsolver(solver); 225 | 226 | am_resetconstraint(c6); 227 | am_delconstraint(c6); 228 | 229 | am_remove(c1); 230 | am_remove(c2); 231 | am_remove(c3); 232 | am_remove(c4); 233 | am_dumpsolver(solver); 234 | ret |= am_add(c4); 235 | ret |= am_add(c3); 236 | ret |= am_add(c2); 237 | ret |= am_add(c1); 238 | assert(ret == AM_OK); 239 | 240 | am_resetsolver(solver, 0); 241 | am_resetsolver(solver, 1); 242 | printf("after reset\n"); 243 | am_dumpsolver(solver); 244 | ret |= am_add(c1); 245 | ret |= am_add(c2); 246 | ret |= am_add(c3); 247 | ret |= am_add(c4); 248 | assert(ret == AM_OK); 249 | 250 | printf("after initialize\n"); 251 | am_dumpsolver(solver); 252 | am_updatevars(solver); 253 | printf("xl: %f, xm: %f, xr: %f\n", 254 | am_value(xl), 255 | am_value(xm), 256 | am_value(xr)); 257 | 258 | am_addedit(xm, AM_MEDIUM); 259 | am_dumpsolver(solver); 260 | am_updatevars(solver); 261 | printf("xl: %f, xm: %f, xr: %f\n", 262 | am_value(xl), 263 | am_value(xm), 264 | am_value(xr)); 265 | 266 | assert(am_hasedit(xm)); 267 | 268 | printf("suggest to 0.0\n"); 269 | am_suggest(xm, 0.0); 270 | am_dumpsolver(solver); 271 | am_updatevars(solver); 272 | printf("xl: %f, xm: %f, xr: %f\n", 273 | am_value(xl), 274 | am_value(xm), 275 | am_value(xr)); 276 | 277 | printf("suggest to 70.0\n"); 278 | am_suggest(xm, 70.0); 279 | am_updatevars(solver); 280 | am_dumpsolver(solver); 281 | 282 | printf("xl: %f, xm: %f, xr: %f\n", 283 | am_value(xl), 284 | am_value(xm), 285 | am_value(xr)); 286 | 287 | am_deledit(xm); 288 | am_updatevars(solver); 289 | am_dumpsolver(solver); 290 | 291 | printf("xl: %f, xm: %f, xr: %f\n", 292 | am_value(xl), 293 | am_value(xm), 294 | am_value(xr)); 295 | 296 | am_delsolver(solver); 297 | printf("allmem = %d\n", (int)allmem); 298 | printf("maxmem = %d\n", (int)maxmem); 299 | assert(allmem == 0); 300 | maxmem = 0; 301 | } 302 | 303 | static void test_binarytree(void) { 304 | const int NUM_ROWS = 9; 305 | const int X_OFFSET = 0; 306 | int nPointsCount, nResult, nRow; 307 | int nCurrentRowPointsCount = 1; 308 | int nCurrentRowFirstPointIndex = 0; 309 | am_Constraint *pC; 310 | am_Solver *pSolver; 311 | am_Var **arrX, **arrY; 312 | 313 | printf("\n\n==========\ntest binarytree\n"); 314 | arrX = (am_Var**)malloc(2048 * sizeof(am_Var*)); 315 | if (arrX == NULL) return; 316 | arrY = arrX + 1024; 317 | 318 | /* Create set of rules to distribute vertexes of a binary tree like this one: 319 | * 0 320 | * / \ 321 | * / \ 322 | * 1 2 323 | * / \ / \ 324 | * 3 4 5 6 325 | */ 326 | 327 | pSolver = am_newsolver(debug_allocf, NULL); 328 | 329 | /* Xroot=500, Yroot=10 */ 330 | arrX[0] = am_newvariable(pSolver); 331 | arrY[0] = am_newvariable(pSolver); 332 | am_addedit(arrX[0], AM_STRONG); 333 | am_addedit(arrY[0], AM_STRONG); 334 | am_suggest(arrX[0], 500.0f + X_OFFSET); 335 | am_suggest(arrY[0], 10.0f); 336 | 337 | for (nRow = 1; nRow < NUM_ROWS; nRow++) { 338 | int nPreviousRowFirstPointIndex = nCurrentRowFirstPointIndex; 339 | int nPoint, nParentPoint = 0; 340 | nCurrentRowFirstPointIndex += nCurrentRowPointsCount; 341 | nCurrentRowPointsCount *= 2; 342 | 343 | for (nPoint = 0; nPoint < nCurrentRowPointsCount; nPoint++) { 344 | arrX[nCurrentRowFirstPointIndex + nPoint] = am_newvariable(pSolver); 345 | arrY[nCurrentRowFirstPointIndex + nPoint] = am_newvariable(pSolver); 346 | 347 | /* Ycur = Yprev_row + 15 */ 348 | pC = am_newconstraint(pSolver, AM_REQUIRED); 349 | am_addterm(pC, arrY[nCurrentRowFirstPointIndex + nPoint], 1.0); 350 | am_setrelation(pC, AM_EQUAL); 351 | am_addterm(pC, arrY[nCurrentRowFirstPointIndex - 1], 1.0); 352 | am_addconstant(pC, 15.0); 353 | nResult = am_add(pC); 354 | assert(nResult == AM_OK); 355 | 356 | if (nPoint > 0) { 357 | /* Xcur >= XPrev + 5 */ 358 | pC = am_newconstraint(pSolver, AM_REQUIRED); 359 | am_addterm(pC, arrX[nCurrentRowFirstPointIndex + nPoint], 1.0); 360 | am_setrelation(pC, AM_GREATEQUAL); 361 | am_addterm(pC, arrX[nCurrentRowFirstPointIndex + nPoint - 1], 1.0); 362 | am_addconstant(pC, 5.0); 363 | nResult = am_add(pC); 364 | assert(nResult == AM_OK); 365 | } else { 366 | /* When these lines added it crashes at the line 109 */ 367 | pC = am_newconstraint(pSolver, AM_REQUIRED); 368 | am_addterm(pC, arrX[nCurrentRowFirstPointIndex + nPoint], 1.0); 369 | am_setrelation(pC, AM_GREATEQUAL); 370 | am_addconstant(pC, 0.0); 371 | nResult = am_add(pC); 372 | assert(nResult == AM_OK); 373 | } 374 | 375 | if ((nPoint % 2) == 1) { 376 | /* Xparent = 0.5 * Xcur + 0.5 * Xprev */ 377 | pC = am_newconstraint(pSolver, AM_REQUIRED); 378 | am_addterm(pC, arrX[nPreviousRowFirstPointIndex + nParentPoint], 1.0); 379 | am_setrelation(pC, AM_EQUAL); 380 | am_addterm(pC, arrX[nCurrentRowFirstPointIndex + nPoint], 0.5); 381 | am_addterm(pC, arrX[nCurrentRowFirstPointIndex + nPoint - 1], 0.5); 382 | /* It crashes here (at the 3rd call of am_add(...))! */ 383 | nResult = am_add(pC); 384 | assert(nResult == AM_OK); 385 | 386 | nParentPoint++; 387 | } 388 | } 389 | } 390 | nPointsCount = nCurrentRowFirstPointIndex + nCurrentRowPointsCount; 391 | (void)nPointsCount; 392 | 393 | /*{ 394 | int i; 395 | for (i = 0; i < nPointsCount; i++) 396 | printf("Point %d: (%f, %f)\n", i, 397 | am_value(arrX[i]), am_value(arrY[i])); 398 | }*/ 399 | 400 | am_delsolver(pSolver); 401 | printf("allmem = %d\n", (int)allmem); 402 | printf("maxmem = %d\n", (int)maxmem); 403 | assert(allmem == 0); 404 | free(arrX); 405 | maxmem = 0; 406 | } 407 | 408 | static void test_unbounded(void) { 409 | am_Solver *solver; 410 | am_Var *x, *y; 411 | am_Constraint *c; 412 | int ret = setjmp(jbuf); 413 | printf("\n\n==========\ntest unbound\n"); 414 | printf("ret = %d\n", ret); 415 | if (ret < 0) { perror("setjmp"); return; } 416 | else if (ret != 0) { printf("out of memory!\n"); return; } 417 | 418 | solver = am_newsolver(debug_allocf, NULL); 419 | x = am_newvariable(solver); 420 | y = am_newvariable(solver); 421 | 422 | /* 10.0 == 0.0 */ 423 | c = am_newconstraint(solver, AM_REQUIRED); 424 | am_addconstant(c, 10.0); 425 | am_setrelation(c, AM_EQUAL); 426 | ret = am_add(c); 427 | printf("ret = %d\n", ret); 428 | assert(ret == AM_UNSATISFIED); 429 | am_dumpsolver(solver); 430 | 431 | /* 0.0 == 0.0 */ 432 | c = am_newconstraint(solver, AM_REQUIRED); 433 | am_addconstant(c, 0.0); 434 | am_setrelation(c, AM_EQUAL); 435 | ret = am_add(c); 436 | printf("ret = %d\n", ret); 437 | assert(ret == AM_OK); 438 | am_dumpsolver(solver); 439 | 440 | am_resetsolver(solver, 1); 441 | 442 | /* x >= 10.0 */ 443 | c = am_newconstraint(solver, AM_REQUIRED); 444 | am_addterm(c, x, 1.0); 445 | am_setrelation(c, AM_GREATEQUAL); 446 | am_addconstant(c, 10.0); 447 | ret = am_add(c); 448 | printf("ret = %d\n", ret); 449 | assert(ret == AM_OK); 450 | am_dumpsolver(solver); 451 | 452 | /* x == 2*y */ 453 | c = am_newconstraint(solver, AM_REQUIRED); 454 | am_addterm(c, x, 1.0); 455 | am_setrelation(c, AM_EQUAL); 456 | am_addterm(c, y, 2.0); 457 | ret = am_add(c); 458 | printf("ret = %d\n", ret); 459 | assert(ret == AM_OK); 460 | am_dumpsolver(solver); 461 | 462 | /* y == 3*x */ 463 | c = am_newconstraint(solver, AM_REQUIRED); 464 | am_addterm(c, y, 1.0); 465 | am_setrelation(c, AM_EQUAL); 466 | am_addterm(c, x, 3.0); 467 | ret = am_add(c); 468 | printf("ret = %d\n", ret); 469 | assert(ret == AM_UNBOUND); 470 | am_dumpsolver(solver); 471 | 472 | am_resetsolver(solver, 1); 473 | 474 | /* x >= 10.0 */ 475 | c = am_newconstraint(solver, AM_REQUIRED); 476 | am_addterm(c, x, 1.0); 477 | am_setrelation(c, AM_GREATEQUAL); 478 | am_addconstant(c, 10.0); 479 | ret = am_add(c); 480 | printf("ret = %d\n", ret); 481 | assert(ret == AM_OK); 482 | am_dumpsolver(solver); 483 | 484 | /* x <= 0.0 */ 485 | c = am_newconstraint(solver, AM_REQUIRED); 486 | am_addterm(c, x, 1.0); 487 | am_setrelation(c, AM_LESSEQUAL); 488 | ret = am_add(c); 489 | printf("ret = %d\n", ret); 490 | assert(ret == AM_UNBOUND); 491 | am_dumpsolver(solver); 492 | 493 | printf("x: %f\n", am_value(x)); 494 | 495 | am_resetsolver(solver, 1); 496 | 497 | /* x == 10.0 */ 498 | c = am_newconstraint(solver, AM_REQUIRED); 499 | am_addterm(c, x, 1.0); 500 | am_setrelation(c, AM_EQUAL); 501 | am_addconstant(c, 10.0); 502 | ret = am_add(c); 503 | printf("ret = %d\n", ret); 504 | assert(ret == AM_OK); 505 | am_dumpsolver(solver); 506 | 507 | /* x == 20.0 */ 508 | c = am_newconstraint(solver, AM_REQUIRED); 509 | am_addterm(c, x, 1.0); 510 | am_setrelation(c, AM_EQUAL); 511 | am_addconstant(c, 20.0); 512 | ret = am_add(c); 513 | printf("ret = %d\n", ret); 514 | assert(ret == AM_UNSATISFIED); 515 | am_dumpsolver(solver); 516 | 517 | /* x == 10.0 */ 518 | c = am_newconstraint(solver, AM_REQUIRED); 519 | am_addterm(c, x, 1.0); 520 | am_setrelation(c, AM_EQUAL); 521 | am_addconstant(c, 10.0); 522 | ret = am_add(c); 523 | printf("ret = %d\n", ret); 524 | assert(ret == AM_OK); 525 | am_dumpsolver(solver); 526 | 527 | am_delsolver(solver); 528 | printf("allmem = %d\n", (int)allmem); 529 | printf("maxmem = %d\n", (int)maxmem); 530 | assert(allmem == 0); 531 | maxmem = 0; 532 | } 533 | 534 | static void test_strength(void) { 535 | am_Solver *solver; 536 | am_Var *x, *y; 537 | am_Constraint *c; 538 | int ret = setjmp(jbuf); 539 | printf("\n\n==========\ntest strength\n"); 540 | printf("ret = %d\n", ret); 541 | if (ret < 0) { perror("setjmp"); return; } 542 | else if (ret != 0) { printf("out of memory!\n"); return; } 543 | 544 | solver = am_newsolver(debug_allocf, NULL); 545 | am_autoupdate(solver, 1); 546 | x = am_newvariable(solver); 547 | y = am_newvariable(solver); 548 | 549 | /* x <= y */ 550 | new_constraint(solver, AM_STRONG, x, 1.0, AM_LESSEQUAL, 0.0, 551 | y, 1.0, END); 552 | new_constraint(solver, AM_MEDIUM, x, 1.0, AM_EQUAL, 50, END); 553 | c = new_constraint(solver, AM_MEDIUM-10, y, 1.0, AM_EQUAL, 40, END); 554 | printf("%f, %f\n", am_value(x), am_value(y)); 555 | assert(am_value(x) == 50); 556 | assert(am_value(y) == 50); 557 | 558 | am_setstrength(c, AM_MEDIUM+10); 559 | printf("%f, %f\n", am_value(x), am_value(y)); 560 | assert(am_value(x) == 40); 561 | assert(am_value(y) == 40); 562 | 563 | am_setstrength(c, AM_MEDIUM-10); 564 | printf("%f, %f\n", am_value(x), am_value(y)); 565 | assert(am_value(x) == 50); 566 | assert(am_value(y) == 50); 567 | 568 | am_delsolver(solver); 569 | printf("allmem = %d\n", (int)allmem); 570 | printf("maxmem = %d\n", (int)maxmem); 571 | assert(allmem == 0); 572 | maxmem = 0; 573 | } 574 | 575 | static void test_suggest(void) { 576 | #if 1 577 | /* This should be valid but fails the (enter.id != 0) assertion in am_dual_optimize() */ 578 | am_Num strength1 = AM_REQUIRED; 579 | am_Num strength2 = AM_REQUIRED; 580 | am_Num width = 76; 581 | #else 582 | /* This mostly works, but still insists on forcing left_child_l = 0 which it should not */ 583 | am_Num strength1 = AM_STRONG; 584 | am_Num strength2 = AM_WEAK; 585 | am_Num width = 76; 586 | #endif 587 | am_Num delta = 0; 588 | am_Num pos; 589 | am_Solver *solver; 590 | am_Var *splitter_l, *splitter_w, *splitter_r; 591 | am_Var *left_child_l, *left_child_w, *left_child_r; 592 | am_Var *splitter_bar_l, *splitter_bar_w, *splitter_bar_r; 593 | am_Var *right_child_l, *right_child_w, *right_child_r; 594 | int ret = setjmp(jbuf); 595 | printf("\n\n==========\ntest suggest\n"); 596 | printf("ret = %d\n", ret); 597 | if (ret < 0) { perror("setjmp"); return; } 598 | else if (ret != 0) { printf("out of memory!\n"); return; } 599 | 600 | solver = am_newsolver(debug_allocf, NULL); 601 | splitter_l = am_newvariable(solver); 602 | splitter_w = am_newvariable(solver); 603 | splitter_r = am_newvariable(solver); 604 | left_child_l = am_newvariable(solver); 605 | left_child_w = am_newvariable(solver); 606 | left_child_r = am_newvariable(solver); 607 | splitter_bar_l = am_newvariable(solver); 608 | splitter_bar_w = am_newvariable(solver); 609 | splitter_bar_r = am_newvariable(solver); 610 | right_child_l = am_newvariable(solver); 611 | right_child_w = am_newvariable(solver); 612 | right_child_r = am_newvariable(solver); 613 | 614 | /* splitter_r = splitter_l + splitter_w */ 615 | /* left_child_r = left_child_l + left_child_w */ 616 | /* splitter_bar_r = splitter_bar_l + splitter_bar_w */ 617 | /* right_child_r = right_child_l + right_child_w */ 618 | new_constraint(solver, AM_REQUIRED, splitter_r, 1.0, AM_EQUAL, 0.0, 619 | splitter_l, 1.0, splitter_w, 1.0, END); 620 | new_constraint(solver, AM_REQUIRED, left_child_r, 1.0, AM_EQUAL, 0.0, 621 | left_child_l, 1.0, left_child_w, 1.0, END); 622 | new_constraint(solver, AM_REQUIRED, splitter_bar_r, 1.0, AM_EQUAL, 0.0, 623 | splitter_bar_l, 1.0, splitter_bar_w, 1.0, END); 624 | new_constraint(solver, AM_REQUIRED, right_child_r, 1.0, AM_EQUAL, 0.0, 625 | right_child_l, 1.0, right_child_w, 1.0, END); 626 | 627 | /* splitter_bar_w = 6 */ 628 | /* splitter_bar_l >= splitter_l + delta */ 629 | /* splitter_bar_r <= splitter_r - delta */ 630 | /* left_child_r = splitter_bar_l */ 631 | /* right_child_l = splitter_bar_r */ 632 | new_constraint(solver, AM_REQUIRED, splitter_bar_w, 1.0, AM_EQUAL, 6.0, END); 633 | new_constraint(solver, AM_REQUIRED, splitter_bar_l, 1.0, AM_GREATEQUAL, 634 | delta, splitter_l, 1.0, END); 635 | new_constraint(solver, AM_REQUIRED, splitter_bar_r, 1.0, AM_LESSEQUAL, 636 | -delta, splitter_r, 1.0, END); 637 | new_constraint(solver, AM_REQUIRED, left_child_r, 1.0, AM_EQUAL, 0.0, 638 | splitter_bar_l, 1.0, END); 639 | new_constraint(solver, AM_REQUIRED, right_child_l, 1.0, AM_EQUAL, 0.0, 640 | splitter_bar_r, 1.0, END); 641 | 642 | /* right_child_r >= splitter_r + 1 */ 643 | /* left_child_w = 256 */ 644 | new_constraint(solver, strength1, right_child_r, 1.0, AM_GREATEQUAL, 1.0, 645 | splitter_r, 1.0, END); 646 | new_constraint(solver, strength2, left_child_w, 1.0, AM_EQUAL, 256.0, END); 647 | 648 | /* splitter_l = 0 */ 649 | /* splitter_r = 76 */ 650 | new_constraint(solver, AM_REQUIRED, splitter_l, 1.0, AM_EQUAL, 0.0, END); 651 | new_constraint(solver, AM_REQUIRED, splitter_r, 1.0, AM_EQUAL, width, END); 652 | 653 | printf("\n\n==========\ntest suggest\n"); 654 | for(pos = -10; pos < 86; pos++) { 655 | am_suggest(splitter_bar_l, pos); 656 | printf("pos: %4g | ", pos); 657 | printf("splitter_l l=%2g, w=%2g, r=%2g | ", am_value(splitter_l), 658 | am_value(splitter_w), am_value(splitter_r)); 659 | printf("left_child_l l=%2g, w=%2g, r=%2g | ", am_value(left_child_l), 660 | am_value(left_child_w), am_value(left_child_r)); 661 | printf("splitter_bar_l l=%2g, w=%2g, r=%2g | ", am_value(splitter_bar_l), 662 | am_value(splitter_bar_w), am_value(splitter_bar_r)); 663 | printf("right_child_l l=%2g, w=%2g, r=%2g | ", am_value(right_child_l), 664 | am_value(right_child_w), am_value(right_child_r)); 665 | printf("\n"); 666 | } 667 | 668 | am_delsolver(solver); 669 | printf("allmem = %d\n", (int)allmem); 670 | printf("maxmem = %d\n", (int)maxmem); 671 | assert(allmem == 0); 672 | maxmem = 0; 673 | } 674 | 675 | static void test_dirty(void) { 676 | am_Solver *solver; 677 | am_Var *xl, *xr, *xw, *xwc; 678 | am_Constraint *c1, *c2; 679 | int ret = setjmp(jbuf); 680 | printf("\n\n==========\ntest dirty\n"); 681 | printf("ret = %d\n", ret); 682 | if (ret < 0) { perror("setjmp"); return; } 683 | else if (ret != 0) { printf("out of memory!\n"); return; } 684 | 685 | solver = am_newsolver(debug_allocf, NULL); 686 | 687 | xl = am_newvariable(solver); 688 | xr = am_newvariable(solver); 689 | xw = am_newvariable(solver); 690 | xwc = am_newvariable(solver); 691 | 692 | 693 | /* c1: xw == xr -xl */ 694 | c1 = am_newconstraint(solver, AM_REQUIRED); 695 | ret = 0; 696 | ret |= am_addterm(c1, xw, 1.0); 697 | ret |= am_setrelation(c1, AM_EQUAL); 698 | ret |= am_addterm(c1, xr, 1.0); 699 | ret |= am_addterm(c1, xl, -1.0); 700 | ret |= am_add(c1); 701 | assert(ret == AM_OK); 702 | 703 | am_updatevars(solver); 704 | printf("xl: %f, xr:%f, xw:%f\n", am_value(xl), am_value(xr), am_value(xw)); 705 | 706 | /* c1: xwc == xw */ 707 | c2 = am_newconstraint(solver, AM_REQUIRED); 708 | ret = 0; 709 | ret |= am_addterm(c2, xwc, 1.0); 710 | ret |= am_setrelation(c2, AM_EQUAL); 711 | ret |= am_addterm(c2, xw, 1.0); 712 | ret |= am_add(c2); 713 | assert(ret == AM_OK); 714 | 715 | /* Sets dirty bit? Related to crash. */ 716 | am_suggest(xwc, 10); 717 | 718 | am_updatevars(solver); 719 | printf("xl:%f, xr:%f, xw:%f, xwc:%f\n", am_value(xl), am_value(xr), am_value(xw), am_value(xwc)); 720 | 721 | /* Remove xwc and c2 */ 722 | am_deledit(xwc); 723 | am_remove(c2); 724 | /* Adding an am_updatevars(solver); here somehow solves the issue. */ 725 | am_delconstraint(c2); 726 | am_delvariable(xwc); 727 | 728 | /* Causes crash: amoeba.h:482: am_sym2var: Assertion `ve != NULL' failed. */ 729 | am_updatevars(solver); 730 | printf("xl:%f, xr:%f, xw:%f\n", am_value(xl), am_value(xr), am_value(xw)); 731 | 732 | /* Manual cleanup */ 733 | am_delconstraint(c1); 734 | am_remove(c1); 735 | 736 | am_delvariable(xl); 737 | am_delvariable(xr); 738 | am_delvariable(xw); 739 | 740 | am_delsolver(solver); 741 | } 742 | 743 | void test_cycling(void) { 744 | am_Solver *solver; 745 | am_Var *va, *vb, *vc, *vd; 746 | 747 | int ret = setjmp(jbuf); 748 | printf("\n\n==========\ntest cycling\n"); 749 | printf("ret = %d\n", ret); 750 | if (ret < 0) { perror("setjmp"); return; } 751 | else if (ret != 0) { printf("out of memory!\n"); return; } 752 | 753 | solver = am_newsolver(debug_allocf, NULL); 754 | va = am_newvariable(solver); 755 | vb = am_newvariable(solver); 756 | vc = am_newvariable(solver); 757 | vd = am_newvariable(solver); 758 | 759 | am_addedit(va, AM_STRONG); 760 | printf("after edit\n"); 761 | am_dumpsolver(solver); 762 | 763 | /* vb == va */ 764 | { 765 | am_Constraint * c = am_newconstraint(solver, AM_REQUIRED); 766 | int ret = 0; 767 | ret |= am_addterm(c, vb, 1.0); 768 | ret |= am_setrelation(c, AM_EQUAL); 769 | ret |= am_addterm(c, va, 1.0); 770 | ret |= am_add(c); 771 | assert(ret == AM_OK); 772 | am_dumpsolver(solver); 773 | } 774 | 775 | /* vb == vc */ 776 | { 777 | am_Constraint * c = am_newconstraint(solver, AM_REQUIRED); 778 | int ret = 0; 779 | ret |= am_addterm(c, vb, 1.0); 780 | ret |= am_setrelation(c, AM_EQUAL); 781 | ret |= am_addterm(c, vc, 1.0); 782 | ret |= am_add(c); 783 | assert(ret == AM_OK); 784 | am_dumpsolver(solver); 785 | } 786 | 787 | /* vc == vd */ 788 | { 789 | am_Constraint * c = am_newconstraint(solver, AM_REQUIRED); 790 | int ret = 0; 791 | ret |= am_addterm(c, vc, 1.0); 792 | ret |= am_setrelation(c, AM_EQUAL); 793 | ret |= am_addterm(c, vd, 1.0); 794 | ret |= am_add(c); 795 | assert(ret == AM_OK); 796 | am_dumpsolver(solver); 797 | } 798 | 799 | /* vd == va */ 800 | { 801 | am_Constraint * c = am_newconstraint(solver, AM_REQUIRED); 802 | int ret = 0; 803 | ret |= am_addterm(c, vd, 1.0); 804 | ret |= am_setrelation(c, AM_EQUAL); 805 | ret |= am_addterm(c, va, 1.0); 806 | ret |= am_add(c); 807 | assert(ret == AM_OK); /* asserts here */ 808 | am_dumpsolver(solver); 809 | } 810 | 811 | am_delsolver(solver); 812 | } 813 | 814 | int main(void) { 815 | test_binarytree(); 816 | test_unbounded(); 817 | test_strength(); 818 | test_suggest(); 819 | test_cycling(); 820 | test_dirty(); 821 | test_all(); 822 | return 0; 823 | } 824 | 825 | /* cc: flags='-ggdb -Wall -fprofile-arcs -ftest-coverage -O2 -Wextra -pedantic -std=c89' */ 826 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | package.path = "" 2 | local amoeba = require "amoeba" 3 | 4 | local S = amoeba.new(true) 5 | print(S) 6 | local xl, xm, xr = 7 | S:var "xl", S:var "xm", S:var "xr" 8 | print(xl) 9 | print(xm) 10 | print(xr) 11 | print(S:constraint() 12 | :add(xl):add(10) 13 | :relation "le" -- or "<=" 14 | :add(xr)) 15 | S:addconstraint((xm*2) :eq (xl + xr)) 16 | S:addconstraint( 17 | S:constraint() 18 | :add(xl):add(10) 19 | :relation "le" -- or "<=" 20 | :add(xr)) -- (xl + 10) :le (xr) 21 | S:addconstraint( 22 | S:constraint()(xr) "<=" (100)) -- (xr) :le (100) 23 | S:addconstraint((xl) :ge (0)) 24 | print(S) 25 | print(xl) 26 | print(xm) 27 | print(xr) 28 | 29 | print('suggest xm to 0') 30 | S:suggest(xm, 0) 31 | print(S) 32 | print(xl) 33 | print(xm) 34 | print(xr) 35 | 36 | print('suggest xm to 70') 37 | S:suggest(xm, 70) 38 | print(S) 39 | print(xl) 40 | print(xm) 41 | print(xr) 42 | 43 | print('delete edit xm') 44 | S:deledit(xm) 45 | print(S) 46 | print(xl) 47 | print(xm) 48 | print(xr) 49 | 50 | --------------------------------------------------------------------------------