Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cppy use update and c++11 compatibility #55

Merged
merged 16 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 3 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,18 @@ branches:

jobs:
include:
- name: "run test suite with python 2.7"
python: 2.7
dist: trusty
- name: "run test suite with python 3.4"
python: 3.4
dist: trusty
- name: "run test suite with python 3.5"
python: 3.5
dist: trusty
- name: "run test suite with python 3.6"
python: 3.6
- name: "run test suite with python 3.7"
python: 3.7
- name: "run test suite with python 3.8"
python: 3.8

env:
- CPPFLAGS=--coverage
before_install:
- pip install --upgrade pip
- pip install https://github.com/nucleic/cppy/tarball/master
install:
- python setup.py develop
script:
Expand Down
13 changes: 12 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
include MANIFEST.in
include COPYING.txt
include LICENSE
include README.rst
include releasenotes.rst
recursive-include kiwi *.h
recursive-include py *.cpp *.h
recursive-include docs/source *.rst
recursive-include docs/source *.png
recursive-include docs/source *.py
recursive-include docs/source *.svg
recursive docs/make.bat
recursive docs/Makefile
recursive-include tests *.py
prune .git
prune dist
prune build
prune docs/build
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ from 10x to 500x faster than the original Cassowary solver with typical use
cases gaining a 40x improvement. Memory savings are consistently > 5x.

In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.

The version 1.1.0 of the Python bindings will be the last one to support
Python 2, moving forward support will be limited to Python 3.5+.
9 changes: 9 additions & 0 deletions benchmarks/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Benchmarks for Kiwi
-------------------

Those benchmarks are mostly used to check the performance of kiwi depending on
different c++ data structure.

Running these benchmarks require to install the perf module::

>>> python enaml_like_benchmarks.py
197 changes: 197 additions & 0 deletions benchmarks/enaml_like_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#------------------------------------------------------------------------------
# Copyright (c) 2019, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
"""Time updating an EditVariable in a set of constraints typical of enaml use.

"""
import perf
from kiwisolver import Variable, Solver, strength

solver = Solver()

# Create custom strength
mmedium = strength.create(0, 1, 0, 1.25)
smedium = strength.create(0, 100, 0)

# Create the variable
left = Variable('left')
height = Variable('height')
top = Variable('top')
width = Variable('width')
contents_top = Variable('contents_top')
contents_bottom = Variable('contents_bottom')
contents_left = Variable('contents_left')
contents_right = Variable('contents_right')
midline = Variable('midline')
ctleft = Variable('ctleft')
ctheight = Variable('ctheight')
cttop = Variable('cttop')
ctwidth = Variable('ctwidth')
lb1left = Variable('lb1left')
lb1height = Variable('lb1height')
lb1top = Variable('lb1top')
lb1width = Variable('lb1width')
lb2left = Variable('lb2left')
lb2height = Variable('lb2height')
lb2top = Variable('lb2top')
lb2width = Variable('lb2width')
lb3left = Variable('lb3left')
lb3height = Variable('lb3height')
lb3top = Variable('lb3top')
lb3width = Variable('lb3width')
fl1left = Variable('fl1left')
fl1height = Variable('fl1height')
fl1top = Variable('fl1top')
fl1width = Variable('fl1width')
fl2left = Variable('fl2left')
fl2height = Variable('fl2height')
fl2top = Variable('fl2top')
fl2width = Variable('fl2width')
fl3left = Variable('fl3left')
fl3height = Variable('fl3height')
fl3top = Variable('fl3top')
fl3width = Variable('fl3width')

# Add the edit variables
solver.addEditVariable(width, 'strong')
solver.addEditVariable(height, 'strong')

# Add the constraints
for c in [(left + -0 >= 0) | "required",
(height + 0 == 0) | "medium",
(top + -0 >= 0) | "required",
(width + -0 >= 0) | "required",
(height + -0 >= 0) | "required",
(- top + contents_top + -10 == 0) | "required",
(lb3height + -16 == 0) | "strong",
(lb3height + -16 >= 0) | "strong",
(ctleft + -0 >= 0) | "required",
(cttop + -0 >= 0) | "required",
(ctwidth + -0 >= 0) | "required",
(ctheight + -0 >= 0) | "required",
(fl3left + -0 >= 0) | "required",
(ctheight + -24 >= 0) | smedium,
(ctwidth + -1.67772e+07 <= 0) | smedium,
(ctheight + -24 <= 0) | smedium,
(fl3top + -0 >= 0) | "required",
(fl3width + -0 >= 0) | "required",
(fl3height + -0 >= 0) | "required",
(lb1width + -67 == 0) | "weak",
(lb2width + -0 >= 0) | "required",
(lb2height + -0 >= 0) | "required",
(fl2height + -0 >= 0) | "required",
(lb3left + -0 >= 0) | "required",
(fl2width + -125 >= 0) | "strong",
(fl2height + -21 == 0) | "strong",
(fl2height + -21 >= 0) | "strong",
(lb3top + -0 >= 0) | "required",
(lb3width + -0 >= 0) | "required",
(fl1left + -0 >= 0) | "required",
(fl1width + -0 >= 0) | "required",
(lb1width + -67 >= 0) | "strong",
(fl2left + -0 >= 0) | "required",
(lb2width + -66 == 0) | "weak",
(lb2width + -66 >= 0) | "strong",
(lb2height + -16 == 0) | "strong",
(fl1height + -0 >= 0) | "required",
(fl1top + -0 >= 0) | "required",
(lb2top + -0 >= 0) | "required",
(- lb2top + lb3top + - lb2height + -10 == 0) | mmedium,
(- lb3top + - lb3height + fl3top + -10 >= 0) | "required",
(- lb3top + - lb3height + fl3top + -10 == 0) | mmedium,
(contents_bottom + - fl3height + - fl3top + -0 == 0) | mmedium,
(fl1top + - contents_top + 0 >= 0) | "required",
(fl1top + - contents_top + 0 == 0) | mmedium,
(contents_bottom + - fl3height + - fl3top + -0 >= 0) | "required",
(- left + - width + contents_right + 10 == 0) | "required",
(- top + - height + contents_bottom + 10 == 0) | "required",
(- left + contents_left + -10 == 0) | "required",
(lb3left + - contents_left + 0 == 0) | mmedium,
(fl1left + - midline + 0 == 0) | "strong",
(fl2left + - midline + 0 == 0) | "strong",
(ctleft + - midline + 0 == 0) | "strong",
(fl1top + 0.5 * fl1height + - lb1top + -0.5 * lb1height + 0 == 0) | "strong",
(lb1left + - contents_left + 0 >= 0) | "required",
(lb1left + - contents_left + 0 == 0) | mmedium,
(- lb1left + fl1left + - lb1width + -10 >= 0) | "required",
(- lb1left + fl1left + - lb1width + -10 == 0) | mmedium,
(- fl1left + contents_right + - fl1width + -0 >= 0) | "required",
(width + 0 == 0) | "medium",
(- fl1top + fl2top + - fl1height + -10 >= 0) | "required",
(- fl1top + fl2top + - fl1height + -10 == 0) | mmedium,
(cttop + - fl2top + - fl2height + -10 >= 0) | "required",
(- ctheight + - cttop + fl3top + -10 >= 0) | "required",
(contents_bottom + - fl3height + - fl3top + -0 >= 0) | "required",
(cttop + - fl2top + - fl2height + -10 == 0) | mmedium,
(- fl1left + contents_right + - fl1width + -0 == 0) | mmedium,
(- lb2top + -0.5 * lb2height + fl2top + 0.5 * fl2height + 0 == 0) | "strong",
(- contents_left + lb2left + 0 >= 0) | "required",
(- contents_left + lb2left + 0 == 0) | mmedium,
(fl2left + - lb2width + - lb2left + -10 >= 0) | "required",
(- ctheight + - cttop + fl3top + -10 == 0) | mmedium,
(contents_bottom + - fl3height + - fl3top + -0 == 0) | mmedium,
(lb1top + -0 >= 0) | "required",
(lb1width + -0 >= 0) | "required",
(lb1height + -0 >= 0) | "required",
(fl2left + - lb2width + - lb2left + -10 == 0) | mmedium,
(- fl2left + - fl2width + contents_right + -0 == 0) | mmedium,
(- fl2left + - fl2width + contents_right + -0 >= 0) | "required",
(lb3left + - contents_left + 0 >= 0) | "required",
(lb1left + -0 >= 0) | "required",
(0.5 * ctheight + cttop + - lb3top + -0.5 * lb3height + 0 == 0) | "strong",
(ctleft + - lb3left + - lb3width + -10 >= 0) | "required",
(- ctwidth + - ctleft + contents_right + -0 >= 0) | "required",
(ctleft + - lb3left + - lb3width + -10 == 0) | mmedium,
(fl3left + - contents_left + 0 >= 0) | "required",
(fl3left + - contents_left + 0 == 0) | mmedium,
(- ctwidth + - ctleft + contents_right + -0 == 0) | mmedium,
(- fl3left + contents_right + - fl3width + -0 == 0) | mmedium,
(- contents_top + lb1top + 0 >= 0) | "required",
(- contents_top + lb1top + 0 == 0) | mmedium,
(- fl3left + contents_right + - fl3width + -0 >= 0) | "required",
(lb2top + - lb1top + - lb1height + -10 >= 0) | "required",
(- lb2top + lb3top + - lb2height + -10 >= 0) | "required",
(lb2top + - lb1top + - lb1height + -10 == 0) | mmedium,
(fl1height + -21 == 0) | "strong",
(fl1height + -21 >= 0) | "strong",
(lb2left + -0 >= 0) | "required",
(lb2height + -16 >= 0) | "strong",
(fl2top + -0 >= 0) | "required",
(fl2width + -0 >= 0) | "required",
(lb1height + -16 >= 0) | "strong",
(lb1height + -16 == 0) | "strong",
(fl3width + -125 >= 0) | "strong",
(fl3height + -21 == 0) | "strong",
(fl3height + -21 >= 0) | "strong",
(lb3height + -0 >= 0) | "required",
(ctwidth + -119 >= 0) | smedium,
(lb3width + -24 == 0) | "weak",
(lb3width + -24 >= 0) | "strong",
(fl1width + -125 >= 0) | "strong",
]:
solver.addConstraint(c)


def bench_update_variables(loops, solver):
"""Suggest new values and update variables.

This mimic the use of kiwi in enaml in the case of a resizing.

"""
t0 = perf.perf_counter()
for w, h in [(400, 600), (600, 400), (800, 1200), (1200, 800),
(400, 800), (800, 400)]*loops:
solver.suggestValue(width, w)
solver.suggestValue(height, h)
solver.updateVariables()

return perf.perf_counter() - t0


runner = perf.Runner()
runner.bench_time_func('kiwi.suggestValue', bench_update_variables,
solver, inner_loops=1)
24 changes: 24 additions & 0 deletions docs/source/basis/solver_internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,27 @@ 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
speedups over std::map.


Symbol representation
^^^^^^^^^^^^^^^^^^^^^

Symbol are used in Kiwi to represent the state of the solver. Since solving the
system requires a large number of manipulation 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.
29 changes: 29 additions & 0 deletions docs/source/developer_notes/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. _developer:

Developer notes
================

These notes are meant to help developers and contributors with regards to some
details of the implementation and coding style of the project.

C++ codebase
------------

The C++ codebase currently targets C++11 compliance. It is header-only since
one of the focus of the library is speed.


Python bindings
---------------

Python bindings targets Python 3.6 and above. The bindings are hand-written and
relies on cppy (https://github.com/nucleic/cppy). Kiwisolver tries to use a
reasonably modern C API and to support sub-interpreter, this has a couple of
consequences:

- all the non exported symbol are enclosed in anonymous namespaces
- kiwisolver does not use static types and only dynamical types (note that the
type slots and related structures are stored in a static variable)
- modules use the multi-phases initialization mechanism as defined in
PEP 489 -- Multi-phase extension module initialization
- static variables use is limited to type slots, method def
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.

Getting started <basis/index.rst>
Use cases <use_cases/index.rst>
Developer notes <developer_notes/index.rst>
API Documentation <api/index.rst>

Indices and tables
Expand Down
4 changes: 3 additions & 1 deletion docs/source/use_cases/enaml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ widgets should be in contact.
When generating the constraints, `hbox` will be passed the container and use
the spacers to generate the constraints by simply glueing the anchors of
surrounding widgets. Each spacer can generate multiple constraints which gives
this process a lot of flexibility.
this process a lot of flexibility. Furthermore, those helpers define the same
variable as the widgets allowing for to position groups with respect to one
another.

.. note::

Expand Down
4 changes: 3 additions & 1 deletion kiwi/AssocVector.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// suitability of this software for any purpose. It is provided "as is"
// without express or implied warranty.
////////////////////////////////////////////////////////////////////////////////
// Updated 2019 by Matthieu Dartiailh for C++11 compliancy
////////////////////////////////////////////////////////////////////////////////
#pragma once

// $Id: AssocVector.h 765 2006-10-18 13:55:32Z syntheticpp $
Expand Down Expand Up @@ -107,7 +109,7 @@ namespace Loki
typedef typename Base::const_reverse_iterator const_reverse_iterator;

class value_compare
: public std::binary_function<value_type, value_type, bool>
: public std::function<bool(value_type, value_type)>
, private key_compare
{
friend class AssocVector;
Expand Down
18 changes: 9 additions & 9 deletions kiwi/maptype.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
Expand All @@ -24,14 +24,14 @@ template<
typename V,
typename C = std::less<K>,
typename A = std::allocator< std::pair<K, V> > >
class MapType
{
public:
typedef Loki::AssocVector<K, V, C, A> Type;
//typedef std::map<K, V, C, A> Type;
private:
MapType();
};
using MapType = Loki::AssocVector<K, V, C, A>;

// template<
// typename K,
// typename V,
// typename C = std::less<K>,
// typename A = std::allocator< std::pair<const K, V> > >
// using MapType = std::map<K, V, C, A>;

} // namespace impl

Expand Down
2 changes: 1 addition & 1 deletion kiwi/row.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Row

public:

typedef MapType<Symbol, double>::Type CellMap;
typedef MapType<Symbol, double> CellMap;

Row() : m_constant( 0.0 ) {}

Expand Down