Merge commit '81396a5322a7a48764fcf254d5d933ba1e57bdc5' as 'kiwi'
This commit is contained in:
196
kiwi/docs/source/basis/solver_internals.rst
Normal file
196
kiwi/docs/source/basis/solver_internals.rst
Normal file
@@ -0,0 +1,196 @@
|
||||
.. _basics-internals:
|
||||
|
||||
Solver internals and tips
|
||||
=========================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
|
||||
Kiwi is not a mere rewriting of Cassowary, and due to this, it does not always
|
||||
perfectly reflect the original implementation. The following sections point out
|
||||
these discrepancies and provide tips on how to work effectively with Kiwi.
|
||||
|
||||
|
||||
Inspecting the solver state
|
||||
---------------------------
|
||||
|
||||
The state of the solver can be inspected by dumping a text representation of
|
||||
its state either to stdout using the ``dump`` method of the solver, or to a
|
||||
string using the ``dumps`` method. Typically, at least a basic understanding of
|
||||
the Cassowary algorithm is necessary to analyse the output.
|
||||
|
||||
A typical output is reproduced below:
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
Objective
|
||||
---------
|
||||
-2 + 2 * e2 + 1 * s8 + -2 * s10
|
||||
|
||||
Tableau
|
||||
-------
|
||||
v1 | 1 + 1 * s10
|
||||
e3 | -1 + 1 * e2 + -1 * s10
|
||||
v4 | -1 + -1 * d5 + -1 * s10
|
||||
s6 | -2 + -1 * s10
|
||||
e9 | -1 + 1 * s8 + -1 * s10
|
||||
|
||||
Infeasible
|
||||
----------
|
||||
e3
|
||||
e9
|
||||
|
||||
Variables
|
||||
---------
|
||||
bar = v1
|
||||
foo = v4
|
||||
|
||||
Edit Variables
|
||||
--------------
|
||||
bar
|
||||
|
||||
Constraints
|
||||
-----------
|
||||
1 * bar + -0 >= 0 | strength = 1
|
||||
1 * bar + 1 <= 0 | strength = 1.001e+09
|
||||
1 * foo + 1 * bar + 0 == 0 | strength = 1.001e+09
|
||||
1 * bar + 0 == 0 | strength = 1
|
||||
|
||||
In the dump, the letters have the following meaning:
|
||||
|
||||
- v: external variable, corresponds to the variable created by you, the user
|
||||
- s: slack symbol, used to represent inequalities
|
||||
- e: error symbol, used to represent non-required constraints
|
||||
- d: dummy variable, always zero, used to keep track of the impact of an
|
||||
external variable in the tableau.
|
||||
- i: invalid symbol, returned when no valid symbol can be found.
|
||||
|
||||
|
||||
Stay constraints emulation
|
||||
--------------------------
|
||||
|
||||
One feature of Cassowary that Kiwi abandoned is the notion of stay
|
||||
constraints. Stay constraints are typically used in under-constrained
|
||||
situations (drag and drop) to allow the solver to find a solution by keeping
|
||||
non-modified variable close to their original position. A typical example is
|
||||
a rectangle whose one corner is being dragged in a drawing application.
|
||||
|
||||
Kiwi does not have stay constraints mostly because in the context of widget
|
||||
placement, the system is typically well constrained, rendering stay constraints
|
||||
unnecessary.
|
||||
|
||||
If your application requires them, several workarounds can be considered:
|
||||
|
||||
- adding/removing non-required equality constraints to mimic stay constraints
|
||||
- using edit-variables to mimic stay constraints
|
||||
|
||||
The first method will require to remove the old constraints as soon as they
|
||||
stop making sense, while the second will require to update the suggested value.
|
||||
|
||||
|
||||
Creating strengths and their internal representation
|
||||
----------------------------------------------------
|
||||
|
||||
Kiwi provides three strengths in addition to the required strength by default:
|
||||
"weak", "medium", and "strong". Contrary to Cassowary, which uses lexicographic
|
||||
ordering to ensure that strength are always respected, Kiwi strives for speed
|
||||
and uses simple floating point numbers.
|
||||
|
||||
.. note::
|
||||
|
||||
Using simple floating point, means that in some rare corner cases, a large
|
||||
number of weak constraints may outweigh a medium constraint. However, in
|
||||
practice, this rarely happens.
|
||||
|
||||
Kiwi allows to create custom strengths in the following manner:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import strength
|
||||
|
||||
my_strength = strength.create(1, 1, 1)
|
||||
my_strength2 = strength.create(1, 1, 1, 2)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
double my_strength = strength::create(1, 1, 1);
|
||||
double my_strength = strength::create(1, 1, 1, 2);
|
||||
|
||||
The first argument is multiplied by 1 000 000, the second argument by 1 000,
|
||||
and the third by 1. No strength can be create larger than the required
|
||||
strength. The default strengths in Kiwi correspond to:
|
||||
|
||||
.. code:: python
|
||||
|
||||
weak = strength.create(0, 0, 1)
|
||||
medium = strength.create(0, 1, 0)
|
||||
strong = strength.create(1, 0, 0)
|
||||
required = strength.create(1000, 1000, 1000)
|
||||
|
||||
While Cassowary differentiates between strength and weight, those two concepts
|
||||
are fused in Kiwi: when creating a strength, one can apply a weight (the fourth
|
||||
argument) that will multiply it.
|
||||
|
||||
.. note::
|
||||
|
||||
Because strengths are implemented as floating point numbers, in order to be
|
||||
effective, strengths must be different enough from one another. The
|
||||
following is unlikely to produce any really useful result.
|
||||
|
||||
.. code:: python
|
||||
|
||||
weak1 = strength.create(0, 0, 1)
|
||||
weak2 = strength.create(0, 0, 2)
|
||||
weak3 = strength.create(0, 0, 3)
|
||||
|
||||
|
||||
Managing memory
|
||||
---------------
|
||||
|
||||
When removing a constraint, Kiwi does not check whether or not the variables
|
||||
used in the constraint are still in use in other constraints. This is mostly
|
||||
because such checks could be quite expensive. However, this means the map of
|
||||
variables can grow over time.
|
||||
|
||||
To avoid this possibly causing large memory leaks, it is recommended to reset
|
||||
the solver state (using the method of the same name) and to add back the
|
||||
constraints, that are still valid at this point.
|
||||
|
||||
|
||||
Representation of constraints
|
||||
-----------------------------
|
||||
|
||||
If you browse through the API documentation you may notice a number of classes
|
||||
that do not appear anywhere in this documentation: Term and Expression.
|
||||
|
||||
Those classes are used internally in constraints and are created automatically
|
||||
by the library. A |Term| represents a variable/symbol and the coefficient that
|
||||
multiplies it, |Expression| represents a sum of terms and a constant value and
|
||||
is used as the left hand side of a constraint.
|
||||
|
||||
|
||||
Performance implementation tricks
|
||||
---------------------------------
|
||||
|
||||
Map type
|
||||
^^^^^^^^
|
||||
|
||||
Kiwi uses maps to represent the state of the solver and to manipulate it. As a
|
||||
consequence, the map type should be fast, with a particular emphasis on
|
||||
iteration. The C++ standard library provides unordered_map and map that could
|
||||
be used in kiwi, but none of those are very friendly to the CPU cache. For
|
||||
this reason, Kiwi uses the AssocVector class implemented in Loki (slightly
|
||||
updated to respect c++11 standards). The use of this class provides a 2x
|
||||
speedup over std::map.
|
||||
|
||||
|
||||
Symbol representation
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Symbols are used in Kiwi to represent the state of the solver. Since solving the
|
||||
system requires a large number of manipulations of the symbols, the operations
|
||||
have to compile down to an efficient representation. In Kiwi, symbols compile
|
||||
down to long long meaning that a vector of them fits in a CPU cache line.
|
||||
Reference in New Issue
Block a user