Squashed 'kiwi/' content from commit 268028e
git-subtree-dir: kiwi git-subtree-split: 268028ee4a694dcd89e4b1e683bf2f9ac48c08d9
This commit is contained in:
213
docs/source/basis/basic_systems.rst
Normal file
213
docs/source/basis/basic_systems.rst
Normal file
@@ -0,0 +1,213 @@
|
||||
.. _basis-basic-systems:
|
||||
|
||||
Constraints definition and system solving
|
||||
=========================================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
A system within Kiwi is defined by a set of constraints, which may
|
||||
be either equalities or inequalities (limited to >= and <=, as strict inequalities
|
||||
are not supported). Each constraint can be assigned a specific 'strength',
|
||||
indicating its relative importance in the problem-solving process. The subsequent
|
||||
sections will delve into the methods of defining these constraints and extracting
|
||||
results from the solver.
|
||||
|
||||
Defining variables and constraints
|
||||
----------------------------------
|
||||
|
||||
The initial step involves defining variables, which represent
|
||||
the values that the solver aims to determine. These variables are
|
||||
encapsulated by |Variable| objects. The creation of these objects
|
||||
can be accomplished as follows:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import Variable
|
||||
|
||||
x1 = Variable('x1')
|
||||
x2 = Variable('x2')
|
||||
xm = Variable('xm')
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
#include <kiwi/kiwi.h>
|
||||
|
||||
using namespace kiwi
|
||||
|
||||
Variable x1("x1");
|
||||
Variable x2("x2");
|
||||
Variable xm("xm");
|
||||
|
||||
.. note::
|
||||
|
||||
Naming your variables is not mandatory but it is recommended since it will
|
||||
help the solver in providing more meaningful error messages.
|
||||
|
||||
Now that we have some variables we can define our constraints.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
constraints = [x1 >= 0, x2 <= 100, x2 >= x1 + 10, xm == (x1 + x2) / 2]
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
Constraint constraints[] = { Constraint {x1 >= 0},
|
||||
Constraint {x2 <= 100},
|
||||
Constraint {x2 >= x1 + 20},
|
||||
Constraint {xm == (x1 + x2) / 2}
|
||||
};
|
||||
|
||||
Next, add these variables to the solver, an instance of |Solver|:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import Solver
|
||||
|
||||
solver = Solver()
|
||||
|
||||
for cn in constraints:
|
||||
solver.addConstraint(cn)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
Solver solver;
|
||||
|
||||
for(auto& constraint : constraints)
|
||||
{
|
||||
solver.addConstraint(constraint);
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
You can start adding constraints to the solver without creating all your
|
||||
variables first.
|
||||
|
||||
|
||||
So far, we have defined a system representing three points on the segment
|
||||
[0, 100], with one of them being the middle of the others, which cannot get
|
||||
closer than 10. All those constraints have to be satisfied; in the context
|
||||
of Cassowary, they are required constraints.
|
||||
|
||||
.. note::
|
||||
|
||||
Cassowary (and Kiwi) allows for redundant constraints, which means
|
||||
even with two constraints (x == 10, x + y == 30) being equivalent to a
|
||||
third one (y == 20), all three can be added to the solver without issues.
|
||||
|
||||
However, it is advisable not to add the same constraint multiple times
|
||||
in the same form to the solver.
|
||||
|
||||
|
||||
Managing constraints strength
|
||||
-----------------------------
|
||||
|
||||
Cassowary also supports constraints that are not required. Those are only
|
||||
respected on a best effort basis. To express that a constraint is not required
|
||||
we need to assign it a *strength*. Kiwi specifies three standard strengths
|
||||
besides the "required" strength: strong, medium, weak. A strong constraint
|
||||
will always win over a medium constraint, which in turn will always override
|
||||
a weak constraint [#f1]_ .
|
||||
|
||||
In our example, let's assume x1 would like to be at 40, without this being a
|
||||
requirement. This is translated as follows:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.addConstraint((x1 == 40) | "weak")
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.addConstraint(x1 == 40 | strength::weak);
|
||||
|
||||
|
||||
Adding edit variables
|
||||
---------------------
|
||||
|
||||
So far our system is pretty static; we have no way of trying to find solutions
|
||||
for a particular value of `xm`, let's say. This is a problem. In a real
|
||||
application (e.g. a GUI layout), we would like to find the size of the widgets
|
||||
based on the top window but also react to the window resizing, so actually
|
||||
adding and removing constraints all the time wouldn't be optimal. And there is
|
||||
a better way: edit variables.
|
||||
|
||||
Edit variables are variables for which you can suggest values. Edit variables
|
||||
have a strength which can be at most strong (the value of a edit variable can
|
||||
never be required).
|
||||
|
||||
For the sake of our example, we will make "xm" editable:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.addEditVariable(xm, 'strong')
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.addEditVariable(xm, strength::strong);
|
||||
|
||||
Once a variable has been added as an edit variable, you can suggest a value for
|
||||
it and the solver will try to solve the system with it.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.suggestValue(xm, 60)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.suggestValue(xm, 60);
|
||||
|
||||
This would give the following solution: ``xm == 60, x1 == 40, x2 == 80``.
|
||||
|
||||
|
||||
Solving and updating variables
|
||||
------------------------------
|
||||
|
||||
Kiwi solves the system each time a constraint is added or removed, or a new
|
||||
value is suggested for an edit variable. Solving the system each time makes for
|
||||
faster updates and allows to keep the solver in a consinstent state. However,
|
||||
the variable values are not updated automatically, and you need to ask
|
||||
the solver to perform this operation before reading the values, as illustrated
|
||||
below:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.suggestValue(xm, 90)
|
||||
solver.updateVariables()
|
||||
print(xm.value(), x1.value(), x2.value())
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.suggestValue(xm, 90);
|
||||
solver.updateVariables();
|
||||
std::cout << xm.value() << ", " << x1.value() << ", " << x2.value();
|
||||
|
||||
This last update creates an infeasible situation by pushing x2 further than
|
||||
100, if we keep x1 where it would like to be. As a consequence, we get the
|
||||
following solution: ``xm == 90, x1 == 80, x2 == 100``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
To determine if a non-required constraint was violated when solving the system,
|
||||
you can use the constraint's ``violated`` method.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
.. [#f1] Actually, there are some corner cases in which this can be violated.
|
||||
See :ref:`basics-internals`
|
||||
22
docs/source/basis/index.rst
Normal file
22
docs/source/basis/index.rst
Normal file
@@ -0,0 +1,22 @@
|
||||
.. _basis:
|
||||
|
||||
Kiwisolver usage
|
||||
================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
This section of the docs aims at getting you up and running with Kiwi. You will
|
||||
in particular learn how to install Kiwi, create a system of constraints, solve,
|
||||
update it etc... By the end of it you will know how to use the solver.
|
||||
|
||||
However if you are not familiar with Cassowary (or constraints solver in
|
||||
general) it may not be enough to get you started using it in your project.
|
||||
Hopefully the real world use cases described in :ref:`uses` will shed
|
||||
more light on how to use it in real applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation.rst
|
||||
basic_systems.rst
|
||||
solver_internals.rst
|
||||
87
docs/source/basis/installation.rst
Normal file
87
docs/source/basis/installation.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
.. _basis-installation:
|
||||
|
||||
Installing Kiwisolver on Python
|
||||
===============================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
Kiwisolver is supported on Python 3.7+. Installing it is a straight-forward
|
||||
process. There are three approaches to choose from.
|
||||
|
||||
The easy way: Pre-compiled packages
|
||||
-----------------------------------
|
||||
|
||||
The easiest way to install atom is through pre-compiled packages. Kiwisolver is
|
||||
distributed pre-compiled in two-forms.
|
||||
|
||||
Conda packages
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
If you use the `Anaconda`_ Python distribution platform (or `Miniconda`_, its
|
||||
lighter-weight companion), the latest release of Kiwisolver can be installed
|
||||
using conda from the default channel or the conda-forge channel::
|
||||
|
||||
$ conda install kiwisolver
|
||||
|
||||
$ conda install kiwisolver -c conda-forge
|
||||
|
||||
.. _Anaconda: https://store.continuum.io/cshop/anaconda
|
||||
.. _Miniconda: https://conda.io/miniconda.html
|
||||
|
||||
Wheels
|
||||
^^^^^^
|
||||
|
||||
If you don't use Anaconda, you can install Kiwisolver pre-compiled,
|
||||
through PIP, for most common platforms::
|
||||
|
||||
$ pip install kiwisolver
|
||||
|
||||
Compiling it yourself: The Hard Way
|
||||
-----------------------------------
|
||||
|
||||
Building Kiwisolver from scratch requires Python and a C++ compiler. On Unix
|
||||
platform getting a C++ compiler properly configured is generally
|
||||
straighforward. On Windows, starting with Python 3.6 the free version of the
|
||||
Microsoft toolchain should work out of the box. Installing Kiwisolver is then
|
||||
as simple as::
|
||||
|
||||
$ pip install .
|
||||
|
||||
.. note::
|
||||
|
||||
For MacOSX users on OSX Mojave, one needs to set MACOSX_DEPLOYMENT_TARGET
|
||||
to higher than 10.9 to force the compiler to use the new C++ stdlib::
|
||||
|
||||
$ export MACOSX_DEPLOYMENT_TARGET=10.10
|
||||
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
|
||||
Kiwisolver is known to run on Windows, OSX, and Linux; and compiles cleanly
|
||||
with MSVC, Clang, GCC, and MinGW. If you encounter a bug, please report
|
||||
it on the `Issue Tracker`_.
|
||||
|
||||
.. _Issue Tracker: http://github.com/nucleic/enaml/issues
|
||||
|
||||
|
||||
Checking your install
|
||||
---------------------
|
||||
|
||||
Once you installed kiwisolver you should be able to import it as follows:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import kiwisolver
|
||||
|
||||
.. note::
|
||||
|
||||
On Windows, the import may fail with `ImportError: DLL load failed`. If it
|
||||
does, it means your system is missing the Microsoft Visual C++
|
||||
redistributable matching your Python version. To fix the issue download
|
||||
and install the package corresponding to your Python version
|
||||
(https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads):
|
||||
|
||||
- Python 2.7: Visual C++ Redistributable 2008
|
||||
- Python 3.4: Visual C++ Redistributable 2010
|
||||
- Python 3.5+: Visual C++ Redistributable 2015 or more recent
|
||||
196
docs/source/basis/solver_internals.rst
Normal file
196
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