Squashed 'kiwi/' content from commit 268028e

git-subtree-dir: kiwi
git-subtree-split: 268028ee4a694dcd89e4b1e683bf2f9ac48c08d9
This commit is contained in:
2024-02-11 15:32:50 -06:00
commit 81396a5322
76 changed files with 13184 additions and 0 deletions

View 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`

View 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

View 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

View 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.