Full Code of starwing/amoeba for AI

master 0f53f23e37cf cached
14 files
306.7 KB
87.7k tokens
247 symbols
1 requests
Download .txt
Showing preview only (316K chars total). Download the full file or copy to clipboard to get everything.
Repository: starwing/amoeba
Branch: master
Commit: 0f53f23e37cf
Files: 14
Total size: 306.7 KB

Directory structure:
gitextract_dcl1tyb6/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── amoeba.h
├── amoeba.lua
├── enaml_like_benchmark.cpp
├── lua_amoeba.c
├── nanobench.h
├── test.c
├── test.lua
├── yoga_benchmark.cpp
└── yogaone.cpp

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test.yml
================================================
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  test_ubuntu:
    if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: ["ubuntu-latest"]
    steps:
      - name: Install LCov
        run: |
          sudo apt install libperlio-gzip-perl libjson-perl libcapture-tiny-perl libdatetime-perl
          wget "https://github.com/linux-test-project/lcov/releases/download/v1.16/lcov-1.16.tar.gz"
          tar zxf "lcov-1.16.tar.gz"
          cd "lcov-1.16"
          sudo make install
      - uses: actions/checkout@master
      - name: Run Tests
        run: |
          gcc -o test -O test.c --coverage && ./test
      - name: Run LCov
        run: |
          mkdir -p coverage
          lcov -c -d . -o coverage/lcov.info.all
          lcov -r coverage/lcov.info.all '/usr/*' -o coverage/lcov.info
      - name: Coveralls
        uses: coverallsapp/github-action@master
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          parallel: true
  finish:
    needs: test_ubuntu
    runs-on: ubuntu-latest
    steps:
    - name: Coveralls Finished
      uses: coverallsapp/github-action@master
      with:
        github-token: ${{ secrets.github_token }}
        parallel-finished: true


================================================
FILE: .gitignore
================================================
*.dll
*.exp
*.ilk
*.lib
*.pdb
*.exe

*.so


================================================
FILE: .travis.yml
================================================
language: c

compiler:
    - gcc

before_install:
    - pip install --user urllib3[secure] cpp-coveralls

    # Work around https://github.com/eddyxu/cpp-coveralls/issues/108 by manually
    # installing the pyOpenSSL module and injecting it into urllib3 as per
    # https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2
    - sed -i -e '/^import sys$/a import urllib3.contrib.pyopenssl\nurllib3.contrib.pyopenssl.inject_into_urllib3()' `which coveralls`


install:
    - gcc -shared -Wall -O3 -Wextra -pedantic -std=c89 -xc amoeba.h -o amoeba.so
    - gcc -Wall -fprofile-arcs -ftest-coverage -O0 -Wextra -pedantic -std=c89 test.c -o test

script:
    - ./test

after_success:
    - coveralls

notifications:
    email:
        on_success: change
        on_failure: always



================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Xavier Wang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Amoeba -- the constraint solving algorithm in pure C

[![Build Status](https://img.shields.io/github/actions/workflow/status/starwing/amoeba/test.yml?branch=master)](https://github.com/starwing/amoeba/actions?query=branch%3Amaster)[![Coverage Status](https://img.shields.io/coveralls/github/starwing/amoeba)](https://coveralls.io/github/starwing/amoeba?branch=master)

Amoeba is a pure C implement of Cassowary algorithm. It:
- Uses Clean C, which is the cross set of ANSI C89 and C++, like the Lua language.
- Is a single-file library. For more single-file library, see the stb project [here][1].
- Largely impressed by [kiwi][2], the C++ implement of Cassowary algorithm, and the algorithm [paper][3].
- With a Lua binding.
- With the same license as the [Lua language][4].

[1]: https://github.com/nothings/stb
[2]: https://github.com/nucleic/kiwi
[3]: http://constraints.cs.washington.edu/solvers/uist97.html
[4]: https://www.lua.org/license.html

Improvement relative to Kiwi:
- Single header library that easy to distribution.
- Lua binding vs. Python binding.
- Suggest cache makes suggest performance much better than kiwi.
- Dump/load support improves the performance of building solver.

## How To Use

This libary export a constraint solver interface, to solve a constraint problem, you should use it in steps:

- create a new solver: `am_newsolver()`
- create some variables: `am_newvariable()` binding the `am_Num` values.
- create some constraints that may use variables: `am_newconstraint()`.
- make constraints by construct equation using:
  - `am_addterm()` add a $a \times variable$ term to constraint equation items.
  - `am_setrelation()` to specify the equal/greater/less sign in center of equation.
  - `am_addconstant()` to add a number without variable.
  - `am_setstrength()` to specify the priority of this constraint in all constraints.
- after make up a constraint, you could add it into solver by `am_add()`.
- and you can read out the result of each variable from the binding `am_Num`.
- or you can manually specify a new value to variable with `am_sugguest()`.
- after done, use `am_delsolver()` to free al memory.

below is a tiny example to demonstrate the steps:

```c
#define AM_IMPLEMENTATION // include implementations of library
#include "amoeba.h"       // and interface

int main(void) {
    // first, create a solver:
    am_Solver *S = am_newsolver(NULL, NULL);
    int ret;

    // create some variable:
    am_Num l, m, r;
    am_Id vl = am_newvariable(S, &l);
    am_Id vm = am_newvariable(S, &m);
    am_Id vr = am_newvariable(S, &r);

    // create the constraint: 
    am_Constraint *c1 = am_newconstraint(S, AM_REQUIRED);
    am_Constraint *c2 = am_newconstraint(S, AM_REQUIRED);

    // c1: m is in middle of l and r:
    //     i.e. m = (l + r) / 2, or 2*m = l + r
    am_addterm(c1, vm, 2.f);
    am_setrelation(c1, AM_EQUAL);
    am_addterm(c1, vl, 1.f);
    am_addterm(c1, vr, 1.f);
    // apply c1
    ret = am_add(c1);
    assert(ret == AM_OK);

    // c2: r - l >= 100
    am_addterm(c2, vr, 1.f);
    am_addterm(c2, vl, -1.f);
    am_setrelation(c2, AM_GREATEQUAL);
    am_addconstant(c2, 100.f);
    // apply c2
    ret = am_add(c2);
    assert(ret == AM_OK);

    // now we set variable l to 20
    am_suggest(S, vl, 20.f);

    // and see the value of m and r:
    am_updatevars(S);

    // r should by 20 + 100 == 120:
    assert(r == 120.f);

    // and m should in middle of l and r:
    assert(m == 70.f);
    
    // done with solver
    am_delsolver(S);
    return 0;
}
```


## Reference

### Return value

All functions below that returns `int` are returning error codes:
- `AM_OK`: Operations success.
- `AM_FAILED`: The operation is failed, most caused by invalid arguments.
- `AM_UNSATISFIED`: Specific constraints added cannot satisfied.
- `AM_UNBOUND`: Failed to add constraints because there are unbound variables in it

### Dump & Load

Amoeba supports to store whole state of solver into bianry data and to load
back in new solver. Notice that the state of loaded solver will be earsed. 

To perform a dump or load operations, you should prepare `am_Dumper` or
`am_Loader` to control the behavior in operations.

- `struct am_Dumper`

  A user-defined dump behavior control type.  The user must provide `reader`
  and `var_name` function pointers in it. there is a example:

  ```c
  /* dumps into a string buffer */
  typedef struct MyDumper {
      am_Dumper base;
      char buf[MY_BUF_SIZE]; /* buffer */
      size_t len;            /* and length */
  } MyDumper;

  static const char *dump_varname(am_Dumper *d, unsigned idx, am_Id var, am_Num *value) {
      /* you should returns a var name here, or NULL if you do not want a name
       * for this variable.
       * A common implement retrives variable context from value pointer, and
       * get name from it.  */
      MyContext *ctx = (MyContext*)value;
      return ctx->name;
  }

  static const char *dump_consname(am_Dumper *d, unsigned idx, am_Constraint *cons) {
      /* Same as `var_name`, but retrieve constraint's name */
      MyContext *ctx = *(MyContext**)cons;
      return ctx->name;
  }

  static int dump_writer(am_Dumper *d, const void *buf, size_t len) {
      /* Write the actually data */
      MyDumper *myd = (MyDumper*)d;
      if (myd->len + len > MY_BUF_SIZE)
          return AM_FAILED; /* data out of buffer, errors returned */
      memcpy(myd->buf + myd->len, buf, len);
      myd->len += len;
      /* All return value other than `AM_OK` will be returned directly by
       * `am_dump()`. */
      return AM_OK;
  }

  /* do actually dump */
  MyDumper d;
  d.base.var_name  = dump_varname;
  d.base.cons_name = dump_consname;
  d.base.writer    = dump_writer;
  d.len = 0;

  int ret = am_dump(solver, &d.base);
  assert(ret == AM_OK);
  /* now uses the d.buf & d.len */
  ```

- `struct am_Loader`

  A user-defined dump behavior control type.  The user must provide `reader`
  and `load_var` function pointers in it. there is a example:

  ```c
  /* loads from a string buffer */
  typedef struct MyLoader {
      am_Loader base;
      VarMap    vars;
      ConsMap   cons;
      const char *buf; /* the buffer to read */
      size_t remain;   /* remain bytes */
  } MyLoader;

  static am_Num *load_var(am_Loader *l, const char *name, unsigned idx, am_Id var) {
      /* you should returns a binding for variable named `name`, or `AM_FAILED`
       * will be returned by `am_load()`.
       * A common implement creates variable context into some map, returns it.
       * Notice that `name` can be `NULL` if no name avaialable.  */
      MyLoader *myl = (MyLoader*)l;
      MyContext *ctx = my_create_ctx_by_idx_and_name(&myl->vars, idx, name);
      return &ctx->value;
  }

  static void load_cons(am_Loader *l, const char *name, unsigned idx, am_Constraint *cons) {
      /* same as `var_name`, but can be NULL in `am_Loader*` means do not
       * retrieve any constraints from previous state.
       * Notice that `name` can be `NULL`.  */
      MyLoader *myl = (MyLoader*)l;
      MyContext *ctx = save_cons_and_create_context(&myl->cons, idx, name, cons);
      *(MyContext**)cons = ctx;
  }

  static const char *load_reader(am_Loader *l, size_t *plen) {
      /* Get the data to reads, if returns `NULL` or store `0` into `plen`,
       * stops reading */
      MyLoader *myl = (MyLoader*)l;
      const char *buf = myl->buf;
      *plen = myl->len;
      if (buf) {
          my->buf = NULL;
          my->len = 0;
      }
      return buf;
  }

  /* do actually load */
  MyLoader l;
  l.base.load_var  = load_var;
  l.base.load_cons = load_cons; /* optional */
  l.base.reader    = load_reader;
  d.buf = get_some_data(&d.len);

  am_Solver *solver = am_newsolver(NULL, NULL);
  int ret = am_load(solver, &d.base);
  assert(ret == AM_OK);
  /* now uses the solver */
  ```

### Routines

- `am_Solver *am_newsolver(am_Allocf *allocf, void *ud);`

  Create a new solver with custom memory alloculator, pass NULL for use default ones.

- `void am_resetsolver(am_Solver *solver);`

  Remove all constraints from solver, all variables and constraints created are keeped.

- `void am_delsolver(am_Solver *solver);`

  Delete a solver and frees all memory it used, after that, all variables/constraints created from this solver are all freed.

- `void am_updatevars(am_Solver *solver);`

  Refresh variables' value into it's constrainted value.
  You could use `am_autoupdate()` to update all variables automatically when solver changes.

- `void am_autoupdate(am_Solver *solver, int auto_update);`

  Set the auto update flags.
  If setted, all variables will be updated to its latest values right after any changes done to the `solver`.

- `int am_dump(am_Solver *solver, am_Dumper *d);`

  Dump whole solver state (without edits) into binary data.

- `int am_load(am_Solver *solver, am_Loader *l);`

  Loads binary data back into solver. Note that all previous states are
  discarded, but previous vars & constraints are keeped. i.e. the
  `am_resetsolver()` called before loaded.

- `am_Id am_newvariable(am_Solver *solver, am_Num *pvalue);`

  Create a new variable with a value `pvalue` binding to it. Returns `0` on
  error.

  Notice that the pvalue pointer can be retrived in `am_dump()` or
  `am_varvalue()`, so you could put context data here:

  ```c
  typedef struct MyVarContext {
      am_Num value;
      const char *name;
      /* etc.. */
  } MyVarContext;
  MyVarContext *ctx = new_context();
  am_Id var = am_newvariable(solver, &ctx->value);
  /* retrive the context: */
  MyVarContext *ctx = (MyVarContext*)am_varvalue(var, NULL);
  ```

- `void am_delvariable(am_Solver *solver, am_Id var);`

  Remove this variable, after that, it cannot be added into further constraints.

- `void am_refcount(am_Solver *solver, am_Id var);`

  Retrieve the refcount of the variable.

- `void am_varvalue(am_Solver *solver, am_Id var, am_Num *newvalue);`

  Retrieve or reset the value binding of the variable. if `newvalue` is `NULL`,
  Only retrieve the old value, otherwise the `newvalue` will be setted into
  `var` and original value returned.

- `int am_add(am_Constraint *cons);`

  Add constraint into the solver it's created from.

- `int am_hasconstraint(am_Constraint *cons);`

  Check whether a constraint has been added into the solver or not.

- `void am_remove(am_Constraint *cons);`

  Remove added constraint.

- `int am_clearedits(am_Solver *solver);`

  Remove all variable suggests from `solver`.

- `int am_hasedit(am_Solver *solver, am_Id var);`

  Check whether a variable has suggested value in the `solver`.

- `int am_addedit(am_Solver *solver, am_Id var, am_Num strength);`

  Prepare to change the value of variables or the `strength` value if the variable is in edit now.

- `void am_suggest(am_Solver *solver, am_Id var, am_Num value);`

  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 `am_suggest()` directly without `am_addedit()`.

- `void am_deledit(am_Solver *solver, am_Id var);`

  Cancel the modify of variable.
  The value binding by `var` will restore to the referred value according the solver.

- `am_Constraint *am_newconstraint(am_Solver *solver, am_Num strength);`

  Create a new constraints. Notice that where are one pointer's space can be
  used to store context data in the head of `am_Constraint`, example:

  ```c
  am_Constraint *cons = am_newconstraint(solver, AM_REQUIRED);
  MyContext *ctx = new_my_context();
  ctx->mydata = 1;
  *(MyContext**)cons = ctx;
  ```

- `am_Constraint *am_cloneconstraint(am_Constraint *other, am_Num strength);`

  Clone a new constraint from the existing one with new `strength`.

- `void am_resetconstraint(am_Constraint *cons);`

  Remove all terms and variables from the constraint, restore it to blank state.

- `void am_delconstraint(am_Constraint *cons);`

  Free the constraint. if it's added into solver, remove it first.

- `int am_addterm(am_Constraint *cons, am_Id var, am_Num multiplier);`

  Add a term into constraint.

  Example: a constraint like `2*m == x+y`, has terms `2*m`, `1*x` and `1*y`.
  So to makeup this constraint, you could:

  ```c
  // 2*m == x + y
  am_addterm(c, m, 2.0); // 2*m
  am_setrelation(c, AM_EQUAL); // =
  am_addterm(c, x, 1.0); // x
  am_addterm(c, y, 1.0); // y
  ```

- `int am_setrelation(am_Constraint *cons, int relation);`

  Set the relations of constraint, could be one of these:

  - `AM_EQUAL`
  - `AM_GREATEQUAL`
  - `AM_LESSEQUAL`

  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.

- `int am_addconstant(am_Constraint *cons, am_Num constant);`

  Add a constant without variable into constraint as a term.

- `int am_setstrength(am_Constraint *cons, am_Num strength);`

  Set the strength of a constraint.

- `int am_mergeconstraint(am_Constraint *cons, const am_Constraint *other, am_Num multiplier);`

  Merge other constraints into `cons`, with a multiplier multiples with `other`.



================================================
FILE: amoeba.h
================================================
#ifndef amoeba_h
#define amoeba_h

#ifndef AM_NS_BEGIN
# ifdef __cplusplus
#   define AM_NS_BEGIN extern "C" {
#   define AM_NS_END   }
# else
#   define AM_NS_BEGIN
#   define AM_NS_END
# endif
#endif /* AM_NS_BEGIN */

#ifndef AM_STATIC
# ifdef __GNUC__
#   define AM_STATIC static __attribute((unused))
# else
#   define AM_STATIC static
# endif
#endif

#ifdef AM_STATIC_API
# ifndef AM_IMPLEMENTATION
#  define AM_IMPLEMENTATION
# endif
# define AM_API AM_STATIC
#endif

#if !defined(AM_API) && defined(_WIN32)
# ifdef AM_IMPLEMENTATION
#  define AM_API __declspec(dllexport)
# else
#  define AM_API __declspec(dllimport)
# endif
#endif

#ifndef AM_API
# define AM_API extern
#endif

#define AM_OK           (0)
#define AM_FAILED       (-1)
#define AM_UNSATISFIED  (-2)
#define AM_UNBOUND      (-3)

#define AM_LESSEQUAL    (1)
#define AM_EQUAL        (2)
#define AM_GREATEQUAL   (3)

#define AM_REQUIRED     ((am_Num)1 / 0.0) /* INFINITY */
#define AM_STRONG       ((am_Num)1000000)
#define AM_MEDIUM       ((am_Num)1000)
#define AM_WEAK         ((am_Num)1)

#include <stddef.h>

AM_NS_BEGIN


#ifdef AM_NUM
typedef AM_NUM am_Num;
#elif defined(AM_USE_DOUBLE)
typedef double am_Num;
#else
typedef float  am_Num;
#endif

typedef struct am_Solver     am_Solver;
typedef struct am_Constraint am_Constraint;

typedef unsigned am_Id;

typedef enum am_AllocType {
    am_AllocSolver = 1,
    am_AllocConstraint,
    am_AllocSuggest,
    am_AllocHash,
    am_AllocDump
} am_AllocType;

typedef void *am_Allocf (void **pud, void *ptr,
        size_t nsize, size_t osize, am_AllocType ty);

AM_API am_Solver *am_newsolver   (am_Allocf *allocf, void *ud);
AM_API void       am_resetsolver (am_Solver *S);
AM_API void       am_delsolver   (am_Solver *S);

AM_API void am_updatevars (am_Solver *S);
AM_API void am_autoupdate (am_Solver *S, int auto_update);

AM_API int  am_add    (am_Constraint *cons);
AM_API void am_remove (am_Constraint *cons);

AM_API void am_clearedits (am_Solver *S);
AM_API int  am_hasedit (am_Solver *S, am_Id var);
AM_API int  am_addedit (am_Solver *S, am_Id var, am_Num strength);
AM_API void am_suggest (am_Solver *S, am_Id var, am_Num value);
AM_API void am_deledit (am_Solver *S, am_Id var);

AM_API am_Id am_newvariable (am_Solver *S, am_Num *pvalue);
AM_API void  am_delvariable (am_Solver *S, am_Id var);

AM_API am_Num *am_varvalue (am_Solver *S, am_Id var, am_Num *newvalue);

AM_API int am_refcount      (am_Solver *S, am_Id var);
AM_API int am_hasconstraint (am_Constraint *cons);

AM_API am_Constraint *am_newconstraint   (am_Solver *S, am_Num strength);
AM_API am_Constraint *am_cloneconstraint (am_Constraint *other, am_Num strength);

AM_API void am_resetconstraint (am_Constraint *cons);
AM_API void am_delconstraint   (am_Constraint *cons);

AM_API int am_addterm     (am_Constraint *cons, am_Id var, am_Num multiplier);
AM_API int am_setrelation (am_Constraint *cons, int relation);
AM_API int am_addconstant (am_Constraint *cons, am_Num constant);
AM_API int am_setstrength (am_Constraint *cons, am_Num strength);

AM_API int am_mergeconstraint (am_Constraint *cons, const am_Constraint *other, am_Num multiplier);

/* dump & load */

typedef struct am_Dumper am_Dumper;
typedef struct am_Loader am_Loader;

AM_API int am_dump (am_Solver *S, am_Dumper *dumper);
AM_API int am_load (am_Solver *S, am_Loader *loader);

struct am_Dumper {
    const char *(*var_name)  (am_Dumper *D, unsigned idx, am_Id var, am_Num *value);
    const char *(*cons_name) (am_Dumper *D, unsigned idx, am_Constraint *cons);

    int (*writer) (am_Dumper *D, const void *buf, size_t len);
};

struct am_Loader {
    am_Num *(*load_var)  (am_Loader *L, const char *name, unsigned idx, am_Id var);
    void    (*load_cons) (am_Loader *L, const char *name, unsigned idx, am_Constraint *cons);

    const char *(*reader) (am_Loader *L, size_t *plen);
};


AM_NS_END

#endif /* amoeba_h */

#if defined(AM_IMPLEMENTATION) && !defined(am_implemented)
#define am_implemented

#include <assert.h>
#include <float.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define AM_EXTERNAL     (0)
#define AM_SLACK        (1)
#define AM_ERROR        (2)
#define AM_DUMMY        (3)

#define am_isexternal(key)   ((key).type == AM_EXTERNAL)
#define am_isslack(key)      ((key).type == AM_SLACK)
#define am_iserror(key)      ((key).type == AM_ERROR)
#define am_isdummy(key)      ((key).type == AM_DUMMY)
#define am_ispivotable(key)  (am_isslack(key) || am_iserror(key))

#define am_alloc(T,S,ty) ((T*)(S)->allocf((void**)(S),NULL,sizeof(T),0,(ty)))
#define am_free(S,p,ty)  ((S)->allocf((void**)(S),(p),0,sizeof(*(p)),(ty)))

#define AM_MIN_HASHSIZE  8
#define AM_POOL_SIZE     4096
#define AM_UNSIGNED_BITS (sizeof(unsigned)*CHAR_BIT)

#ifdef AM_USE_DOUBLE
# define AM_NUM_MAX DBL_MAX
# define AM_NUM_EPS 1e-6
#else
# define AM_NUM_MAX FLT_MAX
# define AM_NUM_EPS 1e-4
#endif

#ifndef AM_DEBUG
# ifdef __GNUC__
#   pragma GCC diagnostic ignored "-Wvariadic-macros"
# endif
# define AM_DEBUG(...) ((void)0)
#endif /* AM_DEBUG */

#define amC(cond) do { if (!(cond)) return \
    AM_DEBUG(__FILE__ ":%d: " #cond "\n", __LINE__), AM_FAILED; } while (0)
#define amE(cond) amC((cond) == AM_OK)

AM_NS_BEGIN


typedef unsigned am_Size;

typedef struct am_Symbol {
    unsigned id   : AM_UNSIGNED_BITS - 2;
    unsigned type : 2;
} am_Symbol;

typedef struct am_MemPool {
    size_t size;
    void  *freed;
    void  *pages;
} am_MemPool;

typedef struct am_Key {
    int       next;
    am_Symbol sym;
} am_Key;

typedef struct am_Arena {
    size_t size;
    void  *buf;
} am_Arena;

typedef struct am_Table {
    am_Size size;
    am_Size count;
    am_Size value_size : AM_UNSIGNED_BITS - 1;
    am_Size arena      : 1;
    am_Size last_free;
    am_Key *keys;
    void   *vals;
} am_Table;

typedef struct am_Iterator {
    const am_Table *t;
    am_Symbol key;
    am_Size   offset;
} am_Iterator;

typedef struct am_Row {
    am_Symbol infeasible_next;
    am_Num    constant;
    am_Table  terms;
} am_Row;

typedef struct am_Var {
    am_Symbol next;
    unsigned  refcount;
    am_Num   *pvalue;
} am_Var;

struct am_Constraint {
    void      *ud;
    am_Row     expression;
    am_Symbol  sym; /* AM_EXTERNAL for pool cons, AM_DUMMY for suggest */
    am_Symbol  marker;
    am_Symbol  other;
    unsigned   marker_id;
    unsigned   other_id : AM_UNSIGNED_BITS - 2;
    unsigned   relation : 2;
    am_Num     strength;
    am_Solver *S;
};

typedef struct am_Suggest {
    am_Size       age;
    am_Num        edit_value;
    am_Constraint constraint;
    am_Table      dirtyset;
} am_Suggest;

struct am_Solver {
    void      *ud; /* must be the first for `am_default_allocf` */
    am_Allocf *allocf;
    am_Row     objective;
    am_Table   vars;            /* symbol -> am_Var */
    am_Table   constraints;     /* symbol -> am_Constraint* */
    am_Table   suggests;        /* symbol -> am_Suggest* */
    am_Table   rows;            /* symbol -> am_Row */
    am_Arena   arena;
    am_Size    auto_update;
    am_Size    age; /* counts of rows changed */
    am_Size    current_id;
    am_Size    current_cons;
    am_Symbol  infeasible_rows;
    am_Symbol  dirty_vars;
    am_MemPool conspool;
};

/* utils */

static int am_approx(am_Num a, am_Num b)
{ return a > b ? a - b < AM_NUM_EPS : b - a < AM_NUM_EPS; }

static int am_nearzero(am_Num a)
{ return am_approx(a, 0.0f); }

static am_Symbol am_null(void)
{ am_Symbol null = { 0, 0 }; return null; }

static void am_poolfree(am_MemPool *pool, void *obj)
{ *(void**)obj = pool->freed, pool->freed = obj; }

static void am_freearena(am_Solver *S, am_Arena *a) {
    if (!a->buf) return;
    S->allocf(&S->ud, a->buf, 0, a->size, am_AllocDump);
    a->buf = NULL, a->size = 0;
}

static void am_allocarena(am_Solver *S, am_Arena *a, size_t size) {
    assert(a->buf == NULL);
    a->buf = S->allocf(&S->ud, NULL, size, 0, am_AllocDump);
    if (a->buf) a->size = size;
}

static void am_initpool(am_MemPool *pool, size_t size) {
    pool->size  = size;
    pool->freed = pool->pages = NULL;
    assert(size > sizeof(void*) && size < AM_POOL_SIZE/4);
}

static void am_freepool(am_MemPool *pool) {
    const size_t offset = AM_POOL_SIZE - sizeof(void*);
    while (pool->pages != NULL) {
        void *next = *(void**)((char*)pool->pages + offset);
        free(pool->pages);
        pool->pages = next;
    }
}

static void *am_poolalloc(am_MemPool *pool) {
    void *obj = pool->freed;
    if (obj == NULL) {
        const size_t offset = AM_POOL_SIZE - sizeof(void*);
        void *end, *ptr = malloc(AM_POOL_SIZE);
        if (ptr == NULL) abort();
        *(void**)((char*)ptr + offset) = pool->pages;
        pool->pages = ptr;
        end = (char*)ptr + (offset/pool->size-1)*pool->size;
        while (end != ptr) {
            *(void**)end = pool->freed;
            pool->freed = (void**)end;
            end = (char*)end - pool->size;
        }
        return end;
    }
    pool->freed = *(void**)obj;
    return obj;
}

static void *am_default_allocf(void **pud, void *ptr, size_t nsize, size_t osize, am_AllocType ty) {
    void *newptr;
    (void)osize;
    if (ty == am_AllocConstraint) {
        am_Solver *S = (am_Solver*)pud;
        if (nsize) return am_poolalloc(&S->conspool);
        return am_poolfree(&S->conspool, ptr), (void*)NULL;
    }
    if (nsize == 0) return free(ptr), (void*)NULL;
    if ((newptr = realloc(ptr, nsize)) == NULL) abort();
    return newptr;
}

static am_Symbol am_newsymbol(am_Solver *S, int type) {
    am_Symbol sym;
    unsigned id = ++S->current_id;
    if (id > 0x3FFFFFFF) abort(); /* id runs out */
    assert(type >= AM_EXTERNAL && type <= AM_DUMMY);
    sym.id   = id;
    sym.type = type;
    return sym;
}

/* hash table */

#define amH_val(t,i)  ((char*)(t)->vals+(i)*(t)->value_size)
#define am_val(T,it)  ((T*)amH_val((it).t,(it).offset-1))

static am_Iterator am_itertable(const am_Table *t)
{ am_Iterator it; it.t = t, it.key = am_null(), it.offset = 0; return it; }

static void am_inittable(am_Table *t, size_t value_size)
{ memset(t, 0, sizeof(*t)), t->value_size = (am_Size)value_size; }

static void am_resettable(am_Table *t)
{ t->last_free = t->count = 0; memset(t->keys, 0, t->size * sizeof(am_Key)); }

static am_Size am_calcsize(am_Size request)
{ am_Size s = AM_MIN_HASHSIZE; while (s < request) s <<= 1; return s; }

static int am_nextentry(am_Iterator *it) {
    am_Size cur = it->offset, size = it->t->size;
    am_Symbol key;
    while (cur < size && (key = it->t->keys[cur].sym).id == 0) cur++;
    if (cur == size) return it->key = am_null(), it->offset = 0;
    return it->key = key, it->offset = cur + 1;
}

static void am_freetable(const am_Solver *S, am_Table *t) {
    size_t hsize = t->size * (sizeof(am_Key) + t->value_size);
    if (hsize == 0) return;
    if (!t->arena) S->allocf((void**)S, t->keys, 0, hsize, am_AllocHash);
    am_inittable(t, t->value_size);
}

static const void *am_gettable(const am_Table *t, unsigned key) {
    am_Size cur;
    if (t->size == 0) return NULL;
    cur = key & (t->size - 1);
    for (; t->keys[cur].sym.id != key; cur += t->keys[cur].next)
        if (t->keys[cur].next == 0) return NULL;
    return amH_val(t, cur);
}

static int amH_alloc(am_Table *t, const am_Solver *S, size_t request, size_t vsize, void **arena) {
    size_t size, hsize;
    am_Key *keys;
    amC(request <= (~(am_Size)0 >> 1));
    size = am_calcsize((am_Size)request);
    hsize = size * (sizeof(am_Key) + vsize);
    if (arena) keys = (am_Key*)*arena, *arena = (char*)*arena + hsize;
    else keys = (am_Key*)S->allocf((void**)S, NULL, hsize, 0, am_AllocHash);
    amC(keys != NULL);
    t->size = (am_Size)size;
    t->value_size = vsize;
    t->arena = (arena != NULL);
    t->keys = keys;
    t->vals = keys + t->size;
    return am_resettable(t), AM_OK;
}

static void *amH_rawset(am_Table *t, am_Symbol key) {
    int mp = key.id & (t->size - 1);
    am_Key *ks = t->keys;
    if (ks[mp].sym.id) {
        int f = t->last_free, othern;
        while (f < (int)t->size && (ks[f].sym.id || ks[f].next))
            f += 1;
        if (f == (int)t->size) return NULL;
        t->last_free = f + 1;
        if ((othern = ks[mp].sym.id & (t->size - 1)) == mp) {
            if (ks[mp].next) ks[f].next = (mp + ks[mp].next) - f;
            ks[mp].next = (f - mp), mp = f;
        } else {
            assert(ks[othern].next != 0);
            while (othern + ks[othern].next != mp)
                othern += ks[othern].next;
            ks[othern].next = (f - othern);
            ks[f] = ks[mp], memcpy(amH_val(t,f), amH_val(t,mp), t->value_size);
            if (ks[mp].next) ks[f].next += (mp - f), ks[mp].next = 0;
        }
    }
    return ks[mp].sym = key, amH_val(t, mp);
}

static int amH_resize(const am_Solver *S, am_Table *t, size_t request, void **arena) {
    am_Table nt;
    am_Size i;
    amE(amH_alloc(&nt, S, request, t->value_size, arena));
    for (i = 0; i < t->size; ++i) {
        void *value;
        if (!t->keys[i].sym.id) continue;
        value = amH_rawset(&nt, t->keys[i].sym);
        assert(value != NULL), memcpy(value, amH_val(t, i), t->value_size);
    }
    nt.count = t->count;
    if (!t->arena) am_freetable(S, t);
    return (*t = nt), AM_OK;
}

static int am_growtable(const am_Solver *S, am_Table *t, size_t grow, void **a)
{ return (grow += t->count)*2 < t->size ? AM_OK : amH_resize(S, t, grow, a); }

static void *am_settable(const am_Solver *S, am_Table *t, am_Symbol key) {
    void *value;
    assert(key.id != 0 && am_gettable(t, key.id) == NULL);
    if (!t->size && amH_resize(S, t, 0, NULL) != AM_OK) return NULL;
    if ((value = amH_rawset(t, key)) == NULL) {
        if (amH_resize(S, t, t->count*2, NULL) != AM_OK) return NULL;
        value = amH_rawset(t, key); /* retry and must success */
    }
    return t->count += 1, assert(value != NULL), value;
}

static void am_deltable(am_Table *t, const void *value) {
    size_t offset = ((char*)value - (char*)t->vals)/t->value_size;
    t->count -= 1, t->keys[offset].sym = am_null();
}

/* expression (row) */

static void am_freerow(am_Solver *S, am_Row *row)
{ am_freetable(S, &row->terms); }

static void am_resetrow(am_Row *row)
{ row->constant = 0.0f; am_resettable(&row->terms); }

static void am_initrow(am_Row *row) {
    row->infeasible_next = am_null();
    row->constant = 0.0f;
    am_inittable(&row->terms, sizeof(am_Num));
}

static void am_multiply(am_Row *row, am_Num multiplier) {
    am_Iterator it = am_itertable(&row->terms);
    row->constant *= multiplier;
    while (am_nextentry(&it))
        *am_val(am_Num,it) *= multiplier;
}

static void am_addvar(am_Solver *S, am_Row *row, am_Symbol sym, am_Num value) {
    am_Num *term;
    if (sym.id == 0 || am_nearzero(value)) return;
    term = (am_Num*)am_gettable(&row->terms, sym.id);
    if (term == NULL) {
        term = (am_Num*)am_settable(S, &row->terms, sym);
        assert(term != NULL);
        *term = value;
    } else if (am_nearzero(*term += value))
        am_deltable(&row->terms, term);
}

static void am_addrow(am_Solver *S, am_Row *row, const am_Row *other, am_Num multiplier) {
    am_Iterator it = am_itertable(&other->terms);
    row->constant += other->constant*multiplier;
    while (am_nextentry(&it))
        am_addvar(S, row, it.key, *am_val(am_Num,it)*multiplier);
}

/* variables & constraints */

AM_API int am_hasconstraint(am_Constraint *cons)
{ return cons != NULL && cons->marker.id != 0; }

AM_API void am_autoupdate(am_Solver *S, int auto_update)
{ S->auto_update = auto_update; }

static am_Var *am_id2var(am_Solver *S, am_Id var)
{ return S ? (am_Var*)am_gettable(&S->vars, var) : NULL; }

AM_API am_Id am_newvariable(am_Solver *S, am_Num *pvalue) {
    am_Symbol sym;
    am_Var *ve;
    if (S == NULL || pvalue == NULL) return 0;
    sym = am_newsymbol(S, AM_EXTERNAL);
    ve = (am_Var*)am_settable(S, &S->vars, sym);
    if (ve == NULL) return 0;
    memset(ve, 0, sizeof(*ve));
    ve->pvalue   = pvalue;
    ve->refcount = 1;
    return sym.id;
}

AM_API void am_delvariable(am_Solver *S, am_Id var) {
    am_Var *ve = am_id2var(S, var);
    if (ve && --ve->refcount == 0) {
        assert(!am_isdummy(ve->next));
        am_deledit(S, var);
        am_deltable(&S->vars, ve);
    }
}

AM_API int am_refcount(am_Solver *S, am_Id var) {
    am_Var *ve = am_id2var(S, var);
    return ve ? ve->refcount : 0;
}

AM_API am_Num *am_varvalue(am_Solver *S, am_Id var, am_Num *newvalue) {
    am_Var *ve = am_id2var(S, var);
    am_Num *pvalue = ve ? ve->pvalue : NULL;
    if (ve && newvalue) ve->pvalue = newvalue;
    return pvalue;
}

AM_API void am_updatevars(am_Solver *S) {
    while (S->dirty_vars.id != 0) {
        am_Symbol var = S->dirty_vars;
        am_Var *ve = (am_Var*)am_gettable(&S->vars, var.id);
        assert(ve != NULL);
        S->dirty_vars = ve->next;
        ve->next = am_null();
        if (ve->refcount == 1) {
            am_deledit(S, var.id);
            am_deltable(&S->vars, ve);
        } else {
            am_Row *row = (am_Row*)am_gettable(&S->rows, var.id);
            *ve->pvalue = row ? row->constant : 0.0f;
            ve->refcount -= 1;
        }
    }
}

static int am_initconstraint(am_Solver *S, am_Symbol sym, am_Num strength, am_Constraint *cons) {
    am_Constraint **ce = (am_Constraint**)am_settable(S, &S->constraints, sym);
    amC(ce != NULL);
    *ce = cons;
    memset(cons, 0, sizeof(*cons));
    cons->S        = S;
    cons->sym      = sym;
    cons->strength = am_nearzero(strength) ? AM_REQUIRED : strength;
    am_initrow(&(*ce)->expression);
    return AM_OK;
}

AM_API am_Constraint *am_newconstraint(am_Solver *S, am_Num strength) {
    am_Symbol sym = {0, AM_EXTERNAL}; /* AM_EXTERNAL for pool cons */
    am_Constraint *cons;
    int ret;
    if (S == NULL || strength < 0.f) return NULL;
    cons = am_alloc(am_Constraint, S, am_AllocConstraint);
    if (cons == NULL) return NULL;
    sym.id = ++S->current_cons;
    ret = am_initconstraint(S, sym, strength, cons);
    if (ret != AM_OK) am_free(S, cons, am_AllocConstraint), cons = NULL;
    return cons;
}

AM_API void am_delconstraint(am_Constraint *cons) {
    am_Solver *S = cons ? cons->S : NULL;
    am_Iterator it;
    am_Constraint **ce;
    if (S == NULL) return;
    am_remove(cons);
    ce = (am_Constraint**)am_gettable(&S->constraints, cons->sym.id);
    assert(ce != NULL && *ce == cons);
    am_deltable(&S->constraints, ce);
    it = am_itertable(&cons->expression.terms);
    while (am_nextentry(&it))
        am_delvariable(cons->S, it.key.id);
    am_freerow(S, &cons->expression);
    if (am_isexternal(cons->sym)) am_free(S, cons, am_AllocConstraint);
}

AM_API am_Constraint *am_cloneconstraint(am_Constraint *other, am_Num strength) {
    am_Constraint *cons;
    if (other == NULL) return NULL;
    cons = am_newconstraint(other->S,
            am_nearzero(strength) ? other->strength : strength);
    am_mergeconstraint(cons, other, 1.0f);
    cons->relation = other->relation;
    return cons;
}

AM_API int am_mergeconstraint(am_Constraint *cons, const am_Constraint *other, am_Num multiplier) {
    am_Iterator it;
    amC(cons && other && cons->marker.id == 0 && cons->S == other->S);
    if (cons->relation == AM_GREATEQUAL) multiplier = -multiplier;
    cons->expression.constant += other->expression.constant*multiplier;
    it = am_itertable(&other->expression.terms);
    while (am_nextentry(&it)) {
        am_Var *ve = (am_Var*)am_gettable(&cons->S->vars, it.key.id);
        assert(ve != NULL);
        ve->refcount += 1;
        am_addvar(cons->S, &cons->expression, it.key,
                *am_val(am_Num,it)*multiplier);
    }
    return AM_OK;
}

AM_API void am_resetconstraint(am_Constraint *cons) {
    am_Iterator it;
    if (cons == NULL) return;
    am_remove(cons);
    cons->relation = 0;
    it = am_itertable(&cons->expression.terms);
    while (am_nextentry(&it))
        am_delvariable(cons->S, it.key.id);
    am_resetrow(&cons->expression);
}

AM_API int am_addterm(am_Constraint *cons, am_Id var, am_Num multiplier) {
    am_Symbol sym = {0, AM_EXTERNAL};
    am_Var *ve;
    amC(cons && var && cons->marker.id == 0);
    if (cons->relation == AM_GREATEQUAL) multiplier = -multiplier;
    amC(ve = (am_Var*)am_gettable(&cons->S->vars, var));
    am_addvar(cons->S, &cons->expression, (sym.id=var, sym), multiplier);
    return ve->refcount += 1, AM_OK;
}

AM_API int am_addconstant(am_Constraint *cons, am_Num constant) {
    amC(cons && cons->marker.id == 0);
    cons->expression.constant +=
        cons->relation == AM_GREATEQUAL ? -constant : constant;
    return AM_OK;
}

AM_API int am_setrelation(am_Constraint *cons, int relation) {
    assert(relation >= AM_LESSEQUAL && relation <= AM_GREATEQUAL);
    amC(cons && cons->marker.id == 0 && cons->relation == 0);
    if (relation != AM_GREATEQUAL) am_multiply(&cons->expression, -1.0f);
    cons->relation = relation;
    return AM_OK;
}

AM_API int am_hasedit(am_Solver *S, am_Id var) {
    if (S == NULL) return 0;
    return am_gettable(&S->suggests, var) != NULL;
}

static am_Suggest *am_newedit(am_Solver *S, am_Id var, am_Num strength) {
    am_Symbol sym = {0, AM_EXTERNAL};
    am_Suggest **s;
    am_Var *ve;
    int ret;
    if (S == NULL || var == 0 || strength < 0.f) return NULL;
    if ((ve = (am_Var*)am_gettable(&S->vars, var)) == NULL) return NULL;
    s = (am_Suggest**)am_settable(S, &S->suggests, (sym.id=var, sym));
    if (s == NULL) return NULL;
    *s = am_alloc(am_Suggest, S, am_AllocSuggest);
    if (*s == NULL) return am_deltable(&S->suggests, s), (am_Suggest*)NULL;
    memset(*s, 0, sizeof(**s));
    sym.id = ++S->current_cons, sym.type = AM_DUMMY; /* local cons */
    am_initconstraint(S, sym, strength, &(*s)->constraint);
    am_setrelation(&(*s)->constraint, AM_EQUAL);
    am_addterm(&(*s)->constraint, var, 1.0f); /* var must have positive signture */
    am_addconstant(&(*s)->constraint, -*ve->pvalue);
    ret = am_add(&(*s)->constraint);
    assert(ret == AM_OK), (void)ret;
    (*s)->edit_value = *ve->pvalue;
    return *s;
}

AM_API int am_addedit(am_Solver *S, am_Id var, am_Num strength) {
    am_Suggest **s;
    amC(S && var);
    if (strength >= AM_STRONG) strength = AM_STRONG;
    s = (am_Suggest**)am_gettable(&S->suggests, var);
    if (s) return assert(*s), am_setstrength(&(*s)->constraint, strength);
    amC(am_newedit(S, var, strength));
    return AM_OK;
}

AM_API void am_deledit(am_Solver *S, am_Id var) {
    am_Suggest **ps, *s;
    if (S == NULL || var == 0) return;
    ps = (am_Suggest**)am_gettable(&S->suggests, var);
    if (ps == NULL) return;
    s = *ps, am_deltable(&S->suggests, ps);
    am_freetable(S, &s->dirtyset);
    am_delconstraint(&(s->constraint));
    am_free(S, s, am_AllocSuggest);
}

AM_API void am_clearedits(am_Solver *S) {
    am_Iterator it;
    if (S == NULL) return;
    it = am_itertable(&S->suggests);
    if (!S->auto_update) am_updatevars(S);
    while (am_nextentry(&it)) {
        am_Suggest *s = *am_val(am_Suggest*,it);
        am_freetable(S, &s->dirtyset);
        am_delconstraint(&s->constraint);
        am_free(S, s, am_AllocSuggest);
    }
    am_resettable(&S->suggests);
}

/* Cassowary algorithm */

static int am_takerow(am_Solver *S, am_Symbol sym, am_Row *dst) {
    am_Row *row = (am_Row*)am_gettable(&S->rows, sym.id);
    if (row == NULL) return AM_FAILED;
    am_deltable(&S->rows, row);
    dst->constant = row->constant;
    dst->terms    = row->terms;
    return AM_OK;
}

static void am_solvefor(am_Solver *S, am_Row *row, am_Symbol enter, am_Symbol leave) {
    am_Num *term = (am_Num*)am_gettable(&row->terms, enter.id);
    am_Num reciprocal = 1.0f / *term;
    assert(enter.id != leave.id && !am_nearzero(*term));
    am_deltable(&row->terms, term);
    if (!am_approx(reciprocal, -1.0f)) am_multiply(row, -reciprocal);
    if (leave.id != 0) am_addvar(S, row, leave, reciprocal);
}

static void am_eliminate(am_Solver *S, am_Row *dst, am_Symbol out, const am_Row *row) {
    am_Num *term = (am_Num*)am_gettable(&dst->terms, out.id);
    if (!term) return;
    am_deltable(&dst->terms, term);
    am_addrow(S, dst, row, *term);
}

static void am_markdirty(am_Solver *S, am_Symbol var) {
    am_Var *ve = (am_Var*)am_gettable(&S->vars, var.id);
    assert(ve != NULL);
    if (am_isdummy(ve->next)) return;
    ve->next.id = S->dirty_vars.id;
    ve->next.type = AM_DUMMY;
    S->dirty_vars = var;
    ve->refcount += 1;
}

static void am_infeasible(am_Solver *S, am_Symbol sym, am_Row *row) {
    if (row->constant < 0.0f && !am_isdummy(row->infeasible_next)) {
        row->infeasible_next.id = S->infeasible_rows.id;
        row->infeasible_next.type = AM_DUMMY;
        S->infeasible_rows = sym;
    }
}

static void am_substitute_rows(am_Solver *S, am_Symbol var, am_Row *expr) {
    am_Iterator it = am_itertable(&S->rows);
    while (am_nextentry(&it)) {
        am_Row *row = am_val(am_Row,it);
        am_eliminate(S, row, var, expr);
        if (am_isexternal(it.key))
            am_markdirty(S, it.key);
        else
            am_infeasible(S, it.key, row);
    }
    am_eliminate(S, &S->objective, var, expr);
}

static int am_putrow(am_Solver *S, am_Symbol sym, const am_Row *src) {
    am_Row *row;
    assert(am_gettable(&S->rows, sym.id) == NULL);
    row = (am_Row*)am_settable(S, &S->rows, sym);
    assert(row != NULL);
    row->infeasible_next = am_null();
    row->constant = src->constant;
    row->terms    = src->terms;
    return AM_OK;
}

static void am_optimize(am_Solver *S, am_Row *objective) {
    for (;;) {
        am_Symbol enter = am_null(), leave = am_null();
        am_Num r, min_ratio = AM_NUM_MAX, *term;
        am_Iterator it = am_itertable(&objective->terms);
        am_Row tmp;

        assert(S->infeasible_rows.id == 0);
        while (am_nextentry(&it))
            if (!am_isdummy(it.key) && *am_val(am_Num,it) < 0.0f)
            { enter = it.key; break; }
        if (enter.id == 0) return;

        it = am_itertable(&S->rows);
        while (am_nextentry(&it)) {
            am_Row *row = am_val(am_Row,it);
            if (am_isexternal(it.key)) continue;
            term = (am_Num*)am_gettable(&row->terms, enter.id);
            if (term == NULL || *term > 0.0f) continue;
            r = -row->constant / *term;
            if (r < min_ratio || (am_approx(r, min_ratio)
                        && it.key.id < leave.id))
                min_ratio = r, leave = it.key;
        }
        assert(leave.id != 0);
        am_takerow(S, leave, &tmp);
        am_solvefor(S, &tmp, enter, leave);
        am_substitute_rows(S, enter, &tmp);
        if (objective != &S->objective)
            am_eliminate(S, objective, enter, &tmp);
        am_putrow(S, enter, &tmp);
    }
}

static void am_mergerow(am_Solver *S, am_Row *row, am_Symbol var, am_Num multiplier) {
    am_Row *oldrow = (am_Row*)am_gettable(&S->rows, var.id);
    if (oldrow) am_addrow(S, row, oldrow, multiplier);
    else        am_addvar(S, row, var, multiplier);
}

static am_Row am_makerow(am_Solver *S, am_Constraint *cons) {
    am_Iterator it = am_itertable(&cons->expression.terms);
    am_Row row;
    am_initrow(&row);
    row.constant = cons->expression.constant;
    while (am_nextentry(&it)) {
        am_markdirty(S, it.key);
        am_mergerow(S, &row, it.key, *am_val(am_Num,it));
    }
    if (cons->relation != AM_EQUAL) {
        cons->marker.id = cons->marker_id, cons->marker.type = AM_SLACK;
        am_addvar(S, &row, cons->marker, -1.0f);
        if (cons->strength < AM_REQUIRED) {
            cons->other.id = cons->other_id, cons->other.type = AM_ERROR;
            am_addvar(S, &row, cons->other, 1.0f);
            am_addvar(S, &S->objective, cons->other, cons->strength);
        }
    } else if (cons->strength >= AM_REQUIRED) {
        cons->marker.id = cons->marker_id, cons->marker.type = AM_DUMMY;
        am_addvar(S, &row, cons->marker, 1.0f);
    } else {
        cons->marker.id = cons->marker_id, cons->marker.type = AM_ERROR;
        cons->other.id = cons->other_id, cons->other.type = AM_ERROR;
        am_addvar(S, &row, cons->marker, -1.0f);
        am_addvar(S, &row, cons->other,   1.0f);
        am_addvar(S, &S->objective, cons->marker, cons->strength);
        am_addvar(S, &S->objective, cons->other,  cons->strength);
    }
    if (row.constant < 0.0f) am_multiply(&row, -1.0f);
    return row;
}

static int am_add_with_artificial(am_Solver *S, am_Row *row, am_Constraint *cons) {
    am_Symbol a = am_newsymbol(S, AM_SLACK);
    am_Iterator it;
    am_Row tmp;
    am_Num *term;
    int ret;
    --S->current_id; /* artificial variable will be removed */
    am_initrow(&tmp);
    am_addrow(S, &tmp, row, 1.0f);
    am_putrow(S, a, row);
    am_optimize(S, &tmp);
    ret = am_nearzero(tmp.constant) ? AM_OK : AM_UNBOUND;
    am_freerow(S, &tmp);
    if (am_takerow(S, a, &tmp) == AM_OK) {
        am_Symbol enter = am_null();
        if (tmp.terms.count == 0) return am_freerow(S, &tmp), ret;
        it = am_itertable(&tmp.terms);
        while (am_nextentry(&it))
            if (am_ispivotable(it.key)) { enter = it.key; break; }
        if (enter.id == 0) return am_freerow(S, &tmp), AM_UNBOUND;
        am_solvefor(S, &tmp, enter, a);
        am_substitute_rows(S, enter, &tmp);
        am_putrow(S, enter, &tmp);
    }
    it = am_itertable(&S->rows);
    while (am_nextentry(&it)) {
        am_Row *row = am_val(am_Row,it);
        am_Num *term = (am_Num*)am_gettable(&row->terms, a.id);
        if (term) am_deltable(&row->terms, term);
    }
    term = (am_Num*)am_gettable(&S->objective.terms, a.id);
    if (term) am_deltable(&S->objective.terms, term);
    if (ret != AM_OK) am_remove(cons);
    return ret;
}

static int am_try_addrow(am_Solver *S, am_Row *row, am_Constraint *cons) {
    am_Symbol subject = am_null();
    am_Iterator it = am_itertable(&row->terms);
    while (am_nextentry(&it))
        if (am_isexternal(it.key)) { subject = it.key; break; }
    if (subject.id == 0 && am_ispivotable(cons->marker)) {
        am_Num *term = (am_Num*)am_gettable(&row->terms, cons->marker.id);
        if (*term < 0.0f) subject = cons->marker;
    }
    if (subject.id == 0 && am_ispivotable(cons->other)) {
        am_Num *term = (am_Num*)am_gettable(&row->terms, cons->other.id);
        if (*term < 0.0f) subject = cons->other;
    }
    if (subject.id == 0) {
        it = am_itertable(&row->terms);
        while (am_nextentry(&it))
            if (!am_isdummy(it.key)) break;
        if (it.offset == 0) {
            if (am_nearzero(row->constant))
                subject = cons->marker;
            else {
                am_freerow(S, row);
                return AM_UNSATISFIED;
            }
        }
    }
    if (subject.id == 0) return am_add_with_artificial(S, row, cons);
    am_solvefor(S, row, subject, am_null());
    am_substitute_rows(S, subject, row);
    am_putrow(S, subject, row);
    return AM_OK;
}

static void am_remove_errors(am_Solver *S, am_Constraint *cons) {
    if (am_iserror(cons->marker))
        am_mergerow(S, &S->objective, cons->marker, -cons->strength);
    if (am_iserror(cons->other))
        am_mergerow(S, &S->objective, cons->other, -cons->strength);
    if (S->objective.terms.count == 0)
        S->objective.constant = 0.0f;
    cons->marker = cons->other = am_null();
}

AM_API int am_add(am_Constraint *cons) {
    am_Solver *S = cons ? cons->S : NULL;
    am_Symbol sym;
    am_Row row;
    int ret;
    amC(S && cons->marker.id == 0);
    if (cons->marker_id == 0)
        cons->marker_id = ((sym = am_newsymbol(S, 0)), sym.id);
    if (cons->other_id == 0 && cons->strength < AM_REQUIRED)
        cons->other_id = ((sym = am_newsymbol(S, 0)), sym.id);
    row = am_makerow(S, cons);
    if ((ret = am_try_addrow(S, &row, cons)) != AM_OK)
        am_remove_errors(S, cons);
    else {
        am_optimize(S, &S->objective);
        if (S->auto_update) am_updatevars(S);
        S->age += 1;
    }
    assert(S->infeasible_rows.id == 0);
    return ret;
}

static am_Symbol am_get_leaving_row(am_Solver *S, am_Symbol marker) {
    am_Symbol first = am_null(), second = am_null(), third = am_null();
    am_Num r1 = AM_NUM_MAX, r2 = AM_NUM_MAX;
    am_Iterator it = am_itertable(&S->rows);
    while (am_nextentry(&it)) {
        am_Row *row = am_val(am_Row,it);
        am_Num *term = (am_Num*)am_gettable(&row->terms, marker.id);
        if (term == NULL) continue;
        if (am_isexternal(it.key))
            third = it.key;
        else if (*term < 0.0f) {
            am_Num r = -row->constant / *term;
            if (r < r1) r1 = r, first = it.key;
        } else {
            am_Num r = row->constant / *term;
            if (r < r2) r2 = r, second = it.key;
        }
    }
    return first.id ? first : second.id ? second : third;
}

AM_API void am_remove(am_Constraint *cons) {
    am_Solver *S;
    am_Symbol marker;
    am_Row tmp;
    if (cons == NULL || cons->marker.id == 0) return;
    S = cons->S, marker = cons->marker;
    am_remove_errors(S, cons);
    if (am_takerow(S, marker, &tmp) != AM_OK) {
        am_Symbol leave = am_get_leaving_row(S, marker);
        assert(leave.id != 0);
        am_takerow(S, leave, &tmp);
        am_solvefor(S, &tmp, marker, leave);
        am_substitute_rows(S, marker, &tmp);
    }
    am_freerow(S, &tmp);
    am_optimize(S, &S->objective);
    S->age += 1;
    if (S->auto_update) am_updatevars(S);
}

AM_API int am_setstrength(am_Constraint *cons, am_Num strength) {
    amC(cons);
    strength = am_nearzero(strength) ? AM_REQUIRED : strength;
    if (cons->strength == strength) return AM_OK;
    if (cons->strength >= AM_REQUIRED || strength >= AM_REQUIRED)
        return am_remove(cons), cons->strength = strength, am_add(cons);
    if (cons->marker.id != 0) {
        am_Solver *S = cons->S;
        am_Num diff = strength - cons->strength;
        am_mergerow(S, &S->objective, cons->marker, diff);
        am_mergerow(S, &S->objective, cons->other,  diff);
        am_optimize(S, &S->objective);
        if (S->auto_update) am_updatevars(S);
        S->age += 1;
    }
    cons->strength = strength;
    return AM_OK;
}

static void am_cached_sugggest(am_Solver *S, am_Suggest *s, am_Num delta) {
    am_Iterator it = am_itertable(&s->dirtyset);
    am_Symbol marker = s->constraint.marker;
    int pure = 1;
    while (am_nextentry(&it)) {
        am_Row *row = (am_Row*)am_gettable(&S->rows, it.key.id);
        am_Num *term;
        assert(row != NULL);
        term = (am_Num*)am_gettable(&row->terms, marker.id);
        assert(term != NULL);
        row->constant += *term * delta;
        if (am_isexternal(it.key))
            am_markdirty(S, it.key);
        else if (!am_nearzero(row->constant) && row->constant < 0.0f)
            pure = 0, am_infeasible(S, it.key, row);
    }
    if (!pure) s->age = 0;
}

static void am_delta_edit_constant(am_Solver *S, am_Suggest *s, am_Num delta) {
    am_Iterator it = am_itertable(&S->rows);
    am_Constraint *cons = &s->constraint;
    am_Row *row;
    int pure = 1;
    if ((row = (am_Row*)am_gettable(&S->rows, cons->marker.id)) != NULL)
    { row->constant -= delta, am_infeasible(S, cons->marker, row); return; }
    if ((row = (am_Row*)am_gettable(&S->rows, cons->other.id)) != NULL)
    { row->constant += delta, am_infeasible(S, cons->other, row); return; }
    if (s->age == S->age) { am_cached_sugggest(S, s, delta); return; }
    am_resettable(&s->dirtyset);
    while (am_nextentry(&it)) {
        am_Row *row = am_val(am_Row,it);
        am_Num *term = (am_Num*)am_gettable(&row->terms, cons->marker.id);
        if (term == NULL) continue;
        row->constant += *term*delta;
        am_settable(S, &s->dirtyset, it.key);
        if (am_isexternal(it.key))
            am_markdirty(S, it.key);
        else if (!am_nearzero(row->constant) && row->constant < 0.0f)
            pure = 0, am_infeasible(S, it.key, row);
    }
    if (pure) s->age = S->age;
}

static void am_dual_optimize(am_Solver *S) {
    while (S->infeasible_rows.id != 0) {
        am_Symbol enter = am_null(), leave;
        am_Num r, min_ratio = AM_NUM_MAX;
        am_Iterator it;
        am_Row tmp, *row =
            (am_Row*)am_gettable(&S->rows, S->infeasible_rows.id);
        assert(row != NULL);
        leave = S->infeasible_rows;
        S->infeasible_rows = row->infeasible_next;
        row->infeasible_next = am_null();
        if (am_nearzero(row->constant) || row->constant >= 0.0f) continue;
        it = am_itertable(&row->terms);
        while (am_nextentry(&it)) {
            am_Num term = *am_val(am_Num,it);
            am_Num *objterm;
            if (am_isdummy(it.key) || term <= 0.0f) continue;
            objterm = (am_Num*)am_gettable(&S->objective.terms, it.key.id);
            r = objterm ? *objterm / term : 0.0f;
            if (min_ratio > r) min_ratio = r, enter = it.key;
        }
        assert(enter.id != 0);
        am_takerow(S, leave, &tmp);
        am_solvefor(S, &tmp, enter, leave);
        am_substitute_rows(S, enter, &tmp);
        am_putrow(S, enter, &tmp);
    }
}

AM_API void am_suggest(am_Solver *S, am_Id var, am_Num value) {
    am_Suggest **ps, *s;
    am_Num delta;
    if (S == NULL || var == 0) return;
    ps = (am_Suggest**)am_gettable(&S->suggests, var);
    s = ps ? *ps : am_newedit(S, var, AM_MEDIUM);
    assert(s != NULL);
    delta = value - s->edit_value, s->edit_value = value;
    am_delta_edit_constant(S, s, delta);
    am_dual_optimize(S);
    if (S->auto_update) am_updatevars(S);
}

AM_API am_Solver *am_newsolver(am_Allocf *allocf, void *ud) {
    am_Solver *S;
    if (allocf == NULL) allocf = am_default_allocf;
    S = (am_Solver*)allocf(&ud, NULL, sizeof(am_Solver), 0, am_AllocSolver);
    if (S == NULL) return NULL;
    memset(S, 0, sizeof(*S));
    S->ud     = ud;
    S->allocf = allocf;
    am_initrow(&S->objective);
    am_inittable(&S->vars, sizeof(am_Var));
    am_inittable(&S->constraints, sizeof(am_Constraint*));
    am_inittable(&S->suggests, sizeof(am_Suggest*));
    am_inittable(&S->rows, sizeof(am_Row));
    am_initpool(&S->conspool, sizeof(am_Constraint));
    return S;
}

AM_API void am_delsolver(am_Solver *S) {
    am_Iterator it = am_itertable(&S->constraints);
    while (am_nextentry(&it)) {
        am_Constraint *cons = *am_val(am_Constraint*,it);
        am_freerow(S, &cons->expression);
        if (am_isexternal(cons->sym)) am_free(S, cons, am_AllocConstraint);
    }
    it = am_itertable(&S->suggests);
    while (am_nextentry(&it)) {
        am_Suggest *s = *am_val(am_Suggest*,it);
        am_freetable(S, &s->dirtyset);
        am_freetable(S, &s->constraint.expression.terms);
        am_free(S, s, am_AllocSuggest);
    }
    it = am_itertable(&S->rows);
    while (am_nextentry(&it))
        am_freerow(S, am_val(am_Row,it));
    am_freerow(S, &S->objective);
    am_freetable(S, &S->vars);
    am_freetable(S, &S->constraints);
    am_freetable(S, &S->suggests);
    am_freetable(S, &S->rows);
    am_freepool(&S->conspool);
    am_freearena(S, &S->arena);
    am_free(S, S, am_AllocSolver);
}

AM_API void am_resetsolver(am_Solver *S) {
    am_Iterator it;
    if (S == NULL) return;
    am_clearedits(S);
    it = am_itertable(&S->constraints);
    while (am_nextentry(&it)) {
        am_Constraint *cons = *am_val(am_Constraint*,it);
        cons->marker = cons->other = am_null();
    }
    it = am_itertable(&S->rows);
    while (am_nextentry(&it))
        am_freerow(S, am_val(am_Row,it));
    am_resettable(&S->rows);
    am_resetrow(&S->objective);
    am_freearena(S, &S->arena);
    assert(S->infeasible_rows.id == 0);
    S->age = 0;
}

/* dump & load */

#ifndef AM_NAME_LEN
# define AM_NAME_LEN 256
#endif /* AM_NAME_LEN */
#ifndef AM_BUF_LEN
# define AM_BUF_LEN  4096
#endif /* AM_BUF_LEN */

typedef struct am_DumpCtx {
    const am_Solver *S;
    unsigned  *syms; 
    unsigned  *cons;
    am_Table   symmap;
    am_Dumper *dumper;
    char      *p;
    int        ret;
    char       buf[AM_BUF_LEN];
} am_DumpCtx;

static int am_intcmp(const void *lhs, const void *rhs)
{ return *(const int*)lhs - *(const int*)rhs; }

static int am_writechar(am_DumpCtx *ctx, int ch) {
    if ((ctx->p - ctx->buf) >= AM_BUF_LEN) {
        amE(ctx->ret = ctx->dumper->writer(ctx->dumper, ctx->buf, AM_BUF_LEN));
        ctx->p = ctx->buf;
    }
    return (*ctx->p++ = ch & 0xFF), AM_OK;
}

static int am_writeraw(am_DumpCtx *ctx, am_Size n, int width) {
    switch (width) {
    default: return AM_FAILED;
    case 32: amE(am_writechar(ctx, n >> 24));
             amE(am_writechar(ctx, n >> 16)); /* FALLTHROUGH */
    case 16: amE(am_writechar(ctx, n >> 8));
             amE(am_writechar(ctx, n & 0xFF));
    }
    return AM_OK;
}

static int am_writeuint32(am_DumpCtx *ctx, am_Size n) {
    if (n <= 0x7F) amE(am_writechar(ctx, n));
    else if (n <= 0xFF) {
        amE(am_writechar(ctx, 0xCC));
        amE(am_writechar(ctx, n));
    } else if (n <= 0xFFFF) {
        amE(am_writechar(ctx, 0xCD));
        amE(am_writeraw(ctx, n, 16));
    } else {
        amE(am_writechar(ctx, 0xCE));
        amE(am_writeraw(ctx, n, 32));
    }
    return AM_OK;
}

static int am_writefloat(am_DumpCtx *ctx, am_Num n) {
    union { double f64; float f32; unsigned u32; } u;
    if (sizeof(am_Num) == sizeof(float))
        u.f32 = n;
    else
        u.f64 = n, u.f32 = (float)u.f64;
    amE(am_writechar(ctx, 0xCA));
    amE(am_writeraw(ctx, u.u32, 32));
    return AM_OK;
}

static int am_writestring(am_DumpCtx *ctx, const char *name) {
    am_Dumper *d = ctx->dumper;
    size_t namelen = (name ? strlen(name) : 0), buflen;
    amC(namelen < AM_NAME_LEN && namelen <= 0xFF);
    if (namelen < 32)
        amE(am_writechar(ctx, 0xA0 + (int)namelen));
    else {
        amE(am_writechar(ctx, 0xD9));
        amE(am_writechar(ctx, (int)namelen));
    }
    if ((buflen = (ctx->p - ctx->buf)) + namelen > AM_BUF_LEN) {
        ctx->p = ctx->buf;
        if (buflen) amE(ctx->ret = d->writer(d, ctx->buf, buflen));
        if (namelen > AM_BUF_LEN) return ctx->ret = d->writer(d, name, namelen);
    }
    memcpy(ctx->p, name, namelen);
    return ctx->p += namelen, AM_OK;
}

static int am_writecount(am_DumpCtx *ctx, am_Size count) {
    if (count < 16)
        amE(am_writechar(ctx, 0x90 + count));
    else if (count <= 0xFFFF) {
        amE(am_writechar(ctx, 0xDC));
        amE(am_writeraw(ctx, count, 16));
    } else {
        amE(am_writechar(ctx, 0xDD));
        amE(am_writeraw(ctx, count, 32));
    }
    return AM_OK;
}

static unsigned am_mapid(am_DumpCtx *ctx, unsigned key) {
    unsigned *id = (unsigned*)am_gettable(&ctx->symmap, key);
    return assert(id != NULL), *id;
}

static int am_writerow(am_DumpCtx *ctx, const am_Row *row) {
    am_Iterator it = am_itertable(&row->terms);
    amE(am_writecount(ctx, row->terms.count * 2 + 1));
    amE(am_writefloat(ctx, row->constant));
    while (am_nextentry(&it)) {
        amE(am_writeuint32(ctx, am_mapid(ctx, it.key.id) << 2 | it.key.type));
        amE(am_writefloat(ctx, *am_val(am_Num,it)));
    }
    return AM_OK;
}

static int am_writevars(am_DumpCtx *ctx) {
    const am_Solver *S = ctx->S;
    am_Size i;
    amE(am_writecount(ctx, S->vars.count));
    for (i = 0; i < S->vars.count; ++i) {
        am_Var *ve = (am_Var*)am_gettable(&S->vars, ctx->syms[i]);
        assert(ve != NULL);
        amE(am_writestring(ctx,
                    ctx->dumper->var_name(ctx->dumper, i, ctx->syms[i], ve->pvalue)));
    }
    return AM_OK;
}

static int am_writeconstraints(am_DumpCtx *ctx) {
    const am_Solver *S = ctx->S;
    am_Size i;
    amE(am_writecount(ctx, S->constraints.count));
    for (i = 0; i < S->constraints.count; ++i) {
        am_Constraint **ce = (am_Constraint**)am_gettable(
                &S->constraints, ctx->cons[i]);
        unsigned id;
        assert(ce != NULL);
        amE(am_writecount(ctx, 6));
        /* [name, strength, marker, other, relation, row] */
        amE(am_writestring(ctx, ctx->dumper->cons_name ?
                    ctx->dumper->cons_name(ctx->dumper, i, (*ce)) : NULL));
        amE(am_writefloat(ctx, (*ce)->strength));
        if (id = 0, (*ce)->marker.type)
            id = am_mapid(ctx, (*ce)->marker_id) << 2 | (*ce)->marker.type;
        amE(am_writeuint32(ctx, id));
        if (id = 0, (*ce)->other.type)
            id = am_mapid(ctx, (*ce)->other_id) << 2 | (*ce)->other.type;
        amE(am_writeuint32(ctx, id));
        amE(am_writeuint32(ctx, (*ce)->relation));
        amE(am_writerow(ctx, &(*ce)->expression));
    }
    return AM_OK;
}

static int am_writerows(am_DumpCtx *ctx) {
    const am_Solver *S = ctx->S;
    size_t arena = 0;
    am_Iterator it = am_itertable(&S->rows);
    while (am_nextentry(&it))
        arena += am_calcsize(am_val(am_Row,it)->terms.count);
    if (arena > ~(am_Size)0) arena = 0;
    amE(am_writecount(ctx, S->rows.count*2 + 1));
    amE(am_writeuint32(ctx, (am_Size)arena));
    while (am_nextentry(&it)) {
        amE(am_writeuint32(ctx, am_mapid(ctx, it.key.id) << 2 | it.key.type));
        amE(am_writerow(ctx, am_val(am_Row,it)));
    }
    return AM_OK;
}

static int am_collect(am_DumpCtx *ctx) {
    const am_Solver *S = ctx->S;
    am_Symbol sym = {0, 0};
    size_t i, vc = S->vars.count, cc = 0, count = 0;
    am_Iterator it = am_itertable(&S->vars);
    while (am_nextentry(&it)) ctx->syms[count++] = it.key.id;
    it = am_itertable(&S->constraints);
    while (am_nextentry(&it)) {
        am_Constraint *cons = *am_val(am_Constraint*,it);
        if (cons->marker.type) ctx->syms[count++] = cons->marker_id;
        if (cons->other.type) ctx->syms[count++] = cons->other_id;
        ctx->cons[cc++] = it.key.id;
    }
    qsort(ctx->cons, cc, sizeof(unsigned), am_intcmp);
    qsort(ctx->syms, vc, sizeof(unsigned), am_intcmp);
    qsort(ctx->syms + vc, count - vc, sizeof(unsigned), am_intcmp);
    am_inittable(&ctx->symmap, sizeof(unsigned));
    amE(am_growtable(S, &ctx->symmap, count, NULL));
    for (i = 0; i < count; ++i) {
        unsigned *val = (unsigned*)am_settable(S, &ctx->symmap,
                (sym.id = ctx->syms[i], sym));
        assert(val != NULL), *val = (unsigned)i;
    }
    return assert(count == ctx->symmap.count), AM_OK;
}

static int am_dumpall(am_DumpCtx *ctx) {
    const am_Solver *S = ctx->S;
    am_Dumper *d = ctx->dumper;
    amE(am_writecount(ctx, 5));
    /* [total, vars, constraints, rows, objective] */
    amE(am_writeuint32(ctx, ctx->symmap.count));
    amE(am_writevars(ctx));
    amE(am_writeconstraints(ctx));
    amE(am_writerows(ctx));
    amE(am_writerow(ctx, &S->objective));
    if (ctx->p > ctx->buf)
        amE(ctx->ret = d->writer(d, ctx->buf, (ctx->p - ctx->buf)));
    return ctx->ret;
}

AM_API int am_dump(am_Solver *S, am_Dumper *dumper) {
    size_t cons_alloc, sym_alloc;
    am_DumpCtx ctx;
    ctx.ret = AM_FAILED;
    amC(S && dumper && dumper->writer && dumper->var_name);
    am_clearedits(S);
    cons_alloc = sizeof(unsigned) * S->constraints.count;
    sym_alloc = sizeof(unsigned) * (cons_alloc*2 + S->vars.count);
    memset(&ctx, 0, sizeof(ctx));
    ctx.syms = (unsigned*)S->allocf(&S->ud, NULL, sym_alloc, 0, am_AllocDump);
    ctx.cons = (unsigned*)S->allocf(&S->ud, NULL, cons_alloc, 0, am_AllocDump);
    ctx.S = S;
    if (ctx.syms && ctx.cons && (ctx.ret = am_collect(&ctx)) == AM_OK) {
        ctx.dumper = dumper;
        ctx.p = ctx.buf;
        ctx.ret = am_dumpall(&ctx);
    }
    if (ctx.syms) S->allocf(&S->ud, ctx.syms, 0, sym_alloc, am_AllocDump);
    if (ctx.cons) S->allocf(&S->ud, ctx.cons, 0, cons_alloc, am_AllocDump);
    am_freetable(S, &ctx.symmap);
    return ctx.ret;
}

typedef struct am_LoadCtx {
    am_Solver  *S;
    am_Loader  *loader;
    size_t      n;
    const char *p, *s;
    am_Size     offset;
    char        buf[AM_NAME_LEN];
} am_LoadCtx;

#define am_getchar(ctx) ((ctx)->n-- > 0 ? *(ctx)->p++ & 0xFF : am_fill(ctx))

static int am_fill(am_LoadCtx *ctx) {
    ctx->p = ctx->loader->reader(ctx->loader, &ctx->n);
    amC(ctx->p && ctx->n);
    return ctx->n -= 1, *ctx->p++ & 0xFF;
}

static int am_readraw_slow(am_LoadCtx *ctx, unsigned *pv, int width) {
    int c1 = 0, c2 = 0, c3, c4;
    switch (width) {
    default: return AM_FAILED;
    case 32: amC((c1 = am_getchar(ctx)) != AM_FAILED);
             amC((c2 = am_getchar(ctx)) != AM_FAILED); /* FALLTHROUGH */
    case 16: amC((c3 = am_getchar(ctx)) != AM_FAILED);
             amC((c4 = am_getchar(ctx)) != AM_FAILED);
    }
    return (*pv = c1 << 24 | c2 << 16 | c3 << 8 | c4), AM_OK;
}

static int am_readraw32(am_LoadCtx *ctx, unsigned *pv) {
    if (ctx->n >= 4) {
        unsigned n = *ctx->p++ & 0xFF;
        n <<= 8; n |= *ctx->p++ & 0xFF;
        n <<= 8; n |= *ctx->p++ & 0xFF;
        n <<= 8; n |= *ctx->p++ & 0xFF;
        return *pv = n, ctx->n -= 4, AM_OK;
    }
    return am_readraw_slow(ctx, pv, 32);
}

static int am_readraw16(am_LoadCtx *ctx, unsigned *pv) {
    if (ctx->n >= 2) {
        unsigned n = *ctx->p++ & 0xFF;
        n <<= 8; n |= *ctx->p++ & 0xFF;
        return *pv = n, ctx->n -= 2, AM_OK;
    }
    return am_readraw_slow(ctx, pv, 16);
}

static int am_readuint32(am_LoadCtx *ctx, am_Size *pv) {
    int ty, c;
    switch (ty = am_getchar(ctx)) {
    default: amC(ty <= 0x7F); *pv = ty; break;
    case 0xCC: amC((c = am_getchar(ctx)) != AM_FAILED); *pv = c; break;
    case 0xCD: amE(am_readraw16(ctx, pv)); break;
    case 0xCE: amE(am_readraw32(ctx, pv)); break;
    }
    return AM_OK;
}

static int am_readfloat(am_LoadCtx *ctx, am_Num *pv) {
    union { float f32; unsigned u32[2]; } u;
    amC(am_getchar(ctx) == 0xCA);
    amE(am_readraw32(ctx, &u.u32[0]));
    return *pv = (am_Num)u.f32, AM_OK;
}

static int am_readstring(am_LoadCtx *ctx) {
    char *buf = ctx->buf;
    am_Size size;
    int ty, c;
    switch (ty = am_getchar(ctx)) {
    default:   amC(ty >= 0xA0 && ty <= 0xBF); size = ty - 0xA0;   break;
    case 0xD9: amC((c = am_getchar(ctx)) != AM_FAILED); size = c; break;
    }
    ctx->s = size ? ctx->buf : NULL;
    if (ctx->n >= size)
        return memcpy(ctx->buf, ctx->p, size), ctx->buf[size] = 0,
               ctx->p += size, ctx->n -= size, AM_OK;
    for (;;) {
        memcpy(buf, ctx->p, ctx->n), buf += ctx->n, size -= (am_Size)ctx->n;
        amC((c = am_fill(ctx)) != AM_FAILED);
        *buf++ = c, size -= 1;
        if (size <= ctx->n) {
            memcpy(buf, ctx->p, size), buf[size] = 0;
            return (ctx->n -= size, ctx->p += size), AM_OK;
        }
    }
}

static int am_readcount(am_LoadCtx *ctx, am_Size *pcount) {
    int ty;
    switch (ty = am_getchar(ctx)) {
    default: amC(ty >= 0x90 && ty <= 0x9F); *pcount = ty - 0x90; break;
    case 0xDC: amE(am_readraw16(ctx, pcount)); break;
    case 0xDD: amE(am_readraw32(ctx, pcount)); break; 
    }
    return AM_OK;
}

static int am_readrow(am_LoadCtx *ctx, am_Row *row, void **arena) {
    am_Size i, count, value;
    amE(am_readcount(ctx, &count));
    amC(count >= 1 && (count & 1) == 1);
    amE(am_growtable(ctx->S, &row->terms, count/2, arena));
    amE(am_readfloat(ctx, &row->constant));
    for (i = 1; i < count; i += 2) {
        am_Symbol sym = {0, 0};
        am_Num *term;
        amE(am_readuint32(ctx, &value));
        sym.id = ctx->offset+(value>>2), sym.type = value & 3;
        amC(term = (am_Num*)am_settable(ctx->S, &row->terms, sym));
        amE(am_readfloat(ctx, term));
    }
    return AM_OK;
}

static int am_readvars(am_LoadCtx *ctx) {
    am_Solver *S = ctx->S;
    am_Size i, count;
    am_Symbol sym = {0, AM_EXTERNAL};
    amE(am_readcount(ctx, &count));
    amE(am_growtable(S, &S->vars, count, NULL));
    for (i = 0; i < count; ++i) {
        am_Var *ve;
        am_Num *pvalue;
        amE(am_readstring(ctx));
        pvalue = ctx->loader->load_var(ctx->loader, ctx->s, i, ctx->offset+i);
        amC(pvalue != NULL);
        sym.id = ctx->offset+i;
        amC(ve = (am_Var*)am_settable(S, &S->vars, sym));
        memset(ve, 0, sizeof(*ve));
        ve->pvalue   = pvalue;
        ve->refcount = 1;
    }
    return AM_OK;
}

static int am_readmarker(am_LoadCtx *ctx, am_Symbol *sym, unsigned *id) {
    am_Size value;
    amE(am_readuint32(ctx, &value));
    *id = ctx->offset + (value >> 2);
    sym->type = value & 3;
    if (sym->type) sym->id = *id;
    return AM_OK;
}

static int am_readconstraints(am_LoadCtx *ctx) {
    am_Size i, count, value;
    am_Solver *S = ctx->S;
    amE(am_readcount(ctx, &count));
    amE(am_growtable(S, &S->constraints, count, NULL));
    for (i = 0; i < count; ++i) {
        am_Constraint *cons;
        am_Num strength;
        amE(am_readcount(ctx, &value));
        amC(value == 6); /* [name, strength, marker, other, relation, row] */
        amE(am_readstring(ctx));
        amE(am_readfloat(ctx, &strength));
        amC(cons = am_newconstraint(S, strength));
        if (ctx->loader->load_cons)
            ctx->loader->load_cons(ctx->loader, ctx->s, i, cons);
        amE(am_readmarker(ctx, &cons->marker, &cons->marker_id));
        amE(am_readmarker(ctx, &cons->other, &value));
        cons->other_id = value;
        amE(am_readuint32(ctx, &value));
        amC(value >= AM_LESSEQUAL && value <= AM_GREATEQUAL);
        cons->relation = value;
        amE(am_readrow(ctx, &cons->expression, NULL));
    }
    return AM_OK;
}

static int am_readrows(am_LoadCtx *ctx) {
    am_Solver *S = ctx->S;
    am_Size i, count, value, arena;
    void **arena_buf = NULL, *ptr = NULL;
    amE(am_readcount(ctx, &count));
    amC((count & 1) == 1);
    amE(am_readuint32(ctx, &arena));
    if (arena) {
        am_allocarena(S, &S->arena, arena * (sizeof(am_Key)+sizeof(am_Num)));
        ptr = S->arena.buf, arena_buf = &ptr;
    }
    amE(am_growtable(S, &S->rows, count/2, NULL));
    for (i = 1; i < count; i += 2) {
        am_Symbol sym;
        am_Row *row;
        amE(am_readuint32(ctx, &value));
        sym.id = ctx->offset + (value >> 2), sym.type = value & 3;
        amC(row = (am_Row*)am_settable(S, &S->rows, sym));
        am_initrow(row);
        amE(am_readrow(ctx, row, arena_buf));
    }
    assert(!ptr || (am_Size)((char*)ptr-(char*)S->arena.buf) == S->arena.size);
    return AM_OK;
}

AM_API int am_load(am_Solver *S, am_Loader *loader) {
    am_LoadCtx ctx;
    am_Size count, total;
    amC(S && loader && loader->reader && loader->load_var);
    memset(&ctx, 0, sizeof(ctx));
    ctx.S = S;
    ctx.offset = S->current_id + 1;
    ctx.loader = loader;
    amE(am_readcount(&ctx, &count));
    amC(count == 5); /* [total, vars, constraints, rows, objective] */
    amE(am_readuint32(&ctx, &total));
    amC(ctx.offset + total <= 0x3FFFFFFF);
    am_resetsolver(S);
    amE(am_readvars(&ctx));
    amE(am_readconstraints(&ctx));
    amE(am_readrows(&ctx));
    amE(am_readrow(&ctx, &S->objective, NULL));
    return S->current_id = ctx.offset + total, AM_OK;
}


AM_NS_END

#endif /* AM_IMPLEMENTATION */

/* cc: flags+='-shared -O2 -pedantic -std=c89 -DAM_IMPLEMENTATION -xc'
   unixcc: output='amoeba.so'
   win32cc: output='amoeba.dll' */



================================================
FILE: amoeba.lua
================================================

local function meta(name, parent)
   local t = {}
   t.__name  = name
   t.__index = t
   return setmetatable(t, parent)
end

local function approx(a, b)
   if a > b then return a - b < 1e-6 end
   return b - a < 1e-6
end

local function near_zero(n)
   return approx(n, 0.0)
end

local Variable, Expression, Constraint do

Variable   = meta "Variable"
Expression = meta "Expression"
Constraint = meta "Constraint"

Constraint.REQUIRED = 1000000000.0
Constraint.STRONG   = 1000000.0
Constraint.MEDIUM   = 1000.0
Constraint.WEAK     = 1.0

function Variable:__unm() return Expression.new(self, -1.0) end
function Expression:__unm() return Expression.new(self):multiply(-1) end

function Variable:__add(other) return Expression.new(self) + other end
function Variable:__sub(other) return Expression.new(self) - other end
function Variable:__mul(other) return Expression.new(self) * other end
function Variable:__div(other) return Expression.new(self) / other end

function Variable:le(other) return Expression.new(self):le(other) end
function Variable:eq(other) return Expression.new(self):eq(other) end
function Variable:ge(other) return Expression.new(self):ge(other) end

function Expression:__add(other) return Expression.new(self):add(other) end
function Expression:__sub(other) return Expression.new(self):add(-other) end
function Expression:__mul(other) return Expression.new(self):multiply(other) end
function Expression:__div(other) return Expression.new(self):multiply(1.0/other) end

function Expression:le(other) return Constraint.new("<=", self, other) end
function Expression:eq(other) return Constraint.new("==", self, other) end
function Expression:ge(other) return Constraint.new(">=", self, other) end

function Constraint:__call(...) return self:add(...) end

function Variable.new(name, type, id)
   type = type or "external"
   assert(type == "external" or
          type == "slack" or
          type == "error" or
          type == "dummy", type)
   local self = {
      id    = id,
      name  = name,
      value = 0.0,
      type  = type or "external",
      is_dummy        = type == "dummy",
      is_slack        = type == "slack",
      is_error        = type == "error",
      is_external     = type == "external",
      is_pivotable    = type == "slack" or type == "error",
      is_restricted   = type ~= "external",
   }
   return setmetatable(self, Variable)
end

function Variable:__tostring()
   return ("amoeba.Variable: %s = %g"):format(self.name, self.value)
end

function Expression.new(other, multiplier, constant)
   local self = setmetatable({}, Expression)
   return self:add(other, multiplier, constant)
end

function Expression:tostring()
   local t = { ("%g"):format(self.constant or 0.0) }
   for k, v in self:iter_vars() do
      t[#t+1] = v < 0.0 and ' - ' or ' + '
      v = math.abs(v)
      if not approx(v, 1.0) then
         t[#t+1] = ("%g*"):format(v)
      end
      t[#t+1] = k.name
   end
   return table.concat(t)
end

function Expression:__tostring()
   return "Exp: "..self:tostring()
end

function Expression:add(other, multiplier, constant)
   if other == nil then return self end
   self.constant = (self.constant or 0.0) + (constant or 0.0)
   multiplier = multiplier or 1.0
   if tonumber(other) then
      self.constant = self.constant + other*multiplier
      return self
   end
   local mt = getmetatable(other)
   if mt == Variable then
      multiplier = (self[other] or 0.0) + multiplier
      self[other] = not near_zero(multiplier) and multiplier or nil
   elseif mt == Expression then
      for k, v in pairs(other) do
         multiplier = (self[k] or 0.0) + multiplier * v
         self[k] = not near_zero(multiplier) and multiplier or nil
      end
      self.constant = self.constant or 0.0
   else
      error("constant/variable/expression expected")
   end
   return self
end

function Expression:multiply(other)
   if tonumber(other) then
      for k, v in pairs(self) do
         self[k] = v * other
      end
      return self
   end
   local mt = getmetatable(other)
   if mt == Variable then
      return self:multiply(Expression.new(other))
   elseif mt == Expression then
      if other:is_constant() then
         return self:multiply(other.constant)
      elseif self.constant then
         local constant = self.constant
         self.constant = 0.0
         return self:add(other):multiply(constant)
      end
      error("attempt to multiply two non-constant expression")
   else
      error("number/variable/constant expression expected")
   end
end

function Expression:choose_pivotable()
   for k in pairs(self) do
      if k.is_pivotable then
         return k
      end
   end
end

function Expression:is_constant()
   local f, state = self:iter_vars()
   return f(state) == nil
end

function Expression:solve_for(new, old)
   -- expr: old ==    a[n] *new +        constant +    a[i]*      v[i]...
   -- =>    new == (1/a[n])*old - 1/a[n]*constant - (1/a[n])*a[i]*v[i]...
   local multiplier = assert(self[new])
   assert(new ~= old and not near_zero(multiplier))
   self[new] = nil
   local reciprocal = 1.0 / multiplier
   self:multiply(-reciprocal)
   if old then self[old] = reciprocal end
   return new
end

function Expression:substitute_out(var, expr)
   assert(var ~= "constant")
   local multiplier = self[var]
   if not multiplier then return end
   self[var] = nil
   self:add(expr, multiplier)
end

function Expression:iter_vars()
   return function(self1, k1)
      local k, v = next(self1, k1)
      if k == 'constant' then
         return next(self1, k1)
      end
      return k, v
   end, self
end

function Constraint.new(op, expr1, expr2, strength)
   local self = setmetatable({}, Constraint)
   if not op then
      self.expression = Expression.new()
   else
      self:relation(op)
      if self.op == '<=' then
         self.expression = Expression.new(expr2 or 0.0):add(expr1, -1.0)
      else
         self.expression = Expression.new(expr1 or 0.0):add(expr2, -1.0)
      end
   end
   return self:strength(strength or Constraint.REQUIRED)
end

function Constraint:__tostring()
   local repr = "amoeba.Constraint: ["..self.expression:tostring()
   if self.is_inequality then
      repr = repr .. " >= 0.0]"
   else
      repr = repr .. " == 0.0]"
   end
   return repr
end

function Constraint:add(other, multiplier, constant)
   if other == ">=" or other == "<=" or other == "==" then
      self:relation(other)
   else
      multiplier = multiplier or 1.0
      if self.op == '>=' then multiplier = -multiplier end
      self.expression:add(other, multiplier, constant)
   end
   return self
end

function Constraint:relation(op)
   assert(op == '==' or op == '<=' or op == '>=' or
          op == 'eq' or op == 'le' or op == 'ge',
          "op must be '==', '>=' or '<='")
   if op == 'eq'     then op = '=='
   elseif op == 'le' then op = '<='
   elseif op == 'ge' then op = '>=' end
   self.op = op
   if self.op ~= '==' then
      self.is_inequality = true
   end
   if self.op ~= '>=' and self.expression then
      self.expression:multiply(-1.0)
   end
   return self
end

function Constraint:strength(strength)
   if self.solver then
      self.solver:setstrength(self, strength)
   else
      self.weight = Constraint[strength] or tonumber(strength) or self.weight
      self.is_required = self.weight >= Constraint.REQUIRED
   end
   return self
end

function Constraint:clone(strength)
   local new = Constraint.new():strength(strength)
   new:add(self)
   new.op            = self.op
   new.is_inequality = self.is_inequality
   return new
end

end

local SimplexSolver = meta "SimplexSolver" do

-- implements

local function update_external_variables(self)
   for var in pairs(self.vars) do
      local row = self.rows[var]
      var.value = row and row.constant or 0.0
   end
end

local function substitute_out(self, var, expr)
   for k, row in pairs(self.rows) do
      row:substitute_out(var, expr)
      if k.is_restricted and row.constant < 0.0 then
         self.infeasible_rows[#self.infeasible_rows+1] = k
      end
   end
   self.objective:substitute_out(var, expr)
end

local function optimize(self, objective)
   objective = objective or self.objective
   while true do
      local entry, exit
      for var, multiplier in objective:iter_vars() do
         if not var.is_dummy and multiplier < 0.0 then
            entry = var
            break
         end
      end
      if not entry then return end

      local r
      local min_ratio = math.huge
      for var, row in pairs(self.rows) do
         local multiplier = row[entry]
         if multiplier and var.is_pivotable and multiplier < 0.0 then
            r = -row.constant / multiplier
            if r < min_ratio or (approx(r, min_ratio) and
                                 var.id < exit.id) then
               min_ratio, exit = r, var
            end
         end
      end
      assert(exit, "objective function is unbounded")

      -- do pivot
      local row = self.rows[exit]
      self.rows[exit] = nil
      row:solve_for(entry, exit)
      substitute_out(self, entry, row)
      if objective ~= self.objective then
         objective:substitute_out(entry, row)
      end
      self.rows[entry] = row
   end
end

local function make_variable(self, type)
   local id = self.last_varid
   self.last_varid = id + 1
   local prefix = type == "eplus" and "ep" or
                  type == "eminus" and "em" or
                  type == "dummy" and "d" or
                  type == "artificial" and "a" or "s"
   if not type or type == "artificial" then
      type = "slack"
   elseif type == "eplus" or type == "eminus" then
      type = "error"
   end
   return Variable.new(prefix..id, type, id)
end

local function make_expression(self, cons)
   local expr = Expression.new(cons.expression.constant)
   local var1, var2
   for k, v in cons.expression:iter_vars() do
      if not k.id then
         k.id = self.last_varid
         self.last_varid = k.id + 1
      end
      if not self.vars[k] then
         self.vars[k] = true
      end
      expr:add(self.rows[k] or k, v)
   end
   if cons.is_inequality then
      var1 = make_variable(self) -- slack
      expr[var1] = -1.0
      if not cons.is_required then
         var2 = make_variable(self, "eminus")
         expr[var2] = 1.0
         self.objective[var2] = cons.weight
      end
   elseif cons.is_required then
      var1 = make_variable(self, 'dummy')
      expr[var1] = 1.0
   else
      var1 = make_variable(self, 'eplus')
      var2 = make_variable(self, 'eminus')
      expr[var1] = -1.0
      expr[var2] =  1.0
      self.objective[var1] = cons.weight
      self.objective[var2] = cons.weight
   end
   if expr.constant < 0.0 then expr:multiply(-1.0) end
   return expr, var1, var2
end

local function choose_subject(_, expr, var1, var2)
   for k in expr:iter_vars() do
      if k.is_external then return k end
   end
   if var1 and var1.is_pivotable then return var1 end
   if var2 and var2.is_pivotable then return var2 end
   for k in expr:iter_vars() do
      if not k.is_dummy then return nil end -- no luck
   end
   if not near_zero(expr.constant) then
      return nil, "unsatisfiable required constraint added"
   end
   return var1
end

local function add_with_artificial_variable(self, expr)
   local a = make_variable(self, 'artificial')
   self.last_varid = self.last_varid - 1

   self.rows[a] = expr
   optimize(self, expr)
   local row = self.rows[a]
   self.rows[a] = nil

   local success = near_zero(expr.constant)
   if row then
      if row:is_constant() then
         return success
      end
      local entering = row:choose_pivotable()
      if not entering then return false end

      row:solve_for(entering, a)
      self.rows[entering] = row
   end

   for _, r in pairs(self.rows) do r[a] = nil end
   self.objective[a] = nil
   return success
end

local function get_marker_leaving_row(self, marker)
   local r1, r2 = math.huge, math.huge
   local first, second, third
   for var, row in pairs(self.rows) do
      local multiplier = row[marker]
      if multiplier then
         if var.is_external then
            third = var
         elseif multiplier < 0.0 then
            local r = -row.constant / multiplier
            if r < r1 then r1 = r; first = var end
         else
            local r = row.constant / multiplier
            if r < r2 then r2 = r; second = var end
         end
      end
   end
   return first or second or third
end

local function delta_edit_constant(self, delta, var1, var2)
   local row = self.rows[var1]
   if row then
      row.constant = row.constant - delta
      if row.constant < 0.0 then
         self.infeasible_rows[#self.infeasible_rows+1] = var1
      end
      return
   end
   row = self.rows[var2]
   if row then
      row.constant = row.constant + delta
      if row.constant < 0.0 then
         self.infeasible_rows[#self.infeasible_rows+1] = var2
      end
      return
   end
   for var, r in pairs(self.rows) do
      r.constant = r.constant + (r[var1] or 0.0)*delta
      if var.is_restricted and r.constant < 0.0 then
         self.infeasible_rows[#self.infeasible_rows+1] = var
      end
   end
end

local function dual_optimize(self)
   while true do
      local count = #self.infeasible_rows
      if count == 0 then return end
      local exit = self.infeasible_rows[count]
      self.infeasible_rows[count] = nil

      local row = self.rows[exit]
      if row and row.constant < 0.0 then
         local entry
         local min_ratio = math.huge
         for var, multiplier in row:iter_vars() do
            if multiplier > 0.0 and not var.is_dummy then
               local r = (self.objective[var] or 0.0) / multiplier
               if r < min_ratio then
                  min_ratio, entry = r, var
               end
            end
         end
         assert(entry, "dual optimize failed")

         -- pivot
         self.rows[exit] = nil
         row:solve_for(entry, exit)
         substitute_out(self, entry, row)
         self.rows[entry] = row
      end
   end
end

-- interface

function SimplexSolver:hasvariable(var) return self.vars[var] end
function SimplexSolver:hasconstraint(cons) return self.constraints[cons] end
function SimplexSolver:hasedit(var) return self.edits[var] end
function SimplexSolver.var(_, ...) return Variable.new(...) end
function SimplexSolver.constraint(_, ...) return Constraint.new(...) end

function SimplexSolver.new()
   local self = {}
   self.last_varid = 1

   self.vars        = {}
   self.edits       = {}
   self.constraints = {}

   self.objective       = Expression.new()
   self.rows            = {}
   self.infeasible_rows = {}

   return setmetatable(self, SimplexSolver)
end

function SimplexSolver:__tostring()
   local t = { "amoeba.Solver: {\n" }
   t[#t+1] = ("  objective = %s\n"):format(self.objective:tostring())
   if next(self.rows) then
      t[#t+1] =  "  rows:\n"
      local keys = {}
      for k in pairs(self.rows) do
         keys[#keys+1] = k
      end
      table.sort(keys, function(a, b) return a.id < b.id end)
      for idx, k in ipairs(keys) do local v = self.rows[k]
         t[#t+1] = ("    %d. %s(%g) = %s\n"):format(idx, k.name, k.value, v:tostring())
      end
   end
   if next(self.edits) then
      t[#t+1] = "  edits:\n"
      local idx = 1
      for k, v in pairs(self.edits) do
         t[#t+1] = ("    %d. %s = %s; info = { %s, %s, %g }\n"):format(
            idx, k.name, k.value, v.plus.name, v.minus.name,
            v.prev_constant)
         idx = idx + 1
      end
   end
   if #self.infeasible_rows ~= 0 then
      t[#t+1] = "  infeasible_rows: {"
      for _, var in ipairs(self.infeasible_rows) do
         t[#t+1] = (" %s"):format(var.name)
      end
      t[#t+1] = " }\n"
   end
   if #self.vars ~= 0 then
      t[#t+1] = " vars: {"
      for var in pairs(self.vars) do
         t[#t+1] = (" %s"):format(var.name)
      end
      t[#t+1] = " }\n"
   end
   t[#t+1] = "}"
   return table.concat(t)
end

function SimplexSolver:addconstraint(cons, ...)
   if getmetatable(cons) ~= Constraint then
      cons = Constraint.new(cons, ...)
   end
   if self.constraints[cons] then return cons end
   local expr, var1, var2 = make_expression(self, cons)
   local subject, err = choose_subject(self, expr, var1, var2)
   if subject then
      expr:solve_for(subject)
      substitute_out(self, subject, expr)
      self.rows[subject] = expr
   elseif err then
      return nil, err
   elseif not add_with_artificial_variable(self, expr) then
      return nil, "constraint added may unbounded"
   end
   self.constraints[cons] = {
      marker = var1,
      other = var2,
   }
   cons.solver = self
   optimize(self)
   update_external_variables(self)
   return cons
end

function SimplexSolver:delconstraint(cons)
   local info = self.constraints[cons]
   if not info then return end
   self.constraints[cons] = nil

   if info.marker and info.marker.is_error then
      self.objective:add(self.rows[info.marker] or info.marker, -cons.weight)
   end
   if info.other and info.other.is_error then
      self.objective:add(self.rows[info.other] or info.other, -cons.weight)
   end
   if self.objective:is_constant() then
      self.objective.constant = 0.0
   end

   local row = self.rows[info.marker]
   if row then
      self.rows[info.marker] = nil
   else
      local var = assert(get_marker_leaving_row(self, info.marker),
                         "failed to find leaving row")
      row = self.rows[var]
      self.rows[var] = nil
      row:solve_for(info.marker, var)
      substitute_out(self, info.marker, row)
   end
   cons.solver = nil
   optimize(self)
   update_external_variables(self)
   return cons
end

function SimplexSolver:addedit(var, strength)
   if self.edits[var] then return end
   strength = strength or Constraint.MEDIUM
   assert(strength < Constraint.REQUIRED, "attempt to edit a required var")
   local cons = Constraint.new("==", var, var.value, strength)
   assert(self:addconstraint(cons))
   local info = self.constraints[cons]
   self.edits[var] = {
      constraint = cons,
      plus = info.marker,
      minus = info.other,
      prev_constant = var.value or 0.0,
   }
   return self
end

function SimplexSolver:deledit(var)
   local info = self.edits[var]
   if info then
      self:delconstraint(info.constraint)
      self.edits[var] = nil
   end
end

function SimplexSolver:suggest(var, value)
   local info = self.edits[var]
   if not info then self:addedit(var); info = self.edits[var] end
   local delta = value - info.prev_constant
   info.prev_constant = value
   delta_edit_constant(self, delta, info.plus, info.minus)
   dual_optimize(self)
   update_external_variables(self)
end

function SimplexSolver:setstrength(cons, strength)
   local info = self.constraints[cons]
   if not info then cons.weight = strength end
   assert(info.marker and info.marker.is_error, "attempt to change required strength")
   local multiplier = strength / cons.strength
   cons.weight = strength
   self.is_required = self.weight >= Constraint.REQUIRED
   if near_zero(diff) then return self end

   self.objective:add(self.rows[info.marker] or info.marker, multiplier)
   self.objective:add(self.rows[info.other] or info.other, multiplier)
   optimize(self)
   update_external_variables(self)
   return self
end

function SimplexSolver:resolve()
   dual_optimize(self)
   set_external_variables()
   reset_stay_constant(self)
   self.infeasible_rows = {}
end

function SimplexSolver:set_constant(cons, constant)
   local info = self.constraints[cons]
   if not info then return end
   local delta = info.prev_constant - constant
   info.prev_constant = constant

   if info.marker.is_slack or cons.is_required then
      for var, row in pairs(self.rows) do
         row:add((row[info.marker] or 0.0) * -delta)
         if var.is_restricted and row.constant < 0.0 then
            self.infeasible_rows[#self.infeasible_rows+1] = var
         end
      end
   else
      delta_edit_constant(self, delta, info.marker, info.other)
   end
   dual_optimize(self)
   update_external_variables(self)
end

end

return SimplexSolver


================================================
FILE: enaml_like_benchmark.cpp
================================================
/*-----------------------------------------------------------------------------
| Copyright (c) 2020, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/

// Time updating an EditVariable in a set of constraints typical of enaml use.

#define ANKERL_NANOBENCH_IMPLEMENT
#include "nanobench.h"

#define AM_IMPLEMENTATION
#include "amoeba.h"

static void build_solver(am_Solver* S, am_Id width, am_Id height, am_Num *values) {
    /* Create custom strength */
    am_Num mmedium = AM_MEDIUM * 1.25;
    am_Num smedium = AM_MEDIUM * 100;

    /* Create the variable */
    am_Id left            = am_newvariable(S, &values[0]);
    am_Id top             = am_newvariable(S, &values[1]);
    am_Id contents_top    = am_newvariable(S, &values[2]);
    am_Id contents_bottom = am_newvariable(S, &values[3]);
    am_Id contents_left   = am_newvariable(S, &values[4]);
    am_Id contents_right  = am_newvariable(S, &values[5]);
    am_Id midline         = am_newvariable(S, &values[6]);
    am_Id ctleft          = am_newvariable(S, &values[7]);
    am_Id ctheight        = am_newvariable(S, &values[8]);
    am_Id cttop           = am_newvariable(S, &values[9]);
    am_Id ctwidth         = am_newvariable(S, &values[10]);
    am_Id lb1left         = am_newvariable(S, &values[11]);
    am_Id lb1height       = am_newvariable(S, &values[12]);
    am_Id lb1top          = am_newvariable(S, &values[13]);
    am_Id lb1width        = am_newvariable(S, &values[14]);
    am_Id lb2left         = am_newvariable(S, &values[15]);
    am_Id lb2height       = am_newvariable(S, &values[16]);
    am_Id lb2top          = am_newvariable(S, &values[17]);
    am_Id lb2width        = am_newvariable(S, &values[18]);
    am_Id lb3left         = am_newvariable(S, &values[19]);
    am_Id lb3height       = am_newvariable(S, &values[20]);
    am_Id lb3top          = am_newvariable(S, &values[21]);
    am_Id lb3width        = am_newvariable(S, &values[22]);
    am_Id fl1left         = am_newvariable(S, &values[23]);
    am_Id fl1height       = am_newvariable(S, &values[24]);
    am_Id fl1top          = am_newvariable(S, &values[25]);
    am_Id fl1width        = am_newvariable(S, &values[26]);
    am_Id fl2left         = am_newvariable(S, &values[27]);
    am_Id fl2height       = am_newvariable(S, &values[28]);
    am_Id fl2top          = am_newvariable(S, &values[29]);
    am_Id fl2width        = am_newvariable(S, &values[30]);
    am_Id fl3left         = am_newvariable(S, &values[31]);
    am_Id fl3height       = am_newvariable(S, &values[32]);
    am_Id fl3top          = am_newvariable(S, &values[33]);
    am_Id fl3width        = am_newvariable(S, &values[34]);

#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
# pragma GCC diagnostic ignored "-Wc99-extensions"
#endif

    /* Add the constraints */
    const struct Info {
        struct Item {
            am_Id  var;
            am_Num mul;
        } term[5];
        am_Num constant;
        int    relation;
        am_Num strength;
    } constraints[] = {
        { {{left}},                                                -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{height}},                                              0,            AM_EQUAL,      AM_MEDIUM   },
        { {{top}},                                                 -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{width}},                                               -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{height}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{top,-1},{contents_top}},                               -10,          AM_EQUAL,      AM_REQUIRED },
        { {{lb3height}},                                           -16,          AM_EQUAL,      AM_STRONG   },
        { {{lb3height}},                                           -16,          AM_GREATEQUAL, AM_STRONG   },
        { {{ctleft}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{cttop}},                                               -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{ctwidth}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{ctheight}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl3left}},                                             0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{ctheight}},                                            -24,          AM_GREATEQUAL, smedium     },
        { {{ctwidth}},                                             -1.67772e+07, AM_LESSEQUAL,  smedium     },
        { {{ctheight}},                                            -24,          AM_LESSEQUAL,  smedium     },
        { {{fl3top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl3width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl3height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1width}},                                            -67,          AM_EQUAL,      AM_WEAK     },
        { {{lb2width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl2height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb3left}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl2width}},                                            -125,         AM_GREATEQUAL, AM_STRONG   },
        { {{fl2height}},                                           -21,          AM_EQUAL,      AM_STRONG   },
        { {{fl2height}},                                           -21,          AM_GREATEQUAL, AM_STRONG   },
        { {{lb3top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb3width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1left}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl1width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1width}},                                            -67,          AM_GREATEQUAL, AM_STRONG   },
        { {{fl2left}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2width}},                                            -66,          AM_EQUAL,      AM_WEAK     },
        { {{lb2width}},                                            -66,          AM_GREATEQUAL, AM_STRONG   },
        { {{lb2height}},                                           -16,          AM_EQUAL,      AM_STRONG   },
        { {{fl1height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl1top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2top,-1},{lb3top},{lb2height,-1}},                   -10,          AM_EQUAL,      mmedium     },
        { {{lb3top,-1},{lb3height,-1},{fl3top}},                   -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{lb3top,-1},{lb3height,-1},{fl3top}},                   -10,          AM_EQUAL,      mmedium     },
        { {{contents_bottom},{fl3height,-1},{fl3top,-1}},          -0,           AM_EQUAL,      mmedium     },
        { {{fl1top},{contents_top,-1}},                            0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{fl1top},{contents_top,-1}},                            0,            AM_EQUAL,      mmedium     },
        { {{contents_bottom},{fl3height,-1},{fl3top,-1}},          -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{left,-1},{width,-1},{contents_right}},                 10,           AM_EQUAL,      AM_REQUIRED },
        { {{top,-1},{height,-1},{contents_bottom}},                10,           AM_EQUAL,      AM_REQUIRED },
        { {{left,-1},{contents_left}},                             -10,          AM_EQUAL,      AM_REQUIRED },
        { {{lb3left},{contents_left,-1}},                          0,            AM_EQUAL,      mmedium     },
        { {{fl1left},{midline,-1}},                                0,            AM_EQUAL,      AM_STRONG   },
        { {{fl2left},{midline,-1}},                                0,            AM_EQUAL,      AM_STRONG   },
        { {{ctleft},{midline,-1}},                                 0,            AM_EQUAL,      AM_STRONG   },
        { {{fl1top},{fl1height,0.5},{lb1top,-1},{lb1height,-0.5}}, 0,            AM_EQUAL,      AM_STRONG   },
        { {{lb1left},{contents_left,-1}},                          0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1left},{contents_left,-1}},                          0,            AM_EQUAL,      mmedium     },
        { {{lb1left,-1},{fl1left},{lb1width,-1}},                  -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1left,-1},{fl1left},{lb1width,-1}},                  -10,          AM_EQUAL,      mmedium     },
        { {{fl1left,-1},{contents_right},{fl1width,-1}},           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{width}},                                               0,            AM_EQUAL,      AM_MEDIUM   },
        { {{fl1top,-1},{fl2top},{fl1height,-1}},                   -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{fl1top,-1},{fl2top},{fl1height,-1}},                   -10,          AM_EQUAL,      mmedium     },
        { {{cttop},{fl2top,-1},{fl2height,-1}},                    -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{ctheight,-1},{cttop,-1},{fl3top}},                     -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{contents_bottom},{fl3height,-1},{fl3top,-1}},          -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{cttop},{fl2top,-1},{fl2height,-1}},                    -10,          AM_EQUAL,      mmedium     },
        { {{fl1left,-1},{contents_right},{fl1width,-1}},           -0,           AM_EQUAL,      mmedium     },
        { {{lb2top,-1},{lb2height,-0.5},{fl2top},{fl2height,0.5}}, 0,            AM_EQUAL,      AM_STRONG   },
        { {{contents_left,-1},{lb2left}},                          0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{contents_left,-1},{lb2left}},                          0,            AM_EQUAL,      mmedium     },
        { {{fl2left},{lb2width,-1},{lb2left,-1}},                  -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{ctheight,-1},{cttop,-1},{fl3top}},                     -10,          AM_EQUAL,      mmedium     },
        { {{contents_bottom},{fl3height,-1},{fl3top,-1}},          -0,           AM_EQUAL,      mmedium     },
        { {{lb1top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl2left},{lb2width,-1},{lb2left,-1}},                  -10,          AM_EQUAL,      mmedium     },
        { {{fl2left,-1},{fl2width,-1},{contents_right}},           -0,           AM_EQUAL,      mmedium     },
        { {{fl2left,-1},{fl2width,-1},{contents_right}},           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb3left},{contents_left,-1}},                          0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1left}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{ctheight,0.5},{cttop},{lb3top,-1},{lb3height,-0.5}},   0,            AM_EQUAL,      AM_STRONG   },
        { {{ctleft},{lb3left,-1},{lb3width,-1}},                   -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{ctwidth,-1},{ctleft,-1},{contents_right}},             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{ctleft},{lb3left,-1},{lb3width,-1}},                   -10,          AM_EQUAL,      mmedium     },
        { {{fl3left},{contents_left,-1}},                          0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{fl3left},{contents_left,-1}},                          0,            AM_EQUAL,      mmedium     },
        { {{ctwidth,-1},{ctleft,-1},{contents_right}},             -0,           AM_EQUAL,      mmedium     },
        { {{fl3left,-1},{contents_right},{fl3width,-1}},           -0,           AM_EQUAL,      mmedium     },
        { {{contents_top,-1},{lb1top}},                            0,            AM_GREATEQUAL, AM_REQUIRED },
        { {{contents_top,-1},{lb1top}},                            0,            AM_EQUAL,      mmedium     },
        { {{fl3left,-1},{contents_right},{fl3width,-1}},           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2top},{lb1top,-1},{lb1height,-1}},                   -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2top,-1},{lb3top},{lb2height,-1}},                   -10,          AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2top},{lb1top,-1},{lb1height,-1}},                   -10,          AM_EQUAL,      mmedium     },
        { {{fl1height}},                                           -21,          AM_EQUAL,      AM_STRONG   },
        { {{fl1height}},                                           -21,          AM_GREATEQUAL, AM_STRONG   },
        { {{lb2left}},                                             -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb2height}},                                           -16,          AM_GREATEQUAL, AM_STRONG   },
        { {{fl2top}},                                              -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{fl2width}},                                            -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{lb1height}},                                           -16,          AM_GREATEQUAL, AM_STRONG   },
        { {{lb1height}},                                           -16,          AM_EQUAL,      AM_STRONG   },
        { {{fl3width}},                                            -125,         AM_GREATEQUAL, AM_STRONG   },
        { {{fl3height}},                                           -21,          AM_EQUAL,      AM_STRONG   },
        { {{fl3height}},                                           -21,          AM_GREATEQUAL, AM_STRONG   },
        { {{lb3height}},                                           -0,           AM_GREATEQUAL, AM_REQUIRED },
        { {{ctwidth}},                                             -119,         AM_GREATEQUAL, smedium     },
        { {{lb3width}},                                            -24,          AM_EQUAL,      AM_WEAK     },
        { {{lb3width}},                                            -24,          AM_GREATEQUAL, AM_STRONG   },
        { {{fl1width}},                                            -125,         AM_GREATEQUAL, AM_STRONG   },
    };

#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif

    size_t i;
    int ret;

    /* Add the edit variables */
    ret = am_addedit(S, width, AM_STRONG);
    assert(ret == AM_OK), (void)ret;
    ret = am_addedit(S, height, AM_STRONG);
    assert(ret == AM_OK), (void)ret;

    for (i = 0; i < sizeof(constraints)/sizeof(constraints[0]); ++i) {
        am_Constraint *c = am_newconstraint(S, constraints[i].strength);
        const struct Info::Item *p;
        for (p = constraints[i].term; p->var; ++p) {
            ret = am_addterm(c, p->var, p->mul ? p->mul : 1);
            assert(ret == AM_OK), (void)ret;
        }
        ret = am_addconstant(c, constraints[i].constant);
        assert(ret == AM_OK), (void)ret;
        ret = am_setrelation(c, constraints[i].relation);
        assert(ret == AM_OK), (void)ret;
        ret = am_add(c);
        assert(ret == AM_OK), (void)ret;
    }
}

int main() {
    am_Num values[35];
    am_Solver *S;

    // demo how to use a memory pool across solvers.
    am_MemPool conspool; am_initpool(&conspool, sizeof(am_Constraint));
    const size_t hash_size = (sizeof(am_Num) + sizeof(am_Key)) * 8;
    am_MemPool hashpool; am_initpool(&hashpool, hash_size);
    auto alloc = [&](void *ptr, size_t ns, size_t os, am_AllocType ty) {
        if (ty == am_AllocConstraint) {
            if (ns) return am_poolalloc(&conspool);
            return am_poolfree(&conspool, ptr), (void*)0;
        }
        if (ty == am_AllocHash) {
            if (ns == hash_size && os == 0) return am_poolalloc(&hashpool);
            if (os == hash_size && ns == 0) return am_poolfree(&hashpool, ptr), (void*)0;
        }
        if (ns) return realloc(ptr, ns);
        return free(ptr), (void*)0;
    };
    auto alloc_func = [](void** pud, void *ptr, size_t ns, size_t os, am_AllocType ty) {
        const auto func = &decltype(alloc)::operator();
        auto alloc_ptr = *(decltype(alloc)**)(pud);
        return (alloc_ptr->*func)(ptr, ns, os, ty);
    };

#if !DISABLE_BUILD
    ankerl::nanobench::Bench().minEpochIterations(100).run("building solver", [&] {
        am_Solver *S = am_newsolver(alloc_func, &alloc);
        am_Num w, h;
        am_Id width = am_newvariable(S, &w);
        am_Id height = am_newvariable(S, &h);
        build_solver(S, width, height, values);
        ankerl::nanobench::doNotOptimizeAway(S); //< prevent the compiler to optimize away the S
        am_delsolver(S);
    });
#endif /* !DISABLE_BUILD */

#if !DISABLE_LOAD
    std::vector<char> buf; 
    {
        am_Num w, h;
        S = am_newsolver(NULL, NULL);
        am_Id width = am_newvariable(S, &w);
        am_Id height = am_newvariable(S, &h);
        build_solver(S, width, height, values);

        struct MyDumper {
            am_Dumper base;
            std::vector<char> *buf;
        };
        MyDumper d;
        d.buf = &buf;
        d.base.var_name = [](am_Dumper*, unsigned idx, am_Id, am_Num*) {
            return idx == 0 ? "width" : idx == 1 ? "height" : NULL;
        };
        d.base.cons_name = nullptr;
        d.base.writer = [](am_Dumper* rd, const void *buf, size_t len) {
            MyDumper *d = (MyDumper*)rd;
            d->buf->insert(d->buf->end(), (char*)buf, (char*)buf+len);
            return AM_OK;
        };
        int ret = am_dump(S, &d.base);
        assert(ret == AM_OK), (void)ret;
        am_delsolver(S);
    }

    ankerl::nanobench::Bench().minEpochIterations(10000).run("load solver", [&] {
        struct MyLoader {
            am_Loader base;
            am_Num values[37];
            std::vector<char> *buf;
        };
        MyLoader l;
        l.buf = &buf;
        l.base.load_var = [](am_Loader* rl, const char*, unsigned idx, am_Id) {
            MyLoader *l = (MyLoader*)rl;
            return &l->values[idx];
        };
        l.base.load_cons = nullptr;
        l.base.reader = [](am_Loader* rl, size_t *plen) {
            MyLoader *l = (MyLoader*)rl;
            std::vector<char> *buf = l->buf;
            if (buf) {
                *plen = buf->size();
                l->buf = nullptr;
                return (const char*)buf->data();
            }
            return (const char*)nullptr;
        };
        am_Solver *S = am_newsolver(alloc_func, &alloc);
        int ret = am_load(S, &l.base);
        assert(ret == AM_OK), (void)ret;
        am_delsolver(S);
    });
#endif /* !DISABLE_LOAD */

#if !DISABLE_SUGGEST
    struct Size {
        int width;
        int height;
    };

    Size sizes[] = {
        { 400, 600 },
        { 600, 400 },
        { 800, 1200 },
        { 1200, 800 },
        { 400, 800 },
        { 800, 400 }
    };

    S = am_newsolver(NULL, NULL);
    am_Num width, height;
    am_Id widthVar = am_newvariable(S, &width);
    am_Id heightVar = am_newvariable(S, &height);
    build_solver(S, widthVar, heightVar, values);

    for (const Size& size : sizes) {
        am_Num width = (am_Num)size.width;
        am_Num height = (am_Num)size.height;

        ankerl::nanobench::Bench().minEpochIterations(100000).run("suggest value " + std::to_string(size.width) + "x" + std::to_string(size.height), [&] {
            am_suggest(S, widthVar, width);
            am_suggest(S, heightVar, height);
            am_updatevars(S);
        });
    }

    ankerl::nanobench::doNotOptimizeAway(width);
    ankerl::nanobench::doNotOptimizeAway(height);

    am_delsolver(S);
#endif /* !DISABLE_SUGGEST */

    am_freepool(&conspool);
    am_freepool(&hashpool);
    return 0;
}

// cc: flags+='-ggdb -O3 -DNDEBUG'


================================================
FILE: lua_amoeba.c
================================================
#define LUA_LIB
#include <lua.h>
#include <lauxlib.h>
#include <string.h>

#define AM_STATIC_API
#include "amoeba.h"

#define AML_SOLVER_TYPE "amoeba.Solver"
#define AML_VAR_TYPE    "amoeba.Variable"
#define AML_CONS_TYPE   "amoeba.Constraint"

enum aml_ItemType { AML_VAR, AML_CONS, AML_CONSTANT };

typedef struct aml_Solver {
    am_Solver *solver;
    int        ref_vars;
    int        ref_cons;
} aml_Solver;

typedef struct aml_Var {
    am_Num      value;
    am_Id       var;
    aml_Solver *S;
    const char *name;
} aml_Var;

typedef struct aml_Cons {
    am_Constraint *cons;
} aml_Cons;

typedef struct aml_Item {
    int            type;
    am_Id          var;
    am_Constraint *cons;
    am_Num         value;
} aml_Item;

/* utils */

static int aml_argferror(lua_State *L, int idx, const char *fmt, ...) {
    va_list l;
    va_start(l, fmt);
    lua_pushvfstring(L, fmt, l);
    va_end(l);
    return luaL_argerror(L, idx, lua_tostring(L, -1));
}

static int aml_typeerror(lua_State *L, int idx, const char *tname) {
    return aml_argferror(L, idx, "%s expected, got %s",
            tname, luaL_typename(L, idx));
}

static void aml_setweak(lua_State *L, const char *mode) {
    lua_createtable(L, 0, 1);
    lua_pushstring(L, mode);
    lua_setfield(L, -2, "__mode");
    lua_setmetatable(L, -2);
}

static am_Id aml_checkvar(lua_State *L, aml_Solver *S, int idx) {
    aml_Var *lvar = (aml_Var*)luaL_testudata(L, idx, AML_VAR_TYPE);
    const char *name;
    if (lvar != NULL) {
        if (lvar->var == 0) luaL_argerror(L, idx, "invalid variable");
        return lvar->var;
    }
    name = luaL_checkstring(L, idx);
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    if (lua_getfield(L, -2, name) == LUA_TUSERDATA)
        return lua_remove(L, -2), aml_checkvar(L, S, -1);
    lua_pop(L, 2);
    return aml_argferror(L, idx, "variable named '%s' not exists",
            lua_tostring(L, idx));
}

static aml_Cons *aml_registercons(lua_State *L, am_Constraint *cons) {
    aml_Cons *lcons = (aml_Cons*)lua_newuserdata(L, sizeof(aml_Cons));
    aml_Solver *S = *(aml_Solver**)cons;
    lcons->cons = cons;
    luaL_setmetatable(L, AML_CONS_TYPE);
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_cons);
    lua_pushvalue(L, -2);
    lua_rawsetp(L, -2, lcons);
    lua_pop(L, 1);
    return lcons;
}

static aml_Cons *aml_newcons(lua_State *L, aml_Solver *S, am_Num strength) {
    am_Constraint *cons = am_newconstraint(S->solver, strength);
    if (cons == NULL) luaL_error(L, "Create constraint failed");
    *(aml_Solver**)cons = S;
    return aml_registercons(L, cons);
}

static aml_Item aml_checkitem(lua_State *L, aml_Solver *S, int idx) {
    aml_Item item = { 0 };
    aml_Cons *lcons;
    aml_Var  *lvar;
    switch (lua_type(L, idx)) {
    case LUA_TSTRING:
        item.var = aml_checkvar(L, S, idx);
        item.type = AML_VAR;
        return item;
    case LUA_TNUMBER:
        item.value = lua_tonumber(L, idx);
        item.type  = AML_CONSTANT;
        return item;
    case LUA_TUSERDATA:
        lcons = (aml_Cons*)luaL_testudata(L, idx, AML_CONS_TYPE);
        if (lcons) {
            if (lcons->cons == NULL) luaL_argerror(L, idx, "invalid constraint");
            item.cons  = lcons->cons;
            item.type  = AML_CONS;
            return item;
        }
        lvar = luaL_testudata(L, idx, AML_VAR_TYPE);
        if (lvar) {
            if (lvar->var == 0) luaL_argerror(L, idx, "invalid variable");
            item.var = lvar->var;
            item.type  = AML_VAR;
            return item;
        }
        /* FALLTHROUGH */
    default:
        aml_typeerror(L, idx, "number/string/variable/constraint");
    }
    return item;
}

static aml_Solver *aml_checkitems(lua_State *L, int start, aml_Item *items) {
    aml_Var *lvar;
    aml_Cons *lcons;
    if ((lcons = (aml_Cons*)luaL_testudata(L, start, AML_CONS_TYPE)) != NULL) {
        aml_Solver *S = *(aml_Solver**)lcons->cons;
        items[0].type = AML_CONS, items[0].cons = lcons->cons;
        items[1] = aml_checkitem(L, S, start+1);
        return S;
    }
    if ((lcons = (aml_Cons*)luaL_testudata(L, start+1, AML_CONS_TYPE)) != NULL) {
        aml_Solver *S = *(aml_Solver**)lcons->cons;
        items[1].type = AML_CONS, items[1].cons = lcons->cons;
        items[0] = aml_checkitem(L, S, start);
        return S;
    }
    if ((lvar = (aml_Var*)luaL_testudata(L, start, AML_VAR_TYPE)) != NULL) {
        if (lvar->var == 0) luaL_argerror(L, start, "invalid variable");
        items[0].type = AML_VAR, items[0].var = lvar->var;
        items[1] = aml_checkitem(L, lvar->S, start+1);
        return lvar->S;
    }
    if ((lvar = (aml_Var*)luaL_testudata(L, start+1, AML_VAR_TYPE)) != NULL) {
        if (lvar->var == 0) luaL_argerror(L, start+1, "invalid variable");
        items[1].type = AML_VAR, items[1].var = lvar->var;
        items[0] = aml_checkitem(L, lvar->S, start);
        return lvar->S;
    }
    aml_typeerror(L, start, "variable/constraint");
    return NULL;
}

static int aml_pusherror(lua_State *L, int ret) {
    if (ret == AM_OK) return lua_settop(L, 1), 1;
    lua_pushnil(L);
    switch (ret) {
    default:             lua_pushfstring(L, "Unknown Error: %d", ret); break;
    case AM_FAILED:      lua_pushliteral(L, "FAILED"); break;
    case AM_UNSATISFIED: lua_pushliteral(L, "UNSATISFIED"); break;
    case AM_UNBOUND:     lua_pushliteral(L, "UNBOUND"); break;
    }
    lua_pushinteger(L, ret);
    return 3;
}

static void aml_performitem(lua_State *L, am_Constraint *cons, aml_Item *item, am_Num coef) {
    int ret = AM_FAILED;
    switch (item->type) {
    case AML_CONSTANT: ret = am_addconstant(cons, item->value*coef); break;
    case AML_VAR:      ret = am_addterm(cons, item->var, coef); break;
    case AML_CONS:     ret = am_mergeconstraint(cons, item->cons, coef); break;
    }
    if (ret != AM_OK) aml_pusherror(L, ret), lua_pop(L, 1), lua_error(L);
}

static am_Num aml_checkstrength(lua_State *L, int idx, am_Num def) {
    int type = lua_type(L, idx);
    const char *s;
    switch (type) {
    case LUA_TSTRING:
        s = lua_tostring(L, idx);
        if (strcmp(s, "required") == 0) return AM_REQUIRED;
        if (strcmp(s, "strong")   == 0) return AM_STRONG;
        if (strcmp(s, "medium")   == 0) return AM_MEDIUM;
        if (strcmp(s, "weak")     == 0) return AM_WEAK;
        aml_argferror(L, idx, "invalid strength value '%s'", s);
        break;
    case LUA_TNONE:
    case LUA_TNIL:    return def;
    case LUA_TNUMBER: return lua_tonumber(L, idx);
    }
    aml_typeerror(L, idx, "number/string");
    return 0.0f;
}

static int aml_checkrelation(lua_State *L, int idx) {
    const char *op = luaL_checkstring(L, idx);
    if (strcmp(op, "==") == 0)      return AM_EQUAL;
    else if (strcmp(op, "<=") == 0) return AM_LESSEQUAL;
    else if (strcmp(op, ">=") == 0) return AM_GREATEQUAL;
    else if (strcmp(op, "eq") == 0)  return AM_EQUAL;
    else if (strcmp(op, "le") == 0)  return AM_LESSEQUAL;
    else if (strcmp(op, "ge") == 0)  return AM_GREATEQUAL;
    return aml_argferror(L, 2, "invalid relation operator: '%s'", op);
}

static aml_Cons *aml_makecons(lua_State *L, aml_Solver *S, int start) {
    aml_Cons *lcons;
    int op = aml_checkrelation(L, start);
    am_Num strength = aml_checkstrength(L, start+3, AM_REQUIRED);
    aml_Item items[2];
    aml_checkitems(L, start+1, items);
    lcons = aml_newcons(L, S, strength);
    aml_performitem(L, lcons->cons, &items[0], 1.0f);
    am_setrelation(lcons->cons, op);
    aml_performitem(L, lcons->cons, &items[1], 1.0f);
    return lcons;
}

static void aml_dumpkey(luaL_Buffer *B, int idx, am_Symbol sym) {
    lua_State *L = B->L;
    aml_Var *lvar;
    lua_rawgeti(L, idx, sym.id);
    lvar = (aml_Var*)luaL_testudata(L, -1, AML_VAR_TYPE);
    lua_pop(L, 1);
    if (lvar) luaL_addstring(B, lvar->name);
    else {
        int ch = 'v';
        switch (sym.type) {
        case AM_EXTERNAL: ch = 'v'; break;
        case AM_SLACK:    ch = 's'; break;
        case AM_ERROR:    ch = 'e'; break;
        case AM_DUMMY:    ch = 'd'; break;
        }
        lua_pushfstring(L, "%c%d", ch, sym.id);
        luaL_addvalue(B);
    }
}

static void aml_dumprow(luaL_Buffer *B, int idx, am_Row *row) {
    am_Iterator it = am_itertable(&row->terms);
    lua_State *L = B->L;
    lua_pushfstring(L, "%f", row->constant);
    luaL_addvalue(B);
    while ((am_nextentry(&it))) {
        am_Num coef = *am_val(am_Num,it);
        lua_pushfstring(L, " %c ", coef > 0.0f ? '+' : '-');
        luaL_addvalue(B);
        if (coef < 0.0f) coef = -coef;
        if (!am_approx(coef, 1.0f)) {
            lua_pushfstring(L, "%f*", coef);
            luaL_addvalue(B);
        }
        aml_dumpkey(B, idx, it.key);
    }
}

/* expression */

static int Lexpr_neg(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    aml_Solver *S = *(aml_Solver**)lcons;
    aml_Cons *newcons = aml_newcons(L, S, AM_REQUIRED);
    return aml_pusherror(L, am_mergeconstraint(newcons->cons, lcons->cons, -1.0f));
}

static int Lexpr_add(lua_State *L) {
    aml_Cons *lcons;
    aml_Item items[2];
    aml_Solver *S = aml_checkitems(L, 1, items);
    lcons = aml_newcons(L, S, AM_REQUIRED);
    aml_performitem(L, lcons->cons, &items[0], 1.0f);
    aml_performitem(L, lcons->cons, &items[1], 1.0f);
    return 1;
}

static int Lexpr_sub(lua_State *L) {
    aml_Cons *lcons;
    aml_Item items[2];
    aml_Solver *S = aml_checkitems(L, 1, items);
    lcons = aml_newcons(L, S, AM_REQUIRED);
    aml_performitem(L, lcons->cons, &items[0], 1.0f);
    aml_performitem(L, lcons->cons, &items[1], -1.0f);
    return 1;
}

static int Lexpr_mul(lua_State *L) {
    aml_Item items[2];
    aml_Solver *S = aml_checkitems(L, 1, items);
    if (items[0].type == AML_CONSTANT) {
        aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED);
        aml_performitem(L, lcons->cons, &items[1], items[0].value);
    }
    else if (items[1].type == AML_CONSTANT) {
        aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED);
        aml_performitem(L, lcons->cons, &items[0], items[1].value);
    }
    else luaL_error(L, "attempt to multiply two expression");
    return 1;
}

static int Lexpr_div(lua_State *L) {
    aml_Item items[2];
    aml_Solver *S = aml_checkitems(L, 1, items);
    if (items[0].type == AML_CONSTANT)
        luaL_error(L, "attempt to divide a expression");
    if (items[1].type == AML_CONSTANT) {
        aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED);
        aml_performitem(L, lcons->cons, &items[0], 1.0f/items[1].value);
    }
    else luaL_error(L, "attempt to divide two expression");
    return 1;
}

static int Lexpr_cmp(lua_State *L, int op) {
    aml_Item items[2];
    aml_Solver *S = aml_checkitems(L, 1, items);
    aml_Cons *lcons = aml_newcons(L, S, AM_REQUIRED);
    aml_performitem(L, lcons->cons, &items[0], 1.0f);
    am_setrelation(lcons->cons, op);
    aml_performitem(L, lcons->cons, &items[1], 1.0f);
    return 1;
}

static int Lexpr_le(lua_State *L) { return Lexpr_cmp(L, AM_LESSEQUAL); }
static int Lexpr_eq(lua_State *L) { return Lexpr_cmp(L, AM_EQUAL); }
static int Lexpr_ge(lua_State *L) { return Lexpr_cmp(L, AM_GREATEQUAL); }

/* variable */

static aml_Var *aml_newvar(lua_State *L, aml_Solver *S, am_Id var, const char *name) {
    aml_Var *lvar = (aml_Var*)lua_newuserdata(L, sizeof(aml_Var));
    if (var == 0) {
        var = am_newvariable(S->solver, &lvar->value);
        if (var == 0) luaL_error(L, "Create variable failed");
    }
    if (name == NULL) {
        lua_settop(L, 1);
        lua_pushfstring(L, "v%d", var);
        name = lua_tostring(L, 2);
    }
    lvar->S    = S;
    lvar->var  = var;
    lvar->name = name;
    luaL_setmetatable(L, AML_VAR_TYPE);
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    lua_pushvalue(L, -2);
    lua_setfield(L, -2, name);
    lua_pushvalue(L, -2);
    lua_rawseti(L, -2, var);
    lua_pop(L, 1);
    return lvar;
}

static int Lvar_new(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    int type = lua_type(L, 2);
    if (type != LUA_TNONE || type != LUA_TNIL) {
        if (type != LUA_TSTRING && type != LUA_TNUMBER)
            return aml_typeerror(L, 2, "number/string");
        lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
        lua_pushvalue(L, 2);
        if (lua_rawget(L, -2) != LUA_TNIL) return 1;
        if (type == LUA_TNUMBER)
            aml_argferror(L, 2, "variable#%d not exists", lua_tointeger(L, 2));
        lua_pop(L, 1);
    }
    return aml_newvar(L, S, 0, lua_tostring(L, 2)), 1;
}

static int Lvar_delete(lua_State *L) {
    aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE);
    if (lvar->var == 0) return 0;
    am_delvariable(lvar->S->solver, lvar->var);
    lua_rawgeti(L, LUA_REGISTRYINDEX, lvar->S->ref_vars);
    lua_pushnil(L);
    lua_setfield(L, -2, lvar->name);
    lvar->var  = 0;
    lvar->name = NULL;
    return 0;
}

static int Lvar_value(lua_State *L) {
    aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE);
    if (lvar->var == 0) luaL_argerror(L, 1, "invalid variable");
    return lua_pushnumber(L, lvar->value), 1;
}

static int Lvar_tostring(lua_State *L) {
    aml_Var *lvar = (aml_Var*)luaL_checkudata(L, 1, AML_VAR_TYPE);
    if (lvar->var) lua_pushfstring(L, AML_VAR_TYPE "(%p): %s = %f",
                lvar->var, lvar->name, lvar->value);
    else lua_pushstring(L, AML_VAR_TYPE ": deleted");
    return 1;
}

static void open_variable(lua_State *L) {
    luaL_Reg libs[] = {
        { "__neg", Lexpr_neg },
        { "__add", Lexpr_add },
        { "__sub", Lexpr_sub },
        { "__mul", Lexpr_mul },
        { "__div", Lexpr_div },
        { "le", Lexpr_le },
        { "eq", Lexpr_eq },
        { "ge", Lexpr_ge },
        { "__tostring", Lvar_tostring },
        { "__gc", Lvar_delete },
#define ENTRY(name) { #name, Lvar_##name }
        ENTRY(new),
        ENTRY(delete),
        ENTRY(value),
#undef  ENTRY
        { NULL, NULL }
    };
    if (luaL_newmetatable(L, AML_VAR_TYPE)) {
        luaL_setfuncs(L, libs, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }
}

/* constraint */

static int Lcons_new(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    if (lua_gettop(L) >= 3) aml_makecons(L, S, 2);
    else aml_newcons(L, S, aml_checkstrength(L, 2, AM_REQUIRED));
    return 1;
}

static int Lcons_delete(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    aml_Solver *S = *(aml_Solver**)lcons->cons;
    if (lcons->cons == NULL) return 0;
    am_delconstraint(lcons->cons);
    lcons->cons = NULL;
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    lua_pushnil(L);
    lua_rawsetp(L, -2, lcons);
    return 0;
}

static int Lcons_reset(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint");
    am_resetconstraint(lcons->cons);
    return lua_settop(L, 1), 1;
}

static int Lcons_add(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    aml_Solver *S = *(aml_Solver**)lcons;
    aml_Item item;
    int ret;
    luaL_argcheck(L, lcons->cons, 1, "invalid constraint");
    if (lua_type(L, 2) == LUA_TSTRING) {
        const char *s = lua_tostring(L, 2);
        if (s[0] == '<' || s[0] == '>' || s[0] == '=') {
            ret = am_setrelation(lcons->cons, aml_checkrelation(L, 2));
            if (ret != AM_OK) luaL_error(L, "constraint has been added to solver!");
            return lua_settop(L, 1), 1;
        }
    }
    item = aml_checkitem(L, S, 2);
    aml_performitem(L, lcons->cons, &item, 1.0f);
    return lua_settop(L, 1), 1;
}

static int Lcons_relation(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    int op = aml_checkrelation(L, 2);
    if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint");
    if (am_setrelation(lcons->cons, op) != AM_OK)
        luaL_error(L, "constraint has been added to solver!");
    return lua_settop(L, 1), 1;
}

static int Lcons_strength(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    am_Num strength = aml_checkstrength(L, 2, AM_REQUIRED);
    if (lcons->cons == NULL) luaL_argerror(L, 1, "invalid constraint");
    if (am_setstrength(lcons->cons, strength) != AM_OK)
        luaL_error(L, "constraint has been added to solver!");
    return lua_settop(L, 1), 1;
}

static int Lcons_tostring(lua_State *L) {
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 1, AML_CONS_TYPE);
    aml_Solver *S = *(aml_Solver**)lcons;
    luaL_Buffer B;
    if (lcons->cons == NULL) {
        lua_pushstring(L, AML_CONS_TYPE ": deleted");
        return 1;
    }
    lua_settop(L, 1);
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    luaL_buffinit(L, &B);
    lua_pushfstring(L, AML_CONS_TYPE "(%p): [", lcons->cons);
    luaL_addvalue(&B);
    aml_dumprow(&B, 2, &lcons->cons->expression);
    if (lcons->cons->relation == AM_EQUAL)
        luaL_addstring(&B, " == 0.0]");
    else
        luaL_addstring(&B, " >= 0.0]");
    if (lcons->cons->marker.id != 0) {
        luaL_addstring(&B, "(added:");
        aml_dumpkey(&B, 2, lcons->cons->marker);
        luaL_addchar(&B, '-');
        aml_dumpkey(&B, 2, lcons->cons->other);
        luaL_addchar(&B, ')');
    }
    luaL_pushresult(&B);
    return 1;
}

static void open_constraint(lua_State *L) {
    luaL_Reg libs[] = {
        { "__call", Lcons_add },
        { "__neg", Lexpr_neg },
        { "__add", Lexpr_add },
        { "__sub", Lexpr_sub },
        { "__mul", Lexpr_mul },
        { "__div", Lexpr_div },
        { "le", Lexpr_le },
        { "eq", Lexpr_eq },
        { "ge", Lexpr_ge },
        { "__bor", Lcons_strength },
        { "__tostring", Lcons_tostring },
        { "__gc", Lcons_delete },
#define ENTRY(name) { #name, Lcons_##name }
        ENTRY(new),
        ENTRY(delete),
        ENTRY(reset),
        ENTRY(add),
        ENTRY(relation),
        ENTRY(strength),
#undef  ENTRY
        { NULL, NULL }
    };
    if (luaL_newmetatable(L, AML_CONS_TYPE)) {
        luaL_setfuncs(L, libs, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }
}

/* dump & load */

typedef struct aml_Dumper {
    am_Dumper base;
    luaL_Buffer *B;
} aml_Dumper;

static const char *aml_varname(am_Dumper *d, unsigned idx, am_Id var, am_Num *value)
{ return (void)d, (void)idx, (void)var, ((aml_Var*)value)->name; }

static int aml_writer(am_Dumper *d, const void *buf, size_t len) {
    /* Write the actually data */
    aml_Dumper *ld = (aml_Dumper*)d;
    luaL_addlstring(ld->B, buf, len);
    return AM_OK;
}

static int Ldump(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    luaL_Buffer B;
    aml_Dumper d;
    int ret;
    memset(&d, 0, sizeof(d));
    d.base.var_name = aml_varname;
    d.base.writer = aml_writer;
    d.B = &B;
    luaL_buffinit(L, &B);
    ret = am_dump(S->solver, &d.base);
    if (ret == AM_OK)
        return luaL_pushresult(&B), 1;
    return aml_pusherror(L, ret);
}

typedef struct aml_Loader {
    am_Loader  base;
    lua_State  *L;
    aml_Solver *S;
    const char *s;
    size_t      len;
    int         cons_index;
    int         loader_index;
} aml_Loader;

static am_Num *aml_loadvar(am_Loader *l, const char *name, unsigned idx, am_Id var) {
    aml_Loader *ll = (aml_Loader*)l;
    aml_Var *lvar = aml_newvar(ll->L, ll->S, var, name);
    return (void)idx, &lvar->value;
}

static void aml_loadcons(am_Loader *l, const char *name, unsigned idx, am_Constraint *cons) {
    aml_Loader *ll = (aml_Loader*)l;
    if (ll->cons_index == 0) return;
    (void)name;
    *(aml_Solver**)cons = ll->S;
    aml_registercons(ll->L, cons);
    lua_rawseti(ll->L, ll->cons_index, idx);
}

static const char *aml_reader(am_Loader *l, size_t *plen) {
    aml_Loader *ll = (aml_Loader*)l;
    lua_State *L = ll->L;
    if (ll->loader_index == 0) {
        const char *p = ll->s;
        *plen = ll->len;
        ll->s = NULL, ll->len = 0;
        return p;
    }
    luaL_checkstack(L, 2, "too many nested functions");
    lua_pushvalue(L, 1);
    lua_call(L, 0, 1);
    if (lua_isnil(L, -1))
        return lua_pop(L, 1), *plen = 0, NULL;
    else if (!lua_isstring(L, -1))
        luaL_error(L, "reader function must return a string");
    lua_replace(L, ll->loader_index);  /* save string in reserved slot */
    return lua_tolstring(L, ll->loader_index, plen);
}

static int Lload(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    aml_Loader ll;
    memset(&ll, 0, sizeof(ll));
    ll.s = lua_tolstring(L, 1, &ll.len);
    ll.L = L, ll.S = S;
    ll.cons_index = (!lua_isnone(L, 2) ? 2 : 0);
    ll.base.load_var = aml_loadvar;
    ll.base.load_cons = aml_loadcons;
    ll.base.reader = aml_reader;
    if (ll.s == NULL) {
        luaL_checktype(L, 1, LUA_TFUNCTION);
        ll.loader_index = 3;
        lua_settop(L, 3);  /* create reserved slot */
    }
    return aml_pusherror(L, am_load(S->solver, &ll.base));
}

/* solver */

static int Lnew(lua_State *L) {
    aml_Solver *S = lua_newuserdata(L, sizeof(aml_Solver));
    if ((S->solver = am_newsolver(NULL, NULL)) == NULL)
        return 0;
    lua_createtable(L, 0, 4); aml_setweak(L, "v");
    S->ref_vars = luaL_ref(L, LUA_REGISTRYINDEX);
    lua_createtable(L, 0, 4); aml_setweak(L, "v");
    S->ref_cons = luaL_ref(L, LUA_REGISTRYINDEX);
    luaL_setmetatable(L, AML_SOLVER_TYPE);
    am_autoupdate(S->solver, lua_toboolean(L, 1));
    return 1;
}

static int Ldelete(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    if (S->solver == NULL) return 0;
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    lua_pushnil(L);
    while (lua_next(L, -2)) {
        aml_Var *lvar = (aml_Var*)luaL_testudata(L, -1, AML_VAR_TYPE);
        if (lvar && lvar->var) {
            am_delvariable(lvar->S->solver, lvar->var);
            lvar->var  = 0, lvar->name = NULL;
        }
        lua_pop(L, 1);
    }
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_cons);
    lua_pushnil(L);
    while (lua_next(L, -2)) {
        aml_Cons *lcons = (aml_Cons*)luaL_testudata(L, -1, AML_CONS_TYPE);
        if (lcons && lcons->cons) {
            am_delconstraint(lcons->cons);
            lcons->cons = NULL;
        }
        lua_pop(L, 1);
    }
    luaL_unref(L, LUA_REGISTRYINDEX, S->ref_vars);
    luaL_unref(L, LUA_REGISTRYINDEX, S->ref_cons);
    am_delsolver(S->solver);
    S->solver = NULL;
    return 0;
}

static int Ltostring(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    luaL_Buffer B;
    lua_settop(L, 1);
    lua_rawgeti(L, LUA_REGISTRYINDEX, S->ref_vars);
    luaL_buffinit(L, &B);
    lua_pushfstring(L, AML_SOLVER_TYPE "(%p): {", S->solver);
    luaL_addvalue(&B);
    luaL_addstring(&B, "\n  objective = ");
    aml_dumprow(&B, 2, &S->solver->objective);
    if (S->solver->rows.count != 0) {
        am_Iterator it = am_itertable(&S->solver->rows);
        int idx = 0;
        lua_pushfstring(L, "\n  rows(%d):", S->solver->rows.count);
        luaL_addvalue(&B);
        while (am_nextentry(&it)) {
            am_Row *row = am_val(am_Row,it);
            lua_pushfstring(L, "\n    %d. ", ++idx);
            luaL_addvalue(&B);
            aml_dumpkey(&B, 2, it.key);
            luaL_addstring(&B, " = ");
            aml_dumprow(&B, 2, row);
        }
    }
    if (S->solver->infeasible_rows.id != 0) {
        am_Symbol key = S->solver->infeasible_rows;
        am_Row *row = (am_Row*)am_gettable(&S->solver->rows, key.id);
        luaL_addstring(&B, "\n  infeasible rows: ");
        aml_dumpkey(&B, 2, key);
        while (row != NULL) {
            luaL_addstring(&B, ", ");
            aml_dumpkey(&B, 2, key);
            row = (am_Row*)am_gettable(&S->solver->rows, row->infeasible_next.id);
        }
    }
    luaL_addstring(&B, "\n}");
    luaL_pushresult(&B);
    return 1;
}

static int Lreset(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_resetsolver(S->solver);
    return lua_settop(L, 1), 1;
}

static int Lclearedits(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_clearedits(S->solver);
    return lua_settop(L, 1), 1;
}

static int Lautoupdate(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_autoupdate(S->solver, lua_toboolean(L, 2));
    return 0;
}

static int Laddconstraint(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    aml_Cons *lcons = (aml_Cons*)luaL_testudata(L, 2, AML_CONS_TYPE);
    if (lcons == NULL) lcons = aml_makecons(L, S, 2);
    return aml_pusherror(L, am_add(lcons->cons));
}

static int Ldelconstraint(lua_State *L) {
    luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    aml_Cons *lcons = (aml_Cons*)luaL_checkudata(L, 2, AML_CONS_TYPE);
    am_remove(lcons->cons);
    return lua_settop(L, 1), 1;
}

static int Laddedit(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_Id  var = aml_checkvar(L, S, 2);
    am_Num strength = aml_checkstrength(L, 3, AM_MEDIUM);
    return aml_pusherror(L, am_addedit(S->solver, var, strength));
}

static int Ldeledit(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_Id var = aml_checkvar(L, S, 2);
    am_deledit(S->solver, var);
    return lua_settop(L, 1), 1;
}

static int Lsuggest(lua_State *L) {
    aml_Solver *S = (aml_Solver*)luaL_checkudata(L, 1, AML_SOLVER_TYPE);
    am_Id  var = aml_checkvar(L, S, 2);
    am_Num value = (am_Num)luaL_checknumber(L, 3);
    am_suggest(S->solver, var, value);
    return lua_settop(L, 1), 1;
}

LUALIB_API int luaopen_amoeba(lua_State *L) {
    luaL_Reg libs[] = {
        { "var",        Lvar_new  },
        { "constraint", Lcons_new },
        { "__tostring", Ltostring },
#define ENTRY(name) { #name, L##name }
        ENTRY(new),
        ENTRY(delete),
        ENTRY(autoupdate),
        ENTRY(reset),
        ENTRY(addconstraint),
        ENTRY(delconstraint),
        ENTRY(clearedits),
        ENTRY(addedit),
        ENTRY(deledit),
        ENTRY(suggest),
        ENTRY(dump),
        ENTRY(load),
#undef  ENTRY
        { NULL, NULL }
    };
    open_variable(L);
    open_constraint(L);
    if (luaL_newmetatable(L, AML_SOLVER_TYPE)) {
        luaL_setfuncs(L, libs, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }
    return 1;
}

/* maccc: flags+='-undefined dynamic_lookup -bundle -O2' output='amoeba.so'
 * win32cc: flags+='-DLUA_BUILD_AS_DLL -shared -O3' libs+='-llua54' output='amoeba.dll' */



================================================
FILE: nanobench.h
================================================
//  __   _ _______ __   _  _____  ______  _______ __   _ _______ _     _
//  | \  | |_____| | \  | |     | |_____] |______ | \  | |       |_____|
//  |  \_| |     | |  \_| |_____| |_____] |______ |  \_| |_____  |     |
//
// Microbenchmark framework for C++11/14/17/20
// https://github.com/martinus/nanobench
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2019-2020 Martin Ankerl <martin.ankerl@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef ANKERL_NANOBENCH_H_INCLUDED
#define ANKERL_NANOBENCH_H_INCLUDED

// see https://semver.org/
#define ANKERL_NANOBENCH_VERSION_MAJOR 4 // incompatible API changes
#define ANKERL_NANOBENCH_VERSION_MINOR 2 // backwards-compatible changes
#define ANKERL_NANOBENCH_VERSION_PATCH 0 // backwards-compatible bug fixes

///////////////////////////////////////////////////////////////////////////////////////////////////
// public facing api - as minimal as possible
///////////////////////////////////////////////////////////////////////////////////////////////////

#include <chrono>  // high_resolution_clock
#include <cstring> // memcpy
#include <iosfwd>  // for std::ostream* custom output target in Config
#include <string>  // all names
#include <vector>  // holds all results

#define ANKERL_NANOBENCH(x) ANKERL_NANOBENCH_PRIVATE_##x()

#define ANKERL_NANOBENCH_PRIVATE_CXX() __cplusplus
#define ANKERL_NANOBENCH_PRIVATE_CXX98() 199711L
#define ANKERL_NANOBENCH_PRIVATE_CXX11() 201103L
#define ANKERL_NANOBENCH_PRIVATE_CXX14() 201402L
#define ANKERL_NANOBENCH_PRIVATE_CXX17() 201703L

#if ANKERL_NANOBENCH(CXX) >= ANKERL_NANOBENCH(CXX17)
#    define ANKERL_NANOBENCH_PRIVATE_NODISCARD() [[nodiscard]]
#else
#    define ANKERL_NANOBENCH_PRIVATE_NODISCARD()
#endif

#if defined(__clang__)
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() \
        _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpadded\"")
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() _Pragma("clang diagnostic pop")
#else
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH()
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP()
#endif

#if defined(__GNUC__)
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Weffc++\"")
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() _Pragma("GCC diagnostic pop")
#else
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH()
#    define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP()
#endif

#if defined(ANKERL_NANOBENCH_LOG_ENABLED)
#    include <iostream>
#    define ANKERL_NANOBENCH_LOG(x)                                                 \
        do {                                                                        \
            std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl; \
        } while (0)
#else
#    define ANKERL_NANOBENCH_LOG(x) \
        do {                        \
        } while (0)
#endif

#if defined(__linux__) && !defined(ANKERL_NANOBENCH_DISABLE_PERF_COUNTERS)
#    define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 1
#else
#    define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 0
#endif

#if defined(__clang__)
#    define ANKERL_NANOBENCH_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))
#else
#    define ANKERL_NANOBENCH_NO_SANITIZE(...)
#endif

#if defined(_MSC_VER)
#    define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __declspec(noinline)
#else
#    define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __attribute__((noinline))
#endif

// workaround missing "is_trivially_copyable" in g++ < 5.0
// See https://stackoverflow.com/a/31798726/48181
#if defined(__GNUC__) && __GNUC__ < 5
#    define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
#else
#    define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
#endif

// declarations ///////////////////////////////////////////////////////////////////////////////////

namespace ankerl {
namespace nanobench {

using Clock = std::conditional<std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock,
                               std::chrono::steady_clock>::type;
class Bench;
struct Config;
class Result;
class Rng;
class BigO;

/**
 * @brief Renders output from a mustache-like template and benchmark results.
 *
 * The templating facility here is heavily inspired by [mustache - logic-less templates](https://mustache.github.io/).
 * It adds a few more features that are necessary to get all of the captured data out of nanobench. Please read the
 * excellent [mustache manual](https://mustache.github.io/mustache.5.html) to see what this is all about.
 *
 * nanobench output has two nested layers, *result* and *measurement*.  Here is a hierarchy of the allowed tags:
 *
 * * `{{#result}}` Marks the begin of the result layer. Whatever comes after this will be instantiated as often as
 *   a benchmark result is available. Within it, you can use these tags:
 *
 *    * `{{title}}` See Bench::title().
 *
 *    * `{{name}}` Benchmark name, usually directly provided with Bench::run(), but can also be set with Bench::name().
 *
 *    * `{{unit}}` Unit, e.g. `byte`. Defaults to `op`, see Bench::title().
 *
 *    * `{{batch}}` Batch size, see Bench::batch().
 *
 *    * `{{complexityN}}` Value used for asymptotic complexity calculation. See Bench::complexityN().
 *
 *    * `{{epochs}}` Number of epochs, see Bench::epochs().
 *
 *    * `{{clockResolution}}` Accuracy of the clock, i.e. what's the smallest time possible to measure with the clock.
 *      For modern systems, this can be around 20 ns. This value is automatically determined by nanobench at the first
 *      benchmark that is run, and used as a static variable throughout the application's runtime.
 *
 *    * `{{clockResolutionMultiple}}` Configuration multiplier for `clockResolution`. See Bench::clockResolutionMultiple().
 *      This is the target runtime for each measurement (epoch). That means the more accurate your clock is, the faster
 *      will be the benchmark. Basing the measurement's runtime on the clock resolution is the main reason why nanobench is so fast.
 *
 *    * `{{maxEpochTime}}` Configuration for a maximum time each measurement (epoch) is allowed to take. Note that at least
 *      a single iteration will be performed, even when that takes longer than maxEpochTime. See Bench::maxEpochTime().
 *
 *    * `{{minEpochTime}}` Minimum epoch time, usually not set. See Bench::minEpochTime().
 *
 *    * `{{minEpochIterations}}` See Bench::minEpochIterations().
 *
 *    * `{{epochIterations}}` See Bench::epochIterations().
 *
 *    * `{{warmup}}` Number of iterations used before measuring starts. See Bench::warmup().
 *
 *    * `{{relative}}` True or false, depending on the setting you have used. See Bench::relative().
 *
 *    Apart from these tags, it is also possible to use some mathematical operations on the measurement data. The operations
 *    are of the form `{{command(name)}}`.  Currently `name` can be one of `elapsed`, `iterations`. If performance counters
 *    are available (currently only on current Linux systems), you also have `pagefaults`, `cpucycles`,
 *    `contextswitches`, `instructions`, `branchinstructions`, and `branchmisses`. All the measuers (except `iterations`) are
 *    provided for a single iteration (so `elapsed` is the time a single iteration took). The following tags are available:
 *
 *    * `{{median(<name>)}}` Calculate median of a measurement data set, e.g. `{{median(elapsed)}}`.
 *
 *    * `{{average(<name>)}}` Average (mean) calculation.
 *
 *    * `{{medianAbsolutePercentError(<name>)}}` Calculates MdAPE, the Median Absolute Percentage Error. The MdAPE is an excellent
 *      metric for the variation of measurements. It is more robust to outliers than the
 *      [Mean absolute percentage error (M-APE)](https://en.wikipedia.org/wiki/Mean_absolute_percentage_error).
 *      @f[
 *       \mathrm{MdAPE}(e) = \mathrm{med}\{| \frac{e_i - \mathrm{med}\{e\}}{e_i}| \}
 *      @f]
 *      E.g. for *elapsed*: First, @f$ \mathrm{med}\{e\} @f$ calculates the median by sorting and then taking the middle element
 *      of all *elapsed* measurements. This is used to calculate the absolute percentage
 *      error to this median for each measurement, as in  @f$ | \frac{e_i - \mathrm{med}\{e\}}{e_i}| @f$. All these results
 *      are sorted, and the middle value is chosen as the median absolute percent error.
 *
 *      This measurement is a bit hard to interpret, but it is very robust against outliers. E.g. a value of 5% means that half of the
 *      measurements deviate less than 5% from the median, and the other deviate more than 5% from the median.
 *
 *    * `{{sum(<name>)}}` Sums of all the measurements. E.g. `{{sum(iterations)}}` will give you the total number of iterations
*        measured in this benchmark.
 *
 *    * `{{minimum(<name>)}}` Minimum of all measurements.
 *
 *    * `{{maximum(<name>)}}` Maximum of all measurements.
 *
 *    * `{{sumProduct(<first>, <second>)}}` Calculates the sum of the products of corresponding measures:
 *      @f[
 *          \mathrm{sumProduct}(a,b) = \sum_{i=1}^{n}a_i\cdot b_i
 *      @f]
 *      E.g. to calculate total runtime of the benchmark, you multiply iterations with elapsed time for each measurement, and
 *      sum these results up:
 *      `{{sumProduct(iterations, elapsed)}}`.
 *
 *    * `{{#measurement}}` To access individual measurement results, open the begin tag for measurements.
 *
 *       * `{{elapsed}}` Average elapsed wall clock time per iteration, in seconds.
 *
 *       * `{{iterations}}` Number of iterations in the measurement. The number of iterations will fluctuate due
 *         to some applied randomness, to enhance accuracy.
 *
 *       * `{{pagefaults}}` Average number of pagefaults per iteration.
 *
 *       * `{{cpucycles}}` Average number of CPU cycles processed per iteration.
 *
 *       * `{{contextswitches}}` Average number of context switches per iteration.
 *
 *       * `{{instructions}}` Average number of retired instructions per iteration.
 *
 *       * `{{branchinstructions}}` Average number of branches executed per iteration.
 *
 *       * `{{branchmisses}}` Average number of branches that were missed per iteration.
 *
 *    * `{{/measurement}}` Ends the measurement tag.
 *
 * * `{{/result}}` Marks the end of the result layer. This is the end marker for the template part that will be instantiated
 *   for each benchmark result.
 *
 *
 *  For the layer tags *result* and *measurement* you additionally can use these special markers:
 *
 *  * ``{{#-first}}`` - Begin marker of a template that will be instantiated *only for the first* entry in the layer. Use is only
 *    allowed between the begin and end marker of the layer allowed. So between ``{{#result}}`` and ``{{/result}}``, or between
 *    ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-first}}``.
 *
 *  * ``{{^-first}}`` - Begin marker of a template that will be instantiated *for each except the first* entry in the layer. This,
 *    this is basically the inversion of ``{{#-first}}``. Use is only allowed between the begin and end marker of the layer allowed.
 *    So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``.
 *
 *  * ``{{/-first}}`` - End marker for either ``{{#-first}}`` or ``{{^-first}}``.
 *
 *  * ``{{#-last}}`` - Begin marker of a template that will be instantiated *only for the last* entry in the layer. Use is only
 *    allowed between the begin and end marker of the layer allowed. So between ``{{#result}}`` and ``{{/result}}``, or between
 *    ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-last}}``.
 *
 *  * ``{{^-last}}`` - Begin marker of a template that will be instantiated *for each except the last* entry in the layer. This,
 *    this is basically the inversion of ``{{#-last}}``. Use is only allowed between the begin and end marker of the layer allowed.
 *    So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``.
 *
 *  * ``{{/-last}}`` - End marker for either ``{{#-last}}`` or ``{{^-last}}``.
 *
   @verbatim embed:rst

   For an overview of all the possible data you can get out of nanobench, please see the tutorial at :ref:`tutorial-template-json`.

   The templates that ship with nanobench are:

   * :cpp:func:`templates::csv() <ankerl::nanobench::templates::csv()>`
   * :cpp:func:`templates::json() <ankerl::nanobench::templates::json()>`
   * :cpp:func:`templates::htmlBoxplot() <ankerl::nanobench::templates::htmlBoxplot()>`
   * :cpp:func:`templates::pyperf() <ankerl::nanobench::templates::pyperf()>`

   @endverbatim
 *
 * @param mustacheTemplate The template.
 * @param bench Benchmark, containing all the results.
 * @param out Output for the generated output.
 */
void render(char const* mustacheTemplate, Bench const& bench, std::ostream& out);
void render(std::string const& mustacheTemplate, Bench const& bench, std::ostream& out);

/**
 * Same as render(char const* mustacheTemplate, Bench const& bench, std::ostream& out), but for when
 * you only have results available.
 *
 * @param mustacheTemplate The template.
 * @param results All the results to be used for rendering.
 * @param out Output for the generated output.
 */
void render(char const* mustacheTemplate, std::vector<Result> const& results, std::ostream& out);
void render(std::string const& mustacheTemplate, std::vector<Result> const& results, std::ostream& out);

// Contains mustache-like templates
namespace templates {

/*!
  @brief CSV data for the benchmark results.

  Generates a comma-separated values dataset. First line is the header, each following line is a summary of each benchmark run.

  @verbatim embed:rst
  See the tutorial at :ref:`tutorial-template-csv` for an example.
  @endverbatim
 */
char const* csv() noexcept;

/*!
  @brief HTML output that uses plotly to generate an interactive boxplot chart. See the tutorial for an example output.

  The output uses only the elapsed wall clock time, and displays each epoch as a single dot.
  @verbatim embed:rst
  See the tutorial at :ref:`tutorial-template-html` for an example.
  @endverbatim

  @see ankerl::nanobench::render()
 */
char const* htmlBoxplot() noexcept;

/*!
 @brief Output in pyperf  compatible JSON format, which can be used for more analyzations.
 @verbatim embed:rst
 See the tutorial at :ref:`tutorial-template-pyperf` for an example how to further analyze the output.
 @endverbatim
 */
char const* pyperf() noexcept;

/*!
  @brief Template to generate JSON data.

  The generated JSON data contains *all* data that has been generated. All times are as double values, in seconds. The output can get
  quite large.
  @verbatim embed:rst
  See the tutorial at :ref:`tutorial-template-json` for an example.
  @endverbatim
 */
char const* json() noexcept;

} // namespace templates

namespace detail {

template <typename T>
struct PerfCountSet;

class IterationLogic;
class PerformanceCounters;

#if ANKERL_NANOBENCH(PERF_COUNTERS)
class LinuxPerformanceCounters;
#endif

} // namespace detail
} // namespace nanobench
} // namespace ankerl

// definitions ////////////////////////////////////////////////////////////////////////////////////

namespace ankerl {
namespace nanobench {
namespace detail {

template <typename T>
struct PerfCountSet {
    T pageFaults{};
    T cpuCycles{};
    T contextSwitches{};
    T instructions{};
    T branchInstructions{};
    T branchMisses{};
};

} // namespace detail

ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
struct Config {
    // actual benchmark config
    std::string mBenchmarkTitle = "benchmark";
    std::string mBenchmarkName = "noname";
    std::string mUnit = "op";
    double mBatch = 1.0;
    double mComplexityN = -1.0;
    size_t mNumEpochs = 11;
    size_t mClockResolutionMultiple = static_cast<size_t>(1000);
    std::chrono::nanoseconds mMaxEpochTime = std::chrono::milliseconds(100);
    std::chrono::nanoseconds mMinEpochTime{};
    uint64_t mMinEpochIterations{1};
    uint64_t mEpochIterations{0}; // If not 0, run *exactly* these number of iterations per epoch.
    uint64_t mWarmup = 0;
    std::ostream* mOut = nullptr;
    std::chrono::duration<double> mTimeUnit = std::chrono::nanoseconds{1};
    std::string mTimeUnitName = "ns";
    bool mShowPerformanceCounters = true;
    bool mIsRelative = false;

    Config();
    ~Config();
    Config& operator=(Config const&);
    Config& operator=(Config&&);
    Config(Config const&);
    Config(Config&&) noexcept;
};
ANKERL_NANOBENCH(IGNORE_PADDED_POP)

// Result returned after a benchmark has finished. Can be used as a baseline for relative().
ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
class Result {
public:
    enum class Measure : size_t {
        elapsed,
        iterations,
        pagefaults,
        cpucycles,
        contextswitches,
        instructions,
        branchinstructions,
        branchmisses,
        _size
    };

    explicit Result(Config const& benchmarkConfig);

    ~Result();
    Result& operator=(Result const&);
    Result& operator=(Result&&);
    Result(Result const&);
    Result(Result&&) noexcept;

    // adds new measurement results
    // all values are scaled by iters (except iters...)
    void add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc);

    ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept;

    ANKERL_NANOBENCH(NODISCARD) double median(Measure m) const;
    ANKERL_NANOBENCH(NODISCARD) double medianAbsolutePercentError(Measure m) const;
    ANKERL_NANOBENCH(NODISCARD) double average(Measure m) const;
    ANKERL_NANOBENCH(NODISCARD) double sum(Measure m) const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double sumProduct(Measure m1, Measure m2) const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double minimum(Measure m) const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double maximum(Measure m) const noexcept;

    ANKERL_NANOBENCH(NODISCARD) bool has(Measure m) const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double get(size_t idx, Measure m) const;
    ANKERL_NANOBENCH(NODISCARD) bool empty() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) size_t size() const noexcept;

    // Finds string, if not found, returns _size.
    static Measure fromString(std::string const& str);

private:
    Config mConfig{};
    std::vector<std::vector<double>> mNameToMeasurements{};
};
ANKERL_NANOBENCH(IGNORE_PADDED_POP)

/**
 * An extremely fast random generator. Currently, this implements *RomuDuoJr*, developed by Mark Overton. Source:
 * http://www.romu-random.org/
 *
 * RomuDuoJr is extremely fast and provides reasonable good randomness. Not enough for large jobs, but definitely
 * good enough for a benchmarking framework.
 *
 *  * Estimated capacity: @f$ 2^{51} @f$ bytes
 *  * Register pressure: 4
 *  * State size: 128 bits
 *
 * This random generator is a drop-in replacement for the generators supplied by ``<random>``. It is not
 * cryptographically secure. It's intended purpose is to be very fast so that benchmarks that make use
 * of randomness are not distorted too much by the random generator.
 *
 * Rng also provides a few non-standard helpers, optimized for speed.
 */
class Rng final {
public:
    /**
     * @brief This RNG provides 64bit randomness.
     */
    using result_type = uint64_t;

    static constexpr uint64_t(min)();
    static constexpr uint64_t(max)();

    /**
     * As a safety precausion, we don't allow copying. Copying a PRNG would mean you would have two random generators that produce the
     * same sequence, which is generally not what one wants. Instead create a new rng with the default constructor Rng(), which is
     * automatically seeded from `std::random_device`. If you really need a copy, use copy().
     */
    Rng(Rng const&) = delete;

    /**
     * Same as Rng(Rng const&), we don't allow assignment. If you need a new Rng create one with the default constructor Rng().
     */
    Rng& operator=(Rng const&) = delete;

    // moving is ok
    Rng(Rng&&) noexcept = default;
    Rng& operator=(Rng&&) noexcept = default;
    ~Rng() noexcept = default;

    /**
     * @brief Creates a new Random generator with random seed.
     *
     * Instead of a default seed (as the random generators from the STD), this properly seeds the random generator from
     * `std::random_device`. It guarantees correct seeding. Note that seeding can be relatively slow, depending on the source of
     * randomness used. So it is best to create a Rng once and use it for all your randomness purposes.
     */
    Rng();

    /*!
      Creates a new Rng that is seeded with a specific seed. Each Rng created from the same seed will produce the same randomness
      sequence. This can be useful for deterministic behavior.

      @verbatim embed:rst
      .. note::

         The random algorithm might change between nanobench releases. Whenever a faster and/or better random
         generator becomes available, I will switch the implementation.
      @endverbatim

      As per the Romu paper, this seeds the Rng with splitMix64 algorithm and performs 10 initial rounds for further mixing up of the
      internal state.

      @param seed  The 64bit seed. All values are allowed, even 0.
     */
    explicit Rng(uint64_t seed) noexcept;
    Rng(uint64_t x, uint64_t y) noexcept;

    /**
     * Creates a copy of the Rng, thus the copy provides exactly the same random sequence as the original.
     */
    ANKERL_NANOBENCH(NODISCARD) Rng copy() const noexcept;

    /**
     * @brief Produces a 64bit random value. This should be very fast, thus it is marked as inline. In my benchmark, this is ~46 times
     * faster than `std::default_random_engine` for producing 64bit random values. It seems that the fastest std contender is
     * `std::mt19937_64`. Still, this RNG is 2-3 times as fast.
     *
     * @return uint64_t The next 64 bit random value.
     */
    inline uint64_t operator()() noexcept;

    // This is slightly biased. See

    /**
     * Generates a random number between 0 and range (excluding range).
     *
     * The algorithm only produces 32bit numbers, and is slightly biased. The effect is quite small unless your range is close to the
     * maximum value of an integer. It is possible to correct the bias with rejection sampling (see
     * [here](https://lemire.me/blog/2016/06/30/fast-random-shuffling/), but this is most likely irrelevant in practices for the
     * purposes of this Rng.
     *
     * See Daniel Lemire's blog post [A fast alternative to the modulo
     * reduction](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/)
     *
     * @param range Upper exclusive range. E.g a value of 3 will generate random numbers 0, 1, 2.
     * @return uint32_t Generated random values in range [0, range(.
     */
    inline uint32_t bounded(uint32_t range) noexcept;

    // random double in range [0, 1(
    // see http://prng.di.unimi.it/

    /**
     * Provides a random uniform double value between 0 and 1. This uses the method described in [Generating uniform doubles in the
     * unit interval](http://prng.di.unimi.it/), and is extremely fast.
     *
     * @return double Uniformly distributed double value in range [0,1(, excluding 1.
     */
    inline double uniform01() noexcept;

    /**
     * Shuffles all entries in the given container. Although this has a slight bias due to the implementation of bounded(), this is
     * preferable to `std::shuffle` because it is over 5 times faster. See Daniel Lemire's blog post [Fast random
     * shuffling](https://lemire.me/blog/2016/06/30/fast-random-shuffling/).
     *
     * @param container The whole container will be shuffled.
     */
    template <typename Container>
    void shuffle(Container& container) noexcept;

private:
    static constexpr uint64_t rotl(uint64_t x, unsigned k) noexcept;

    uint64_t mX;
    uint64_t mY;
};

/**
 * @brief Main entry point to nanobench's benchmarking facility.
 *
 * It holds configuration and results from one or more benchmark runs. Usually it is used in a single line, where the object is
 * constructed, configured, and then a benchmark is run. E.g. like this:
 *
 *     ankerl::nanobench::Bench().unit("byte").batch(1000).run("random fluctuations", [&] {
 *         // here be the benchmark code
 *     });
 *
 * In that example Bench() constructs the benchmark, it is then configured with unit() and batch(), and after configuration a
 * benchmark is executed with run(). Once run() has finished, it prints the result to `std::cout`. It would also store the results
 * in the Bench instance, but in this case the object is immediately destroyed so it's not available any more.
 */
ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
class Bench {
public:
    /**
     * @brief Creates a new benchmark for configuration and running of benchmarks.
     */
    Bench();

    Bench(Bench&& other);
    Bench& operator=(Bench&& other);
    Bench(Bench const& other);
    Bench& operator=(Bench const& other);
    ~Bench() noexcept;

    /*!
      @brief Repeatedly calls `op()` based on the configuration, and performs measurements.

      This call is marked with `noinline` to prevent the compiler to optimize beyond different benchmarks. This can have quite a big
      effect on benchmark accuracy.

      @verbatim embed:rst
      .. note::

        Each call to your lambda must have a side effect that the compiler can't possibly optimize it away. E.g. add a result to an
        externally defined number (like `x` in the above example), and finally call `doNotOptimizeAway` on the variables the compiler
        must not remove. You can also use :cpp:func:`ankerl::nanobench::doNotOptimizeAway` directly in the lambda, but be aware that
        this has a small overhead.

      @endverbatim

      @tparam Op The code to benchmark.
     */
    template <typename Op>
    ANKERL_NANOBENCH(NOINLINE)
    Bench& run(char const* benchmarkName, Op&& op);

    template <typename Op>
    ANKERL_NANOBENCH(NOINLINE)
    Bench& run(std::string const& benchmarkName, Op&& op);

    /**
     * @brief Same as run(char const* benchmarkName, Op op), but instead uses the previously set name.
     * @tparam Op The code to benchmark.
     */
    template <typename Op>
    ANKERL_NANOBENCH(NOINLINE)
    Bench& run(Op&& op);

    /**
     * @brief Title of the benchmark, will be shown in the table header. Changing the title will start a new markdown table.
     *
     * @param benchmarkTitle The title of the benchmark.
     */
    Bench& title(char const* benchmarkTitle);
    Bench& title(std::string const& benchmarkTitle);
    ANKERL_NANOBENCH(NODISCARD) std::string const& title() const noexcept;

    /// Name of the benchmark, will be shown in the table row.
    Bench& name(char const* benchmarkName);
    Bench& name(std::string const& benchmarkName);
    ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept;

    /**
     * @brief Sets the batch size.
     *
     * E.g. number of processed byte, or some other metric for the size of the processed data in each iteration. If you benchmark
     * hashing of a 1000 byte long string and want byte/sec as a result, you can specify 1000 as the batch size.
     *
     * @tparam T Any input type is internally cast to `double`.
     * @param b batch size
     */
    template <typename T>
    Bench& batch(T b) noexcept;
    ANKERL_NANOBENCH(NODISCARD) double batch() const noexcept;

    /**
     * @brief Sets the operation unit.
     *
     * Defaults to "op". Could be e.g. "byte" for string processing. This is used for the table header, e.g. to show `ns/byte`. Use
     * singular (*byte*, not *bytes*). A change clears the currently collected results.
     *
     * @param unit The unit name.
     */
    Bench& unit(char const* unit);
    Bench& unit(std::string const& unit);
    ANKERL_NANOBENCH(NODISCARD) std::string const& unit() const noexcept;

    /**
     * @brief Sets the time unit to be used for the default output.
     *
     * Nanobench defaults to using ns (nanoseconds) as output in the markdown. For some benchmarks this is too coarse, so it is
     * possible to configure this. E.g. use `timeUnit(1ms, "ms")` to show `ms/op` instead of `ns/op`.
     *
     * @param tu Time unit to display the results in, default is 1ns.
     * @param tuName Name for the time unit, default is "ns"
     */
    Bench& timeUnit(std::chrono::duration<double> const& tu, std::string const& tuName);
    ANKERL_NANOBENCH(NODISCARD) std::string const& timeUnitName() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) std::chrono::duration<double> const& timeUnit() const noexcept;

    /**
     * @brief Set the output stream where the resulting markdown table will be printed to.
     *
     * The default is `&std::cout`. You can disable all output by setting `nullptr`.
     *
     * @param outstream Pointer to output stream, can be `nullptr`.
     */
    Bench& output(std::ostream* outstream) noexcept;
    ANKERL_NANOBENCH(NODISCARD) std::ostream* output() const noexcept;

    /**
     * Modern processors have a very accurate clock, being able to measure as low as 20 nanoseconds. This is the main trick nanobech to
     * be so fast: we find out how accurate the clock is, then run the benchmark only so often that the clock's accuracy is good enough
     * for accurate measurements.
     *
     * The default is to run one epoch for 1000 times the clock resolution. So for 20ns resolution and 11 epochs, this gives a total
     * runtime of
     *
     * @f[
     * 20ns * 1000 * 11 \approx 0.2ms
     * @f]
     *
     * To be precise, nanobench adds a 0-20% random noise to each evaluation. This is to prevent any aliasing effects, and further
     * improves accuracy.
     *
     * Total runtime will be higher though: Some initial time is needed to find out the target number of iterations for each epoch, and
     * there is some overhead involved to start & stop timers and calculate resulting statistics and writing the output.
     *
     * @param multiple Target number of times of clock resolution. Usually 1000 is a good compromise between runtime and accuracy.
     */
    Bench& clockResolutionMultiple(size_t multiple) noexcept;
    ANKERL_NANOBENCH(NODISCARD) size_t clockResolutionMultiple() const noexcept;

    /**
     * @brief Controls number of epochs, the number of measurements to perform.
     *
     * The reported result will be the median of evaluation of each epoch. The higher you choose this, the more
     * deterministic the result be and outliers will be more easily removed. Also the `err%` will be more accurate the higher this
     * number is. Note that the `err%` will not necessarily decrease when number of epochs is increased. But it will be a more accurate
     * representation of the benchmarked code's runtime stability.
     *
     * Choose the value wisely. In practice, 11 has been shown to be a reasonable choice between runtime performance and accuracy.
     * This setting goes hand in hand with minEpocIterations() (or minEpochTime()). If you are more interested in *median* runtime, you
     * might want to increase epochs(). If you are more interested in *mean* runtime, you might want to increase minEpochIterations()
     * instead.
     *
     * @param numEpochs Number of epochs.
     */
    Bench& epochs(size_t numEpochs) noexcept;
    ANKERL_NANOBENCH(NODISCARD) size_t epochs() const noexcept;

    /**
     * @brief Upper limit for the runtime of each epoch.
     *
     * As a safety precausion if the clock is not very accurate, we can set an upper limit for the maximum evaluation time per
     * epoch. Default is 100ms. At least a single evaluation of the benchmark is performed.
     *
     * @see minEpochTime(), minEpochIterations()
     *
     * @param t Maximum target runtime for a single epoch.
     */
    Bench& maxEpochTime(std::chrono::nanoseconds t) noexcept;
    ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds maxEpochTime() const noexcept;

    /**
     * @brief Minimum time each epoch should take.
     *
     * Default is zero, so we are fully relying on clockResolutionMultiple(). In most cases this is exactly what you want. If you see
     * that the evaluation is unreliable with a high `err%`, you can increase either minEpochTime() or minEpochIterations().
     *
     * @see maxEpochTime(), minEpochIterations()
     *
     * @param t Minimum time each epoch should take.
     */
    Bench& minEpochTime(std::chrono::nanoseconds t) noexcept;
    ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds minEpochTime() const noexcept;

    /**
     * @brief Sets the minimum number of iterations each epoch should take.
     *
     * Default is 1, and we rely on clockResolutionMultiple(). If the `err%` is high and you want a more smooth result, you might want
     * to increase the minimum number or iterations, or increase the minEpochTime().
     *
     * @see minEpochTime(), maxEpochTime(), minEpochIterations()
     *
     * @param numIters Minimum number of iterations per epoch.
     */
    Bench& minEpochIterations(uint64_t numIters) noexcept;
    ANKERL_NANOBENCH(NODISCARD) uint64_t minEpochIterations() const noexcept;

    /**
     * Sets exactly the number of iterations for each epoch. Ignores all other epoch limits. This forces nanobench to use exactly
     * the given number of iterations for each epoch, not more and not less. Default is 0 (disabled).
     *
     * @param numIters Exact number of iterations to use. Set to 0 to disable.
     */
    Bench& epochIterations(uint64_t numIters) noexcept;
    ANKERL_NANOBENCH(NODISCARD) uint64_t epochIterations() const noexcept;

    /**
     * @brief Sets a number of iterations that are initially performed without any measurements.
     *
     * Some benchmarks need a few evaluations to warm up caches / database / whatever access. Normally this should not be needed, since
     * we show the median result so initial outliers will be filtered away automatically. If the warmup effect is large though, you
     * might want to set it. Default is 0.
     *
     * @param numWarmupIters Number of warmup iterations.
     */
    Bench& warmup(uint64_t numWarmupIters) noexcept;
    ANKERL_NANOBENCH(NODISCARD) uint64_t warmup() const noexcept;

    /**
     * @brief Marks the next run as the baseline.
     *
     * Call `relative(true)` to mark the run as the baseline. Successive runs will be compared to this run. It is calculated by
     *
     * @f[
     * 100\% * \frac{baseline}{runtime}
     * @f]
     *
     *  * 100% means it is exactly as fast as the baseline
     *  * >100% means it is faster than the baseline. E.g. 200% means the current run is twice as fast as the baseline.
     *  * <100% means it is slower than the baseline. E.g. 50% means it is twice as slow as the baseline.
     *
     * See the tutorial section "Comparing Results" for example usage.
     *
     * @param isRelativeEnabled True to enable processing
     */
    Bench& relative(bool isRelativeEnabled) noexcept;
    ANKERL_NANOBENCH(NODISCARD) bool relative() const noexcept;

    /**
     * @brief Enables/disables performance counters.
     *
     * On Linux nanobench has a powerful feature to use performance counters. This enables counting of retired instructions, count
     * number of branches, missed branches, etc. On default this is enabled, but you can disable it if you don't need that feature.
     *
     * @param showPerformanceCounters True to enable, false to disable.
     */
    Bench& performanceCounters(bool showPerformanceCounters) noexcept;
    ANKERL_NANOBENCH(NODISCARD) bool performanceCounters() const noexcept;

    /**
     * @brief Retrieves all benchmark results collected by the bench object so far.
     *
     * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to
     * see all the nitty gritty detials.
     *
     * @return All results collected so far.
     */
    ANKERL_NANOBENCH(NODISCARD) std::vector<Result> const& results() const noexcept;

    /*!
      @verbatim embed:rst

      Convenience shortcut to :cpp:func:`ankerl::nanobench::doNotOptimizeAway`.

      @endverbatim
     */
    template <typename Arg>
    Bench& doNotOptimizeAway(Arg&& arg);

    /*!
      @verbatim embed:rst

      Sets N for asymptotic complexity calculation, so it becomes possible to calculate `Big O
      <https://en.wikipedia.org/wiki/Big_O_notation>`_ from multiple benchmark evaluations.

      Use :cpp:func:`ankerl::nanobench::Bench::complexityBigO` when the evaluation has finished. See the tutorial
      :ref:`asymptotic-complexity` for details.

      @endverbatim

      @tparam T Any type is cast to `double`.
      @param b Length of N for the next benchmark run, so it is possible to calculate `bigO`.
     */
    template <typename T>
    Bench& complexityN(T b) noexcept;
    ANKERL_NANOBENCH(NODISCARD) double complexityN() const noexcept;

    /*!
      Calculates [Big O](https://en.wikipedia.org/wiki/Big_O_notation>) of the results with all preconfigured complexity functions.
      Currently these complexity functions are fitted into the benchmark results:

       @f$ \mathcal{O}(1) @f$,
       @f$ \mathcal{O}(n) @f$,
       @f$ \mathcal{O}(\log{}n) @f$,
       @f$ \mathcal{O}(n\log{}n) @f$,
       @f$ \mathcal{O}(n^2) @f$,
       @f$ \mathcal{O}(n^3) @f$.

      If we e.g. evaluate the complexity of `std::sort`, this is the result of `std::cout << bench.complexityBigO()`:

      ```
      |   coefficient |   err% | complexity
      |--------------:|-------:|------------
      |   5.08935e-09 |   2.6% | O(n log n)
      |   6.10608e-08 |   8.0% | O(n)
      |   1.29307e-11 |  47.2% | O(n^2)
      |   2.48677e-15 |  69.6% | O(n^3)
      |   9.88133e-06 | 132.3% | O(log n)
      |   5.98793e-05 | 162.5% | O(1)
      ```

      So in this case @f$ \mathcal{O}(n\log{}n) @f$ provides the best approximation.

      @verbatim embed:rst
      See the tutorial :ref:`asymptotic-complexity` for details.
      @endverbatim
      @return Evaluation results, which can be printed or otherwise inspected.
     */
    std::vector<BigO> complexityBigO() const;

    /**
     * @brief Calculates bigO for a custom function.
     *
     * E.g. to calculate the mean squared error for @f$ \mathcal{O}(\log{}\log{}n) @f$, which is not part of the default set of
     * complexityBigO(), you can do this:
     *
     * ```
     * auto logLogN = bench.complexityBigO("O(log log n)", [](double n) {
     *     return std::log2(std::log2(n));
     * });
     * ```
     *
     * The resulting mean squared error can be printed with `std::cout << logLogN`. E.g. it prints something like this:
     *
     * ```text
     * 2.46985e-05 * O(log log n), rms=1.48121
     * ```
     *
     * @tparam Op Type of mapping operation.
     * @param name Name for the function, e.g. "O(log log n)"
     * @param op Op's operator() maps a `double` with the desired complexity function, e.g. `log2(log2(n))`.
     * @return BigO Error calculation, which is streamable to std::cout.
     */
    template <typename Op>
    BigO complexityBigO(char const* name, Op op) const;

    template <typename Op>
    BigO complexityBigO(std::string const& name, Op op) const;

    /*!
      @verbatim embed:rst

      Convenience shortcut to :cpp:func:`ankerl::nanobench::render`.

      @endverbatim
     */
    Bench& render(char const* templateContent, std::ostream& os);
    Bench& render(std::string const& templateContent, std::ostream& os);

    Bench& config(Config const& benchmarkConfig);
    ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept;

private:
    Config mConfig{};
    std::vector<Result> mResults{};
};
ANKERL_NANOBENCH(IGNORE_PADDED_POP)

/**
 * @brief Makes sure none of the given arguments are optimized away by the compiler.
 *
 * @tparam Arg Type of the argument that shouldn't be optimized away.
 * @param arg The input that we mark as being used, even though we don't do anything with it.
 */
template <typename Arg>
void doNotOptimizeAway(Arg&& arg);

namespace detail {

#if defined(_MSC_VER)
void doNotOptimizeAwaySink(void const*);

template <typename T>
void doNotOptimizeAway(T const& val);

#else

// These assembly magic is directly from what Google Benchmark is doing. I have previously used what facebook's folly was doing, but
// this seemd to have compilation problems in some cases. Google Benchmark seemed to be the most well tested anyways.
// see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307
template <typename T>
void doNotOptimizeAway(T const& val) {
    // NOLINTNEXTLINE(hicpp-no-assembler)
    asm volatile("" : : "r,m"(val) : "memory");
}

template <typename T>
void doNotOptimizeAway(T& val) {
#    if defined(__clang__)
    // NOLINTNEXTLINE(hicpp-no-assembler)
    asm volatile("" : "+r,m"(val) : : "memory");
#    else
    // NOLINTNEXTLINE(hicpp-no-assembler)
    asm volatile("" : "+m,r"(val) : : "memory");
#    endif
}
#endif

// internally used, but visible because run() is templated.
// Not movable/copy-able, so we simply use a pointer instead of unique_ptr. This saves us from
// having to include <memory>, and the template instantiation overhead of unique_ptr which is unfortunately quite significant.
ANKERL_NANOBENCH(IGNORE_EFFCPP_PUSH)
class IterationLogic {
public:
    explicit IterationLogic(Bench const& config) noexcept;
    ~IterationLogic();

    ANKERL_NANOBENCH(NODISCARD) uint64_t numIters() const noexcept;
    void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept;
    void moveResultTo(std::vector<Result>& results) noexcept;

private:
    struct Impl;
    Impl* mPimpl;
};
ANKERL_NANOBENCH(IGNORE_EFFCPP_POP)

ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
class PerformanceCounters {
public:
    PerformanceCounters(PerformanceCounters const&) = delete;
    PerformanceCounters& operator=(PerformanceCounters const&) = delete;

    PerformanceCounters();
    ~PerformanceCounters();

    void beginMeasure();
    void endMeasure();
    void updateResults(uint64_t numIters);

    ANKERL_NANOBENCH(NODISCARD) PerfCountSet<uint64_t> const& val() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) PerfCountSet<bool> const& has() const noexcept;

private:
#if ANKERL_NANOBENCH(PERF_COUNTERS)
    LinuxPerformanceCounters* mPc = nullptr;
#endif
    PerfCountSet<uint64_t> mVal{};
    PerfCountSet<bool> mHas{};
};
ANKERL_NANOBENCH(IGNORE_PADDED_POP)

// Gets the singleton
PerformanceCounters& performanceCounters();

} // namespace detail

class BigO {
public:
    using RangeMeasure = std::vector<std::pair<double, double>>;

    template <typename Op>
    static RangeMeasure mapRangeMeasure(RangeMeasure data, Op op) {
        for (auto& rangeMeasure : data) {
            rangeMeasure.first = op(rangeMeasure.first);
        }
        return data;
    }

    static RangeMeasure collectRangeMeasure(std::vector<Result> const& results);

    template <typename Op>
    BigO(char const* bigOName, RangeMeasure const& rangeMeasure, Op rangeToN)
        : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {}

    template <typename Op>
    BigO(std::string const& bigOName, RangeMeasure const& rangeMeasure, Op rangeToN)
        : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {}

    BigO(char const* bigOName, RangeMeasure const& scaledRangeMeasure);
    BigO(std::string const& bigOName, RangeMeasure const& scaledRangeMeasure);
    ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double constant() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) double normalizedRootMeanSquare() const noexcept;
    ANKERL_NANOBENCH(NODISCARD) bool operator<(BigO const& other) const noexcept;

private:
    std::string mName{};
    double mConstant{};
    double mNormalizedRootMeanSquare{};
};
std::ostream& operator<<(std::ostream& os, BigO const& bigO);
std::ostream& operator<<(std::ostream& os, std::vector<ankerl::nanobench::BigO> const& bigOs);

} // namespace nanobench
} // namespace ankerl

// implementation /////////////////////////////////////////////////////////////////////////////////

namespace ankerl {
namespace nanobench {

constexpr uint64_t(Rng::min)() {
    return 0;
}

constexpr uint64_t(Rng::max)() {
    return (std::numeric_limits<uint64_t>::max)();
}

ANKERL_NANOBENCH_NO_SANITIZE("integer")
uint64_t Rng::operator()() noexcept {
    auto x = mX;

    mX = UINT64_C(15241094284759029579) * mY;
    mY = rotl(mY - x, 27);

    return x;
}

ANKERL_NANOBENCH_NO_SANITIZE("integer")
uint32_t Rng::bounded(uint32_t range) noexcept {
    uint64_t r32 = static_cast<uint32_t>(operator()());
    auto multiresult = r32 * range;
    return static_cast<uint32_t>(multiresult >> 32U);
}

double Rng::uniform01() noexcept {
    auto i = (UINT64_C(0x3ff) << 52U) | (operator()() >> 12U);
    // can't use union in c++ here for type puning, it's undefined behavior.
    // std::memcpy is optimized anyways.
    double d;
    std::memcpy(&d, &i, sizeof(double));
    return d - 1.0;
}

template <typename Container>
void Rng::shuffle(Container& container) noexcept {
    auto size = static_cast<uint32_t>(container.size());
    for (auto i = size; i > 1U; --i) {
        using std::swap;
        auto p = bounded(i); // number in [0, i)
        swap(container[i - 1], container[p]);
    }
}

constexpr uint64_t Rng::rotl(uint64_t x, unsigned k) noexcept {
    return (x << k) | (x >> (64U - k));
}

template <typename Op>
ANKERL_NANOBENCH_NO_SANITIZE("integer")
Bench& Bench::run(Op&& op) {
    // It is important that this method is kept short so the compiler can do better optimizations/ inlining of op()
    detail::IterationLogic iterationLogic(*this);
    auto& pc = detail::performanceCounters();

    while (auto n = iterationLogic.numIters()) {
        pc.beginMeasure();
        Clock::time_point before = Clock::now();
        while (n-- > 0) {
            op();
        }
        Clock::time_point after = Clock::now();
        pc.endMeasure();
        pc.updateResults(iterationLogic.numIters());
        iterationLogic.add(after - before, pc);
    }
    iterationLogic.moveResultTo(mResults);
    return *this;
}

// Performs all evaluations.
template <typename Op>
Bench& Bench::run(char const* benchmarkName, Op&& op) {
    name(benchmarkName);
    return run(std::forward<Op>(op));
}

template <typename Op>
Bench& Bench::run(std::string const& benchmarkName, Op&& op) {
    name(benchmarkName);
    return run(std::forward<Op>(op));
}

template <typename Op>
BigO Bench::complexityBigO(char const* benchmarkName, Op op) const {
    return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op);
}

template <typename Op>
BigO Bench::complexityBigO(std::string const& benchmarkName, Op op) const {
    return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op);
}

// Set the batch size, e.g. number of processed bytes, or some other metric for the size of the processed data in each iteration.
// Any argument is cast to double.
template <typename T>
Bench& Bench::batch(T b) noexcept {
    mConfig.mBatch = static_cast<double>(b);
    return *this;
}

// Sets the computation complexity of the next run. Any argument is cast to double.
template <typename T>
Bench& Bench::complexityN(T n) noexcept {
    mConfig.mComplexityN = static_cast<double>(n);
    return *this;
}

// Convenience: makes sure none of the given arguments are optimized away by the compiler.
template <typename Arg>
Bench& Bench::doNotOptimizeAway(Arg&& arg) {
    detail::doNotOptimizeAway(std::forward<Arg>(arg));
    return *this;
}

// Makes sure none of the given arguments are optimized away by the compiler.
template <typename Arg>
void doNotOptimizeAway(Arg&& arg) {
    detail::doNotOptimizeAway(std::forward<Arg>(arg));
}

namespace detail {

#if defined(_MSC_VER)
template <typename T>
void doNotOptimizeAway(T const& val) {
    doNotOptimizeAwaySink(&val);
}

#endif

} // namespace detail
} // namespace nanobench
} // namespace ankerl

#if defined(ANKERL_NANOBENCH_IMPLEMENT)

///////////////////////////////////////////////////////////////////////////////////////////////////
// implementation part - only visible in .cpp
///////////////////////////////////////////////////////////////////////////////////////////////////

#    include <algorithm> // sort, reverse
#    include <atomic>    // compare_exchange_strong in loop overhead
#    include <cstdlib>   // getenv
#    include <cstring>   // strstr, strncmp
#    include <fstream>   // ifstream to parse proc files
#    include <iomanip>   // setw, setprecision
#    include <iostream>  // cout
#    include <numeric>   // accumulate
#    include <random>    // random_device
#    include <sstream>   // to_s in Number
#    include <stdexcept> // throw for rendering templates
#    include <tuple>     // std::tie
#    if defined(__linux__)
#        include <unistd.h> //sysconf
#    endif
#    if ANKERL_NANOBENCH(PERF_COUNTERS)
#        include <map> // map

#        include <linux/perf_event.h>
#        include <sys/ioctl.h>
#        include <sys/syscall.h>
#        include <unistd.h>
#    endif

// declarations ///////////////////////////////////////////////////////////////////////////////////

namespace ankerl {
namespace nanobench {

// helper stuff that is only intended to be used internally
namespace detail {

struct TableInfo;

// formatting utilities
namespace fmt {

class NumSep;
class StreamStateRestorer;
class Number;
class MarkDownColumn;
class MarkDownCode;

} // namespace fmt
} // namespace detail
} // namespace nanobench
} // namespace ankerl

// definitions ////////////////////////////////////////////////////////////////////////////////////

namespace ankerl {
namespace nanobench {

uint64_t splitMix64(uint64_t& state) noexcept;

namespace detail {

// helpers to get double values
template <typename T>
inline double d(T t) noexcept {
    return static_cast<double>(t);
}
inline double d(Clock::duration duration) noexcept {
    return std::chrono::duration_cast<std::chrono::duration<double>>(duration).count();
}

// Calculates clock resolution once, and remembers the result
inline Clock::duration clockResolution() noexcept;

} // namespace detail

namespace templates {

char const* csv() noexcept {
    return R"DELIM("title";"name";"unit";"batch";"elapsed";"error %";"instructions";"branches";"branch misses";"total"
{{#result}}"{{title}}";"{{name}}";"{{unit}}";{{batch}};{{median(elapsed)}};{{medianAbsolutePercentError(elapsed)}};{{median(instructions)}};{{median(branchinstructions)}};{{median(branchmisses)}};{{sumProduct(iterations, elapsed)}}
{{/result}})DELIM";
}

char const* htmlBoxplot() noexcept {
    return R"DELIM(<html>

<head>
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>

<body>
    <div id="myDiv"></div>
    <script>
        var data = [
            {{#result}}{
                name: '{{name}}',
                y: [{{#measurement}}{{elapsed}}{{^-last}}, {{/last}}{{/measurement}}],
            },
            {{/result}}
        ];
        var title = '{{title}}';

        data = data.map(a => Object.assign(a, { boxpoints: 'all', pointpos: 0, type: 'box' }));
        var layout = { title: { text: title }, showlegend: false, yaxis: { title: 'time per unit', rangemode: 'tozero', autorange: true } }; Plotly.newPlot('myDiv', data, layout, {responsive: true});
    </script>
</body>

</html>)DELIM";
}

char const* pyperf() noexcept {
    return R"DELIM({
    "benchmarks": [
        {
            "runs": [
                {
                    "values": [
{{#measurement}}                        {{elapsed}}{{^-last}},
{{/last}}{{/measurement}}
                    ]
                }
            ]
        }
    ],
    "metadata": {
        "loops": {{sum(iterations)}},
        "inner_loops": {{batch}},
        "name": "{{title}}",
        "unit": "second"
    },
    "version": "1.0"
})DELIM";
}

char const* json() noexcept {
    return R"DELIM({
    "results": [
{{#result}}        {
            "title": "{{title}}",
            "name": "{{name}}",
            "unit": "{{unit}}",
            "batch": {{batch}},
            "complexityN": {{complexityN}},
            "epochs": {{epochs}},
            "clockResolution": {{clockResolution}},
            "clockResolutionMultiple": {{clockResolutionMultiple}},
            "maxEpochTime": {{maxEpochTime}},
            "minEpochTime": {{minEpochTime}},
            "minEpochIterations": {{minEpochIterations}},
            "epochIterations": {{epochIterations}},
            "warmup": {{warmup}},
            "relative": {{relative}},
            "median(elapsed)": {{median(elapsed)}},
            "medianAbsolutePercentError(elapsed)": {{medianAbsolutePercentError(elapsed)}},
            "median(instructions)": {{median(instructions)}},
            "medianAbsolutePercentError(instructions)": {{medianAbsolutePercentError(instructions)}},
            "median(cpucycles)": {{median(cpucycles)}},
            "median(contextswitches)": {{median(contextswitches)}},
            "median(pagefaults)": {{median(pagefaults)}},
            "median(branchinstructions)": {{median(branchinstructions)}},
            "median(branchmisses)": {{median(branchmisses)}},
            "totalTime": {{sumProduct(iterations, elapsed)}},
            "measurements": [
{{#measurement}}                {
                    "iterations": {{iterations}},
                    "elapsed": {{elapsed}},
                    "pagefaults": {{pagefaults}},
                    "cpucycles": {{cpucycles}},
                    "contextswitches": {{contextswitches}},
                    "instructions": {{instructions}},
                    "branchinstructions": {{branchinstructions}},
                    "branchmisses": {{branchmisses}}
                }{{^-last}},{{/-last}}
{{/measurement}}            ]
        }{{^-last}},{{/-last}}
{{/result}}    ]
})DELIM";
}

ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
struct Node {
    enum class Type { tag, content, section, inverted_section };

    char const* begin;
    char const* end;
    std::vector<Node> children;
    Type type;

    template <size_t N>
    // NOLINTNEXTLINE(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
    bool operator==(char const (&str)[N]) const noexcept {
        return static_cast<size_t>(std::distance(begin, end) + 1) == N && 0 == strncmp(str, begin, N - 1);
    }
};
ANKERL_NANOBENCH(IGNORE_PADDED_POP)

static std::vector<Node> parseMustacheTemplate(char const** tpl) {
    std::vector<Node> nodes;

    while (true) {
        auto begin = std::strstr(*tpl, "{{");
        auto end = begin;
        if (begin != nullptr) {
            begin += 2;
            end = std::strstr(begin, "}}");
        }

        if (begin == nullptr || end == nullptr) {
            // nothing found, finish node
            nodes.emplace_back(Node{*tpl, *tpl + std::strlen(*tpl), std::vector<Node>{}, Node::Type::content});
            return nodes;
        }

        nodes.emplace_back(Node{*tpl, begin - 2, std::vector<Node>{}, Node::Type::content});

        // we found a tag
        *tpl = end + 2;
        switch (*begin) {
        case '/':
            // finished! bail out
            return nodes;

        case '#':
            nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::section});
            break;

        case '^':
            nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::inverted_section});
            break;

        default:
            nodes.emplace_back(Node{begin, end, std::vector<Node>{}, Node::Type::tag});
            break;
        }
    }
}

static bool generateFirstLast(Node const& n, size_t idx, size_t size, std::ostream& out) {
    ANKERL_NANOBENCH_LOG("n.type=" << static_cast<int>(n.type));
    bool matchFirst = n == "-first";
    bool matchLast = n == "-last";
    if (!matchFirst && !matchLast) {
        return false;
    }

    bool doWrite = false;
    if (n.type == Node::Type::section) {
        doWrite = (matchFirst && idx == 0) || (matchLast && idx == size - 1);
    } else if (n.type == Node::Type::inverted_section) {
        doWrite = (matchFirst && idx != 0) || (matchLast && idx != size - 1);
    }

    if (doWrite) {
        for (auto const& child : n.children) {
            if (child.type == Node::Type::content) {
                out.write(child.begin, std::distance(child.begin, child.end));
            }
        }
    }
    return true;
}

static bool matchCmdArgs(std::string const& str, std::vector<std::string>& matchResult) {
    matchResult.clear();
    auto idxOpen = str.find('(');
    auto idxClose = str.find(')', idxOpen);
    if (idxClose == std::string::npos) {
        return false;
    }

    matchResult.emplace_back(str.substr(0, idxOpen));

    // split by comma
    matchResult.emplace_back(std::string{});
    for (size_t i = idxOpen + 1; i != idxClose; ++i) {
        if (str[i] == ' ' || str[i] == '\t') {
            // skip whitespace
            continue;
        }
        if (str[i] == ',') {
            // got a comma => new string
            matchResult.emplace_back(std::string{});
            continue;
        }
        // no whitespace no comma, append
        matchResult.back() += str[i];
    }
    return t
Download .txt
gitextract_dcl1tyb6/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── amoeba.h
├── amoeba.lua
├── enaml_like_benchmark.cpp
├── lua_amoeba.c
├── nanobench.h
├── test.c
├── test.lua
├── yoga_benchmark.cpp
└── yogaone.cpp
Download .txt
SYMBOL INDEX (247 symbols across 6 files)

FILE: amoeba.h
  type AM_NUM (line 61) | typedef AM_NUM am_Num;
  type am_Num (line 63) | typedef double am_Num;
  type am_Num (line 65) | typedef float  am_Num;
  type am_Solver (line 68) | typedef struct am_Solver     am_Solver;
  type am_Constraint (line 69) | typedef struct am_Constraint am_Constraint;
  type am_Id (line 71) | typedef unsigned am_Id;
  type am_AllocType (line 73) | typedef enum am_AllocType {
  type am_Dumper (line 123) | typedef struct am_Dumper am_Dumper;
  type am_Loader (line 124) | typedef struct am_Loader am_Loader;
  type am_Dumper (line 129) | struct am_Dumper {
  type am_Loader (line 136) | struct am_Loader {
  type am_Symbol (line 199) | typedef struct am_Symbol {
  type am_MemPool (line 204) | typedef struct am_MemPool {
  type am_Key (line 210) | typedef struct am_Key {
  type am_Arena (line 215) | typedef struct am_Arena {
  type am_Table (line 220) | typedef struct am_Table {
  type am_Iterator (line 230) | typedef struct am_Iterator {
  type am_Row (line 236) | typedef struct am_Row {
  type am_Var (line 242) | typedef struct am_Var {
  type am_Constraint (line 248) | struct am_Constraint {
  type am_Suggest (line 261) | typedef struct am_Suggest {
  type am_Solver (line 268) | struct am_Solver {
  function am_approx (line 288) | static int am_approx(am_Num a, am_Num b)
  function am_nearzero (line 291) | static int am_nearzero(am_Num a)
  function am_Symbol (line 294) | static am_Symbol am_null(void)
  function am_poolfree (line 297) | static void am_poolfree(am_MemPool *pool, void *obj)
  function am_freearena (line 300) | static void am_freearena(am_Solver *S, am_Arena *a) {
  function am_allocarena (line 306) | static void am_allocarena(am_Solver *S, am_Arena *a, size_t size) {
  function am_initpool (line 312) | static void am_initpool(am_MemPool *pool, size_t size) {
  function am_freepool (line 318) | static void am_freepool(am_MemPool *pool) {
  function am_Symbol (line 360) | static am_Symbol am_newsymbol(am_Solver *S, int type) {
  function am_Iterator (line 375) | static am_Iterator am_itertable(const am_Table *t)
  function am_inittable (line 378) | static void am_inittable(am_Table *t, size_t value_size)
  function am_resettable (line 381) | static void am_resettable(am_Table *t)
  function am_Size (line 384) | static am_Size am_calcsize(am_Size request)
  function am_nextentry (line 387) | static int am_nextentry(am_Iterator *it) {
  function am_freetable (line 395) | static void am_freetable(const am_Solver *S, am_Table *t) {
  function amH_alloc (line 411) | static int amH_alloc(am_Table *t, const am_Solver *S, size_t request, si...
  function amH_resize (line 452) | static int amH_resize(const am_Solver *S, am_Table *t, size_t request, v...
  function am_growtable (line 467) | static int am_growtable(const am_Solver *S, am_Table *t, size_t grow, vo...
  function am_deltable (line 481) | static void am_deltable(am_Table *t, const void *value) {
  function am_freerow (line 488) | static void am_freerow(am_Solver *S, am_Row *row)
  function am_resetrow (line 491) | static void am_resetrow(am_Row *row)
  function am_initrow (line 494) | static void am_initrow(am_Row *row) {
  function am_multiply (line 500) | static void am_multiply(am_Row *row, am_Num multiplier) {
  function am_addvar (line 507) | static void am_addvar(am_Solver *S, am_Row *row, am_Symbol sym, am_Num v...
  function am_addrow (line 519) | static void am_addrow(am_Solver *S, am_Row *row, const am_Row *other, am...
  function AM_API (line 528) | AM_API int am_hasconstraint(am_Constraint *cons)
  function AM_API (line 531) | AM_API void am_autoupdate(am_Solver *S, int auto_update)
  function am_Var (line 534) | static am_Var *am_id2var(am_Solver *S, am_Id var)
  function AM_API (line 537) | AM_API am_Id am_newvariable(am_Solver *S, am_Num *pvalue) {
  function AM_API (line 550) | AM_API void am_delvariable(am_Solver *S, am_Id var) {
  function AM_API (line 559) | AM_API int am_refcount(am_Solver *S, am_Id var) {
  function AM_API (line 564) | AM_API am_Num *am_varvalue(am_Solver *S, am_Id var, am_Num *newvalue) {
  function AM_API (line 571) | AM_API void am_updatevars(am_Solver *S) {
  function am_initconstraint (line 589) | static int am_initconstraint(am_Solver *S, am_Symbol sym, am_Num strengt...
  function AM_API (line 601) | AM_API am_Constraint *am_newconstraint(am_Solver *S, am_Num strength) {
  function AM_API (line 614) | AM_API void am_delconstraint(am_Constraint *cons) {
  function AM_API (line 630) | AM_API am_Constraint *am_cloneconstraint(am_Constraint *other, am_Num st...
  function AM_API (line 640) | AM_API int am_mergeconstraint(am_Constraint *cons, const am_Constraint *...
  function AM_API (line 656) | AM_API void am_resetconstraint(am_Constraint *cons) {
  function AM_API (line 667) | AM_API int am_addterm(am_Constraint *cons, am_Id var, am_Num multiplier) {
  function AM_API (line 677) | AM_API int am_addconstant(am_Constraint *cons, am_Num constant) {
  function AM_API (line 684) | AM_API int am_setrelation(am_Constraint *cons, int relation) {
  function AM_API (line 692) | AM_API int am_hasedit(am_Solver *S, am_Id var) {
  function am_Suggest (line 697) | static am_Suggest *am_newedit(am_Solver *S, am_Id var, am_Num strength) {
  function AM_API (line 720) | AM_API int am_addedit(am_Solver *S, am_Id var, am_Num strength) {
  function AM_API (line 730) | AM_API void am_deledit(am_Solver *S, am_Id var) {
  function AM_API (line 741) | AM_API void am_clearedits(am_Solver *S) {
  function am_takerow (line 757) | static int am_takerow(am_Solver *S, am_Symbol sym, am_Row *dst) {
  function am_solvefor (line 766) | static void am_solvefor(am_Solver *S, am_Row *row, am_Symbol enter, am_S...
  function am_eliminate (line 775) | static void am_eliminate(am_Solver *S, am_Row *dst, am_Symbol out, const...
  function am_markdirty (line 782) | static void am_markdirty(am_Solver *S, am_Symbol var) {
  function am_infeasible (line 792) | static void am_infeasible(am_Solver *S, am_Symbol sym, am_Row *row) {
  function am_substitute_rows (line 800) | static void am_substitute_rows(am_Solver *S, am_Symbol var, am_Row *expr) {
  function am_putrow (line 813) | static int am_putrow(am_Solver *S, am_Symbol sym, const am_Row *src) {
  function am_optimize (line 824) | static void am_optimize(am_Solver *S, am_Row *objective) {
  function am_mergerow (line 858) | static void am_mergerow(am_Solver *S, am_Row *row, am_Symbol var, am_Num...
  function am_Row (line 864) | static am_Row am_makerow(am_Solver *S, am_Constraint *cons) {
  function am_add_with_artificial (line 896) | static int am_add_with_artificial(am_Solver *S, am_Row *row, am_Constrai...
  function am_try_addrow (line 932) | static int am_try_addrow(am_Solver *S, am_Row *row, am_Constraint *cons) {
  function am_remove_errors (line 965) | static void am_remove_errors(am_Solver *S, am_Constraint *cons) {
  function AM_API (line 975) | AM_API int am_add(am_Constraint *cons) {
  function am_Symbol (line 997) | static am_Symbol am_get_leaving_row(am_Solver *S, am_Symbol marker) {
  function AM_API (line 1018) | AM_API void am_remove(am_Constraint *cons) {
  function AM_API (line 1038) | AM_API int am_setstrength(am_Constraint *cons, am_Num strength) {
  function am_cached_sugggest (line 1057) | static void am_cached_sugggest(am_Solver *S, am_Suggest *s, am_Num delta) {
  function am_delta_edit_constant (line 1076) | static void am_delta_edit_constant(am_Solver *S, am_Suggest *s, am_Num d...
  function am_dual_optimize (line 1101) | static void am_dual_optimize(am_Solver *S) {
  function AM_API (line 1130) | AM_API void am_suggest(am_Solver *S, am_Id var, am_Num value) {
  function AM_API (line 1143) | AM_API am_Solver *am_newsolver(am_Allocf *allocf, void *ud) {
  function AM_API (line 1160) | AM_API void am_delsolver(am_Solver *S) {
  function AM_API (line 1187) | AM_API void am_resetsolver(am_Solver *S) {
  type am_DumpCtx (line 1215) | typedef struct am_DumpCtx {
  function am_intcmp (line 1226) | static int am_intcmp(const void *lhs, const void *rhs)
  function am_writechar (line 1229) | static int am_writechar(am_DumpCtx *ctx, int ch) {
  function am_writeraw (line 1237) | static int am_writeraw(am_DumpCtx *ctx, am_Size n, int width) {
  function am_writeuint32 (line 1248) | static int am_writeuint32(am_DumpCtx *ctx, am_Size n) {
  function am_writefloat (line 1263) | static int am_writefloat(am_DumpCtx *ctx, am_Num n) {
  function am_writestring (line 1274) | static int am_writestring(am_DumpCtx *ctx, const char *name) {
  function am_writecount (line 1293) | static int am_writecount(am_DumpCtx *ctx, am_Size count) {
  function am_mapid (line 1306) | static unsigned am_mapid(am_DumpCtx *ctx, unsigned key) {
  function am_writerow (line 1311) | static int am_writerow(am_DumpCtx *ctx, const am_Row *row) {
  function am_writevars (line 1322) | static int am_writevars(am_DumpCtx *ctx) {
  function am_writeconstraints (line 1335) | static int am_writeconstraints(am_DumpCtx *ctx) {
  function am_writerows (line 1361) | static int am_writerows(am_DumpCtx *ctx) {
  function am_collect (line 1377) | static int am_collect(am_DumpCtx *ctx) {
  function am_dumpall (line 1403) | static int am_dumpall(am_DumpCtx *ctx) {
  function AM_API (line 1418) | AM_API int am_dump(am_Solver *S, am_Dumper *dumper) {
  type am_LoadCtx (line 1441) | typedef struct am_LoadCtx {
  function am_fill (line 1452) | static int am_fill(am_LoadCtx *ctx) {
  function am_readraw_slow (line 1458) | static int am_readraw_slow(am_LoadCtx *ctx, unsigned *pv, int width) {
  function am_readraw32 (line 1470) | static int am_readraw32(am_LoadCtx *ctx, unsigned *pv) {
  function am_readraw16 (line 1481) | static int am_readraw16(am_LoadCtx *ctx, unsigned *pv) {
  function am_readuint32 (line 1490) | static int am_readuint32(am_LoadCtx *ctx, am_Size *pv) {
  function am_readfloat (line 1501) | static int am_readfloat(am_LoadCtx *ctx, am_Num *pv) {
  function am_readstring (line 1508) | static int am_readstring(am_LoadCtx *ctx) {
  function am_readcount (line 1531) | static int am_readcount(am_LoadCtx *ctx, am_Size *pcount) {
  function am_readrow (line 1541) | static int am_readrow(am_LoadCtx *ctx, am_Row *row, void **arena) {
  function am_readvars (line 1558) | static int am_readvars(am_LoadCtx *ctx) {
  function am_readmarker (line 1579) | static int am_readmarker(am_LoadCtx *ctx, am_Symbol *sym, unsigned *id) {
  function am_readconstraints (line 1588) | static int am_readconstraints(am_LoadCtx *ctx) {
  function am_readrows (line 1614) | static int am_readrows(am_LoadCtx *ctx) {
  function AM_API (line 1639) | AM_API int am_load(am_Solver *S, am_Loader *loader) {

FILE: enaml_like_benchmark.cpp
  function build_solver (line 17) | static void build_solver(am_Solver* S, am_Id width, am_Id height, am_Num...
  function main (line 217) | int main() {

FILE: lua_amoeba.c
  type aml_ItemType (line 13) | enum aml_ItemType { AML_VAR, AML_CONS, AML_CONSTANT }
  type aml_Solver (line 15) | typedef struct aml_Solver {
  type aml_Var (line 21) | typedef struct aml_Var {
  type aml_Cons (line 28) | typedef struct aml_Cons {
  type aml_Item (line 32) | typedef struct aml_Item {
  function aml_argferror (line 41) | static int aml_argferror(lua_State *L, int idx, const char *fmt, ...) {
  function aml_typeerror (line 49) | static int aml_typeerror(lua_State *L, int idx, const char *tname) {
  function aml_setweak (line 54) | static void aml_setweak(lua_State *L, const char *mode) {
  function am_Id (line 61) | static am_Id aml_checkvar(lua_State *L, aml_Solver *S, int idx) {
  function aml_Cons (line 77) | static aml_Cons *aml_registercons(lua_State *L, am_Constraint *cons) {
  function aml_Cons (line 89) | static aml_Cons *aml_newcons(lua_State *L, aml_Solver *S, am_Num strengt...
  function aml_Item (line 96) | static aml_Item aml_checkitem(lua_State *L, aml_Solver *S, int idx) {
  function aml_Solver (line 131) | static aml_Solver *aml_checkitems(lua_State *L, int start, aml_Item *ite...
  function aml_pusherror (line 162) | static int aml_pusherror(lua_State *L, int ret) {
  function aml_performitem (line 175) | static void aml_performitem(lua_State *L, am_Constraint *cons, aml_Item ...
  function am_Num (line 185) | static am_Num aml_checkstrength(lua_State *L, int idx, am_Num def) {
  function aml_checkrelation (line 205) | static int aml_checkrelation(lua_State *L, int idx) {
  function aml_Cons (line 216) | static aml_Cons *aml_makecons(lua_State *L, aml_Solver *S, int start) {
  function aml_dumpkey (line 229) | static void aml_dumpkey(luaL_Buffer *B, int idx, am_Symbol sym) {
  function aml_dumprow (line 249) | static void aml_dumprow(luaL_Buffer *B, int idx, am_Row *row) {
  function Lexpr_neg (line 269) | static int Lexpr_neg(lua_State *L) {
  function Lexpr_add (line 276) | static int Lexpr_add(lua_State *L) {
  function Lexpr_sub (line 286) | static int Lexpr_sub(lua_State *L) {
  function Lexpr_mul (line 296) | static int Lexpr_mul(lua_State *L) {
  function Lexpr_div (line 311) | static int Lexpr_div(lua_State *L) {
  function Lexpr_cmp (line 324) | static int Lexpr_cmp(lua_State *L, int op) {
  function Lexpr_le (line 334) | static int Lexpr_le(lua_State *L) { return Lexpr_cmp(L, AM_LESSEQUAL); }
  function Lexpr_eq (line 335) | static int Lexpr_eq(lua_State *L) { return Lexpr_cmp(L, AM_EQUAL); }
  function Lexpr_ge (line 336) | static int Lexpr_ge(lua_State *L) { return Lexpr_cmp(L, AM_GREATEQUAL); }
  function aml_Var (line 340) | static aml_Var *aml_newvar(lua_State *L, aml_Solver *S, am_Id var, const...
  function Lvar_new (line 364) | static int Lvar_new(lua_State *L) {
  function Lvar_delete (line 380) | static int Lvar_delete(lua_State *L) {
  function Lvar_value (line 392) | static int Lvar_value(lua_State *L) {
  function Lvar_tostring (line 398) | static int Lvar_tostring(lua_State *L) {
  function open_variable (line 406) | static void open_variable(lua_State *L) {
  function Lcons_new (line 434) | static int Lcons_new(lua_State *L) {
  function Lcons_delete (line 441) | static int Lcons_delete(lua_State *L) {
  function Lcons_reset (line 453) | static int Lcons_reset(lua_State *L) {
  function Lcons_add (line 460) | static int Lcons_add(lua_State *L) {
  function Lcons_relation (line 479) | static int Lcons_relation(lua_State *L) {
  function Lcons_strength (line 488) | static int Lcons_strength(lua_State *L) {
  function Lcons_tostring (line 497) | static int Lcons_tostring(lua_State *L) {
  function open_constraint (line 526) | static void open_constraint(lua_State *L) {
  type aml_Dumper (line 559) | typedef struct aml_Dumper {
  function aml_writer (line 567) | static int aml_writer(am_Dumper *d, const void *buf, size_t len) {
  function Ldump (line 574) | static int Ldump(lua_State *L) {
  type aml_Loader (line 590) | typedef struct aml_Loader {
  function am_Num (line 600) | static am_Num *aml_loadvar(am_Loader *l, const char *name, unsigned idx,...
  function aml_loadcons (line 606) | static void aml_loadcons(am_Loader *l, const char *name, unsigned idx, a...
  function Lload (line 635) | static int Lload(lua_State *L) {
  function Lnew (line 655) | static int Lnew(lua_State *L) {
  function Ldelete (line 668) | static int Ldelete(lua_State *L) {
  function Ltostring (line 698) | static int Ltostring(lua_State *L) {
  function Lreset (line 738) | static int Lreset(lua_State *L) {
  function Lclearedits (line 744) | static int Lclearedits(lua_State *L) {
  function Lautoupdate (line 750) | static int Lautoupdate(lua_State *L) {
  function Laddconstraint (line 756) | static int Laddconstraint(lua_State *L) {
  function Ldelconstraint (line 763) | static int Ldelconstraint(lua_State *L) {
  function Laddedit (line 770) | static int Laddedit(lua_State *L) {
  function Ldeledit (line 777) | static int Ldeledit(lua_State *L) {
  function Lsuggest (line 784) | static int Lsuggest(lua_State *L) {
  function LUALIB_API (line 792) | LUALIB_API int luaopen_amoeba(lua_State *L) {

FILE: nanobench.h
  function namespace (line 119) | namespace ankerl {
  function namespace (line 357) | namespace ankerl {
  function namespace (line 1263) | namespace ankerl {
  function namespace (line 1292) | namespace detail {
  function namespace (line 1308) | namespace templates {
  function matchCmdArgs (line 1495) | static bool matchCmdArgs(std::string const& str, std::vector<std::string...
  function generateConfigTag (line 1523) | static bool generateConfigTag(Node const& n, Config const& config, std::...
  function std (line 1572) | static std::ostream& generateResultTag(Node const& n, Result const& r, s...
  function generateResultMeasurement (line 1627) | static void generateResultMeasurement(std::vector<Node> const& nodes, si...
  function doNotOptimizeAwaySink (line 1896) | void doNotOptimizeAwaySink(void const*) {}
  function T (line 1903) | T num{}
  function isEndlessRunning (line 1919) | bool isEndlessRunning(std::string const& name) {
  function isWarningsEnabled (line 1925) | bool isWarningsEnabled() {
  function gatherStabilityInformation (line 1930) | void gatherStabilityInformation(std::vector<std::string>& warnings, std:...
  function calcBestNumIters (line 2078) | ANKERL_NANOBENCH(NODISCARD) uint64_t calcBestNumIters(std::chrono::nanos...
  function upscale (line 2094) | void upscale(std::chrono::nanoseconds elapsed) {
  function add (line 2109) | void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc...
  function add (line 2313) | void IterationLogic::add(std::chrono::nanoseconds elapsed, PerformanceCo...
  function moveResultTo (line 2317) | void IterationLogic::moveResultTo(std::vector<Result>& results) noexcept {
  function LinuxPerformanceCounters (line 2323) | ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
  function LinuxPerformanceCounters (line 2482) | ANKERL_NANOBENCH(IGNORE_PADDED_POP)
  function monitor (line 2490) | bool LinuxPerformanceCounters::monitor(perf_sw_ids swId, LinuxPerformanc...
  function monitor (line 2494) | bool LinuxPerformanceCounters::monitor(perf_hw_id hwId, LinuxPerformance...
  function monitor (line 2541) | bool LinuxPerformanceCounters::monitor(uint32_t type, uint64_t eventid, ...
  function beginMeasure (line 2629) | void PerformanceCounters::beginMeasure() {
  function endMeasure (line 2633) | void PerformanceCounters::endMeasure() {
  function updateResults (line 2637) | void PerformanceCounters::updateResults(uint64_t numIters) {
  function beginMeasure (line 2645) | void PerformanceCounters::beginMeasure() {}
  function endMeasure (line 2646) | void PerformanceCounters::endMeasure() {}
  function updateResults (line 2647) | void PerformanceCounters::updateResults(uint64_t) {}
  function namespace (line 2659) | namespace fmt {
  function typename (line 2806) | constexpr typename std::underlying_type<T>::type u(T val) noexcept {
  function add (line 2816) | void Result::add(Clock::duration totalElapsed, uint64_t iters, detail::P...
  function Config (line 2862) | Config const& Result::config() const noexcept {
  function median (line 2879) | double Result::median(Measure m) const {
  function average (line 2885) | double Result::average(Measure m) const {
  function medianAbsolutePercentError (line 2896) | double Result::medianAbsolutePercentError(Measure m) const {

FILE: test.c
  function am_dumpkey (line 35) | static void am_dumpkey(am_Symbol sym) {
  function am_dumprow (line 46) | static void am_dumprow(const am_Row *row) {
  function am_dumpsolver (line 61) | static void am_dumpsolver(am_Solver *S) {
  function am_Constraint (line 77) | static am_Constraint* new_constraint(am_Solver* in_solver, double in_str...
  function test_all (line 103) | static void test_all(void) {
  function test_binarytree (line 346) | static void test_binarytree(void) {
  function test_unbounded (line 457) | static void test_unbounded(void) {
  function test_strength (line 586) | static void test_strength(void) {
  function test_suggest (line 628) | static void test_suggest(void) {
  function test_dirty (line 732) | static void test_dirty(void) {
  function test_cycling (line 800) | void test_cycling(void) {
  type MyDumper (line 874) | typedef struct MyDumper {
  function dump_writer (line 892) | static int dump_writer(am_Dumper *d, const void *buf, size_t len) {
  type MyLoader (line 901) | typedef struct MyLoader {
  function am_Num (line 908) | static am_Num *load_var(am_Loader *l, const char *name, unsigned i, am_I...
  function load_cons (line 913) | void load_cons(am_Loader *l, const char *name, unsigned i, am_Constraint...
  function build_solver (line 924) | static void build_solver(am_Solver* S, am_Id width, am_Id height, am_Num...
  function test_dumpload (line 1126) | static void test_dumpload(void) {
  function main (line 1246) | int main(void) {

FILE: yoga_benchmark.cpp
  function YGNodeRef (line 6) | YGNodeRef build_layout(float width, float height) {
  function suggest (line 129) | void suggest(YGNodeRef root_node, float width, float height) {
  function main (line 137) | int main() {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (325K chars).
[
  {
    "path": ".github/workflows/test.yml",
    "chars": 1704,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: CI\n\n# Controls when the action will run. \non:\n  #"
  },
  {
    "path": ".gitignore",
    "chars": 42,
    "preview": "*.dll\n*.exp\n*.ilk\n*.lib\n*.pdb\n*.exe\n\n*.so\n"
  },
  {
    "path": ".travis.yml",
    "chars": 788,
    "preview": "language: c\n\ncompiler:\n    - gcc\n\nbefore_install:\n    - pip install --user urllib3[secure] cpp-coveralls\n\n    # Work aro"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2018 Xavier Wang\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 13214,
    "preview": "# Amoeba -- the constraint solving algorithm in pure C\n\n[![Build Status](https://img.shields.io/github/actions/workflow/"
  },
  {
    "path": "amoeba.h",
    "chars": 55108,
    "preview": "#ifndef amoeba_h\n#define amoeba_h\n\n#ifndef AM_NS_BEGIN\n# ifdef __cplusplus\n#   define AM_NS_BEGIN extern \"C\" {\n#   defin"
  },
  {
    "path": "amoeba.lua",
    "chars": 20133,
    "preview": "\nlocal function meta(name, parent)\n   local t = {}\n   t.__name  = name\n   t.__index = t\n   return setmetatable(t, parent"
  },
  {
    "path": "enaml_like_benchmark.cpp",
    "chars": 21120,
    "preview": "/*-----------------------------------------------------------------------------\n| Copyright (c) 2020, Nucleic Developmen"
  },
  {
    "path": "lua_amoeba.c",
    "chars": 26908,
    "preview": "#define LUA_LIB\n#include <lua.h>\n#include <lauxlib.h>\n#include <string.h>\n\n#define AM_STATIC_API\n#include \"amoeba.h\"\n\n#d"
  },
  {
    "path": "nanobench.h",
    "chars": 118949,
    "preview": "//  __   _ _______ __   _  _____  ______  _______ __   _ _______ _     _\n//  | \\  | |_____| | \\  | |     | |_____] |____"
  },
  {
    "path": "test.c",
    "chars": 47694,
    "preview": "#include <stdio.h>\n\n#define AM_BUF_LEN 1 /* touch multiple write code */\n#define AM_DEBUG printf\n#define AM_IMPLEMENTATI"
  },
  {
    "path": "test.lua",
    "chars": 815,
    "preview": "package.path = \"\"\nlocal amoeba = require \"amoeba\"\n\nlocal S = amoeba.new(true)\nprint(S)\nlocal xl, xm, xr =\n   S:var \"xl\","
  },
  {
    "path": "yoga_benchmark.cpp",
    "chars": 5805,
    "preview": "#include <yoga/Yoga.h>\n\n#define ANKERL_NANOBENCH_IMPLEMENT\n#include \"nanobench.h\"\n\nYGNodeRef build_layout(float width, f"
  },
  {
    "path": "yogaone.cpp",
    "chars": 694,
    "preview": "#include \"yoga/YGConfig.cpp\"\n#include \"yoga/YGEnums.cpp\"\n#include \"yoga/YGNode.cpp\"\n#include \"yoga/YGNodeLayout.cpp\"\n#in"
  }
]

About this extraction

This page contains the full source code of the starwing/amoeba GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (306.7 KB), approximately 87.7k tokens, and a symbol index with 247 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!