Merge commit '81396a5322a7a48764fcf254d5d933ba1e57bdc5' as 'kiwi'

This commit is contained in:
2024-02-11 15:32:50 -06:00
76 changed files with 13184 additions and 0 deletions

12
kiwi/.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [MatthieuDartiailh]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

116
kiwi/.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Continuous Integration
on:
schedule:
- cron: '0 0 * * 2'
push:
branches:
- main
pull_request:
branches:
- main
paths:
- .github/workflows/ci.yml
- "benchmarks/**"
- "kiwi/**"
- "py/**"
- setup.py
- pyproject.toml
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9']
steps:
- uses: actions/checkout@v3
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r lint_requirements.txt
pip install -e .
- name: Formatting
if: always()
run: |
ruff format py --check
- name: Linting
if: always()
run: |
ruff py
- name: Typing
if: always()
# We test twice to ensure the type annotations are properly installed
run: |
mypy py
cd py/tests
mypy .
benchmark:
name: C++ Benchmark
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get install -y g++-11
- name: Build and run benchmark (C++11)
run: cd benchmarks && ./build_and_run_bench.sh
- name: Build and run benchmark (C++20)
env:
CXX_COMPILER: g++-11
CXX_FLAGS: -std=c++20
run: cd benchmarks && ./build_and_run_bench.sh
tests:
name: Unit tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev', 'pypy-3.7', 'pypy-3.8']
steps:
- uses: actions/checkout@v3
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Install project
env:
CPPFLAGS: --coverage
KIWI_DISABLE_FH4: 1
run: |
pip install .
- name: Test with pytest
run: |
pip install pytest
python -X dev -m pytest py -W error
- name: Generate C++ coverage reports
if: (github.event_name != 'schedule' && matrix.os != 'windows-latest')
run: |
bash -c "find . -type f -name '*.gcno' -exec gcov -pb --all-blocks {} +" || true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
if: (github.event_name != 'schedule' && matrix.os != 'windows-latest')
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true

45
kiwi/.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Documentation building
on:
schedule:
- cron: '0 0 * * 2'
push:
branches:
- main
pull_request:
branches:
- main
paths:
- .github/workflows/docs.yml
- "kiwi/**"
- "py/**"
- "docs/**"
- setup.py
- pyproject.toml
jobs:
docs:
name: Docs building
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r docs/requirements.txt
- name: Install project
run: |
pip install .
- name: Install graphviz
uses: ts-graphviz/setup-graphviz@v1
- name: Build documentation
run: |
mkdir docs_output;
sphinx-build docs/source docs_output -W -b html;

220
kiwi/.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,220 @@
name: Build and upload wheels
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 3'
push:
tags:
- '*'
jobs:
build_sdist:
name: Build sdist
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Build sdist
run: |
pip install --upgrade pip
pip install wheel build
python -m build . -s
- name: Test sdist
run: |
pip install pytest
pip install dist/*.tar.gz
cd ..
pytest kiwi/py/tests -v -W error
- name: Store artifacts
uses: actions/upload-artifact@v3
with:
name: artifact
path: dist/*
build_wheels:
name: Build wheels on ${{ matrix.os }} for ${{ matrix.archs }} using ${{ matrix.manylinux_version }}+
runs-on: ${{ matrix.os }}
env:
BUILD_COMMIT: main
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# We build separately 3.7 and 3.8 using manylinux1
manylinux_version: [manylinux1, manylinux2010, manylinux2014]
archs: [auto]
include:
- os: ubuntu-latest
archs: aarch64
manylinux_version: manylinux1
- os: ubuntu-latest
archs: ppc64le
manylinux_version: manylinux1
- os: ubuntu-latest
archs: s390x
manylinux_version: manylinux1
- os: ubuntu-latest
archs: aarch64
manylinux_version: manylinux2010
- os: ubuntu-latest
archs: ppc64le
manylinux_version: manylinux2010
- os: ubuntu-latest
archs: s390x
manylinux_version: manylinux2010
- os: ubuntu-latest
archs: aarch64
manylinux_version: manylinux2014
- os: ubuntu-latest
archs: ppc64le
manylinux_version: manylinux2014
- os: ubuntu-latest
archs: s390x
manylinux_version: manylinux2014
- os: windows-latest
archs: ARM64
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Set up QEMU
if: runner.os == 'Linux' && matrix.archs != 'auto'
uses: docker/setup-qemu-action@v2
with:
platforms: all
- name: Install cibuildwheel
run: |
python -m pip install --upgrade pip
python -m pip install wheel cibuildwheel
- name: Build wheels
if: matrix.manylinux_version == 'manylinux1'
env:
CIBW_BUILD: "cp37-* cp38-*"
CIBW_ARCHS_MACOS: x86_64 universal2 arm64
CIBW_ARCHS_LINUX: ${{ matrix.archs }}
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
CIBW_MANYLINUX_I686_IMAGE: manylinux1
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: pytest {package}/py/tests -v -W error
# Do not link against VC2014_1 on Windows
KIWI_DISABLE_FH4: 1
run: |
python -m cibuildwheel . --output-dir dist
- name: Build wheels
if: matrix.manylinux_version == 'manylinux2010'
env:
CIBW_BUILD: "cp39-* cp310-* pp37-* pp38-*"
CIBW_ARCHS_MACOS: x86_64 universal2 arm64
CIBW_ARCHS_LINUX: ${{ matrix.archs }}
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2010
CIBW_MANYLINUX_I686_IMAGE: manylinux2010
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: python -m pytest {package}/py/tests -v
# Do not link against VC2014_1 on Windows
KIWI_DISABLE_FH4: 1
run: |
python -m cibuildwheel . --output-dir dist
- name: Build wheels
if: matrix.manylinux_version == 'manylinux2014'
env:
CIBW_BUILD: "cp312-* cp311-* pp39-*"
CIBW_ARCHS_MACOS: x86_64 universal2 arm64
CIBW_ARCHS_LINUX: ${{ matrix.archs }}
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: python -m pytest {package}/py/tests -v
# Do not link against VC2014_1 on Windows
KIWI_DISABLE_FH4: 1
run: |
python -m cibuildwheel . --output-dir dist
- name: Build wheels
if: runner.os == 'Windows' && matrix.archs != 'auto'
env:
CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*"
CIBW_ARCHS_WINDOWS: ${{ matrix.archs }}
# It is not yet possible to run ARM64 tests, only cross-compile them.
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: python -m pytest {package}/py/tests -v
# Do not link against VC2014_1 on Windows.
KIWI_DISABLE_FH4: 1
run: |
python -m cibuildwheel . --output-dir dist
- name: Store artifacts
uses: actions/upload-artifact@v3
with:
name: artifact
path: dist/*.whl
publish:
if: github.event_name == 'push'
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: dist
- uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
# To test:
# repository_url: https://test.pypi.org/legacy/
github-release:
name: >-
Sign the Python 🐍 distribution 📦 with Sigstore
and create a GitHub Release
runs-on: ubuntu-latest
needs:
- publish
permissions:
contents: write
id-token: write
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: artifact
path: dist
- name: Sign the dists with Sigstore
uses: sigstore/gh-action-sigstore-python@v2.1.0
with:
password: ${{ secrets.pypi_password }}
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release create
'${{ github.ref_name }}'
--repo '${{ github.repository }}'
--generate-notes
- name: Upload artifact signatures to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release upload
'${{ github.ref_name }}' dist/**
--repo '${{ github.repository }}'

14
kiwi/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*.pyc
*.pyd
*.so
# auto-generated by setuptools-scm
py/src/version.h
benchmarks/run_bench
build/
dist/
kiwisolver.egg-info/
.eggs
.vscode
.mypy_cache

View File

@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.6
hooks:
# Run the linter.
- id: ruff
# Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: []

27
kiwi/.readthedocs.yaml Normal file
View File

@@ -0,0 +1,27 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# Build documentation in the docs/source directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Enable epub output
formats:
- epub
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .

71
kiwi/LICENSE Normal file
View File

@@ -0,0 +1,71 @@
=========================
The Kiwi licensing terms
=========================
Kiwi is licensed under the terms of the Modified BSD License (also known as
New or Revised BSD), as follows:
Copyright (c) 2013, Nucleic Development Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
Neither the name of the Nucleic Development Team nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
About Kiwi
----------
Chris Colbert began the Kiwi project in December 2013 in an effort to
create a blisteringly fast UI constraint solver. Chris is still the
project lead.
The Nucleic Development Team is the set of all contributors to the Nucleic
project and its subprojects.
The core team that coordinates development on GitHub can be found here:
http://github.com/nucleic. The current team consists of:
* Chris Colbert
Our Copyright Policy
--------------------
Nucleic uses a shared copyright model. Each contributor maintains copyright
over their contributions to Nucleic. But, it is important to note that these
contributions are typically only changes to the repositories. Thus, the Nucleic
source code, in its entirety is not the copyright of any single person or
institution. Instead, it is the collective copyright of the entire Nucleic
Development Team. If individual contributors want to maintain a record of what
changes/contributions they have specific copyright on, they should indicate
their copyright in the commit message of the change, when they commit the
change to one of the Nucleic repositories.
With this in mind, the following banner should be used in any source code file
to indicate the copyright and license terms:
#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------

17
kiwi/MANIFEST.in Normal file
View File

@@ -0,0 +1,17 @@
include MANIFEST.in
include LICENSE
include README.rst
include releasenotes.rst
recursive-include kiwi *.h
recursive-include py *.cpp *.h *.py *.pyi
include py/kiwisolver/py.typed
recursive-include docs/source *.rst
recursive-include docs/source *.png
recursive-include docs/source *.py
recursive-include docs/source *.svg
include docs/make.bat
include docs/Makefile
prune .git
prune dist
prune build
prune docs/build

23
kiwi/README.rst Normal file
View File

@@ -0,0 +1,23 @@
Welcome to Kiwi
===============
.. image:: https://github.com/nucleic/kiwi/workflows/Continuous%20Integration/badge.svg
:target: https://github.com/nucleic/kiwi/actions
.. image:: https://github.com/nucleic/kiwi/workflows/Documentation%20building/badge.svg
:target: https://github.com/nucleic/kiwi/actions
.. image:: https://codecov.io/gh/nucleic/kiwi/branch/main/graph/badge.svg
:target: https://codecov.io/gh/nucleic/kiwi
.. image:: https://readthedocs.org/projects/kiwisolver/badge/?version=latest
:target: https://kiwisolver.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
algorithm. Kiwi is an implementation of the algorithm based on the
`seminal Cassowary paper <https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf>`_.
It is *not* a refactoring of the original C++ solver. Kiwi has been designed
from the ground up to be lightweight and fast. Kiwi ranges 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 for
Python 3.7+.

View File

@@ -0,0 +1,17 @@
Benchmarks for Kiwi
-------------------
Those benchmarks are mostly used to check the performance of kiwi depending on
different c++ data structure.
# C++
GCC must be installed first on your system (`build-essential` package with apt)
>>> ./build_and_run_bench.sh
# Python
Running these benchmarks require to install the perf module::
>>> python enaml_like_benchmarks.py

View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -o errexit -o nounset # fail on error or on unset variables
# set default values if variables are unset
: "${CXX_COMPILER:=g++}"
: "${CXX_FLAGS:=-std=c++11}"
"$CXX_COMPILER" ${CXX_FLAGS} -O2 -Wall -pedantic -I.. enaml_like_benchmark.cpp -o run_bench
./run_bench

View File

@@ -0,0 +1,224 @@
/*-----------------------------------------------------------------------------
| 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.
#include <kiwi/kiwi.h>
#define ANKERL_NANOBENCH_IMPLEMENT
#include "nanobench.h"
using namespace kiwi;
void build_solver(Solver& solver, Variable& width, Variable& height)
{
// Create custom strength
double mmedium = strength::create(0.0, 1.0, 0.0, 1.25);
double smedium = strength::create(0.0, 100, 0.0);
// Create the variable
Variable left("left");
Variable top("top");
Variable contents_top("contents_top");
Variable contents_bottom("contents_bottom");
Variable contents_left("contents_left");
Variable contents_right("contents_right");
Variable midline("midline");
Variable ctleft("ctleft");
Variable ctheight("ctheight");
Variable cttop("cttop");
Variable ctwidth("ctwidth");
Variable lb1left("lb1left");
Variable lb1height("lb1height");
Variable lb1top("lb1top");
Variable lb1width("lb1width");
Variable lb2left("lb2left");
Variable lb2height("lb2height");
Variable lb2top("lb2top");
Variable lb2width("lb2width");
Variable lb3left("lb3left");
Variable lb3height("lb3height");
Variable lb3top("lb3top");
Variable lb3width("lb3width");
Variable fl1left("fl1left");
Variable fl1height("fl1height");
Variable fl1top("fl1top");
Variable fl1width("fl1width");
Variable fl2left("fl2left");
Variable fl2height("fl2height");
Variable fl2top("fl2top");
Variable fl2width("fl2width");
Variable fl3left("fl3left");
Variable fl3height("fl3height");
Variable fl3top("fl3top");
Variable fl3width("fl3width");
// Add the edit variables
solver.addEditVariable(width, strength::strong);
solver.addEditVariable(height, strength::strong);
// Add the constraints
Constraint constraints[] = {
(left + -0 >= 0) | strength::required,
(height + 0 == 0) | strength::medium,
(top + -0 >= 0) | strength::required,
(width + -0 >= 0) | strength::required,
(height + -0 >= 0) | strength::required,
(-top + contents_top + -10 == 0) | strength::required,
(lb3height + -16 == 0) | strength::strong,
(lb3height + -16 >= 0) | strength::strong,
(ctleft + -0 >= 0) | strength::required,
(cttop + -0 >= 0) | strength::required,
(ctwidth + -0 >= 0) | strength::required,
(ctheight + -0 >= 0) | strength::required,
(fl3left + -0 >= 0) | strength::required,
(ctheight + -24 >= 0) | smedium,
(ctwidth + -1.67772e+07 <= 0) | smedium,
(ctheight + -24 <= 0) | smedium,
(fl3top + -0 >= 0) | strength::required,
(fl3width + -0 >= 0) | strength::required,
(fl3height + -0 >= 0) | strength::required,
(lb1width + -67 == 0) | strength::weak,
(lb2width + -0 >= 0) | strength::required,
(lb2height + -0 >= 0) | strength::required,
(fl2height + -0 >= 0) | strength::required,
(lb3left + -0 >= 0) | strength::required,
(fl2width + -125 >= 0) | strength::strong,
(fl2height + -21 == 0) | strength::strong,
(fl2height + -21 >= 0) | strength::strong,
(lb3top + -0 >= 0) | strength::required,
(lb3width + -0 >= 0) | strength::required,
(fl1left + -0 >= 0) | strength::required,
(fl1width + -0 >= 0) | strength::required,
(lb1width + -67 >= 0) | strength::strong,
(fl2left + -0 >= 0) | strength::required,
(lb2width + -66 == 0) | strength::weak,
(lb2width + -66 >= 0) | strength::strong,
(lb2height + -16 == 0) | strength::strong,
(fl1height + -0 >= 0) | strength::required,
(fl1top + -0 >= 0) | strength::required,
(lb2top + -0 >= 0) | strength::required,
(-lb2top + lb3top + -lb2height + -10 == 0) | mmedium,
(-lb3top + -lb3height + fl3top + -10 >= 0) | strength::required,
(-lb3top + -lb3height + fl3top + -10 == 0) | mmedium,
(contents_bottom + -fl3height + -fl3top + -0 == 0) | mmedium,
(fl1top + -contents_top + 0 >= 0) | strength::required,
(fl1top + -contents_top + 0 == 0) | mmedium,
(contents_bottom + -fl3height + -fl3top + -0 >= 0) | strength::required,
(-left + -width + contents_right + 10 == 0) | strength::required,
(-top + -height + contents_bottom + 10 == 0) | strength::required,
(-left + contents_left + -10 == 0) | strength::required,
(lb3left + -contents_left + 0 == 0) | mmedium,
(fl1left + -midline + 0 == 0) | strength::strong,
(fl2left + -midline + 0 == 0) | strength::strong,
(ctleft + -midline + 0 == 0) | strength::strong,
(fl1top + 0.5 * fl1height + -lb1top + -0.5 * lb1height + 0 == 0) | strength::strong,
(lb1left + -contents_left + 0 >= 0) | strength::required,
(lb1left + -contents_left + 0 == 0) | mmedium,
(-lb1left + fl1left + -lb1width + -10 >= 0) | strength::required,
(-lb1left + fl1left + -lb1width + -10 == 0) | mmedium,
(-fl1left + contents_right + -fl1width + -0 >= 0) | strength::required,
(width + 0 == 0) | strength::medium,
(-fl1top + fl2top + -fl1height + -10 >= 0) | strength::required,
(-fl1top + fl2top + -fl1height + -10 == 0) | mmedium,
(cttop + -fl2top + -fl2height + -10 >= 0) | strength::required,
(-ctheight + -cttop + fl3top + -10 >= 0) | strength::required,
(contents_bottom + -fl3height + -fl3top + -0 >= 0) | strength::required,
(cttop + -fl2top + -fl2height + -10 == 0) | mmedium,
(-fl1left + contents_right + -fl1width + -0 == 0) | mmedium,
(-lb2top + -0.5 * lb2height + fl2top + 0.5 * fl2height + 0 == 0) | strength::strong,
(-contents_left + lb2left + 0 >= 0) | strength::required,
(-contents_left + lb2left + 0 == 0) | mmedium,
(fl2left + -lb2width + -lb2left + -10 >= 0) | strength::required,
(-ctheight + -cttop + fl3top + -10 == 0) | mmedium,
(contents_bottom + -fl3height + -fl3top + -0 == 0) | mmedium,
(lb1top + -0 >= 0) | strength::required,
(lb1width + -0 >= 0) | strength::required,
(lb1height + -0 >= 0) | strength::required,
(fl2left + -lb2width + -lb2left + -10 == 0) | mmedium,
(-fl2left + -fl2width + contents_right + -0 == 0) | mmedium,
(-fl2left + -fl2width + contents_right + -0 >= 0) | strength::required,
(lb3left + -contents_left + 0 >= 0) | strength::required,
(lb1left + -0 >= 0) | strength::required,
(0.5 * ctheight + cttop + -lb3top + -0.5 * lb3height + 0 == 0) | strength::strong,
(ctleft + -lb3left + -lb3width + -10 >= 0) | strength::required,
(-ctwidth + -ctleft + contents_right + -0 >= 0) | strength::required,
(ctleft + -lb3left + -lb3width + -10 == 0) | mmedium,
(fl3left + -contents_left + 0 >= 0) | strength::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) | strength::required,
(-contents_top + lb1top + 0 == 0) | mmedium,
(-fl3left + contents_right + -fl3width + -0 >= 0) | strength::required,
(lb2top + -lb1top + -lb1height + -10 >= 0) | strength::required,
(-lb2top + lb3top + -lb2height + -10 >= 0) | strength::required,
(lb2top + -lb1top + -lb1height + -10 == 0) | mmedium,
(fl1height + -21 == 0) | strength::strong,
(fl1height + -21 >= 0) | strength::strong,
(lb2left + -0 >= 0) | strength::required,
(lb2height + -16 >= 0) | strength::strong,
(fl2top + -0 >= 0) | strength::required,
(fl2width + -0 >= 0) | strength::required,
(lb1height + -16 >= 0) | strength::strong,
(lb1height + -16 == 0) | strength::strong,
(fl3width + -125 >= 0) | strength::strong,
(fl3height + -21 == 0) | strength::strong,
(fl3height + -21 >= 0) | strength::strong,
(lb3height + -0 >= 0) | strength::required,
(ctwidth + -119 >= 0) | smedium,
(lb3width + -24 == 0) | strength::weak,
(lb3width + -24 >= 0) | strength::strong,
(fl1width + -125 >= 0) | strength::strong,
};
for (const auto& constraint : constraints)
solver.addConstraint(constraint);
}
int main()
{
ankerl::nanobench::Bench().run("building solver", [&] {
Solver solver;
Variable width("width");
Variable height("height");
build_solver(solver, width, height);
ankerl::nanobench::doNotOptimizeAway(solver); //< prevent the compiler to optimize away the solver
});
struct Size
{
int width;
int height;
};
Size sizes[] = {
{ 400, 600 },
{ 600, 400 },
{ 800, 1200 },
{ 1200, 800 },
{ 400, 800 },
{ 800, 400 }
};
Solver solver;
Variable widthVar("width");
Variable heightVar("height");
build_solver(solver, widthVar, heightVar);
for (const Size& size : sizes)
{
double width = size.width;
double height = size.height;
ankerl::nanobench::Bench().minEpochIterations(10).run("suggest value " + std::to_string(size.width) + "x" + std::to_string(size.height), [&] {
solver.suggestValue(widthVar, width);
solver.suggestValue(heightVar, height);
solver.updateVariables();
});
}
}

View File

@@ -0,0 +1,206 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2019, 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.
"""
import perf
from kiwisolver import Solver, Variable, 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.67772e07 <= 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
)

3359
kiwi/benchmarks/nanobench.h Normal file

File diff suppressed because it is too large Load Diff

24
kiwi/codecov.yml Normal file
View File

@@ -0,0 +1,24 @@
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
status:
project: yes
patch: yes
changes: yes
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header, diff"
behavior: default
require_changes: no

20
kiwi/docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = kiwi
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

6
kiwi/docs/README.md Normal file
View File

@@ -0,0 +1,6 @@
Building the documentation
==========================
The documentation is built using Sphinx and requires, the Read the Docs
theme (`pip install sphinx-rtd-theme`) and the sphinxtabs sphinx extension
(`pip install sphinx-tabs`).

36
kiwi/docs/make.bat Normal file
View File

@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
set SPHINXPROJ=kiwi
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

View File

@@ -0,0 +1,3 @@
sphinx>=4
sphinx-rtd-theme>=1
sphinx-tabs

View File

@@ -0,0 +1,4 @@
Kiwisolver C++ API
==================
Under construction

View File

@@ -0,0 +1,8 @@
kiwisolver
==========
.. toctree::
:maxdepth: 2
Python API <python.rst>
C++ API <cpp.rst>

View File

@@ -0,0 +1,7 @@
Kiwisolver Python API
=====================
.. automodule:: kiwisolver
:members:
:undoc-members:
:show-inheritance:

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.

169
kiwi/docs/source/conf.py Normal file
View File

@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/main/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# NOTE the project needs to be installed for the docs to be properly built.
import kiwisolver
# -- Project information -----------------------------------------------------
project = "kiwisolver"
copyright = "2018-2021, Nucleic team"
author = "Nucleic team"
# The short X.Y version
version = ".".join(kiwisolver.__version__.split(".")[:2])
# The full version, including alpha/beta/rc tags
release = kiwisolver.__version__
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.imgmath",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.autosummary",
"sphinx.ext.napoleon",
"sphinx_tabs.tabs",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The main toctree document.
main_doc = "index"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "kiwidoc"
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(main_doc, "kiwi.tex", "kiwi Documentation", "Nucleic team", "manual"),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(main_doc, "kiwi", "kiwi Documentation", [author], 1)]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
main_doc,
"kiwi",
"kiwi Documentation",
author,
"kiwi",
"One line description of project.",
"Miscellaneous",
),
]

View File

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

View File

@@ -0,0 +1,32 @@
.. kiwi documentation main file, created by
sphinx-quickstart on Mon Oct 29 21:48:45 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Kiwisolver's documentation!
======================================
Kiwisolver is an efficient C++ implementation of the Cassowary constraint
solving algorithm. Kiwi is an implementation of the algorithm based on the
seminal Cassowary paper. It is *not* a refactoring of the original C++ solver.
Kiwisolver has been designed from the ground up to be lightweight and fast.
Kiwisolver range 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.
.. toctree::
:maxdepth: 2
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
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -0,0 +1,16 @@
..
This file holds some common substitutions (mainly for referencing code).
To use it add the directive include:: substitutions.rst at the beginning of
the file.
.. ============================================================================
.. Kiwi substitutions
.. ============================================================================
.. |Variable| replace:: :py:class:`~kiwisolver.Variable`
.. |Solver| replace:: :py:class:`~kiwisolver.Solver`
.. |Term| replace:: :py:class:`~kiwisolver.Term`
.. |Expression| replace:: :py:class:`~kiwisolver.Expression`

View File

@@ -0,0 +1,142 @@
.. _uses-enaml:
Enaml
=====
.. include:: ../substitutions.sub
Enaml is a programming language and framework for creating professional-quality
user interfaces with minimal effort. It relies on Kiwi to layout widgets.
To implement its layout, Enaml uses a nestable model. Containers widgets
handle the constraints generation used to layout their children. Furthermore,
they pass to their their children a representation of the bounding box in
which they should live which allows the widgets to position themselves inside
their parent. Since each leaf component has a "preferred size", the system can
be solved from the bottom-up and set the size the parents based on the required
space of the children. If at a later time the parent is resized, this new input
can be used to solve the layout problem.
The following sections will describe in more details how the constraints are
generated and the preferred size estimated.
Widget variables
----------------
In Enaml, each widget that can be constrained defines a bunch of Kiwi
|Variable|: ``left``, ``top``, ``width`` and ``height``. In addition,
it provides easy access to combination of those: ``right``, ``bottom``,
``h_center``, ``v_center``. Those variables will be used to define the layout.
In addition, because each widget has a preferred size, it defines a set of
constraints related to that preferred size on which the user can act upon by
modifying their strength:
- hug_width: equivalent to (width == hint) | hug_width
- hug_height: equivalent to (height == hint) | hug_height
- resist_width: equivalent to (width >= hint) | resist_width
- resist_height: equivalent to (height >= hint) | resist_height
- limit_width: equivalent to (width <= hint) | limit_width
- limit_height: equivalent to (height <= hint) | limit_height
Finally, widget that can contain other widgets define a set of variables that
they expose to their children to allow to place themselves relative to their
parents. Those are: ``contents_left``, ``contents_top``, ``contents_width``,
``contents_height``, ``contents_right``, ``contents_bottom``,
``contents_h_center``, ``contents_v_center``. Those are usually not equivalent
to the non-prefixed variables (even-though they are related) because of the
container margins.
The base classes used a mixin to implement those behaviors are defined in:
https://github.com/nucleic/enaml/blob/main/enaml/layout/constrainable.py
Constraints definition
----------------------
Using the above variable, one can express any constraints. However, even for
simple vertical or horizontal boxes, the constraints to define, in
particular if one needs to introduce some spacing around the objects, become
quite painful to write by hand.
To make constraints definition easier, Enaml relies on helpers function and
classes. In the following, we will focus on how horizontal and vertical boxes
constraints are handled, by studying the following example in details:
.. image:: enaml_hbox.svg
Here we consider a container widget with three child widgets. The outer black
frame represents the limit of the container. The dashed frame represents the
contents visible to children when defining their constraint. The container uses
the margin definition to relate the outer left, top, width and height to their
'contents' equivalent.
The three widgets are arranged according to a horizontal box for which the
constraints are created using the `hbox` helper function which simply accepts
a list of widgets and spacers . To define the constraints from that list, Enaml
relies on the spacers represented here in orange. Each spacer has a
given size and a policy regarding that size (is it a minimum value, maximum,
how strongly to enforce that size). For each orientation, `hbox` add spacers so
that there is a spacer between each widget and between the widgets and the
parent boundaries. Some spacers can have a zero size, simply meaning that
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. Furthermore, those helpers define the same
variable as the widgets allowing for to position groups with respect to one
another.
.. note::
In practice, `hbox` itself relies on some helpers but the above gives you
the general idea.
For further details you can have a look at the source of the helpers described
in this section which can be found in the Enaml source:
- spacers: https://github.com/nucleic/enaml/blob/main/enaml/layout/spacers.py
- helpers:
- https://github.com/nucleic/enaml/blob/main/enaml/layout/layout_helpers.py
- https://github.com/nucleic/enaml/blob/main/enaml/layout/linear_box_helper.py
- https://github.com/nucleic/enaml/blob/main/enaml/layout/sequence_helper.py
Setting up the solver
---------------------
So far we have only defined the constraints that represent the layout, we will
now turn to how Enaml pass those to the solver and how it handle updates and
solver resets.
By default, each container manages its own solver independently. This has
the advantage of keeping the system relatively smalls and hence allow for
faster updates. When setting up the solver, the container will add for each
widget a set of constraints reflecting the preference of the widget regarding
its size as reported by the widget, and add to those the constraints defining
the layout. It will also add two edit variable representing the width and
height of the container.
Once the solver has been set up it can be used to compute different values,
such as the best size for the container (requesting a size of 0 with a 0.1*weak
strength), its min size (0 size, medium strength) and max size (max size,
medium strength).
When the parent is resized, the solver is invoked again with the new width and
height as suggestion. On the other hand, if the constraints change either
because widgets have been added or removed or because the users modified them,
the solver is reset and the constraints are rebuilt-from scratch. This means
that we never keep the solver around long enough to have to worry about memory
consumption due to unused variables in the solver.
In a complex hierarchy, the top parent will request the sizes of the nested
containers which will trigger the solving of their constraints. At some point
in the nested structure, we will only find widgets which provides a size hint
without requiring to solve constraints (ex: a button). This will allow to solve
the system and then propagate back upward.
Hopefully this brief introduction will have clarified how Enaml make use of
kiwi to layout its widgets. Some fine mechanics have been simplified for the
sake of this description but you can check Enaml sources for a more in depth
description.

View File

@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 449.8 112.7" enable-background="new 0 0 449.8 112.7" xml:space="preserve">
<font horiz-adv-x="1000">
<!-- Copyright 2000, 2004 , 2005Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. -->
<font-face font-family="MyriadPro-Bold" units-per-em="1000" underline-position="-100" underline-thickness="50"/>
<missing-glyph horiz-adv-x="500" d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z"/>
<glyph unicode="C" horiz-adv-x="595" d="M538,138C508,126 457,116 411,116C275,116 195,201 195,336C195,486 289,559 410,559C464,559 507,547 538,534l31,121C542,669 482,685 403,685C199,685 35,557 35,327C35,135 155,-11 388,-11C470,-11 533,5 561,19z"/>
<glyph unicode="W" horiz-adv-x="888" d="M342,0l64,290C422,358 433,422 445,498l2,0C455,421 466,358 479,290l57,-290l165,0l174,674l-155,0l-55,-276C651,318 636,239 626,164l-2,0C614,239 603,311 588,390l-54,284l-162,0l-57,-276C298,315 282,234 271,161l-2,0C258,229 244,316 229,397l-51,277l-163,0l160,-674z"/>
<glyph unicode="a" horiz-adv-x="528" d="M469,289C469,404 417,500 254,500C165,500 98,476 64,457l28,-98C124,379 177,396 227,396C302,396 316,359 316,333l0,-7C143,327 29,266 29,139C29,61 88,-11 187,-11C245,-11 295,10 327,49l3,0l9,-49l137,0C471,27 469,72 469,119M321,178C321,169 320,160 318,152C308,121 277,96 239,96C205,96 179,115 179,154C179,213 241,232 321,231z"/>
<glyph unicode="d" horiz-adv-x="596" d="M383,710l0,-265l-2,0C359,479 312,500 252,500C135,500 32,405 33,240C33,88 126,-11 242,-11C305,-11 364,17 395,72l2,0l7,-72l135,0C537,33 535,91 535,146l0,564M383,218C383,205 382,194 379,183C370,140 335,110 291,110C228,110 187,162 187,245C187,322 223,384 292,384C339,384 372,350 381,309C382,301 383,290 383,282z"/>
<glyph unicode="e" horiz-adv-x="528" d="M493,196C495,207 498,230 498,256C498,377 438,500 280,500C110,500 33,363 33,239C33,86 128,-11 294,-11C360,-11 421,0 471,20l-20,103C410,110 368,103 316,103C245,103 183,133 178,196M177,300C181,341 207,399 271,399C341,399 357,337 357,300z"/>
<glyph unicode="g" horiz-adv-x="585" d="M524,344C524,417 526,458 528,489l-132,0l-5,-58l-2,0C364,471 322,500 255,500C135,500 33,400 33,243C33,102 119,4 240,4C296,4 343,27 372,68l2,0l0,-32C374,-54 319,-93 247,-93C190,-93 136,-74 105,-56l-30,-115C119,-196 187,-209 251,-209C323,-209 396,-195 450,-149C505,-100 524,-23 524,71M372,218C372,205 371,189 367,178C357,142 326,116 287,116C223,116 187,173 187,246C187,335 232,385 287,385C329,385 358,358 369,318C371,310 372,300 372,290z"/>
<glyph unicode="i" horiz-adv-x="274" d="M213,0l0,489l-152,0l0,-489M137,702C88,702 56,669 57,625C56,583 88,549 136,549C186,549 218,583 218,625C217,669 186,702 137,702z"/>
<glyph unicode="n" horiz-adv-x="586" d="M61,0l152,0l0,282C213,296 215,310 219,320C229,348 254,377 296,377C351,377 373,334 373,271l0,-271l152,0l0,290C525,434 450,500 350,500C269,500 219,453 199,422l-3,0l-7,67l-132,0C59,446 61,394 61,333z"/>
<glyph unicode="o" horiz-adv-x="577" d="M294,500C137,500 33,399 33,241C33,83 143,-11 286,-11C417,-11 544,71 544,250C544,397 444,500 294,500M290,392C359,392 387,318 387,245C387,157 349,98 290,98C225,98 190,161 190,245C190,317 217,392 290,392z"/>
<glyph unicode="1" horiz-adv-x="555" d="M236,0l147,0l0,650l-126,0l-172,-80l25,-114l124,59l2,0z"/>
<glyph unicode="r" horiz-adv-x="380" d="M61,0l152,0l0,248C213,260 214,272 216,282C226,329 264,359 320,359C337,359 350,357 361,354l0,144C350,500 343,500 329,500C282,500 222,470 197,399l-4,0l-5,90l-131,0C59,447 61,400 61,328z"/>
<glyph unicode=" " horiz-adv-x="202"/>
<glyph unicode="t" horiz-adv-x="367" d="M82,581l0,-92l-65,0l0,-112l65,0l0,-200C82,108 96,61 124,32C148,7 189,-11 238,-11C281,-11 317,-5 337,3l-1,115C321,115 312,114 291,114C245,114 231,141 231,200l0,177l109,0l0,112l-109,0l0,133z"/>
<glyph unicode="3" horiz-adv-x="555" d="M38,35C76,11 147,-11 231,-11C396,-11 497,73 497,188C497,273 434,332 356,346l0,2C436,376 475,430 475,499C475,588 398,661 259,661C175,661 97,637 58,612l31,-110C116,518 172,541 225,541C289,541 321,512 321,473C321,418 256,399 205,398l-59,0l0,-109l62,0C275,289 339,260 339,196C339,147 299,109 220,109C158,109 96,135 69,149z"/>
<glyph unicode="2" horiz-adv-x="555" d="M503,0l0,125l-245,0l0,2l60,50C412,261 491,348 491,457C491,575 410,661 263,661C175,661 99,631 50,594l43,-109C127,511 176,539 232,539C307,539 339,497 339,444C337,368 268,295 126,168l-84,-76l0,-92z"/>
</font>
<font horiz-adv-x="1000">
<!-- Copyright 2000, 2004 , 2005Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. -->
<font-face font-family="MyriadPro-Regular" units-per-em="1000" underline-position="-100" underline-thickness="50"/>
<missing-glyph horiz-adv-x="500" d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z"/>
<glyph unicode="a" horiz-adv-x="482" d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z"/>
<glyph unicode="b" horiz-adv-x="569" d="M73,125C73,82 71,33 69,0l76,0l4,80l3,0C188,16 244,-11 314,-11C422,-11 531,75 531,248C532,395 447,495 327,495C249,495 193,460 162,406l-2,0l0,304l-87,0M160,281C160,295 163,307 165,317C183,384 239,425 299,425C393,425 443,342 443,245C443,134 388,59 296,59C232,59 181,101 164,162C162,172 160,183 160,194z"/>
<glyph unicode="e" horiz-adv-x="501" d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z"/>
<glyph unicode="f" horiz-adv-x="292" d="M169,0l0,417l117,0l0,67l-117,0l0,26C169,584 188,650 263,650C288,650 306,645 319,639l12,68C314,714 287,721 256,721C215,721 171,708 138,676C97,637 82,575 82,507l0,-23l-68,0l0,-67l68,0l0,-417z"/>
<glyph unicode="g" horiz-adv-x="559" d="M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z"/>
<glyph unicode="h" horiz-adv-x="555" d="M73,0l88,0l0,292C161,309 162,322 167,334C183,382 228,422 285,422C368,422 397,356 397,278l0,-278l88,0l0,288C485,455 381,495 316,495C283,495 252,485 226,470C199,455 177,433 163,408l-2,0l0,302l-88,0z"/>
<glyph unicode="i" horiz-adv-x="234" d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675z"/>
<glyph unicode="l" horiz-adv-x="236" d="M73,0l88,0l0,710l-88,0z"/>
<glyph unicode="m" horiz-adv-x="834" d="M73,0l86,0l0,292C159,307 161,322 166,335C180,379 220,423 275,423C342,423 376,367 376,290l0,-290l86,0l0,299C462,315 465,331 469,343C484,386 523,423 573,423C644,423 678,367 678,274l0,-274l86,0l0,285C764,453 669,495 605,495C559,495 527,483 498,461C478,446 459,425 444,398l-2,0C421,455 371,495 305,495C225,495 180,452 153,406l-3,0l-4,78l-77,0C72,444 73,403 73,353z"/>
<glyph unicode="n" horiz-adv-x="555" d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z"/>
<glyph unicode="o" horiz-adv-x="549" d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z"/>
<glyph unicode="p" horiz-adv-x="569" d="M73,-198l87,0l0,263l2,0C191,17 247,-11 311,-11C425,-11 531,75 531,249C531,396 443,495 326,495C247,495 190,460 154,401l-2,0l-4,83l-79,0C71,438 73,388 73,326M160,280C160,292 163,305 166,316C183,382 239,425 299,425C392,425 443,342 443,245C443,134 389,58 296,58C233,58 180,100 164,161C162,172 160,184 160,197z"/>
<glyph unicode="r" horiz-adv-x="327" d="M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333z"/>
<glyph unicode=" " horiz-adv-x="212"/>
<glyph unicode="t" horiz-adv-x="331" d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116z"/>
</font>
<g>
<g>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="418.2,95.1 418.2,101.1 412.2,101.1 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="12.2842,12.2842" x1="399.9" y1="101.1" x2="37.5" y2="101.1"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="31.4,101.1 25.4,101.1 25.4,95.1 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="11.6852,11.6852" x1="25.4" y1="83.4" x2="25.4" y2="42.5"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="25.4,36.6 25.4,30.6 31.4,30.6 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="12.2842,12.2842" x1="43.6" y1="30.6" x2="406" y2="30.6"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="412.2,30.6 418.2,30.6 418.2,36.6 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="11.6852,11.6852" x1="418.2" y1="48.3" x2="418.2" y2="89.2"/>
</g>
</g>
<rect x="14.7" y="19.5" fill="none" stroke="#000000" stroke-miterlimit="10" width="414.1" height="92.7"/>
<g>
<rect x="34" y="58.4" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="87.4" height="20.8"/>
<text transform="matrix(1 0 0 1 55.2495 72.3085)" font-family="'MyriadPro-Bold'" font-size="12">Widget 1</text>
</g>
<g>
<rect x="144.1" y="48.3" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="102.2" height="41"/>
<text transform="matrix(1 0 0 1 172.7653 72.3085)" font-family="'MyriadPro-Bold'" font-size="12">Widget 2</text>
</g>
<g>
<rect x="275.8" y="44" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="120" height="42.2"/>
<text transform="matrix(1 0 0 1 313.3092 68.6263)" font-family="'MyriadPro-Bold'" font-size="12">Widget 3</text>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="15.9" y1="35.1" x2="24.2" y2="35.1"/>
<g>
<polygon points="17.9,36.8 16.2,35.1 17.9,33.4 16.4,33.4 14.7,35.1 16.4,36.8 "/>
</g>
<g>
<polygon points="22.2,36.8 23.9,35.1 22.2,33.4 23.7,33.4 25.4,35.1 23.7,36.8 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="419.3" y1="32.1" x2="427.7" y2="32.1"/>
<g>
<polygon points="421.3,33.8 419.6,32.1 421.3,30.4 419.9,30.4 418.2,32.1 419.9,33.8 "/>
</g>
<g>
<polygon points="425.7,33.8 427.4,32.1 425.7,30.4 427.1,30.4 428.8,32.1 427.1,33.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="395.8" y1="69.1" x2="418.2" y2="69.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="396.9" y1="69.1" x2="417" y2="69.1"/>
<g>
<polygon fill="#FF8000" points="398.9,70.8 397.2,69.1 398.9,67.4 397.5,67.4 395.8,69.1 397.5,70.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="415,70.8 416.7,69.1 415,67.4 416.5,67.4 418.2,69.1 416.5,70.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="245.7" y1="69.1" x2="275.8" y2="69.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="246.9" y1="69.1" x2="274.6" y2="69.1"/>
<g>
<polygon fill="#FF8000" points="248.9,70.8 247.2,69.1 248.9,67.4 247.4,67.4 245.7,69.1 247.4,70.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="272.6,70.8 274.3,69.1 272.6,67.4 274.1,67.4 275.8,69.1 274.1,70.8 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="122.5" y1="68.8" x2="143" y2="68.8"/>
<g>
<polygon fill="#FF8000" points="124.5,70.5 122.8,68.8 124.5,67.1 123.1,67.1 121.4,68.8 123.1,70.5 "/>
</g>
<g>
<polygon fill="#FF8000" points="141,70.5 142.7,68.8 141,67.1 142.4,67.1 144.1,68.8 142.4,70.5 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="25.4" y1="71.1" x2="34" y2="71.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="26.5" y1="71.1" x2="32.9" y2="71.1"/>
<g>
<polygon fill="#FF8000" points="28.5,72.8 26.8,71.1 28.5,69.4 27.1,69.4 25.4,71.1 27.1,72.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="30.9,72.8 32.6,71.1 30.9,69.4 32.3,69.4 34,71.1 32.3,72.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="73.2" y1="58.9" x2="73.2" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="73.2" y1="57.8" x2="73.2" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="74.9,55.8 73.2,57.5 71.5,55.8 71.5,57.2 73.2,58.9 74.9,57.2 "/>
</g>
<g>
<polygon fill="#FF8000" points="74.9,33.8 73.2,32.1 71.5,33.8 71.5,32.3 73.2,30.6 74.9,32.3 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="73.2" y1="100.7" x2="73.2" y2="79.2"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="73.2" y1="99.6" x2="73.2" y2="80.3"/>
<g>
<polygon fill="#FF8000" points="74.9,97.6 73.2,99.3 71.5,97.6 71.5,99 73.2,100.7 74.9,99 "/>
</g>
<g>
<polygon fill="#FF8000" points="74.9,82.3 73.2,80.6 71.5,82.3 71.5,80.9 73.2,79.2 74.9,80.9 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="195.2" y1="100.6" x2="195.2" y2="89.9"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="195.2" y1="99.4" x2="195.2" y2="91.1"/>
<g>
<polygon fill="#FF8000" points="196.9,97.4 195.2,99.1 193.5,97.4 193.5,98.9 195.2,100.6 196.9,98.9 "/>
</g>
<g>
<polygon fill="#FF8000" points="196.9,93.1 195.2,91.4 193.5,93.1 193.5,91.6 195.2,89.9 196.9,91.6 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="189.9" y1="48.3" x2="189.9" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="189.9" y1="47.1" x2="189.9" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="191.6,45.1 189.9,46.8 188.2,45.1 188.2,46.6 189.9,48.3 191.6,46.6 "/>
</g>
<g>
<polygon fill="#FF8000" points="191.6,33.8 189.9,32.1 188.2,33.8 188.2,32.3 189.9,30.6 191.6,32.3 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="330.9" y1="101.1" x2="330.9" y2="86.2"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="330.9" y1="99.9" x2="330.9" y2="87.3"/>
<g>
<polygon fill="#FF8000" points="332.6,97.9 330.9,99.6 329.2,97.9 329.2,99.4 330.9,101.1 332.6,99.4 "/>
</g>
<g>
<polygon fill="#FF8000" points="332.6,89.3 330.9,87.6 329.2,89.3 329.2,87.9 330.9,86.2 332.6,87.9 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="330.9" y1="44" x2="330.9" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="330.9" y1="42.9" x2="330.9" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="332.6,40.9 330.9,42.6 329.2,40.9 329.2,42.3 330.9,44 332.6,42.3 "/>
</g>
<g>
<polygon fill="#FF8000" points="332.6,33.8 330.9,32.1 329.2,33.8 329.2,32.3 330.9,30.6 332.6,32.3 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="175.8" y1="111" x2="175.8" y2="102.7"/>
<g>
<polygon points="177.5,109.1 175.8,110.7 174.1,109.1 174.1,110.5 175.8,112.2 177.5,110.5 "/>
</g>
<g>
<polygon points="177.5,104.7 175.8,103 174.1,104.7 174.1,103.2 175.8,101.6 177.5,103.2 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="172.8" y1="29" x2="172.8" y2="20.7"/>
<g>
<polygon points="174.5,27 172.8,28.7 171.1,27 171.1,28.4 172.8,30.1 174.5,28.4 "/>
</g>
<g>
<polygon points="174.5,22.6 172.8,20.9 171.1,22.6 171.1,21.2 172.8,19.5 174.5,21.2 "/>
</g>
</g>
</g>
<text transform="matrix(1 0 0 1 175.7678 27.3395)" font-family="'MyriadPro-Regular'" font-size="10">top margin</text>
<text transform="matrix(1 0 0 1 178.9794 109.875)" font-family="'MyriadPro-Regular'" font-size="10">bottom margin</text>
<text transform="matrix(1 0 0 1 1.220703e-04 16.7404)" font-family="'MyriadPro-Regular'" font-size="10">left margin</text>
<text transform="matrix(1 0 0 1 397.8374 16.7405)" font-family="'MyriadPro-Regular'" font-size="10">right margin</text>
<text transform="matrix(1 0 0 1 199.7852 8.3701)" font-family="'MyriadPro-Bold'" font-size="10">Container</text>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,13 @@
.. _uses:
Kiwisolver real uses
====================
The following sections describe how kiwisolver is used in third-party project,
and aim at providing more involved example of how kiwisolver can be used in
real life project.
.. toctree::
:maxdepth: 2
enaml.rst

352
kiwi/kiwi/AssocVector.h Normal file
View File

@@ -0,0 +1,352 @@
////////////////////////////////////////////////////////////////////////////////
// The Loki Library
// Copyright (c) 2001 by Andrei Alexandrescu
// This code accompanies the book:
// Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design
// Patterns Applied". Copyright (c) 2001. Addison-Wesley.
// Permission to use, copy, modify, distribute and sell this software for any
// purpose is hereby granted without fee, provided that the above copyright
// notice appear in all copies and that both that copyright notice and this
// permission notice appear in supporting documentation.
// The author or Addison-Wesley Longman make no representations about the
// 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 $
#include <algorithm>
#include <functional>
#include <vector>
#include <utility>
namespace Loki
{
////////////////////////////////////////////////////////////////////////////////
// class template AssocVectorCompare
// Used by AssocVector
////////////////////////////////////////////////////////////////////////////////
namespace Private
{
template <class Value, class C, class K>
class AssocVectorCompare : public C
{
typedef std::pair<K, Value>
Data;
typedef K first_argument_type;
public:
AssocVectorCompare()
{}
AssocVectorCompare(const C& src) : C(src)
{}
bool operator()(const first_argument_type& lhs,
const first_argument_type& rhs) const
{ return C::operator()(lhs, rhs); }
bool operator()(const Data& lhs, const Data& rhs) const
{ return operator()(lhs.first, rhs.first); }
bool operator()(const Data& lhs,
const first_argument_type& rhs) const
{ return operator()(lhs.first, rhs); }
bool operator()(const first_argument_type& lhs,
const Data& rhs) const
{ return operator()(lhs, rhs.first); }
};
}
////////////////////////////////////////////////////////////////////////////////
// class template AssocVector
// An associative vector built as a syntactic drop-in replacement for std::map
// BEWARE: AssocVector doesn't respect all map's guarantees, the most important
// being:
// * iterators are invalidated by insert and erase operations
// * the complexity of insert/erase is O(N) not O(log N)
// * value_type is std::pair<K, V> not std::pair<const K, V>
// * iterators are random
////////////////////////////////////////////////////////////////////////////////
template
<
class K,
class V,
class C = std::less<K>,
class A = std::allocator< std::pair<K, V> >
>
class AssocVector
: private std::vector< std::pair<K, V>, A >
, private Private::AssocVectorCompare<V, C, K>
{
typedef std::vector<std::pair<K, V>, A> Base;
typedef Private::AssocVectorCompare<V, C, K> MyCompare;
public:
typedef K key_type;
typedef V mapped_type;
typedef typename Base::value_type value_type;
typedef C key_compare;
typedef A allocator_type;
typedef typename Base::iterator iterator;
typedef typename Base::const_iterator const_iterator;
typedef typename Base::size_type size_type;
typedef typename Base::difference_type difference_type;
typedef typename Base::reverse_iterator reverse_iterator;
typedef typename Base::const_reverse_iterator const_reverse_iterator;
class value_compare
: public std::function<bool(value_type, value_type)>
, private key_compare
{
friend class AssocVector;
protected:
value_compare(key_compare pred) : key_compare(pred)
{}
public:
bool operator()(const value_type& lhs, const value_type& rhs) const
{ return key_compare::operator()(lhs.first, rhs.first); }
};
// 23.3.1.1 construct/copy/destroy
explicit AssocVector(const key_compare& comp = key_compare(),
const A& alloc = A())
: Base(alloc), MyCompare(comp)
{}
template <class InputIterator>
AssocVector(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const A& alloc = A())
: Base(first, last, alloc), MyCompare(comp)
{
MyCompare& me = *this;
std::sort(begin(), end(), me);
}
AssocVector& operator=(const AssocVector& rhs)
{
AssocVector(rhs).swap(*this);
return *this;
}
// iterators:
// The following are here because MWCW gets 'using' wrong
iterator begin() { return Base::begin(); }
const_iterator begin() const { return Base::begin(); }
iterator end() { return Base::end(); }
const_iterator end() const { return Base::end(); }
reverse_iterator rbegin() { return Base::rbegin(); }
const_reverse_iterator rbegin() const { return Base::rbegin(); }
reverse_iterator rend() { return Base::rend(); }
const_reverse_iterator rend() const { return Base::rend(); }
// capacity:
bool empty() const { return Base::empty(); }
size_type size() const { return Base::size(); }
size_type max_size() { return Base::max_size(); }
// 23.3.1.2 element access:
mapped_type& operator[](const key_type& key)
{ return insert(value_type(key, mapped_type())).first->second; }
// modifiers:
std::pair<iterator, bool> insert(const value_type& val)
{
bool found(true);
iterator i(lower_bound(val.first));
if (i == end() || this->operator()(val.first, i->first))
{
i = Base::insert(i, val);
found = false;
}
return std::make_pair(i, !found);
}
//Section [23.1.2], Table 69
//http://developer.apple.com/documentation/DeveloperTools/gcc-3.3/libstdc++/23_containers/howto.html#4
iterator insert(iterator pos, const value_type& val)
{
if( (pos == begin() || this->operator()(*(pos-1),val)) &&
(pos == end() || this->operator()(val, *pos)) )
{
return Base::insert(pos, val);
}
return insert(val).first;
}
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{ for (; first != last; ++first) insert(*first); }
void erase(iterator pos)
{ Base::erase(pos); }
size_type erase(const key_type& k)
{
iterator i(find(k));
if (i == end()) return 0;
erase(i);
return 1;
}
void erase(iterator first, iterator last)
{ Base::erase(first, last); }
void swap(AssocVector& other)
{
Base::swap(other);
MyCompare& me = *this;
MyCompare& rhs = other;
std::swap(me, rhs);
}
void clear()
{ Base::clear(); }
// observers:
key_compare key_comp() const
{ return *this; }
value_compare value_comp() const
{
const key_compare& comp = *this;
return value_compare(comp);
}
// 23.3.1.3 map operations:
iterator find(const key_type& k)
{
iterator i(lower_bound(k));
if (i != end() && this->operator()(k, i->first))
{
i = end();
}
return i;
}
const_iterator find(const key_type& k) const
{
const_iterator i(lower_bound(k));
if (i != end() && this->operator()(k, i->first))
{
i = end();
}
return i;
}
size_type count(const key_type& k) const
{ return find(k) != end(); }
iterator lower_bound(const key_type& k)
{
MyCompare& me = *this;
return std::lower_bound(begin(), end(), k, me);
}
const_iterator lower_bound(const key_type& k) const
{
const MyCompare& me = *this;
return std::lower_bound(begin(), end(), k, me);
}
iterator upper_bound(const key_type& k)
{
MyCompare& me = *this;
return std::upper_bound(begin(), end(), k, me);
}
const_iterator upper_bound(const key_type& k) const
{
const MyCompare& me = *this;
return std::upper_bound(begin(), end(), k, me);
}
std::pair<iterator, iterator> equal_range(const key_type& k)
{
MyCompare& me = *this;
return std::equal_range(begin(), end(), k, me);
}
std::pair<const_iterator, const_iterator> equal_range(
const key_type& k) const
{
const MyCompare& me = *this;
return std::equal_range(begin(), end(), k, me);
}
template <class K1, class V1, class C1, class A1>
friend bool operator==(const AssocVector<K1, V1, C1, A1>& lhs,
const AssocVector<K1, V1, C1, A1>& rhs);
bool operator<(const AssocVector& rhs) const
{
const Base& me = *this;
const Base& yo = rhs;
return me < yo;
}
template <class K1, class V1, class C1, class A1>
friend bool operator!=(const AssocVector<K1, V1, C1, A1>& lhs,
const AssocVector<K1, V1, C1, A1>& rhs);
template <class K1, class V1, class C1, class A1>
friend bool operator>(const AssocVector<K1, V1, C1, A1>& lhs,
const AssocVector<K1, V1, C1, A1>& rhs);
template <class K1, class V1, class C1, class A1>
friend bool operator>=(const AssocVector<K1, V1, C1, A1>& lhs,
const AssocVector<K1, V1, C1, A1>& rhs);
template <class K1, class V1, class C1, class A1>
friend bool operator<=(const AssocVector<K1, V1, C1, A1>& lhs,
const AssocVector<K1, V1, C1, A1>& rhs);
};
template <class K, class V, class C, class A>
inline bool operator==(const AssocVector<K, V, C, A>& lhs,
const AssocVector<K, V, C, A>& rhs)
{
const std::vector<std::pair<K, V>, A>& me = lhs;
return me == rhs;
}
template <class K, class V, class C, class A>
inline bool operator!=(const AssocVector<K, V, C, A>& lhs,
const AssocVector<K, V, C, A>& rhs)
{ return !(lhs == rhs); }
template <class K, class V, class C, class A>
inline bool operator>(const AssocVector<K, V, C, A>& lhs,
const AssocVector<K, V, C, A>& rhs)
{ return rhs < lhs; }
template <class K, class V, class C, class A>
inline bool operator>=(const AssocVector<K, V, C, A>& lhs,
const AssocVector<K, V, C, A>& rhs)
{ return !(lhs < rhs); }
template <class K, class V, class C, class A>
inline bool operator<=(const AssocVector<K, V, C, A>& lhs,
const AssocVector<K, V, C, A>& rhs)
{ return !(rhs < lhs); }
// specialized algorithms:
template <class K, class V, class C, class A>
void swap(AssocVector<K, V, C, A>& lhs, AssocVector<K, V, C, A>& rhs)
{ lhs.swap(rhs); }
} // namespace Loki

140
kiwi/kiwi/constraint.h Normal file
View File

@@ -0,0 +1,140 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <cstdlib>
#include <map>
#include <vector>
#include "expression.h"
#include "shareddata.h"
#include "strength.h"
#include "term.h"
#include "variable.h"
#include "util.h"
namespace kiwi
{
enum RelationalOperator
{
OP_LE,
OP_GE,
OP_EQ
};
class Constraint
{
public:
Constraint() = default;
Constraint(const Expression &expr,
RelationalOperator op,
double strength = strength::required) : m_data(new ConstraintData(expr, op, strength)) {}
Constraint(const Constraint &other, double strength) : m_data(new ConstraintData(other, strength)) {}
Constraint(const Constraint &) = default;
Constraint(Constraint &&) noexcept = default;
~Constraint() = default;
const Expression &expression() const
{
return m_data->m_expression;
}
RelationalOperator op() const
{
return m_data->m_op;
}
double strength() const
{
return m_data->m_strength;
}
bool violated() const
{
switch (m_data->m_op)
{
case OP_EQ: return !impl::nearZero(m_data->m_expression.value());
case OP_GE: return m_data->m_expression.value() < 0.0;
case OP_LE: return m_data->m_expression.value() > 0.0;
}
std::abort();
}
bool operator!() const
{
return !m_data;
}
Constraint& operator=(const Constraint &) = default;
Constraint& operator=(Constraint &&) noexcept = default;
private:
static Expression reduce(const Expression &expr)
{
std::map<Variable, double> vars;
for (const auto & term : expr.terms())
vars[term.variable()] += term.coefficient();
std::vector<Term> terms(vars.begin(), vars.end());
return Expression(std::move(terms), expr.constant());
}
class ConstraintData : public SharedData
{
public:
ConstraintData(const Expression &expr,
RelationalOperator op,
double strength) : SharedData(),
m_expression(reduce(expr)),
m_strength(strength::clip(strength)),
m_op(op) {}
ConstraintData(const Constraint &other, double strength) : SharedData(),
m_expression(other.expression()),
m_strength(strength::clip(strength)),
m_op(other.op()) {}
~ConstraintData() = default;
Expression m_expression;
double m_strength;
RelationalOperator m_op;
private:
ConstraintData(const ConstraintData &other);
ConstraintData &operator=(const ConstraintData &other);
};
SharedDataPtr<ConstraintData> m_data;
friend bool operator<(const Constraint &lhs, const Constraint &rhs)
{
return lhs.m_data < rhs.m_data;
}
friend bool operator==(const Constraint &lhs, const Constraint &rhs)
{
return lhs.m_data == rhs.m_data;
}
friend bool operator!=(const Constraint &lhs, const Constraint &rhs)
{
return lhs.m_data != rhs.m_data;
}
};
} // namespace kiwi

184
kiwi/kiwi/debug.h Normal file
View File

@@ -0,0 +1,184 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <iostream>
#include <sstream>
#include <vector>
#include "constraint.h"
#include "solverimpl.h"
#include "term.h"
namespace kiwi
{
namespace impl
{
class DebugHelper
{
public:
static void dump(const SolverImpl &solver, std::ostream &out)
{
out << "Objective" << std::endl;
out << "---------" << std::endl;
dump(*solver.m_objective, out);
out << std::endl;
out << "Tableau" << std::endl;
out << "-------" << std::endl;
dump(solver.m_rows, out);
out << std::endl;
out << "Infeasible" << std::endl;
out << "----------" << std::endl;
dump(solver.m_infeasible_rows, out);
out << std::endl;
out << "Variables" << std::endl;
out << "---------" << std::endl;
dump(solver.m_vars, out);
out << std::endl;
out << "Edit Variables" << std::endl;
out << "--------------" << std::endl;
dump(solver.m_edits, out);
out << std::endl;
out << "Constraints" << std::endl;
out << "-----------" << std::endl;
dump(solver.m_cns, out);
out << std::endl;
out << std::endl;
}
static void dump(const SolverImpl::RowMap &rows, std::ostream &out)
{
for (const auto &rowPair : rows)
{
dump(rowPair.first, out);
out << " | ";
dump(*rowPair.second, out);
}
}
static void dump(const std::vector<Symbol> &symbols, std::ostream &out)
{
for (const auto &symbol : symbols)
{
dump(symbol, out);
out << std::endl;
}
}
static void dump(const SolverImpl::VarMap &vars, std::ostream &out)
{
for (const auto &varPair : vars)
{
out << varPair.first.name() << " = ";
dump(varPair.second, out);
out << std::endl;
}
}
static void dump(const SolverImpl::CnMap &cns, std::ostream &out)
{
for (const auto &cnPair : cns)
dump(cnPair.first, out);
}
static void dump(const SolverImpl::EditMap &edits, std::ostream &out)
{
for (const auto &editPair : edits)
out << editPair.first.name() << std::endl;
}
static void dump(const Row &row, std::ostream &out)
{
for (const auto &rowPair : row.cells())
{
out << " + " << rowPair.second << " * ";
dump(rowPair.first, out);
}
out << std::endl;
}
static void dump(const Symbol &symbol, std::ostream &out)
{
switch (symbol.type())
{
case Symbol::Invalid:
out << "i";
break;
case Symbol::External:
out << "v";
break;
case Symbol::Slack:
out << "s";
break;
case Symbol::Error:
out << "e";
break;
case Symbol::Dummy:
out << "d";
break;
default:
break;
}
out << symbol.id();
}
static void dump(const Constraint &cn, std::ostream &out)
{
for (const auto &term : cn.expression().terms())
{
out << term.coefficient() << " * ";
out << term.variable().name() << " + ";
}
out << cn.expression().constant();
switch (cn.op())
{
case OP_LE:
out << " <= 0 ";
break;
case OP_GE:
out << " >= 0 ";
break;
case OP_EQ:
out << " == 0 ";
break;
default:
break;
}
out << " | strength = " << cn.strength() << std::endl;
}
};
} // namespace impl
namespace debug
{
template <typename T>
void dump(const T &value)
{
impl::DebugHelper::dump(value, std::cout);
}
template <typename T>
void dump(const T &value, std::ostream &out)
{
impl::DebugHelper::dump(value, out);
}
template <typename T>
std::string dumps(const T &value)
{
std::stringstream stream;
impl::DebugHelper::dump(value, stream);
return stream.str();
}
} // namespace debug
} // namespace kiwi

162
kiwi/kiwi/errors.h Normal file
View File

@@ -0,0 +1,162 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <exception>
#include <string>
#include "constraint.h"
#include "variable.h"
namespace kiwi
{
class UnsatisfiableConstraint : public std::exception
{
public:
UnsatisfiableConstraint(Constraint constraint) : m_constraint(std::move(constraint)) {}
~UnsatisfiableConstraint() noexcept {}
const char *what() const noexcept
{
return "The constraint can not be satisfied.";
}
const Constraint &constraint() const
{
return m_constraint;
}
private:
Constraint m_constraint;
};
class UnknownConstraint : public std::exception
{
public:
UnknownConstraint(Constraint constraint) : m_constraint(std::move(constraint)) {}
~UnknownConstraint() noexcept {}
const char *what() const noexcept
{
return "The constraint has not been added to the solver.";
}
const Constraint &constraint() const
{
return m_constraint;
}
private:
Constraint m_constraint;
};
class DuplicateConstraint : public std::exception
{
public:
DuplicateConstraint(Constraint constraint) : m_constraint(std::move(constraint)) {}
~DuplicateConstraint() noexcept {}
const char *what() const noexcept
{
return "The constraint has already been added to the solver.";
}
const Constraint &constraint() const
{
return m_constraint;
}
private:
Constraint m_constraint;
};
class UnknownEditVariable : public std::exception
{
public:
UnknownEditVariable(Variable variable) : m_variable(std::move(variable)) {}
~UnknownEditVariable() noexcept {}
const char *what() const noexcept
{
return "The edit variable has not been added to the solver.";
}
const Variable &variable() const
{
return m_variable;
}
private:
Variable m_variable;
};
class DuplicateEditVariable : public std::exception
{
public:
DuplicateEditVariable(Variable variable) : m_variable(std::move(variable)) {}
~DuplicateEditVariable() noexcept {}
const char *what() const noexcept
{
return "The edit variable has already been added to the solver.";
}
const Variable &variable() const
{
return m_variable;
}
private:
Variable m_variable;
};
class BadRequiredStrength : public std::exception
{
public:
BadRequiredStrength() {}
~BadRequiredStrength() noexcept {}
const char *what() const noexcept
{
return "A required strength cannot be used in this context.";
}
};
class InternalSolverError : public std::exception
{
public:
InternalSolverError() : m_msg("An internal solver error ocurred.") {}
InternalSolverError(const char *msg) : m_msg(msg) {}
InternalSolverError(std::string msg) : m_msg(std::move(msg)) {}
~InternalSolverError() noexcept {}
const char *what() const noexcept
{
return m_msg.c_str();
}
private:
std::string m_msg;
};
} // namespace kiwi

62
kiwi/kiwi/expression.h Normal file
View File

@@ -0,0 +1,62 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <vector>
#include "term.h"
namespace kiwi
{
class Expression
{
public:
Expression(double constant = 0.0) : m_constant(constant) {}
Expression(const Term &term, double constant = 0.0) : m_terms(1, term), m_constant(constant) {}
Expression(std::vector<Term> terms, double constant = 0.0) : m_terms(std::move(terms)), m_constant(constant) {}
Expression(const Expression&) = default;
// Could be marked noexcept but for a bug in the GCC of the manylinux1 image
Expression(Expression&&) = default;
~Expression() = default;
const std::vector<Term> &terms() const
{
return m_terms;
}
double constant() const
{
return m_constant;
}
double value() const
{
double result = m_constant;
for (const Term &term : m_terms)
result += term.value();
return result;
}
Expression& operator=(const Expression&) = default;
// Could be marked noexcept but for a bug in the GCC of the manylinux1 image
Expression& operator=(Expression&&) = default;
private:
std::vector<Term> m_terms;
double m_constant;
};
} // namespace kiwi

19
kiwi/kiwi/kiwi.h Normal file
View File

@@ -0,0 +1,19 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include "constraint.h"
#include "debug.h"
#include "errors.h"
#include "expression.h"
#include "shareddata.h"
#include "solver.h"
#include "strength.h"
#include "symbolics.h"
#include "term.h"
#include "variable.h"
#include "version.h"

37
kiwi/kiwi/maptype.h Normal file
View File

@@ -0,0 +1,37 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include "AssocVector.h"
namespace kiwi
{
namespace impl
{
template <
typename K,
typename V,
typename C = std::less<K>,
typename A = std::allocator<std::pair<K, V>>>
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
} // namespace kiwi

182
kiwi/kiwi/row.h Normal file
View File

@@ -0,0 +1,182 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include "maptype.h"
#include "symbol.h"
#include "util.h"
namespace kiwi
{
namespace impl
{
class Row
{
public:
using CellMap = MapType<Symbol, double>;
Row() : Row(0.0) {}
Row(double constant) : m_constant(constant) {}
Row(const Row &other) = default;
~Row() = default;
const CellMap &cells() const
{
return m_cells;
}
double constant() const
{
return m_constant;
}
/* Add a constant value to the row constant.
The new value of the constant is returned.
*/
double add(double value)
{
return m_constant += value;
}
/* Insert a symbol into the row with a given coefficient.
If the symbol already exists in the row, the coefficient will be
added to the existing coefficient. If the resulting coefficient
is zero, the symbol will be removed from the row.
*/
void insert(const Symbol &symbol, double coefficient = 1.0)
{
if (nearZero(m_cells[symbol] += coefficient))
m_cells.erase(symbol);
}
/* Insert a row into this row with a given coefficient.
The constant and the cells of the other row will be multiplied by
the coefficient and added to this row. Any cell with a resulting
coefficient of zero will be removed from the row.
*/
void insert(const Row &other, double coefficient = 1.0)
{
m_constant += other.m_constant * coefficient;
for (const auto & cellPair : other.m_cells)
{
double coeff = cellPair.second * coefficient;
if (nearZero(m_cells[cellPair.first] += coeff))
m_cells.erase(cellPair.first);
}
}
/* Remove the given symbol from the row.
*/
void remove(const Symbol &symbol)
{
auto it = m_cells.find(symbol);
if (it != m_cells.end())
m_cells.erase(it);
}
/* Reverse the sign of the constant and all cells in the row.
*/
void reverseSign()
{
m_constant = -m_constant;
for (auto &cellPair : m_cells)
cellPair.second = -cellPair.second;
}
/* Solve the row for the given symbol.
This method assumes the row is of the form a * x + b * y + c = 0
and (assuming solve for x) will modify the row to represent the
right hand side of x = -b/a * y - c / a. The target symbol will
be removed from the row, and the constant and other cells will
be multiplied by the negative inverse of the target coefficient.
The given symbol *must* exist in the row.
*/
void solveFor(const Symbol &symbol)
{
double coeff = -1.0 / m_cells[symbol];
m_cells.erase(symbol);
m_constant *= coeff;
for (auto &cellPair : m_cells)
cellPair.second *= coeff;
}
/* Solve the row for the given symbols.
This method assumes the row is of the form x = b * y + c and will
solve the row such that y = x / b - c / b. The rhs symbol will be
removed from the row, the lhs added, and the result divided by the
negative inverse of the rhs coefficient.
The lhs symbol *must not* exist in the row, and the rhs symbol
*must* exist in the row.
*/
void solveFor(const Symbol &lhs, const Symbol &rhs)
{
insert(lhs, -1.0);
solveFor(rhs);
}
/* Get the coefficient for the given symbol.
If the symbol does not exist in the row, zero will be returned.
*/
double coefficientFor(const Symbol &symbol) const
{
CellMap::const_iterator it = m_cells.find(symbol);
if (it == m_cells.end())
return 0.0;
return it->second;
}
/* Substitute a symbol with the data from another row.
Given a row of the form a * x + b and a substitution of the
form x = 3 * y + c the row will be updated to reflect the
expression 3 * a * y + a * c + b.
If the symbol does not exist in the row, this is a no-op.
*/
void substitute(const Symbol &symbol, const Row &row)
{
auto it = m_cells.find(symbol);
if (it != m_cells.end())
{
double coefficient = it->second;
m_cells.erase(it);
insert(row, coefficient);
}
}
private:
CellMap m_cells;
double m_constant;
};
} // namespace impl
} // namespace kiwi

181
kiwi/kiwi/shareddata.h Normal file
View File

@@ -0,0 +1,181 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
/*
Implementation note
===================
SharedDataPtr/SharedData offer the same basic functionality as std::shared_ptr,
but do not use atomic counters under the hood.
Since kiwi operates within a single thread context, atomic counters are not necessary,
especially given the extra CPU cost.
Therefore the use of SharedDataPtr/SharedData is preferred over std::shared_ptr.
*/
namespace kiwi
{
class SharedData
{
public:
SharedData() : m_refcount(0) {}
SharedData(const SharedData &other) = delete;
SharedData(SharedData&& other) = delete;
int m_refcount;
SharedData &operator=(const SharedData &other) = delete;
SharedData &operator=(SharedData&& other) = delete;
};
template <typename T>
class SharedDataPtr
{
public:
using Type = T;
SharedDataPtr() : m_data(nullptr) {}
explicit SharedDataPtr(T *data) : m_data(data)
{
incref(m_data);
}
~SharedDataPtr()
{
decref(m_data);
}
T *data()
{
return m_data;
}
const T *data() const
{
return m_data;
}
operator T *()
{
return m_data;
}
operator const T *() const
{
return m_data;
}
T *operator->()
{
return m_data;
}
const T *operator->() const
{
return m_data;
}
T &operator*()
{
return *m_data;
}
const T &operator*() const
{
return *m_data;
}
bool operator!() const
{
return !m_data;
}
bool operator<(const SharedDataPtr<T> &other) const
{
return m_data < other.m_data;
}
bool operator==(const SharedDataPtr<T> &other) const
{
return m_data == other.m_data;
}
bool operator!=(const SharedDataPtr<T> &other) const
{
return m_data != other.m_data;
}
SharedDataPtr(const SharedDataPtr<T> &other) : m_data(other.m_data)
{
incref(m_data);
}
SharedDataPtr(SharedDataPtr&& other) noexcept : m_data(other.m_data)
{
other.m_data = nullptr;
}
SharedDataPtr<T> &operator=(const SharedDataPtr<T> &other)
{
if (m_data != other.m_data)
{
T *temp = m_data;
m_data = other.m_data;
incref(m_data);
decref(temp);
}
return *this;
}
SharedDataPtr<T>& operator=(SharedDataPtr<T>&& other) noexcept
{
if (m_data != other.m_data)
{
T *temp = m_data;
m_data = other.m_data;
other.m_data = nullptr;
decref(temp);
}
return *this;
}
SharedDataPtr<T> &operator=(T *other)
{
if (m_data != other)
{
T *temp = m_data;
m_data = other;
incref(m_data);
decref(temp);
}
return *this;
}
private:
static void incref(T *data)
{
if (data)
++data->m_refcount;
}
static void decref(T *data)
{
if (data && --data->m_refcount == 0)
delete data;
}
T *m_data;
};
} // namespace kiwi

178
kiwi/kiwi/solver.h Normal file
View File

@@ -0,0 +1,178 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include "constraint.h"
#include "debug.h"
#include "solverimpl.h"
#include "strength.h"
#include "variable.h"
namespace kiwi
{
class Solver
{
public:
Solver() = default;
~Solver() = default;
/* Add a constraint to the solver.
Throws
------
DuplicateConstraint
The given constraint has already been added to the solver.
UnsatisfiableConstraint
The given constraint is required and cannot be satisfied.
*/
void addConstraint( const Constraint& constraint )
{
m_impl.addConstraint( constraint );
}
/* Remove a constraint from the solver.
Throws
------
UnknownConstraint
The given constraint has not been added to the solver.
*/
void removeConstraint( const Constraint& constraint )
{
m_impl.removeConstraint( constraint );
}
/* Test whether a constraint has been added to the solver.
*/
bool hasConstraint( const Constraint& constraint ) const
{
return m_impl.hasConstraint( constraint );
}
/* Add an edit variable to the solver.
This method should be called before the `suggestValue` method is
used to supply a suggested value for the given edit variable.
Throws
------
DuplicateEditVariable
The given edit variable has already been added to the solver.
BadRequiredStrength
The given strength is >= required.
*/
void addEditVariable( const Variable& variable, double strength )
{
m_impl.addEditVariable( variable, strength );
}
/* Remove an edit variable from the solver.
Throws
------
UnknownEditVariable
The given edit variable has not been added to the solver.
*/
void removeEditVariable( const Variable& variable )
{
m_impl.removeEditVariable( variable );
}
/* Test whether an edit variable has been added to the solver.
*/
bool hasEditVariable( const Variable& variable ) const
{
return m_impl.hasEditVariable( variable );
}
/* Suggest a value for the given edit variable.
This method should be used after an edit variable as been added to
the solver in order to suggest the value for that variable. After
all suggestions have been made, the `solve` method can be used to
update the values of all variables.
Throws
------
UnknownEditVariable
The given edit variable has not been added to the solver.
*/
void suggestValue( const Variable& variable, double value )
{
m_impl.suggestValue( variable, value );
}
/* Update the values of the external solver variables.
*/
void updateVariables()
{
m_impl.updateVariables();
}
/* Reset the solver to the empty starting condition.
This method resets the internal solver state to the empty starting
condition, as if no constraints or edit variables have been added.
This can be faster than deleting the solver and creating a new one
when the entire system must change, since it can avoid unecessary
heap (de)allocations.
*/
void reset()
{
m_impl.reset();
}
/* Dump a representation of the solver internals to stdout.
*/
void dump()
{
debug::dump( m_impl );
}
/* Dump a representation of the solver internals to a stream.
*/
void dump( std::ostream& out )
{
debug::dump( m_impl, out );
}
/* Dump a representation of the solver internals to a string.
*/
std::string dumps()
{
return debug::dumps( m_impl );
}
private:
Solver( const Solver& );
Solver& operator=( const Solver& );
impl::SolverImpl m_impl;
};
} // namespace kiwi

825
kiwi/kiwi/solverimpl.h Normal file
View File

@@ -0,0 +1,825 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
#include "constraint.h"
#include "errors.h"
#include "expression.h"
#include "maptype.h"
#include "row.h"
#include "symbol.h"
#include "term.h"
#include "util.h"
#include "variable.h"
namespace kiwi
{
namespace impl
{
class SolverImpl
{
friend class DebugHelper;
struct Tag
{
Symbol marker;
Symbol other;
};
struct EditInfo
{
Tag tag;
Constraint constraint;
double constant;
};
using VarMap = MapType<Variable, Symbol>;
using RowMap = MapType<Symbol, Row*>;
using CnMap = MapType<Constraint, Tag>;
using EditMap = MapType<Variable, EditInfo>;
struct DualOptimizeGuard
{
DualOptimizeGuard( SolverImpl& impl ) : m_impl( impl ) {}
~DualOptimizeGuard() { m_impl.dualOptimize(); }
SolverImpl& m_impl;
};
public:
SolverImpl() : m_objective( new Row() ), m_id_tick( 1 ) {}
SolverImpl( const SolverImpl& ) = delete;
SolverImpl( SolverImpl&& ) = delete;
~SolverImpl() { clearRows(); }
/* Add a constraint to the solver.
Throws
------
DuplicateConstraint
The given constraint has already been added to the solver.
UnsatisfiableConstraint
The given constraint is required and cannot be satisfied.
*/
void addConstraint( const Constraint& constraint )
{
if( m_cns.find( constraint ) != m_cns.end() )
throw DuplicateConstraint( constraint );
// Creating a row causes symbols to be reserved for the variables
// in the constraint. If this method exits with an exception,
// then its possible those variables will linger in the var map.
// Since its likely that those variables will be used in other
// constraints and since exceptional conditions are uncommon,
// i'm not too worried about aggressive cleanup of the var map.
Tag tag;
std::unique_ptr<Row> rowptr( createRow( constraint, tag ) );
Symbol subject( chooseSubject( *rowptr, tag ) );
// If chooseSubject could not find a valid entering symbol, one
// last option is available if the entire row is composed of
// dummy variables. If the constant of the row is zero, then
// this represents redundant constraints and the new dummy
// marker can enter the basis. If the constant is non-zero,
// then it represents an unsatisfiable constraint.
if( subject.type() == Symbol::Invalid && allDummies( *rowptr ) )
{
if( !nearZero( rowptr->constant() ) )
throw UnsatisfiableConstraint( constraint );
else
subject = tag.marker;
}
// If an entering symbol still isn't found, then the row must
// be added using an artificial variable. If that fails, then
// the row represents an unsatisfiable constraint.
if( subject.type() == Symbol::Invalid )
{
if( !addWithArtificialVariable( *rowptr ) )
throw UnsatisfiableConstraint( constraint );
}
else
{
rowptr->solveFor( subject );
substitute( subject, *rowptr );
m_rows[ subject ] = rowptr.release();
}
m_cns[ constraint ] = tag;
// Optimizing after each constraint is added performs less
// aggregate work due to a smaller average system size. It
// also ensures the solver remains in a consistent state.
optimize( *m_objective );
}
/* Remove a constraint from the solver.
Throws
------
UnknownConstraint
The given constraint has not been added to the solver.
*/
void removeConstraint( const Constraint& constraint )
{
auto cn_it = m_cns.find( constraint );
if( cn_it == m_cns.end() )
throw UnknownConstraint( constraint );
Tag tag( cn_it->second );
m_cns.erase( cn_it );
// Remove the error effects from the objective function
// *before* pivoting, or substitutions into the objective
// will lead to incorrect solver results.
removeConstraintEffects( constraint, tag );
// If the marker is basic, simply drop the row. Otherwise,
// pivot the marker into the basis and then drop the row.
auto row_it = m_rows.find( tag.marker );
if( row_it != m_rows.end() )
{
std::unique_ptr<Row> rowptr( row_it->second );
m_rows.erase( row_it );
}
else
{
row_it = getMarkerLeavingRow( tag.marker );
if( row_it == m_rows.end() )
throw InternalSolverError( "failed to find leaving row" );
Symbol leaving( row_it->first );
std::unique_ptr<Row> rowptr( row_it->second );
m_rows.erase( row_it );
rowptr->solveFor( leaving, tag.marker );
substitute( tag.marker, *rowptr );
}
// Optimizing after each constraint is removed ensures that the
// solver remains consistent. It makes the solver api easier to
// use at a small tradeoff for speed.
optimize( *m_objective );
}
/* Test whether a constraint has been added to the solver.
*/
bool hasConstraint( const Constraint& constraint ) const
{
return m_cns.find( constraint ) != m_cns.end();
}
/* Add an edit variable to the solver.
This method should be called before the `suggestValue` method is
used to supply a suggested value for the given edit variable.
Throws
------
DuplicateEditVariable
The given edit variable has already been added to the solver.
BadRequiredStrength
The given strength is >= required.
*/
void addEditVariable( const Variable& variable, double strength )
{
if( m_edits.find( variable ) != m_edits.end() )
throw DuplicateEditVariable( variable );
strength = strength::clip( strength );
if( strength == strength::required )
throw BadRequiredStrength();
Constraint cn( Expression( variable ), OP_EQ, strength );
addConstraint( cn );
EditInfo info;
info.tag = m_cns[ cn ];
info.constraint = cn;
info.constant = 0.0;
m_edits[ variable ] = info;
}
/* Remove an edit variable from the solver.
Throws
------
UnknownEditVariable
The given edit variable has not been added to the solver.
*/
void removeEditVariable( const Variable& variable )
{
auto it = m_edits.find( variable );
if( it == m_edits.end() )
throw UnknownEditVariable( variable );
removeConstraint( it->second.constraint );
m_edits.erase( it );
}
/* Test whether an edit variable has been added to the solver.
*/
bool hasEditVariable( const Variable& variable ) const
{
return m_edits.find( variable ) != m_edits.end();
}
/* Suggest a value for the given edit variable.
This method should be used after an edit variable as been added to
the solver in order to suggest the value for that variable.
Throws
------
UnknownEditVariable
The given edit variable has not been added to the solver.
*/
void suggestValue( const Variable& variable, double value )
{
auto it = m_edits.find( variable );
if( it == m_edits.end() )
throw UnknownEditVariable( variable );
DualOptimizeGuard guard( *this );
EditInfo& info = it->second;
double delta = value - info.constant;
info.constant = value;
// Check first if the positive error variable is basic.
auto row_it = m_rows.find( info.tag.marker );
if( row_it != m_rows.end() )
{
if( row_it->second->add( -delta ) < 0.0 )
m_infeasible_rows.push_back( row_it->first );
return;
}
// Check next if the negative error variable is basic.
row_it = m_rows.find( info.tag.other );
if( row_it != m_rows.end() )
{
if( row_it->second->add( delta ) < 0.0 )
m_infeasible_rows.push_back( row_it->first );
return;
}
// Otherwise update each row where the error variables exist.
for (const auto & rowPair : m_rows)
{
double coeff = rowPair.second->coefficientFor( info.tag.marker );
if( coeff != 0.0 &&
rowPair.second->add( delta * coeff ) < 0.0 &&
rowPair.first.type() != Symbol::External )
m_infeasible_rows.push_back( rowPair.first );
}
}
/* Update the values of the external solver variables.
*/
void updateVariables()
{
auto row_end = m_rows.end();
for (auto &varPair : m_vars)
{
Variable& var = varPair.first;
auto row_it = m_rows.find( varPair.second );
if( row_it == row_end )
var.setValue( 0.0 );
else
var.setValue( row_it->second->constant() );
}
}
/* Reset the solver to the empty starting condition.
This method resets the internal solver state to the empty starting
condition, as if no constraints or edit variables have been added.
This can be faster than deleting the solver and creating a new one
when the entire system must change, since it can avoid unecessary
heap (de)allocations.
*/
void reset()
{
clearRows();
m_cns.clear();
m_vars.clear();
m_edits.clear();
m_infeasible_rows.clear();
m_objective.reset( new Row() );
m_artificial.reset();
m_id_tick = 1;
}
SolverImpl& operator=( const SolverImpl& ) = delete;
SolverImpl& operator=( SolverImpl&& ) = delete;
private:
struct RowDeleter
{
template<typename T>
void operator()( T& pair ) { delete pair.second; }
};
void clearRows()
{
std::for_each( m_rows.begin(), m_rows.end(), RowDeleter() );
m_rows.clear();
}
/* Get the symbol for the given variable.
If a symbol does not exist for the variable, one will be created.
*/
Symbol getVarSymbol( const Variable& variable )
{
auto it = m_vars.find( variable );
if( it != m_vars.end() )
return it->second;
Symbol symbol( Symbol::External, m_id_tick++ );
m_vars[ variable ] = symbol;
return symbol;
}
/* Create a new Row object for the given constraint.
The terms in the constraint will be converted to cells in the row.
Any term in the constraint with a coefficient of zero is ignored.
This method uses the `getVarSymbol` method to get the symbol for
the variables added to the row. If the symbol for a given cell
variable is basic, the cell variable will be substituted with the
basic row.
The necessary slack and error variables will be added to the row.
If the constant for the row is negative, the sign for the row
will be inverted so the constant becomes positive.
The tag will be updated with the marker and error symbols to use
for tracking the movement of the constraint in the tableau.
*/
std::unique_ptr<Row> createRow( const Constraint& constraint, Tag& tag )
{
const Expression& expr( constraint.expression() );
std::unique_ptr<Row> row( new Row( expr.constant() ) );
// Substitute the current basic variables into the row.
for (const auto &term : expr.terms())
{
if( !nearZero( term.coefficient() ) )
{
Symbol symbol( getVarSymbol( term.variable() ) );
auto row_it = m_rows.find( symbol );
if( row_it != m_rows.end() )
row->insert( *row_it->second, term.coefficient() );
else
row->insert( symbol, term.coefficient() );
}
}
// Add the necessary slack, error, and dummy variables.
switch( constraint.op() )
{
case OP_LE:
case OP_GE:
{
double coeff = constraint.op() == OP_LE ? 1.0 : -1.0;
Symbol slack( Symbol::Slack, m_id_tick++ );
tag.marker = slack;
row->insert( slack, coeff );
if( constraint.strength() < strength::required )
{
Symbol error( Symbol::Error, m_id_tick++ );
tag.other = error;
row->insert( error, -coeff );
m_objective->insert( error, constraint.strength() );
}
break;
}
case OP_EQ:
{
if( constraint.strength() < strength::required )
{
Symbol errplus( Symbol::Error, m_id_tick++ );
Symbol errminus( Symbol::Error, m_id_tick++ );
tag.marker = errplus;
tag.other = errminus;
row->insert( errplus, -1.0 ); // v = eplus - eminus
row->insert( errminus, 1.0 ); // v - eplus + eminus = 0
m_objective->insert( errplus, constraint.strength() );
m_objective->insert( errminus, constraint.strength() );
}
else
{
Symbol dummy( Symbol::Dummy, m_id_tick++ );
tag.marker = dummy;
row->insert( dummy );
}
break;
}
}
// Ensure the row as a positive constant.
if( row->constant() < 0.0 )
row->reverseSign();
return row;
}
/* Choose the subject for solving for the row.
This method will choose the best subject for using as the solve
target for the row. An invalid symbol will be returned if there
is no valid target.
The symbols are chosen according to the following precedence:
1) The first symbol representing an external variable.
2) A negative slack or error tag variable.
If a subject cannot be found, an invalid symbol will be returned.
*/
Symbol chooseSubject( const Row& row, const Tag& tag ) const
{
for (const auto &cellPair : row.cells())
{
if( cellPair.first.type() == Symbol::External )
return cellPair.first;
}
if( tag.marker.type() == Symbol::Slack || tag.marker.type() == Symbol::Error )
{
if( row.coefficientFor( tag.marker ) < 0.0 )
return tag.marker;
}
if( tag.other.type() == Symbol::Slack || tag.other.type() == Symbol::Error )
{
if( row.coefficientFor( tag.other ) < 0.0 )
return tag.other;
}
return Symbol();
}
/* Add the row to the tableau using an artificial variable.
This will return false if the constraint cannot be satisfied.
*/
bool addWithArtificialVariable( const Row& row )
{
// Create and add the artificial variable to the tableau
Symbol art( Symbol::Slack, m_id_tick++ );
m_rows[ art ] = new Row( row );
m_artificial.reset( new Row( row ) );
// Optimize the artificial objective. This is successful
// only if the artificial objective is optimized to zero.
optimize( *m_artificial );
bool success = nearZero( m_artificial->constant() );
m_artificial.reset();
// If the artificial variable is not basic, pivot the row so that
// it becomes basic. If the row is constant, exit early.
auto it = m_rows.find( art );
if( it != m_rows.end() )
{
std::unique_ptr<Row> rowptr( it->second );
m_rows.erase( it );
if( rowptr->cells().empty() )
return success;
Symbol entering( anyPivotableSymbol( *rowptr ) );
if( entering.type() == Symbol::Invalid )
return false; // unsatisfiable (will this ever happen?)
rowptr->solveFor( art, entering );
substitute( entering, *rowptr );
m_rows[ entering ] = rowptr.release();
}
// Remove the artificial variable from the tableau.
for (auto &rowPair : m_rows)
rowPair.second->remove(art);
m_objective->remove( art );
return success;
}
/* Substitute the parametric symbol with the given row.
This method will substitute all instances of the parametric symbol
in the tableau and the objective function with the given row.
*/
void substitute( const Symbol& symbol, const Row& row )
{
for( auto& rowPair : m_rows )
{
rowPair.second->substitute( symbol, row );
if( rowPair.first.type() != Symbol::External &&
rowPair.second->constant() < 0.0 )
m_infeasible_rows.push_back( rowPair.first );
}
m_objective->substitute( symbol, row );
if( m_artificial.get() )
m_artificial->substitute( symbol, row );
}
/* Optimize the system for the given objective function.
This method performs iterations of Phase 2 of the simplex method
until the objective function reaches a minimum.
Throws
------
InternalSolverError
The value of the objective function is unbounded.
*/
void optimize( const Row& objective )
{
while( true )
{
Symbol entering( getEnteringSymbol( objective ) );
if( entering.type() == Symbol::Invalid )
return;
auto it = getLeavingRow( entering );
if( it == m_rows.end() )
throw InternalSolverError( "The objective is unbounded." );
// pivot the entering symbol into the basis
Symbol leaving( it->first );
Row* row = it->second;
m_rows.erase( it );
row->solveFor( leaving, entering );
substitute( entering, *row );
m_rows[ entering ] = row;
}
}
/* Optimize the system using the dual of the simplex method.
The current state of the system should be such that the objective
function is optimal, but not feasible. This method will perform
an iteration of the dual simplex method to make the solution both
optimal and feasible.
Throws
------
InternalSolverError
The system cannot be dual optimized.
*/
void dualOptimize()
{
while( !m_infeasible_rows.empty() )
{
Symbol leaving( m_infeasible_rows.back() );
m_infeasible_rows.pop_back();
auto it = m_rows.find( leaving );
if( it != m_rows.end() && !nearZero( it->second->constant() ) &&
it->second->constant() < 0.0 )
{
Symbol entering( getDualEnteringSymbol( *it->second ) );
if( entering.type() == Symbol::Invalid )
throw InternalSolverError( "Dual optimize failed." );
// pivot the entering symbol into the basis
Row* row = it->second;
m_rows.erase( it );
row->solveFor( leaving, entering );
substitute( entering, *row );
m_rows[ entering ] = row;
}
}
}
/* Compute the entering variable for a pivot operation.
This method will return first symbol in the objective function which
is non-dummy and has a coefficient less than zero. If no symbol meets
the criteria, it means the objective function is at a minimum, and an
invalid symbol is returned.
*/
Symbol getEnteringSymbol( const Row& objective ) const
{
for (const auto &cellPair : objective.cells())
{
if( cellPair.first.type() != Symbol::Dummy && cellPair.second < 0.0 )
return cellPair.first;
}
return Symbol();
}
/* Compute the entering symbol for the dual optimize operation.
This method will return the symbol in the row which has a positive
coefficient and yields the minimum ratio for its respective symbol
in the objective function. The provided row *must* be infeasible.
If no symbol is found which meats the criteria, an invalid symbol
is returned.
*/
Symbol getDualEnteringSymbol( const Row& row ) const
{
Symbol entering;
double ratio = std::numeric_limits<double>::max();
for (const auto &cellPair : row.cells())
{
if( cellPair.second > 0.0 && cellPair.first.type() != Symbol::Dummy )
{
double coeff = m_objective->coefficientFor( cellPair.first );
double r = coeff / cellPair.second;
if( r < ratio )
{
ratio = r;
entering = cellPair.first;
}
}
}
return entering;
}
/* Get the first Slack or Error symbol in the row.
If no such symbol is present, and Invalid symbol will be returned.
*/
Symbol anyPivotableSymbol( const Row& row ) const
{
for (const auto &cellPair : row.cells())
{
const Symbol& sym( cellPair.first );
if( sym.type() == Symbol::Slack || sym.type() == Symbol::Error )
return sym;
}
return Symbol();
}
/* Compute the row which holds the exit symbol for a pivot.
This method will return an iterator to the row in the row map
which holds the exit symbol. If no appropriate exit symbol is
found, the end() iterator will be returned. This indicates that
the objective function is unbounded.
*/
RowMap::iterator getLeavingRow( const Symbol& entering )
{
double ratio = std::numeric_limits<double>::max();
auto end = m_rows.end();
auto found = m_rows.end();
for( auto it = m_rows.begin(); it != end; ++it )
{
if( it->first.type() != Symbol::External )
{
double temp = it->second->coefficientFor( entering );
if( temp < 0.0 )
{
double temp_ratio = -it->second->constant() / temp;
if( temp_ratio < ratio )
{
ratio = temp_ratio;
found = it;
}
}
}
}
return found;
}
/* Compute the leaving row for a marker variable.
This method will return an iterator to the row in the row map
which holds the given marker variable. The row will be chosen
according to the following precedence:
1) The row with a restricted basic varible and a negative coefficient
for the marker with the smallest ratio of -constant / coefficient.
2) The row with a restricted basic variable and the smallest ratio
of constant / coefficient.
3) The last unrestricted row which contains the marker.
If the marker does not exist in any row, the row map end() iterator
will be returned. This indicates an internal solver error since
the marker *should* exist somewhere in the tableau.
*/
RowMap::iterator getMarkerLeavingRow( const Symbol& marker )
{
const double dmax = std::numeric_limits<double>::max();
double r1 = dmax;
double r2 = dmax;
auto end = m_rows.end();
auto first = end;
auto second = end;
auto third = end;
for( auto it = m_rows.begin(); it != end; ++it )
{
double c = it->second->coefficientFor( marker );
if( c == 0.0 )
continue;
if( it->first.type() == Symbol::External )
{
third = it;
}
else if( c < 0.0 )
{
double r = -it->second->constant() / c;
if( r < r1 )
{
r1 = r;
first = it;
}
}
else
{
double r = it->second->constant() / c;
if( r < r2 )
{
r2 = r;
second = it;
}
}
}
if( first != end )
return first;
if( second != end )
return second;
return third;
}
/* Remove the effects of a constraint on the objective function.
*/
void removeConstraintEffects( const Constraint& cn, const Tag& tag )
{
if( tag.marker.type() == Symbol::Error )
removeMarkerEffects( tag.marker, cn.strength() );
if( tag.other.type() == Symbol::Error )
removeMarkerEffects( tag.other, cn.strength() );
}
/* Remove the effects of an error marker on the objective function.
*/
void removeMarkerEffects( const Symbol& marker, double strength )
{
auto row_it = m_rows.find( marker );
if( row_it != m_rows.end() )
m_objective->insert( *row_it->second, -strength );
else
m_objective->insert( marker, -strength );
}
/* Test whether a row is composed of all dummy variables.
*/
bool allDummies( const Row& row ) const
{
for (const auto &rowPair : row.cells())
{
if( rowPair.first.type() != Symbol::Dummy )
return false;
}
return true;
}
CnMap m_cns;
RowMap m_rows;
VarMap m_vars;
EditMap m_edits;
std::vector<Symbol> m_infeasible_rows;
std::unique_ptr<Row> m_objective;
std::unique_ptr<Row> m_artificial;
Symbol::Id m_id_tick;
};
} // namespace impl
} // namespace kiwi

44
kiwi/kiwi/strength.h Normal file
View File

@@ -0,0 +1,44 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <algorithm>
namespace kiwi
{
namespace strength
{
inline double create( double a, double b, double c, double w = 1.0 )
{
double result = 0.0;
result += std::max( 0.0, std::min( 1000.0, a * w ) ) * 1000000.0;
result += std::max( 0.0, std::min( 1000.0, b * w ) ) * 1000.0;
result += std::max( 0.0, std::min( 1000.0, c * w ) );
return result;
}
const double required = create( 1000.0, 1000.0, 1000.0 );
const double strong = create( 1.0, 0.0, 0.0 );
const double medium = create( 0.0, 1.0, 0.0 );
const double weak = create( 0.0, 0.0, 1.0 );
inline double clip( double value )
{
return std::max( 0.0, std::min( required, value ) );
}
} // namespace strength
} // namespace kiwi

68
kiwi/kiwi/symbol.h Normal file
View File

@@ -0,0 +1,68 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
namespace kiwi
{
namespace impl
{
class Symbol
{
public:
using Id = unsigned long long;
enum Type
{
Invalid,
External,
Slack,
Error,
Dummy
};
Symbol() : m_id( 0 ), m_type( Invalid ) {}
Symbol( Type type, Id id ) : m_id( id ), m_type( type ) {}
~Symbol() = default;
Id id() const
{
return m_id;
}
Type type() const
{
return m_type;
}
private:
Id m_id;
Type m_type;
friend bool operator<( const Symbol& lhs, const Symbol& rhs )
{
return lhs.m_id < rhs.m_id;
}
friend bool operator==( const Symbol& lhs, const Symbol& rhs )
{
return lhs.m_id == rhs.m_id;
}
};
} // namespace impl
} // namespace kiwi

680
kiwi/kiwi/symbolics.h Normal file
View File

@@ -0,0 +1,680 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <vector>
#include "constraint.h"
#include "expression.h"
#include "term.h"
#include "variable.h"
namespace kiwi
{
// Variable multiply, divide, and unary invert
inline
Term operator*( const Variable& variable, double coefficient )
{
return Term( variable, coefficient );
}
inline
Term operator/( const Variable& variable, double denominator )
{
return variable * ( 1.0 / denominator );
}
inline
Term operator-( const Variable& variable )
{
return variable * -1.0;
}
// Term multiply, divide, and unary invert
inline
Term operator*( const Term& term, double coefficient )
{
return Term( term.variable(), term.coefficient() * coefficient );
}
inline
Term operator/( const Term& term, double denominator )
{
return term * ( 1.0 / denominator );
}
inline
Term operator-( const Term& term )
{
return term * -1.0;
}
// Expression multiply, divide, and unary invert
inline
Expression operator*( const Expression& expression, double coefficient )
{
std::vector<Term> terms;
terms.reserve( expression.terms().size() );
for (const Term &term : expression.terms())
terms.push_back(term * coefficient);
return Expression( std::move(terms), expression.constant() * coefficient );
}
inline
Expression operator/( const Expression& expression, double denominator )
{
return expression * ( 1.0 / denominator );
}
inline
Expression operator-( const Expression& expression )
{
return expression * -1.0;
}
// Double multiply
inline
Expression operator*( double coefficient, const Expression& expression )
{
return expression * coefficient;
}
inline
Term operator*( double coefficient, const Term& term )
{
return term * coefficient;
}
inline
Term operator*( double coefficient, const Variable& variable )
{
return variable * coefficient;
}
// Expression add and subtract
inline
Expression operator+( const Expression& first, const Expression& second )
{
std::vector<Term> terms;
terms.reserve( first.terms().size() + second.terms().size() );
terms.insert( terms.begin(), first.terms().begin(), first.terms().end() );
terms.insert( terms.end(), second.terms().begin(), second.terms().end() );
return Expression( std::move(terms), first.constant() + second.constant() );
}
inline
Expression operator+( const Expression& first, const Term& second )
{
std::vector<Term> terms;
terms.reserve( first.terms().size() + 1 );
terms.insert( terms.begin(), first.terms().begin(), first.terms().end() );
terms.push_back( second );
return Expression( std::move(terms), first.constant() );
}
inline
Expression operator+( const Expression& expression, const Variable& variable )
{
return expression + Term( variable );
}
inline
Expression operator+( const Expression& expression, double constant )
{
return Expression( expression.terms(), expression.constant() + constant );
}
inline
Expression operator-( const Expression& first, const Expression& second )
{
return first + -second;
}
inline
Expression operator-( const Expression& expression, const Term& term )
{
return expression + -term;
}
inline
Expression operator-( const Expression& expression, const Variable& variable )
{
return expression + -variable;
}
inline
Expression operator-( const Expression& expression, double constant )
{
return expression + -constant;
}
// Term add and subtract
inline
Expression operator+( const Term& term, const Expression& expression )
{
return expression + term;
}
inline
Expression operator+( const Term& first, const Term& second )
{
return Expression( { first, second } );
}
inline
Expression operator+( const Term& term, const Variable& variable )
{
return term + Term( variable );
}
inline
Expression operator+( const Term& term, double constant )
{
return Expression( term, constant );
}
inline
Expression operator-( const Term& term, const Expression& expression )
{
return -expression + term;
}
inline
Expression operator-( const Term& first, const Term& second )
{
return first + -second;
}
inline
Expression operator-( const Term& term, const Variable& variable )
{
return term + -variable;
}
inline
Expression operator-( const Term& term, double constant )
{
return term + -constant;
}
// Variable add and subtract
inline
Expression operator+( const Variable& variable, const Expression& expression )
{
return expression + variable;
}
inline
Expression operator+( const Variable& variable, const Term& term )
{
return term + variable;
}
inline
Expression operator+( const Variable& first, const Variable& second )
{
return Term( first ) + second;
}
inline
Expression operator+( const Variable& variable, double constant )
{
return Term( variable ) + constant;
}
inline
Expression operator-( const Variable& variable, const Expression& expression )
{
return variable + -expression;
}
inline
Expression operator-( const Variable& variable, const Term& term )
{
return variable + -term;
}
inline
Expression operator-( const Variable& first, const Variable& second )
{
return first + -second;
}
inline
Expression operator-( const Variable& variable, double constant )
{
return variable + -constant;
}
// Double add and subtract
inline
Expression operator+( double constant, const Expression& expression )
{
return expression + constant;
}
inline
Expression operator+( double constant, const Term& term )
{
return term + constant;
}
inline
Expression operator+( double constant, const Variable& variable )
{
return variable + constant;
}
inline
Expression operator-( double constant, const Expression& expression )
{
return -expression + constant;
}
inline
Expression operator-( double constant, const Term& term )
{
return -term + constant;
}
inline
Expression operator-( double constant, const Variable& variable )
{
return -variable + constant;
}
// Expression relations
inline
Constraint operator==( const Expression& first, const Expression& second )
{
return Constraint( first - second, OP_EQ );
}
inline
Constraint operator==( const Expression& expression, const Term& term )
{
return expression == Expression( term );
}
inline
Constraint operator==( const Expression& expression, const Variable& variable )
{
return expression == Term( variable );
}
inline
Constraint operator==( const Expression& expression, double constant )
{
return expression == Expression( constant );
}
inline
Constraint operator<=( const Expression& first, const Expression& second )
{
return Constraint( first - second, OP_LE );
}
inline
Constraint operator<=( const Expression& expression, const Term& term )
{
return expression <= Expression( term );
}
inline
Constraint operator<=( const Expression& expression, const Variable& variable )
{
return expression <= Term( variable );
}
inline
Constraint operator<=( const Expression& expression, double constant )
{
return expression <= Expression( constant );
}
inline
Constraint operator>=( const Expression& first, const Expression& second )
{
return Constraint( first - second, OP_GE );
}
inline
Constraint operator>=( const Expression& expression, const Term& term )
{
return expression >= Expression( term );
}
inline
Constraint operator>=( const Expression& expression, const Variable& variable )
{
return expression >= Term( variable );
}
inline
Constraint operator>=( const Expression& expression, double constant )
{
return expression >= Expression( constant );
}
// Term relations
inline
Constraint operator==( const Term& term, const Expression& expression )
{
return expression == term;
}
inline
Constraint operator==( const Term& first, const Term& second )
{
return Expression( first ) == second;
}
inline
Constraint operator==( const Term& term, const Variable& variable )
{
return Expression( term ) == variable;
}
inline
Constraint operator==( const Term& term, double constant )
{
return Expression( term ) == constant;
}
inline
Constraint operator<=( const Term& term, const Expression& expression )
{
return expression >= term;
}
inline
Constraint operator<=( const Term& first, const Term& second )
{
return Expression( first ) <= second;
}
inline
Constraint operator<=( const Term& term, const Variable& variable )
{
return Expression( term ) <= variable;
}
inline
Constraint operator<=( const Term& term, double constant )
{
return Expression( term ) <= constant;
}
inline
Constraint operator>=( const Term& term, const Expression& expression )
{
return expression <= term;
}
inline
Constraint operator>=( const Term& first, const Term& second )
{
return Expression( first ) >= second;
}
inline
Constraint operator>=( const Term& term, const Variable& variable )
{
return Expression( term ) >= variable;
}
inline
Constraint operator>=( const Term& term, double constant )
{
return Expression( term ) >= constant;
}
// Variable relations
inline
Constraint operator==( const Variable& variable, const Expression& expression )
{
return expression == variable;
}
inline
Constraint operator==( const Variable& variable, const Term& term )
{
return term == variable;
}
inline
Constraint operator==( const Variable& first, const Variable& second )
{
return Term( first ) == second;
}
inline
Constraint operator==( const Variable& variable, double constant )
{
return Term( variable ) == constant;
}
inline
Constraint operator<=( const Variable& variable, const Expression& expression )
{
return expression >= variable;
}
inline
Constraint operator<=( const Variable& variable, const Term& term )
{
return term >= variable;
}
inline
Constraint operator<=( const Variable& first, const Variable& second )
{
return Term( first ) <= second;
}
inline
Constraint operator<=( const Variable& variable, double constant )
{
return Term( variable ) <= constant;
}
inline
Constraint operator>=( const Variable& variable, const Expression& expression )
{
return expression <= variable;
}
inline
Constraint operator>=( const Variable& variable, const Term& term )
{
return term <= variable;
}
inline
Constraint operator>=( const Variable& first, const Variable& second )
{
return Term( first ) >= second;
}
inline
Constraint operator>=( const Variable& variable, double constant )
{
return Term( variable ) >= constant;
}
// Double relations
inline
Constraint operator==( double constant, const Expression& expression )
{
return expression == constant;
}
inline
Constraint operator==( double constant, const Term& term )
{
return term == constant;
}
inline
Constraint operator==( double constant, const Variable& variable )
{
return variable == constant;
}
inline
Constraint operator<=( double constant, const Expression& expression )
{
return expression >= constant;
}
inline
Constraint operator<=( double constant, const Term& term )
{
return term >= constant;
}
inline
Constraint operator<=( double constant, const Variable& variable )
{
return variable >= constant;
}
inline
Constraint operator>=( double constant, const Expression& expression )
{
return expression <= constant;
}
inline
Constraint operator>=( double constant, const Term& term )
{
return term <= constant;
}
inline
Constraint operator>=( double constant, const Variable& variable )
{
return variable <= constant;
}
// Constraint strength modifier
inline
Constraint operator|( const Constraint& constraint, double strength )
{
return Constraint( constraint, strength );
}
inline
Constraint operator|( double strength, const Constraint& constraint )
{
return constraint | strength;
}
} // namespace kiwi

59
kiwi/kiwi/term.h Normal file
View File

@@ -0,0 +1,59 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <utility>
#include "variable.h"
namespace kiwi
{
class Term
{
public:
Term( Variable variable, double coefficient = 1.0 ) :
m_variable( std::move(variable) ), m_coefficient( coefficient ) {}
// to facilitate efficient map -> vector copies
Term( const std::pair<const Variable, double>& pair ) :
m_variable( pair.first ), m_coefficient( pair.second ) {}
Term(const Term&) = default;
Term(Term&&) noexcept = default;
~Term() = default;
const Variable& variable() const
{
return m_variable;
}
double coefficient() const
{
return m_coefficient;
}
double value() const
{
return m_coefficient * m_variable.value();
}
Term& operator=(const Term&) = default;
Term& operator=(Term&&) noexcept = default;
private:
Variable m_variable;
double m_coefficient;
};
} // namespace kiwi

24
kiwi/kiwi/util.h Normal file
View File

@@ -0,0 +1,24 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
namespace kiwi
{
namespace impl
{
inline bool nearZero(double value)
{
const double eps = 1.0e-8;
return value < 0.0 ? -value < eps : value < eps;
}
} // namespace impl
} // namespace kiwi

119
kiwi/kiwi/variable.h Normal file
View File

@@ -0,0 +1,119 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2017, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include "shareddata.h"
namespace kiwi
{
class Variable
{
public:
class Context
{
public:
Context() = default;
virtual ~Context() {} // LCOV_EXCL_LINE
};
Variable(Context *context = 0) : m_data(new VariableData("", context)) {}
Variable(std::string name, Context *context = 0) : m_data(new VariableData(std::move(name), context)) {}
Variable(const char *name, Context *context = 0) : m_data(new VariableData(name, context)) {}
Variable(const Variable&) = default;
Variable(Variable&&) noexcept = default;
~Variable() = default;
const std::string &name() const
{
return m_data->m_name;
}
void setName(const char *name)
{
m_data->m_name = name;
}
void setName(const std::string &name)
{
m_data->m_name = name;
}
Context *context() const
{
return m_data->m_context.get();
}
void setContext(Context *context)
{
m_data->m_context.reset(context);
}
double value() const
{
return m_data->m_value;
}
void setValue(double value)
{
m_data->m_value = value;
}
// operator== is used for symbolics
bool equals(const Variable &other)
{
return m_data == other.m_data;
}
Variable& operator=(const Variable&) = default;
Variable& operator=(Variable&&) noexcept = default;
private:
class VariableData : public SharedData
{
public:
VariableData(std::string name, Context *context) : SharedData(),
m_name(std::move(name)),
m_context(context),
m_value(0.0) {}
VariableData(const char *name, Context *context) : SharedData(),
m_name(name),
m_context(context),
m_value(0.0) {}
~VariableData() = default;
std::string m_name;
std::unique_ptr<Context> m_context;
double m_value;
private:
VariableData(const VariableData &other);
VariableData &operator=(const VariableData &other);
};
SharedDataPtr<VariableData> m_data;
friend bool operator<(const Variable &lhs, const Variable &rhs)
{
return lhs.m_data < rhs.m_data;
}
};
} // namespace kiwi

14
kiwi/kiwi/version.h Normal file
View File

@@ -0,0 +1,14 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2022, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#define KIWI_MAJOR_VERSION 1
#define KIWI_MINOR_VERSION 4
#define KIWI_MICRO_VERSION 2
#define KIWI_VERSION_HEX 0x010402
#define KIWI_VERSION "1.4.2"

View File

@@ -0,0 +1,4 @@
ruff
mypy
# Allow to lint tests using mypy
pytest

View File

@@ -0,0 +1,42 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2013-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
from ._cext import (
Constraint,
Expression,
Solver,
Term,
Variable,
__kiwi_version__,
__version__,
strength,
)
from .exceptions import (
BadRequiredStrength,
DuplicateConstraint,
DuplicateEditVariable,
UnknownConstraint,
UnknownEditVariable,
UnsatisfiableConstraint,
)
__all__ = [
"BadRequiredStrength",
"DuplicateConstraint",
"DuplicateEditVariable",
"UnknownConstraint",
"UnknownEditVariable",
"UnsatisfiableConstraint",
"strength",
"Variable",
"Term",
"Expression",
"Constraint",
"Solver",
"__version__",
"__kiwi_version__",
]

View File

@@ -0,0 +1,228 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
from typing import Any, Iterable, NoReturn, Tuple, type_check_only
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal # type: ignore
__version__: str
__kiwi_version__: str
# Types
@type_check_only
class Strength:
@property
def weak(self) -> float: ...
@property
def medium(self) -> float: ...
@property
def strong(self) -> float: ...
@property
def required(self) -> float: ...
def create(
self,
a: int | float,
b: int | float,
c: int | float,
weight: int | float = 1.0,
/,
) -> float: ...
# This is meant as a singleton and users should not access the Strength type.
strength: Strength
class Variable:
"""Variable to express a constraint in a solver."""
__hash__: None # type: ignore
def __init__(self, name: str = "", context: Any = None, /) -> None: ...
def name(self) -> str:
"""Get the name of the variable."""
...
def setName(self, name: str, /) -> Any:
"""Set the name of the variable."""
...
def value(self) -> float:
"""Get the current value of the variable."""
...
def context(self) -> Any:
"""Get the context object associated with the variable."""
...
def setContext(self, context: Any, /) -> Any:
"""Set the context object associated with the variable."""
...
def __neg__(self) -> Term: ...
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __mul__(self, other: float) -> Term: ...
def __rmul__(self, other: float) -> Term: ...
def __truediv__(self, other: float) -> Term: ...
def __rtruediv__(self, other: float) -> Term: ...
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __ne__(self, other: Any) -> NoReturn: ...
def __gt__(self, other: Any) -> NoReturn: ...
def __lt__(self, other: Any) -> NoReturn: ...
class Term:
"""Product of a variable by a constant pre-factor."""
__hash__: None # type: ignore
def __init__(
self, variable: Variable, coefficient: int | float = 1.0, /
) -> None: ...
def coefficient(self) -> float:
"""Get the coefficient for the term."""
...
def variable(self) -> Variable:
"""Get the variable for the term."""
...
def value(self) -> float:
"""Get the value for the term."""
...
def __neg__(self) -> Term: ...
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __mul__(self, other: float) -> Term: ...
def __rmul__(self, other: float) -> Term: ...
def __truediv__(self, other: float) -> Term: ...
def __rtruediv__(self, other: float) -> Term: ...
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __ne__(self, other: Any) -> NoReturn: ...
def __gt__(self, other: Any) -> NoReturn: ...
def __lt__(self, other: Any) -> NoReturn: ...
class Expression:
"""Sum of terms and an additional constant."""
__hash__: None # type: ignore
def __init__(
self, terms: Iterable[Term], constant: int | float = 0.0, /
) -> None: ...
def constant(self) -> float:
"" "Get the constant for the expression." ""
...
def terms(self) -> Tuple[Term, ...]:
"""Get the tuple of terms for the expression."""
...
def value(self) -> float:
"""Get the value for the expression."""
...
def __neg__(self) -> Expression: ...
def __add__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __radd__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __sub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __rsub__(self, other: float | Variable | Term | Expression) -> Expression: ...
def __mul__(self, other: float) -> Expression: ...
def __rmul__(self, other: float) -> Expression: ...
def __truediv__(self, other: float) -> Expression: ...
def __rtruediv__(self, other: float) -> Expression: ...
def __eq__(self, other: float | Variable | Term | Expression) -> Constraint: ... # type: ignore
def __ge__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __le__(self, other: float | Variable | Term | Expression) -> Constraint: ...
def __ne__(self, other: Any) -> NoReturn: ...
def __gt__(self, other: Any) -> NoReturn: ...
def __lt__(self, other: Any) -> NoReturn: ...
class Constraint:
def __init__(
self,
expression: Expression,
op: Literal["=="] | Literal["<="] | Literal[">="],
strength: float
| Literal["weak"]
| Literal["medium"]
| Literal["strong"]
| Literal["required"] = "required",
/,
) -> None: ...
def expression(self) -> Expression:
"""Get the expression object for the constraint."""
...
def op(self) -> Literal["=="] | Literal["<="] | Literal[">="]:
"""Get the relational operator for the constraint."""
...
def strength(self) -> float:
"""Get the strength for the constraint."""
...
def violated(self) -> bool:
"""Indicate if the constraint is violated in teh current state of the solver."""
...
def __or__(
self,
other: float
| Literal["weak"]
| Literal["medium"]
| Literal["strong"]
| Literal["required"],
) -> Constraint: ...
def __ror__(
self,
other: float
| Literal["weak"]
| Literal["medium"]
| Literal["strong"]
| Literal["required"],
) -> Constraint: ...
class Solver:
"""Kiwi solver class."""
def __init__(self) -> None: ...
def addConstraint(self, constraint: Constraint, /) -> None:
"""Add a constraint to the solver."""
...
def removeConstraint(self, constraint: Constraint, /) -> None:
"""Remove a constraint from the solver."""
...
def hasConstraint(self, constraint: Constraint, /) -> bool:
"""Check whether the solver contains a constraint."""
...
def addEditVariable(
self,
variable: Variable,
strength: float
| Literal["weak"]
| Literal["medium"]
| Literal["strong"]
| Literal["required"],
/,
) -> None:
"""Add an edit variable to the solver."""
...
def removeEditVariable(self, variable: Variable, /) -> None:
"""Remove an edit variable from the solver."""
...
def hasEditVariable(self, variable: Variable, /) -> bool:
"""Check whether the solver contains an edit variable."""
...
def suggestValue(self, variable: Variable, value: int | float, /) -> None:
"""Suggest a desired value for an edit variable."""
...
def updateVariables(self) -> None:
"""Update the values of the solver variables."""
...
def reset(self) -> None:
"""Reset the solver to the initial empty starting condition."""
...
def dump(self) -> None:
"""Dump a representation of the solver internals to stdout."""
...
def dumps(self) -> str:
"""Dump a representation of the solver internals to a string."""
...

View File

@@ -0,0 +1,51 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2023, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
"""Kiwi exceptions.
Imported by the kiwisolver C extension.
"""
class BadRequiredStrength(Exception):
pass
class DuplicateConstraint(Exception):
__slots__ = ("constraint",)
def __init__(self, constraint):
self.constraint = constraint
class DuplicateEditVariable(Exception):
__slots__ = ("edit_variable",)
def __init__(self, edit_variable):
self.edit_variable = edit_variable
class UnknownConstraint(Exception):
__slots__ = ("constraint",)
def __init__(self, constraint):
self.constraint = constraint
class UnknownEditVariable(Exception):
__slots__ = ("edit_variable",)
def __init__(self, edit_variable):
self.edit_variable = edit_variable
class UnsatisfiableConstraint(Exception):
__slots__ = ("constraint",)
def __init__(self, constraint):
self.constraint = constraint

View File

222
kiwi/py/src/constraint.cpp Normal file
View File

@@ -0,0 +1,222 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <algorithm>
#include <sstream>
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "types.h"
#include "util.h"
namespace kiwisolver
{
namespace
{
PyObject *
Constraint_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
static const char *kwlist[] = {"expression", "op", "strength", 0};
PyObject *pyexpr;
PyObject *pyop;
PyObject *pystrength = 0;
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "OO|O:__new__", const_cast<char **>(kwlist),
&pyexpr, &pyop, &pystrength))
return 0;
if (!Expression::TypeCheck(pyexpr))
return cppy::type_error(pyexpr, "Expression");
kiwi::RelationalOperator op;
if (!convert_to_relational_op(pyop, op))
return 0;
double strength = kiwi::strength::required;
if (pystrength && !convert_to_strength(pystrength, strength))
return 0;
cppy::ptr pycn(PyType_GenericNew(type, args, kwargs));
if (!pycn)
return 0;
Constraint *cn = reinterpret_cast<Constraint *>(pycn.get());
cn->expression = reduce_expression(pyexpr);
if (!cn->expression)
return 0;
kiwi::Expression expr(convert_to_kiwi_expression(cn->expression));
new (&cn->constraint) kiwi::Constraint(expr, op, strength);
return pycn.release();
}
void Constraint_clear(Constraint *self)
{
Py_CLEAR(self->expression);
}
int Constraint_traverse(Constraint *self, visitproc visit, void *arg)
{
Py_VISIT(self->expression);
#if PY_VERSION_HEX >= 0x03090000
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
Py_VISIT(Py_TYPE(self));
#endif
return 0;
}
void Constraint_dealloc(Constraint *self)
{
PyObject_GC_UnTrack(self);
Constraint_clear(self);
self->constraint.~Constraint();
Py_TYPE(self)->tp_free(pyobject_cast(self));
}
PyObject *
Constraint_repr(Constraint *self)
{
std::stringstream stream;
Expression *expr = reinterpret_cast<Expression *>(self->expression);
Py_ssize_t size = PyTuple_GET_SIZE(expr->terms);
for (Py_ssize_t i = 0; i < size; ++i)
{
PyObject *item = PyTuple_GET_ITEM(expr->terms, i);
Term *term = reinterpret_cast<Term *>(item);
stream << term->coefficient << " * ";
stream << reinterpret_cast<Variable *>(term->variable)->variable.name();
stream << " + ";
}
stream << expr->constant;
switch (self->constraint.op())
{
case kiwi::OP_EQ:
stream << " == 0";
break;
case kiwi::OP_LE:
stream << " <= 0";
break;
case kiwi::OP_GE:
stream << " >= 0";
break;
}
stream << " | strength = " << self->constraint.strength();
if (self->constraint.violated())
{
stream << " (VIOLATED)";
}
return PyUnicode_FromString(stream.str().c_str());
}
PyObject *
Constraint_expression(Constraint *self)
{
return cppy::incref(self->expression);
}
PyObject *
Constraint_op(Constraint *self)
{
PyObject *res = 0;
switch (self->constraint.op())
{
case kiwi::OP_EQ:
res = PyUnicode_FromString("==");
break;
case kiwi::OP_LE:
res = PyUnicode_FromString("<=");
break;
case kiwi::OP_GE:
res = PyUnicode_FromString(">=");
break;
}
return res;
}
PyObject *
Constraint_strength(Constraint *self)
{
return PyFloat_FromDouble(self->constraint.strength());
}
PyObject *
Constraint_violated(Constraint *self)
{
if (self->constraint.violated()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
PyObject *
Constraint_or(PyObject *pyoldcn, PyObject *value)
{
if (!Constraint::TypeCheck(pyoldcn))
std::swap(pyoldcn, value);
double strength;
if (!convert_to_strength(value, strength))
return 0;
PyObject *pynewcn = PyType_GenericNew(Constraint::TypeObject, 0, 0);
if (!pynewcn)
return 0;
Constraint *oldcn = reinterpret_cast<Constraint *>(pyoldcn);
Constraint *newcn = reinterpret_cast<Constraint *>(pynewcn);
newcn->expression = cppy::incref(oldcn->expression);
new (&newcn->constraint) kiwi::Constraint(oldcn->constraint, strength);
return pynewcn;
}
static PyMethodDef
Constraint_methods[] = {
{"expression", (PyCFunction)Constraint_expression, METH_NOARGS,
"Get the expression object for the constraint."},
{"op", (PyCFunction)Constraint_op, METH_NOARGS,
"Get the relational operator for the constraint."},
{"strength", (PyCFunction)Constraint_strength, METH_NOARGS,
"Get the strength for the constraint."},
{"violated", (PyCFunction)Constraint_violated, METH_NOARGS,
"Return whether or not the constraint was violated "
"during the last solver pass."},
{0} // sentinel
};
static PyType_Slot Constraint_Type_slots[] = {
{Py_tp_dealloc, void_cast(Constraint_dealloc)}, /* tp_dealloc */
{Py_tp_traverse, void_cast(Constraint_traverse)}, /* tp_traverse */
{Py_tp_clear, void_cast(Constraint_clear)}, /* tp_clear */
{Py_tp_repr, void_cast(Constraint_repr)}, /* tp_repr */
{Py_tp_methods, void_cast(Constraint_methods)}, /* tp_methods */
{Py_tp_new, void_cast(Constraint_new)}, /* tp_new */
{Py_tp_alloc, void_cast(PyType_GenericAlloc)}, /* tp_alloc */
{Py_tp_free, void_cast(PyObject_GC_Del)}, /* tp_free */
{Py_nb_or, void_cast(Constraint_or)}, /* nb_or */
{0, 0},
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject *Constraint::TypeObject = NULL;
PyType_Spec Constraint::TypeObject_Spec = {
"kiwisolver.Constraint", /* tp_name */
sizeof(Constraint), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
Constraint_Type_slots /* slots */
};
bool Constraint::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast(PyType_FromSpec(&TypeObject_Spec));
if (!TypeObject)
{
return false;
}
return true;
}
} // namespace kiwisolver

251
kiwi/py/src/expression.cpp Normal file
View File

@@ -0,0 +1,251 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <sstream>
#include <cppy/cppy.h>
#include "symbolics.h"
#include "types.h"
#include "util.h"
namespace kiwisolver
{
namespace
{
PyObject*
Expression_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
{
static const char *kwlist[] = { "terms", "constant", 0 };
PyObject* pyterms;
PyObject* pyconstant = 0;
if( !PyArg_ParseTupleAndKeywords(
args, kwargs, "O|O:__new__", const_cast<char**>( kwlist ),
&pyterms, &pyconstant ) )
return 0;
cppy::ptr terms( PySequence_Tuple( pyterms ) );
if( !terms )
return 0;
Py_ssize_t end = PyTuple_GET_SIZE( terms.get() );
for( Py_ssize_t i = 0; i < end; ++i )
{
PyObject* item = PyTuple_GET_ITEM( terms.get(), i );
if( !Term::TypeCheck( item ) )
return cppy::type_error( item, "Term" );
}
double constant = 0.0;
if( pyconstant && !convert_to_double( pyconstant, constant ) )
return 0;
PyObject* pyexpr = PyType_GenericNew( type, args, kwargs );
if( !pyexpr )
return 0;
Expression* self = reinterpret_cast<Expression*>( pyexpr );
self->terms = terms.release();
self->constant = constant;
return pyexpr;
}
void
Expression_clear( Expression* self )
{
Py_CLEAR( self->terms );
}
int
Expression_traverse( Expression* self, visitproc visit, void* arg )
{
Py_VISIT( self->terms );
#if PY_VERSION_HEX >= 0x03090000
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
Py_VISIT(Py_TYPE(self));
#endif
return 0;
}
void
Expression_dealloc( Expression* self )
{
PyObject_GC_UnTrack( self );
Expression_clear( self );
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
}
PyObject*
Expression_repr( Expression* self )
{
std::stringstream stream;
Py_ssize_t end = PyTuple_GET_SIZE( self->terms );
for( Py_ssize_t i = 0; i < end; ++i )
{
PyObject* item = PyTuple_GET_ITEM( self->terms, i );
Term* term = reinterpret_cast<Term*>( item );
stream << term->coefficient << " * ";
stream << reinterpret_cast<Variable*>( term->variable )->variable.name();
stream << " + ";
}
stream << self->constant;
return PyUnicode_FromString( stream.str().c_str() );
}
PyObject*
Expression_terms( Expression* self )
{
return cppy::incref( self->terms );
}
PyObject*
Expression_constant( Expression* self )
{
return PyFloat_FromDouble( self->constant );
}
PyObject*
Expression_value( Expression* self )
{
double result = self->constant;
Py_ssize_t size = PyTuple_GET_SIZE( self->terms );
for( Py_ssize_t i = 0; i < size; ++i )
{
PyObject* item = PyTuple_GET_ITEM( self->terms, i );
Term* term = reinterpret_cast<Term*>( item );
Variable* pyvar = reinterpret_cast<Variable*>( term->variable );
result += term->coefficient * pyvar->variable.value();
}
return PyFloat_FromDouble( result );
}
PyObject*
Expression_add( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryAdd, Expression>()( first, second );
}
PyObject*
Expression_sub( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinarySub, Expression>()( first, second );
}
PyObject*
Expression_mul( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryMul, Expression>()( first, second );
}
PyObject*
Expression_div( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryDiv, Expression>()( first, second );
}
PyObject*
Expression_neg( PyObject* value )
{
return UnaryInvoke<UnaryNeg, Expression>()( value );
}
PyObject*
Expression_richcmp( PyObject* first, PyObject* second, int op )
{
switch( op )
{
case Py_EQ:
return BinaryInvoke<CmpEQ, Expression>()( first, second );
case Py_LE:
return BinaryInvoke<CmpLE, Expression>()( first, second );
case Py_GE:
return BinaryInvoke<CmpGE, Expression>()( first, second );
default:
break;
}
PyErr_Format(
PyExc_TypeError,
"unsupported operand type(s) for %s: "
"'%.100s' and '%.100s'",
pyop_str( op ),
Py_TYPE( first )->tp_name,
Py_TYPE( second )->tp_name
);
return 0;
}
static PyMethodDef
Expression_methods[] = {
{ "terms", ( PyCFunction )Expression_terms, METH_NOARGS,
"Get the tuple of terms for the expression." },
{ "constant", ( PyCFunction )Expression_constant, METH_NOARGS,
"Get the constant for the expression." },
{ "value", ( PyCFunction )Expression_value, METH_NOARGS,
"Get the value for the expression." },
{ 0 } // sentinel
};
static PyType_Slot Expression_Type_slots[] = {
{ Py_tp_dealloc, void_cast( Expression_dealloc ) }, /* tp_dealloc */
{ Py_tp_traverse, void_cast( Expression_traverse ) }, /* tp_traverse */
{ Py_tp_clear, void_cast( Expression_clear ) }, /* tp_clear */
{ Py_tp_repr, void_cast( Expression_repr ) }, /* tp_repr */
{ Py_tp_richcompare, void_cast( Expression_richcmp ) }, /* tp_richcompare */
{ Py_tp_methods, void_cast( Expression_methods ) }, /* tp_methods */
{ Py_tp_new, void_cast( Expression_new ) }, /* tp_new */
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
{ Py_nb_add, void_cast( Expression_add ) }, /* nb_add */
{ Py_nb_subtract, void_cast( Expression_sub ) }, /* nb_sub */
{ Py_nb_multiply, void_cast( Expression_mul ) }, /* nb_mul */
{ Py_nb_negative, void_cast( Expression_neg ) }, /* nb_neg */
{ Py_nb_true_divide, void_cast( Expression_div ) }, /* nb_div */
{ 0, 0 },
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject* Expression::TypeObject = NULL;
PyType_Spec Expression::TypeObject_Spec = {
"kiwisolver.Expression", /* tp_name */
sizeof( Expression ), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT|
Py_TPFLAGS_HAVE_GC|
Py_TPFLAGS_BASETYPE, /* tp_flags */
Expression_Type_slots /* slots */
};
bool Expression::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
if( !TypeObject )
{
return false;
}
return true;
}
} // namesapce kiwisolver

187
kiwi/py/src/kiwisolver.cpp Normal file
View File

@@ -0,0 +1,187 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2021, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "types.h"
#include "version.h"
namespace
{
bool ready_types()
{
using namespace kiwisolver;
if( !Variable::Ready() )
{
return false;
}
if( !Term::Ready() )
{
return false;
}
if( !Expression::Ready() )
{
return false;
}
if( !Constraint::Ready() )
{
return false;
}
if( !strength::Ready() )
{
return false;
}
if( !Solver::Ready() )
{
return false;
}
return true;
}
bool add_objects( PyObject* mod )
{
using namespace kiwisolver;
cppy::ptr kiwiversion( PyUnicode_FromString( KIWI_VERSION ) );
if( !kiwiversion )
{
return false;
}
cppy::ptr pyversion( PyUnicode_FromString( PY_KIWI_VERSION ) );
if( !pyversion )
{
return false;
}
cppy::ptr pystrength( PyType_GenericNew( strength::TypeObject, 0, 0 ) );
if( !pystrength )
{
return false;
}
if( PyModule_AddObject( mod, "__version__", pyversion.get() ) < 0 )
{
return false;
}
pyversion.release();
if( PyModule_AddObject( mod, "__kiwi_version__", kiwiversion.get() ) < 0 )
{
return false;
}
kiwiversion.release();
if( PyModule_AddObject( mod, "strength", pystrength.get() ) < 0 )
{
return false;
}
pystrength.release();
// Variable
cppy::ptr var( pyobject_cast( Variable::TypeObject ) );
if( PyModule_AddObject( mod, "Variable", var.get() ) < 0 )
{
return false;
}
var.release();
// Term
cppy::ptr term( pyobject_cast( Term::TypeObject ) );
if( PyModule_AddObject( mod, "Term", term.get() ) < 0 )
{
return false;
}
term.release();
// Expression
cppy::ptr expr( pyobject_cast( Expression::TypeObject ) );
if( PyModule_AddObject( mod, "Expression", expr.get() ) < 0 )
{
return false;
}
expr.release();
// Constraint
cppy::ptr cons( pyobject_cast( Constraint::TypeObject ) );
if( PyModule_AddObject( mod, "Constraint", cons.get() ) < 0 )
{
return false;
}
cons.release();
cppy::ptr solver( pyobject_cast( Solver::TypeObject ) );
if( PyModule_AddObject( mod, "Solver", solver.get() ) < 0 )
{
return false;
}
solver.release();
PyModule_AddObject( mod, "DuplicateConstraint", DuplicateConstraint );
PyModule_AddObject( mod, "UnsatisfiableConstraint", UnsatisfiableConstraint );
PyModule_AddObject( mod, "UnknownConstraint", UnknownConstraint );
PyModule_AddObject( mod, "DuplicateEditVariable", DuplicateEditVariable );
PyModule_AddObject( mod, "UnknownEditVariable", UnknownEditVariable );
PyModule_AddObject( mod, "BadRequiredStrength", BadRequiredStrength );
return true;
}
int
kiwi_modexec( PyObject *mod )
{
if( !ready_types() )
{
return -1;
}
if( !kiwisolver::init_exceptions() )
{
return -1;
}
if( !add_objects( mod ) )
{
return -1;
}
return 0;
}
static PyMethodDef
kiwisolver_methods[] = {
{ 0 } // Sentinel
};
PyModuleDef_Slot kiwisolver_slots[] = {
{Py_mod_exec, reinterpret_cast<void*>( kiwi_modexec ) },
{0, NULL}
};
struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"_cext",
"kiwisolver extension module",
0,
kiwisolver_methods,
kiwisolver_slots,
NULL,
NULL,
NULL
};
} // namespace
PyMODINIT_FUNC PyInit__cext( void )
{
return PyModuleDef_Init( &moduledef );
}

338
kiwi/py/src/solver.cpp Normal file
View File

@@ -0,0 +1,338 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "types.h"
#include "util.h"
namespace kiwisolver
{
namespace
{
PyObject*
Solver_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
{
if( PyTuple_GET_SIZE( args ) != 0 || ( kwargs && PyDict_Size( kwargs ) != 0 ) )
return cppy::type_error( "Solver.__new__ takes no arguments" );
PyObject* pysolver = PyType_GenericNew( type, args, kwargs );
if( !pysolver )
return 0;
Solver* self = reinterpret_cast<Solver*>( pysolver );
new( &self->solver ) kiwi::Solver();
return pysolver;
}
void
Solver_dealloc( Solver* self )
{
self->solver.~Solver();
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
}
PyObject*
Solver_addConstraint( Solver* self, PyObject* other )
{
if( !Constraint::TypeCheck( other ) )
return cppy::type_error( other, "Constraint" );
Constraint* cn = reinterpret_cast<Constraint*>( other );
try
{
self->solver.addConstraint( cn->constraint );
}
catch( const kiwi::DuplicateConstraint& )
{
PyErr_SetObject( DuplicateConstraint, other );
return 0;
}
catch( const kiwi::UnsatisfiableConstraint& )
{
PyErr_SetObject( UnsatisfiableConstraint, other );
return 0;
}
Py_RETURN_NONE;
}
PyObject*
Solver_removeConstraint( Solver* self, PyObject* other )
{
if( !Constraint::TypeCheck( other ) )
return cppy::type_error( other, "Constraint" );
Constraint* cn = reinterpret_cast<Constraint*>( other );
try
{
self->solver.removeConstraint( cn->constraint );
}
catch( const kiwi::UnknownConstraint& )
{
PyErr_SetObject( UnknownConstraint, other );
return 0;
}
Py_RETURN_NONE;
}
PyObject*
Solver_hasConstraint( Solver* self, PyObject* other )
{
if( !Constraint::TypeCheck( other ) )
return cppy::type_error( other, "Constraint" );
Constraint* cn = reinterpret_cast<Constraint*>( other );
return cppy::incref( self->solver.hasConstraint( cn->constraint ) ? Py_True : Py_False );
}
PyObject*
Solver_addEditVariable( Solver* self, PyObject* args )
{
PyObject* pyvar;
PyObject* pystrength;
if( !PyArg_ParseTuple( args, "OO", &pyvar, &pystrength ) )
return 0;
if( !Variable::TypeCheck( pyvar ) )
return cppy::type_error( pyvar, "Variable" );
double strength;
if( !convert_to_strength( pystrength, strength ) )
return 0;
Variable* var = reinterpret_cast<Variable*>( pyvar );
try
{
self->solver.addEditVariable( var->variable, strength );
}
catch( const kiwi::DuplicateEditVariable& )
{
PyErr_SetObject( DuplicateEditVariable, pyvar );
return 0;
}
catch( const kiwi::BadRequiredStrength& e )
{
PyErr_SetString( BadRequiredStrength, e.what() );
return 0;
}
Py_RETURN_NONE;
}
PyObject*
Solver_removeEditVariable( Solver* self, PyObject* other )
{
if( !Variable::TypeCheck( other ) )
return cppy::type_error( other, "Variable" );
Variable* var = reinterpret_cast<Variable*>( other );
try
{
self->solver.removeEditVariable( var->variable );
}
catch( const kiwi::UnknownEditVariable& )
{
PyErr_SetObject( UnknownEditVariable, other );
return 0;
}
Py_RETURN_NONE;
}
PyObject*
Solver_hasEditVariable( Solver* self, PyObject* other )
{
if( !Variable::TypeCheck( other ) )
return cppy::type_error( other, "Variable" );
Variable* var = reinterpret_cast<Variable*>( other );
return cppy::incref( self->solver.hasEditVariable( var->variable ) ? Py_True : Py_False );
}
PyObject*
Solver_suggestValue( Solver* self, PyObject* args )
{
PyObject* pyvar;
PyObject* pyvalue;
if( !PyArg_ParseTuple( args, "OO", &pyvar, &pyvalue ) )
return 0;
if( !Variable::TypeCheck( pyvar ) )
return cppy::type_error( pyvar, "Variable" );
double value;
if( !convert_to_double( pyvalue, value ) )
return 0;
Variable* var = reinterpret_cast<Variable*>( pyvar );
try
{
self->solver.suggestValue( var->variable, value );
}
catch( const kiwi::UnknownEditVariable& )
{
PyErr_SetObject( UnknownEditVariable, pyvar );
return 0;
}
Py_RETURN_NONE;
}
PyObject*
Solver_updateVariables( Solver* self )
{
self->solver.updateVariables();
Py_RETURN_NONE;
}
PyObject*
Solver_reset( Solver* self )
{
self->solver.reset();
Py_RETURN_NONE;
}
PyObject*
Solver_dump( Solver* self )
{
cppy::ptr dump_str( PyUnicode_FromString( self->solver.dumps().c_str() ) );
PyObject_Print( dump_str.get(), stdout, 0 );
Py_RETURN_NONE;
}
PyObject*
Solver_dumps( Solver* self )
{
return PyUnicode_FromString( self->solver.dumps().c_str() );
}
static PyMethodDef
Solver_methods[] = {
{ "addConstraint", ( PyCFunction )Solver_addConstraint, METH_O,
"Add a constraint to the solver." },
{ "removeConstraint", ( PyCFunction )Solver_removeConstraint, METH_O,
"Remove a constraint from the solver." },
{ "hasConstraint", ( PyCFunction )Solver_hasConstraint, METH_O,
"Check whether the solver contains a constraint." },
{ "addEditVariable", ( PyCFunction )Solver_addEditVariable, METH_VARARGS,
"Add an edit variable to the solver." },
{ "removeEditVariable", ( PyCFunction )Solver_removeEditVariable, METH_O,
"Remove an edit variable from the solver." },
{ "hasEditVariable", ( PyCFunction )Solver_hasEditVariable, METH_O,
"Check whether the solver contains an edit variable." },
{ "suggestValue", ( PyCFunction )Solver_suggestValue, METH_VARARGS,
"Suggest a desired value for an edit variable." },
{ "updateVariables", ( PyCFunction )Solver_updateVariables, METH_NOARGS,
"Update the values of the solver variables." },
{ "reset", ( PyCFunction )Solver_reset, METH_NOARGS,
"Reset the solver to the initial empty starting condition." },
{ "dump", ( PyCFunction )Solver_dump, METH_NOARGS,
"Dump a representation of the solver internals to stdout." },
{ "dumps", ( PyCFunction )Solver_dumps, METH_NOARGS,
"Dump a representation of the solver internals to a string." },
{ 0 } // sentinel
};
static PyType_Slot Solver_Type_slots[] = {
{ Py_tp_dealloc, void_cast( Solver_dealloc ) }, /* tp_dealloc */
{ Py_tp_methods, void_cast( Solver_methods ) }, /* tp_methods */
{ Py_tp_new, void_cast( Solver_new ) }, /* tp_new */
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
{ Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */
{ 0, 0 },
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject* Solver::TypeObject = NULL;
PyType_Spec Solver::TypeObject_Spec = {
"kiwisolver.Solver", /* tp_name */
sizeof( Solver ), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT|
Py_TPFLAGS_BASETYPE, /* tp_flags */
Solver_Type_slots /* slots */
};
bool Solver::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
if( !TypeObject )
{
return false;
}
return true;
}
PyObject* DuplicateConstraint;
PyObject* UnsatisfiableConstraint;
PyObject* UnknownConstraint;
PyObject* DuplicateEditVariable;
PyObject* UnknownEditVariable;
PyObject* BadRequiredStrength;
bool init_exceptions()
{
cppy::ptr mod( PyImport_ImportModule( "kiwisolver.exceptions" ) );
if( !mod )
{
return false;
}
DuplicateConstraint = mod.getattr( "DuplicateConstraint" );
if( !DuplicateConstraint )
{
return false;
}
UnsatisfiableConstraint = mod.getattr( "UnsatisfiableConstraint" );
if( !UnsatisfiableConstraint )
{
return false;
}
UnknownConstraint = mod.getattr( "UnknownConstraint" );
if( !UnknownConstraint )
{
return false;
}
DuplicateEditVariable = mod.getattr( "DuplicateEditVariable" );
if( !DuplicateEditVariable )
{
return false;
}
UnknownEditVariable = mod.getattr( "UnknownEditVariable" );
if( !UnknownEditVariable )
{
return false;
}
BadRequiredStrength = mod.getattr( "BadRequiredStrength" );
if( !BadRequiredStrength )
{
return false;
}
return true;
}
} // namespace

149
kiwi/py/src/strength.cpp Normal file
View File

@@ -0,0 +1,149 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "util.h"
#ifdef __clang__
#pragma clang diagnostic ignored "-Wdeprecated-writable-strings"
#endif
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wwrite-strings"
#endif
namespace kiwisolver
{
namespace
{
void
strength_dealloc( PyObject* self )
{
Py_TYPE( self )->tp_free( self );
}
PyObject*
strength_weak( strength* self )
{
return PyFloat_FromDouble( kiwi::strength::weak );
}
PyObject*
strength_medium( strength* self )
{
return PyFloat_FromDouble( kiwi::strength::medium );
}
PyObject*
strength_strong( strength* self )
{
return PyFloat_FromDouble( kiwi::strength::strong );
}
PyObject*
strength_required( strength* self )
{
return PyFloat_FromDouble( kiwi::strength::required );
}
PyObject*
strength_create( strength* self, PyObject* args )
{
PyObject* pya;
PyObject* pyb;
PyObject* pyc;
PyObject* pyw = 0;
if( !PyArg_ParseTuple( args, "OOO|O", &pya, &pyb, &pyc, &pyw ) )
return 0;
double a, b, c;
double w = 1.0;
if( !convert_to_double( pya, a ) )
return 0;
if( !convert_to_double( pyb, b ) )
return 0;
if( !convert_to_double( pyc, c ) )
return 0;
if( pyw && !convert_to_double( pyw, w ) )
return 0;
return PyFloat_FromDouble( kiwi::strength::create( a, b, c, w ) );
}
static PyGetSetDef
strength_getset[] = {
{ "weak", ( getter )strength_weak, 0,
"The predefined weak strength." },
{ "medium", ( getter )strength_medium, 0,
"The predefined medium strength." },
{ "strong", ( getter )strength_strong, 0,
"The predefined strong strength." },
{ "required", ( getter )strength_required, 0,
"The predefined required strength." },
{ 0 } // sentinel
};
static PyMethodDef
strength_methods[] = {
{ "create", ( PyCFunction )strength_create, METH_VARARGS,
"Create a strength from constituent values and optional weight." },
{ 0 } // sentinel
};
static PyType_Slot strength_Type_slots[] = {
{ Py_tp_dealloc, void_cast( strength_dealloc ) }, /* tp_dealloc */
{ Py_tp_getset, void_cast( strength_getset ) }, /* tp_getset */
{ Py_tp_methods, void_cast( strength_methods ) }, /* tp_methods */
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
{ Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */
{ 0, 0 },
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject* strength::TypeObject = NULL;
PyType_Spec strength::TypeObject_Spec = {
"kiwisolver.Strength", /* tp_name */
sizeof( strength ), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT, /* tp_flags */
strength_Type_slots /* slots */
};
bool strength::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
if( !TypeObject )
{
return false;
}
return true;
}
} // namespace

618
kiwi/py/src/symbolics.h Normal file
View File

@@ -0,0 +1,618 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <cppy/cppy.h>
#include "types.h"
#include "util.h"
namespace kiwisolver
{
template<typename Op, typename T>
struct UnaryInvoke
{
PyObject* operator()( PyObject* value )
{
return Op()( reinterpret_cast<T*>( value ) );
}
};
template<typename Op, typename T>
struct BinaryInvoke
{
PyObject* operator()( PyObject* first, PyObject* second )
{
if( T::TypeCheck( first ) )
return invoke<Normal>( reinterpret_cast<T*>( first ), second );
return invoke<Reverse>( reinterpret_cast<T*>( second ), first );
}
struct Normal
{
template<typename U>
PyObject* operator()( T* primary, U secondary )
{
return Op()( primary, secondary );
}
};
struct Reverse
{
template<typename U>
PyObject* operator()( T* primary, U secondary )
{
return Op()( secondary, primary );
}
};
template<typename Invk>
PyObject* invoke( T* primary, PyObject* secondary )
{
if( Expression::TypeCheck( secondary ) )
return Invk()( primary, reinterpret_cast<Expression*>( secondary ) );
if( Term::TypeCheck( secondary ) )
return Invk()( primary, reinterpret_cast<Term*>( secondary ) );
if( Variable::TypeCheck( secondary ) )
return Invk()( primary, reinterpret_cast<Variable*>( secondary ) );
if( PyFloat_Check( secondary ) )
return Invk()( primary, PyFloat_AS_DOUBLE( secondary ) );
if( PyLong_Check( secondary ) )
{
double v = PyLong_AsDouble( secondary );
if( v == -1 && PyErr_Occurred() )
return 0;
return Invk()( primary, v );
}
Py_RETURN_NOTIMPLEMENTED;
}
};
struct BinaryMul
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
Py_RETURN_NOTIMPLEMENTED;
}
};
template<> inline
PyObject* BinaryMul::operator()( Variable* first, double second )
{
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
if( !pyterm )
return 0;
Term* term = reinterpret_cast<Term*>( pyterm );
term->variable = cppy::incref( pyobject_cast( first ) );
term->coefficient = second;
return pyterm;
}
template<> inline
PyObject* BinaryMul::operator()( Term* first, double second )
{
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
if( !pyterm )
return 0;
Term* term = reinterpret_cast<Term*>( pyterm );
term->variable = cppy::incref( first->variable );
term->coefficient = first->coefficient * second;
return pyterm;
}
template<> inline
PyObject* BinaryMul::operator()( Expression* first, double second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
cppy::ptr terms( PyTuple_New( PyTuple_GET_SIZE( first->terms ) ) );
if( !terms )
return 0;
Py_ssize_t end = PyTuple_GET_SIZE( first->terms );
for( Py_ssize_t i = 0; i < end; ++i ) // memset 0 for safe error return
PyTuple_SET_ITEM( terms.get(), i, 0 );
for( Py_ssize_t i = 0; i < end; ++i )
{
PyObject* item = PyTuple_GET_ITEM( first->terms, i );
PyObject* term = BinaryMul()( reinterpret_cast<Term*>( item ), second );
if( !term )
return 0;
PyTuple_SET_ITEM( terms.get(), i, term );
}
expr->terms = terms.release();
expr->constant = first->constant * second;
return pyexpr.release();
}
template<> inline
PyObject* BinaryMul::operator()( double first, Variable* second )
{
return operator()( second, first );
}
template<> inline
PyObject* BinaryMul::operator()( double first, Term* second )
{
return operator()( second, first );
}
template<> inline
PyObject* BinaryMul::operator()( double first, Expression* second )
{
return operator()( second, first );
}
struct BinaryDiv
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
Py_RETURN_NOTIMPLEMENTED;
}
};
template<> inline
PyObject* BinaryDiv::operator()( Variable* first, double second )
{
if( second == 0.0 )
{
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
return 0;
}
return BinaryMul()( first, 1.0 / second );
}
template<> inline
PyObject* BinaryDiv::operator()( Term* first, double second )
{
if( second == 0.0 )
{
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
return 0;
}
return BinaryMul()( first, 1.0 / second );
}
template<> inline
PyObject* BinaryDiv::operator()( Expression* first, double second )
{
if( second == 0.0 )
{
PyErr_SetString( PyExc_ZeroDivisionError, "float division by zero" );
return 0;
}
return BinaryMul()( first, 1.0 / second );
}
struct UnaryNeg
{
template<typename T>
PyObject* operator()( T value )
{
Py_RETURN_NOTIMPLEMENTED;
}
};
template<> inline
PyObject* UnaryNeg::operator()( Variable* value )
{
return BinaryMul()( value, -1.0 );
}
template<> inline
PyObject* UnaryNeg::operator()( Term* value )
{
return BinaryMul()( value, -1.0 );
}
template<> inline
PyObject* UnaryNeg::operator()( Expression* value )
{
return BinaryMul()( value, -1.0 );
}
struct BinaryAdd
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
Py_RETURN_NOTIMPLEMENTED;
}
};
template<> inline
PyObject* BinaryAdd::operator()( Expression* first, Expression* second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
expr->constant = first->constant + second->constant;
expr->terms = PySequence_Concat( first->terms, second->terms );
if( !expr->terms )
return 0;
return pyexpr.release();
}
template<> inline
PyObject* BinaryAdd::operator()( Expression* first, Term* second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
PyObject* terms = PyTuple_New( PyTuple_GET_SIZE( first->terms ) + 1 );
if( !terms )
return 0;
Py_ssize_t end = PyTuple_GET_SIZE( first->terms );
for( Py_ssize_t i = 0; i < end; ++i )
{
PyObject* item = PyTuple_GET_ITEM( first->terms, i );
PyTuple_SET_ITEM( terms, i, cppy::incref( item ) );
}
PyTuple_SET_ITEM( terms, end, cppy::incref( pyobject_cast( second ) ) );
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
expr->terms = terms;
expr->constant = first->constant;
return pyexpr.release();
}
template<> inline
PyObject* BinaryAdd::operator()( Expression* first, Variable* second )
{
cppy::ptr temp( BinaryMul()( second, 1.0 ) );
if( !temp )
return 0;
return operator()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinaryAdd::operator()( Expression* first, double second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
expr->terms = cppy::incref( first->terms );
expr->constant = first->constant + second;
return pyexpr.release();
}
template<> inline
PyObject* BinaryAdd::operator()( Term* first, double second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
expr->constant = second;
expr->terms = PyTuple_Pack( 1, first );
if( !expr->terms )
return 0;
return pyexpr.release();
}
template<> inline
PyObject* BinaryAdd::operator()( Term* first, Expression* second )
{
return operator()( second, first );
}
template<> inline
PyObject* BinaryAdd::operator()( Term* first, Term* second )
{
cppy::ptr pyexpr( PyType_GenericNew( Expression::TypeObject, 0, 0 ) );
if( !pyexpr )
return 0;
Expression* expr = reinterpret_cast<Expression*>( pyexpr.get() );
expr->constant = 0.0;
expr->terms = PyTuple_Pack( 2, first, second );
if( !expr->terms )
return 0;
return pyexpr.release();
}
template<> inline
PyObject* BinaryAdd::operator()( Term* first, Variable* second )
{
cppy::ptr temp( BinaryMul()( second, 1.0 ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinaryAdd::operator()( Variable* first, double second )
{
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
if( !temp )
return 0;
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
}
template<> inline
PyObject* BinaryAdd::operator()( Variable* first, Variable* second )
{
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
if( !temp )
return 0;
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
}
template<> inline
PyObject* BinaryAdd::operator()( Variable* first, Term* second )
{
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
if( !temp )
return 0;
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
}
template<> inline
PyObject* BinaryAdd::operator()( Variable* first, Expression* second )
{
cppy::ptr temp( BinaryMul()( first, 1.0 ) );
if( !temp )
return 0;
return operator()( reinterpret_cast<Term*>( temp.get() ), second );
}
template<> inline
PyObject* BinaryAdd::operator()( double first, Variable* second )
{
return operator()( second, first );
}
template<> inline
PyObject* BinaryAdd::operator()( double first, Term* second )
{
return operator()( second, first );
}
template<> inline
PyObject* BinaryAdd::operator()( double first, Expression* second )
{
return operator()( second, first );
}
struct BinarySub
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
Py_RETURN_NOTIMPLEMENTED;
}
};
template<> inline
PyObject* BinarySub::operator()( Variable* first, double second )
{
return BinaryAdd()( first, -second );
}
template<> inline
PyObject* BinarySub::operator()( Variable* first, Variable* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Variable* first, Term* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Variable* first, Expression* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Term* first, double second )
{
return BinaryAdd()( first, -second );
}
template<> inline
PyObject* BinarySub::operator()( Term* first, Variable* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Term* first, Term* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Term* first, Expression* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Expression* first, double second )
{
return BinaryAdd()( first, -second );
}
template<> inline
PyObject* BinarySub::operator()( Expression* first, Variable* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Expression* first, Term* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( Expression* first, Expression* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( double first, Variable* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( double first, Term* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Term*>( temp.get() ) );
}
template<> inline
PyObject* BinarySub::operator()( double first, Expression* second )
{
cppy::ptr temp( UnaryNeg()( second ) );
if( !temp )
return 0;
return BinaryAdd()( first, reinterpret_cast<Expression*>( temp.get() ) );
}
template<typename T, typename U>
PyObject* makecn( T first, U second, kiwi::RelationalOperator op )
{
cppy::ptr pyexpr( BinarySub()( first, second ) );
if( !pyexpr )
return 0;
cppy::ptr pycn( PyType_GenericNew( Constraint::TypeObject, 0, 0 ) );
if( !pycn )
return 0;
Constraint* cn = reinterpret_cast<Constraint*>( pycn.get() );
cn->expression = reduce_expression( pyexpr.get() );
if( !cn->expression )
return 0;
kiwi::Expression expr( convert_to_kiwi_expression( cn->expression ) );
new( &cn->constraint ) kiwi::Constraint( expr, op, kiwi::strength::required );
return pycn.release();
}
struct CmpEQ
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
return makecn( first, second, kiwi::OP_EQ );
}
};
struct CmpLE
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
return makecn( first, second, kiwi::OP_LE );
}
};
struct CmpGE
{
template<typename T, typename U>
PyObject* operator()( T first, U second )
{
return makecn( first, second, kiwi::OP_GE );
}
};
} // namespace kiwisolver

229
kiwi/py/src/term.cpp Normal file
View File

@@ -0,0 +1,229 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <sstream>
#include <cppy/cppy.h>
#include "symbolics.h"
#include "types.h"
#include "util.h"
namespace kiwisolver
{
namespace
{
PyObject*
Term_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
{
static const char *kwlist[] = { "variable", "coefficient", 0 };
PyObject* pyvar;
PyObject* pycoeff = 0;
if( !PyArg_ParseTupleAndKeywords(
args, kwargs, "O|O:__new__", const_cast<char**>( kwlist ),
&pyvar, &pycoeff ) )
return 0;
if( !Variable::TypeCheck( pyvar ) )
return cppy::type_error( pyvar, "Variable" );
double coefficient = 1.0;
if( pycoeff && !convert_to_double( pycoeff, coefficient ) )
return 0;
PyObject* pyterm = PyType_GenericNew( type, args, kwargs );
if( !pyterm )
return 0;
Term* self = reinterpret_cast<Term*>( pyterm );
self->variable = cppy::incref( pyvar );
self->coefficient = coefficient;
return pyterm;
}
void
Term_clear( Term* self )
{
Py_CLEAR( self->variable );
}
int
Term_traverse( Term* self, visitproc visit, void* arg )
{
Py_VISIT( self->variable );
#if PY_VERSION_HEX >= 0x03090000
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
Py_VISIT(Py_TYPE(self));
#endif
return 0;
}
void
Term_dealloc( Term* self )
{
PyObject_GC_UnTrack( self );
Term_clear( self );
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
}
PyObject*
Term_repr( Term* self )
{
std::stringstream stream;
stream << self->coefficient << " * ";
stream << reinterpret_cast<Variable*>( self->variable )->variable.name();
return PyUnicode_FromString( stream.str().c_str() );
}
PyObject*
Term_variable( Term* self )
{
return cppy::incref( self->variable );
}
PyObject*
Term_coefficient( Term* self )
{
return PyFloat_FromDouble( self->coefficient );
}
PyObject*
Term_value( Term* self )
{
Variable* pyvar = reinterpret_cast<Variable*>( self->variable );
return PyFloat_FromDouble( self->coefficient * pyvar->variable.value() );
}
PyObject*
Term_add( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryAdd, Term>()( first, second );
}
PyObject*
Term_sub( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinarySub, Term>()( first, second );
}
PyObject*
Term_mul( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryMul, Term>()( first, second );
}
PyObject*
Term_div( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryDiv, Term>()( first, second );
}
PyObject*
Term_neg( PyObject* value )
{
return UnaryInvoke<UnaryNeg, Term>()( value );
}
PyObject*
Term_richcmp( PyObject* first, PyObject* second, int op )
{
switch( op )
{
case Py_EQ:
return BinaryInvoke<CmpEQ, Term>()( first, second );
case Py_LE:
return BinaryInvoke<CmpLE, Term>()( first, second );
case Py_GE:
return BinaryInvoke<CmpGE, Term>()( first, second );
default:
break;
}
PyErr_Format(
PyExc_TypeError,
"unsupported operand type(s) for %s: "
"'%.100s' and '%.100s'",
pyop_str( op ),
Py_TYPE( first )->tp_name,
Py_TYPE( second )->tp_name
);
return 0;
}
static PyMethodDef
Term_methods[] = {
{ "variable", ( PyCFunction )Term_variable, METH_NOARGS,
"Get the variable for the term." },
{ "coefficient", ( PyCFunction )Term_coefficient, METH_NOARGS,
"Get the coefficient for the term." },
{ "value", ( PyCFunction )Term_value, METH_NOARGS,
"Get the value for the term." },
{ 0 } // sentinel
};
static PyType_Slot Term_Type_slots[] = {
{ Py_tp_dealloc, void_cast( Term_dealloc ) }, /* tp_dealloc */
{ Py_tp_traverse, void_cast( Term_traverse ) }, /* tp_traverse */
{ Py_tp_clear, void_cast( Term_clear ) }, /* tp_clear */
{ Py_tp_repr, void_cast( Term_repr ) }, /* tp_repr */
{ Py_tp_richcompare, void_cast( Term_richcmp ) }, /* tp_richcompare */
{ Py_tp_methods, void_cast( Term_methods ) }, /* tp_methods */
{ Py_tp_new, void_cast( Term_new ) }, /* tp_new */
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
{ Py_nb_add, void_cast( Term_add ) }, /* nb_add */
{ Py_nb_subtract, void_cast( Term_sub ) }, /* nb_subatract */
{ Py_nb_multiply, void_cast( Term_mul ) }, /* nb_multiply */
{ Py_nb_negative, void_cast( Term_neg ) }, /* nb_negative */
{ Py_nb_true_divide, void_cast( Term_div ) }, /* nb_true_divide */
{ 0, 0 },
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject* Term::TypeObject = NULL;
PyType_Spec Term::TypeObject_Spec = {
"kiwisolver.Term", /* tp_name */
sizeof( Term ), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT|
Py_TPFLAGS_HAVE_GC|
Py_TPFLAGS_BASETYPE, /* tp_flags */
Term_Type_slots /* slots */
};
bool Term::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
if( !TypeObject )
{
return false;
}
return true;
}
} // namespace kiwisolver

138
kiwi/py/src/types.h Normal file
View File

@@ -0,0 +1,138 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <Python.h>
#include <kiwi/kiwi.h>
namespace kiwisolver
{
extern PyObject* DuplicateConstraint;
extern PyObject* UnsatisfiableConstraint;
extern PyObject* UnknownConstraint;
extern PyObject* DuplicateEditVariable;
extern PyObject* UnknownEditVariable;
extern PyObject* BadRequiredStrength;
struct strength
{
PyObject_HEAD;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
};
struct Variable
{
PyObject_HEAD
PyObject* context;
kiwi::Variable variable;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
static bool TypeCheck( PyObject* obj )
{
return PyObject_TypeCheck( obj, TypeObject ) != 0;
}
};
struct Term
{
PyObject_HEAD
PyObject* variable;
double coefficient;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
static bool TypeCheck( PyObject* obj )
{
return PyObject_TypeCheck( obj, TypeObject ) != 0;
}
};
struct Expression
{
PyObject_HEAD
PyObject* terms;
double constant;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
static bool TypeCheck( PyObject* obj )
{
return PyObject_TypeCheck( obj, TypeObject ) != 0;
}
};
struct Constraint
{
PyObject_HEAD
PyObject* expression;
kiwi::Constraint constraint;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
static bool TypeCheck( PyObject* obj )
{
return PyObject_TypeCheck( obj, TypeObject ) != 0;
}
};
struct Solver
{
PyObject_HEAD
kiwi::Solver solver;
static PyType_Spec TypeObject_Spec;
static PyTypeObject* TypeObject;
static bool Ready();
static bool TypeCheck( PyObject* obj )
{
return PyObject_TypeCheck( obj, TypeObject ) != 0;
}
};
bool init_exceptions();
} // namespace kiwisolver

203
kiwi/py/src/util.h Normal file
View File

@@ -0,0 +1,203 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#pragma once
#include <map>
#include <string>
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "types.h"
namespace kiwisolver
{
inline bool
convert_to_double( PyObject* obj, double& out )
{
if( PyFloat_Check( obj ) )
{
out = PyFloat_AS_DOUBLE( obj );
return true;
}
if( PyLong_Check( obj ) )
{
out = PyLong_AsDouble( obj );
if( out == -1.0 && PyErr_Occurred() )
return false;
return true;
}
cppy::type_error( obj, "float, int, or long" );
return false;
}
inline bool
convert_pystr_to_str( PyObject* value, std::string& out )
{
out = PyUnicode_AsUTF8( value );
return true;
}
inline bool
convert_to_strength( PyObject* value, double& out )
{
if( PyUnicode_Check( value ) )
{
std::string str;
if( !convert_pystr_to_str( value, str ) )
return false;
if( str == "required" )
out = kiwi::strength::required;
else if( str == "strong" )
out = kiwi::strength::strong;
else if( str == "medium" )
out = kiwi::strength::medium;
else if( str == "weak" )
out = kiwi::strength::weak;
else
{
PyErr_Format(
PyExc_ValueError,
"string strength must be 'required', 'strong', 'medium', "
"or 'weak', not '%s'",
str.c_str()
);
return false;
}
return true;
}
if( !convert_to_double( value, out ) )
return false;
return true;
}
inline bool
convert_to_relational_op( PyObject* value, kiwi::RelationalOperator& out )
{
if( !PyUnicode_Check( value ) )
{
cppy::type_error( value, "str" );
return false;
}
std::string str;
if( !convert_pystr_to_str( value, str ) )
return false;
if( str == "==" )
out = kiwi::OP_EQ;
else if( str == "<=" )
out = kiwi::OP_LE;
else if( str == ">=" )
out = kiwi::OP_GE;
else
{
PyErr_Format(
PyExc_ValueError,
"relational operator must be '==', '<=', or '>=', not '%s'",
str.c_str()
);
return false;
}
return true;
}
inline PyObject*
make_terms( const std::map<PyObject*, double>& coeffs )
{
typedef std::map<PyObject*, double>::const_iterator iter_t;
cppy::ptr terms( PyTuple_New( coeffs.size() ) );
if( !terms )
return 0;
Py_ssize_t size = PyTuple_GET_SIZE( terms.get() );
for( Py_ssize_t i = 0; i < size; ++i ) // zero tuple for safe early return
PyTuple_SET_ITEM( terms.get(), i, 0 );
Py_ssize_t i = 0;
iter_t it = coeffs.begin();
iter_t end = coeffs.end();
for( ; it != end; ++it, ++i )
{
PyObject* pyterm = PyType_GenericNew( Term::TypeObject, 0, 0 );
if( !pyterm )
return 0;
Term* term = reinterpret_cast<Term*>( pyterm );
term->variable = cppy::incref( it->first );
term->coefficient = it->second;
PyTuple_SET_ITEM( terms.get(), i, pyterm );
}
return terms.release();
}
inline PyObject*
reduce_expression( PyObject* pyexpr ) // pyexpr must be an Expression
{
Expression* expr = reinterpret_cast<Expression*>( pyexpr );
std::map<PyObject*, double> coeffs;
Py_ssize_t size = PyTuple_GET_SIZE( expr->terms );
for( Py_ssize_t i = 0; i < size; ++i )
{
PyObject* item = PyTuple_GET_ITEM( expr->terms, i );
Term* term = reinterpret_cast<Term*>( item );
coeffs[ term->variable ] += term->coefficient;
}
cppy::ptr terms( make_terms( coeffs ) );
if( !terms )
return 0;
PyObject* pynewexpr = PyType_GenericNew( Expression::TypeObject, 0, 0 );
if( !pynewexpr )
return 0;
Expression* newexpr = reinterpret_cast<Expression*>( pynewexpr );
newexpr->terms = terms.release();
newexpr->constant = expr->constant;
return pynewexpr;
}
inline kiwi::Expression
convert_to_kiwi_expression( PyObject* pyexpr ) // pyexpr must be an Expression
{
Expression* expr = reinterpret_cast<Expression*>( pyexpr );
std::vector<kiwi::Term> kterms;
Py_ssize_t size = PyTuple_GET_SIZE( expr->terms );
for( Py_ssize_t i = 0; i < size; ++i )
{
PyObject* item = PyTuple_GET_ITEM( expr->terms, i );
Term* term = reinterpret_cast<Term*>( item );
Variable* var = reinterpret_cast<Variable*>( term->variable );
kterms.push_back( kiwi::Term( var->variable, term->coefficient ) );
}
return kiwi::Expression( kterms, expr->constant );
}
inline const char*
pyop_str( int op )
{
switch( op )
{
case Py_LT:
return "<";
case Py_LE:
return "<=";
case Py_EQ:
return "==";
case Py_NE:
return "!=";
case Py_GT:
return ">";
case Py_GE:
return ">=";
default:
return "";
}
}
} // namespace kiwisolver

270
kiwi/py/src/variable.cpp Normal file
View File

@@ -0,0 +1,270 @@
/*-----------------------------------------------------------------------------
| Copyright (c) 2013-2019, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
#include <cppy/cppy.h>
#include <kiwi/kiwi.h>
#include "symbolics.h"
#include "types.h"
#include "util.h"
namespace kiwisolver
{
namespace
{
PyObject*
Variable_new( PyTypeObject* type, PyObject* args, PyObject* kwargs )
{
static const char *kwlist[] = { "name", "context", 0 };
PyObject* context = 0;
PyObject* name = 0;
if( !PyArg_ParseTupleAndKeywords(
args, kwargs, "|OO:__new__", const_cast<char**>( kwlist ),
&name, &context ) )
return 0;
cppy::ptr pyvar( PyType_GenericNew( type, args, kwargs ) );
if( !pyvar )
return 0;
Variable* self = reinterpret_cast<Variable*>( pyvar.get() );
self->context = cppy::xincref( context );
if( name != 0 )
{
if( !PyUnicode_Check( name ) )
return cppy::type_error( name, "str" );
std::string c_name;
if( !convert_pystr_to_str(name, c_name) )
return 0; // LCOV_EXCL_LINE
new( &self->variable ) kiwi::Variable( c_name );
}
else
{
new( &self->variable ) kiwi::Variable();
}
return pyvar.release();
}
void
Variable_clear( Variable* self )
{
Py_CLEAR( self->context );
}
int
Variable_traverse( Variable* self, visitproc visit, void* arg )
{
Py_VISIT( self->context );
#if PY_VERSION_HEX >= 0x03090000
// This was not needed before Python 3.9 (Python issue 35810 and 40217)
Py_VISIT(Py_TYPE(self));
#endif
return 0;
}
void
Variable_dealloc( Variable* self )
{
PyObject_GC_UnTrack( self );
Variable_clear( self );
self->variable.~Variable();
Py_TYPE( self )->tp_free( pyobject_cast( self ) );
}
PyObject*
Variable_repr( Variable* self )
{
return PyUnicode_FromString( self->variable.name().c_str() );
}
PyObject*
Variable_name( Variable* self )
{
return PyUnicode_FromString( self->variable.name().c_str() );
}
PyObject*
Variable_setName( Variable* self, PyObject* pystr )
{
if( !PyUnicode_Check( pystr ) )
return cppy::type_error( pystr, "str" );
std::string str;
if( !convert_pystr_to_str( pystr, str ) )
return 0;
self->variable.setName( str );
Py_RETURN_NONE;
}
PyObject*
Variable_context( Variable* self )
{
if( self->context )
return cppy::incref( self->context );
Py_RETURN_NONE;
}
PyObject*
Variable_setContext( Variable* self, PyObject* value )
{
if( value != self->context )
{
PyObject* temp = self->context;
self->context = cppy::incref( value );
Py_XDECREF( temp );
}
Py_RETURN_NONE;
}
PyObject*
Variable_value( Variable* self )
{
return PyFloat_FromDouble( self->variable.value() );
}
PyObject*
Variable_add( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryAdd, Variable>()( first, second );
}
PyObject*
Variable_sub( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinarySub, Variable>()( first, second );
}
PyObject*
Variable_mul( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryMul, Variable>()( first, second );
}
PyObject*
Variable_div( PyObject* first, PyObject* second )
{
return BinaryInvoke<BinaryDiv, Variable>()( first, second );
}
PyObject*
Variable_neg( PyObject* value )
{
return UnaryInvoke<UnaryNeg, Variable>()( value );
}
PyObject*
Variable_richcmp( PyObject* first, PyObject* second, int op )
{
switch( op )
{
case Py_EQ:
return BinaryInvoke<CmpEQ, Variable>()( first, second );
case Py_LE:
return BinaryInvoke<CmpLE, Variable>()( first, second );
case Py_GE:
return BinaryInvoke<CmpGE, Variable>()( first, second );
default:
break;
}
PyErr_Format(
PyExc_TypeError,
"unsupported operand type(s) for %s: "
"'%.100s' and '%.100s'",
pyop_str( op ),
Py_TYPE( first )->tp_name,
Py_TYPE( second )->tp_name
);
return 0;
}
static PyMethodDef
Variable_methods[] = {
{ "name", ( PyCFunction )Variable_name, METH_NOARGS,
"Get the name of the variable." },
{ "setName", ( PyCFunction )Variable_setName, METH_O,
"Set the name of the variable." },
{ "context", ( PyCFunction )Variable_context, METH_NOARGS,
"Get the context object associated with the variable." },
{ "setContext", ( PyCFunction )Variable_setContext, METH_O,
"Set the context object associated with the variable." },
{ "value", ( PyCFunction )Variable_value, METH_NOARGS,
"Get the current value of the variable." },
{ 0 } // sentinel
};
static PyType_Slot Variable_Type_slots[] = {
{ Py_tp_dealloc, void_cast( Variable_dealloc ) }, /* tp_dealloc */
{ Py_tp_traverse, void_cast( Variable_traverse ) }, /* tp_traverse */
{ Py_tp_clear, void_cast( Variable_clear ) }, /* tp_clear */
{ Py_tp_repr, void_cast( Variable_repr ) }, /* tp_repr */
{ Py_tp_richcompare, void_cast( Variable_richcmp ) }, /* tp_richcompare */
{ Py_tp_methods, void_cast( Variable_methods ) }, /* tp_methods */
{ Py_tp_new, void_cast( Variable_new ) }, /* tp_new */
{ Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */
{ Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */
{ Py_nb_add, void_cast( Variable_add ) }, /* nb_add */
{ Py_nb_subtract, void_cast( Variable_sub ) }, /* nb_subtract */
{ Py_nb_multiply, void_cast( Variable_mul ) }, /* nb_multiply */
{ Py_nb_negative, void_cast( Variable_neg ) }, /* nb_negative */
{ Py_nb_true_divide, void_cast( Variable_div ) }, /* nb_true_divide */
{ 0, 0 },
};
} // namespace
// Initialize static variables (otherwise the compiler eliminates them)
PyTypeObject* Variable::TypeObject = NULL;
PyType_Spec Variable::TypeObject_Spec = {
"kiwisolver.Variable", /* tp_name */
sizeof( Variable ), /* tp_basicsize */
0, /* tp_itemsize */
Py_TPFLAGS_DEFAULT|
Py_TPFLAGS_HAVE_GC|
Py_TPFLAGS_BASETYPE, /* tp_flags */
Variable_Type_slots /* slots */
};
bool Variable::Ready()
{
// The reference will be handled by the module to which we will add the type
TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) );
if( !TypeObject )
{
return false;
}
return true;
}
} // namespace kiwisolver

View File

@@ -0,0 +1,84 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import re
import pytest
from kiwisolver import Constraint, Variable, strength
@pytest.mark.parametrize("op", ("==", "<=", ">="))
def test_constraint_creation(op) -> None:
"""Test constraints creation and methods."""
v = Variable("foo")
c = Constraint(v + 1, op)
assert c.strength() == strength.required and c.op() == op
e = c.expression()
t = e.terms()
assert (
e.constant() == 1
and len(t) == 1
and t[0].variable() is v
and t[0].coefficient() == 1
)
constraint_format = r"1 \* foo \+ 1 %s 0 | strength = 1.001e\+[0]+9" % op
assert re.match(constraint_format, str(c))
for s in ("weak", "medium", "strong", "required"):
# Not an exact literal...
c = Constraint(v + 1, op, s) # type: ignore
assert c.strength() == getattr(strength, s)
# Ensure we test garbage collection.
del c
gc.collect()
def test_constraint_creation2() -> None:
"""Test for errors in Constraints creation."""
v = Variable("foo")
with pytest.raises(TypeError) as excinfo:
Constraint(1, "==") # type: ignore
assert "Expression" in excinfo.exconly()
with pytest.raises(TypeError) as excinfo:
Constraint(v + 1, 1) # type: ignore
assert "str" in excinfo.exconly()
with pytest.raises(ValueError) as excinfo2:
Constraint(v + 1, "!=") # type: ignore
assert "relational operator" in excinfo2.exconly()
@pytest.mark.parametrize("op", ("==", "<=", ">="))
def test_constraint_repr(op) -> None:
"""Test the repr method of a constraint object."""
v = Variable("foo")
c = Constraint(v + 1, op)
assert op in repr(c)
def test_constraint_or_operator() -> None:
"""Test modifying a constraint strength using the | operator."""
v = Variable("foo")
c = Constraint(v + 1, "==")
for s in ("weak", "medium", "strong", "required", strength.create(1, 1, 0)):
c2 = c | s # type: ignore
if isinstance(s, str):
assert c2.strength() == getattr(strength, s)
else:
assert c2.strength() == s
with pytest.raises(ValueError):
c | "unknown" # type: ignore

View File

@@ -0,0 +1,271 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import math
import operator
import sys
from typing import Tuple
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_expression_creation() -> None:
"""Test the Term constructor."""
v = Variable("foo")
v2 = Variable("bar")
v3 = Variable("aux")
e1 = Expression((v * 1, v2 * 2, v3 * 3))
e2 = Expression((v * 1, v2 * 2, v3 * 3), 10)
for e, val in ((e1, 0), (e2, 10)):
t = e.terms()
assert (
len(t) == 3
and t[0].variable() is v
and t[0].coefficient() == 1
and t[1].variable() is v2
and t[1].coefficient() == 2
and t[2].variable() is v3
and t[2].coefficient() == 3
)
assert e.constant() == val
assert str(e2) == "1 * foo + 2 * bar + 3 * aux + 10"
with pytest.raises(TypeError) as excinfo:
Expression((1, v2 * 2, v3 * 3)) # type: ignore
assert "Term" in excinfo.exconly()
# ensure we test garbage collection.
del e2
gc.collect()
@pytest.fixture()
def expressions():
"""Build expressions, terms and variables to test operations."""
v = Variable("foo")
v2 = Variable("bar")
t = Term(v, 10)
t2 = Term(v2)
e = t + 5
e2 = v2 - 10
return e, e2, t, t2, v, v2
def test_expression_neg(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test neg on an expression."""
e, _, _, _, v, _ = expressions
neg = -e
assert isinstance(neg, Expression)
neg_t = neg.terms()
assert (
len(neg_t) == 1
and neg_t[0].variable() is v
and neg_t[0].coefficient() == -10
and neg.constant() == -5
)
def test_expression_mul(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expresion multiplication."""
e, _, _, _, v, _ = expressions
for mul in (e * 2.0, 2.0 * e):
assert isinstance(mul, Expression)
mul_t = mul.terms()
assert (
len(mul_t) == 1
and mul_t[0].variable() is v
and mul_t[0].coefficient() == 20
and mul.constant() == 10
)
with pytest.raises(TypeError):
e * v # type: ignore
def test_expression_div(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expression divisions."""
e, _, _, _, v, v2 = expressions
div = e / 2
assert isinstance(div, Expression)
div_t = div.terms()
assert (
len(div_t) == 1
and div_t[0].variable() is v
and div_t[0].coefficient() == 5
and div.constant() == 2.5
)
with pytest.raises(TypeError):
e / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
e / 0
def test_expression_addition(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expressions additions."""
e, e2, _, t2, v, v2 = expressions
for add in (e + 2, 2.0 + e):
assert isinstance(add, Expression)
assert add.constant() == 7
terms = add.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == 10
)
add2 = e + v2
assert isinstance(add2, Expression)
assert add2.constant() == 5
terms = add2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
add3 = e + t2
assert isinstance(add3, Expression)
assert add3.constant() == 5
terms = add3.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
add4 = e + e2
assert isinstance(add4, Expression)
assert add4.constant() == -5
terms = add4.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
def test_expressions_substraction(
expressions: Tuple[Expression, Expression, Term, Term, Variable, Variable],
):
"""Test expression substraction."""
e, e2, _, t2, v, v2 = expressions
for sub, diff in zip((e - 2, 2.0 - e), (3, -3)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms = sub.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
)
for sub2, diff in zip((e - v2, v2 - e), (5, -5)):
assert isinstance(sub2, Expression)
assert sub2.constant() == diff
terms = sub2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
and terms[1].variable() is v2
and terms[1].coefficient() == -math.copysign(1, diff)
)
for sub3, diff in zip((e - t2, t2 - e), (5, -5)):
assert isinstance(sub3, Expression)
assert sub3.constant() == diff
terms = sub3.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == math.copysign(10, diff)
and terms[1].variable() is v2
and terms[1].coefficient() == -math.copysign(1, diff)
)
sub4 = e - e2
assert isinstance(sub3, Expression)
assert sub4.constant() == 15
terms = sub4.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 10
and terms[1].variable() is v2
and terms[1].coefficient() == -1
)
@pytest.mark.parametrize(
"op, symbol",
[
(operator.le, "<="),
(operator.eq, "=="),
(operator.ge, ">="),
(operator.lt, None),
(operator.ne, None),
(operator.gt, None),
],
)
def test_expression_rich_compare_operations(op, symbol) -> None:
"""Test using comparison on variables."""
v1 = Variable("foo")
v2 = Variable("bar")
t1 = Term(v1, 10)
e1 = t1 + 5
e2 = v2 - 10
if symbol is not None:
c = op(e1, e2)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v1:
t = (t[1], t[0])
assert (
t[0].variable() is v1
and t[0].coefficient() == 10
and t[1].variable() is v2
and t[1].coefficient() == -1
)
assert e.constant() == 15
assert c.op() == symbol and c.strength() == strength.required
else:
with pytest.raises(TypeError) as excinfo:
op(e1, e2)
if "PyPy" in sys.version:
assert "Expression" in excinfo.exconly()
else:
assert "kiwisolver.Expression" in excinfo.exconly()

View File

@@ -0,0 +1,295 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import pytest
from kiwisolver import (
BadRequiredStrength,
DuplicateConstraint,
DuplicateEditVariable,
Solver,
UnknownConstraint,
UnknownEditVariable,
UnsatisfiableConstraint,
Variable,
)
def test_solver_creation() -> None:
"""Test initializing a solver."""
s = Solver()
assert isinstance(s, Solver)
with pytest.raises(TypeError):
Solver(Variable()) # type: ignore
def test_managing_edit_variable() -> None:
"""Test adding/removing edit variables."""
s = Solver()
v1 = Variable("foo")
v2 = Variable("bar")
with pytest.raises(TypeError):
s.hasEditVariable(object()) # type: ignore
with pytest.raises(TypeError):
s.addEditVariable(object(), "weak") # type: ignore
with pytest.raises(TypeError):
s.removeEditVariable(object()) # type: ignore
with pytest.raises(TypeError):
s.suggestValue(object(), 10) # type: ignore
assert not s.hasEditVariable(v1)
s.addEditVariable(v1, "weak")
assert s.hasEditVariable(v1)
with pytest.raises(DuplicateEditVariable) as e:
s.addEditVariable(v1, "medium")
assert e.value.edit_variable is v1
with pytest.raises(UnknownEditVariable) as e2:
s.removeEditVariable(v2)
assert e2.value.edit_variable is v2
s.removeEditVariable(v1)
assert not s.hasEditVariable(v1)
with pytest.raises(BadRequiredStrength):
s.addEditVariable(v1, "required")
s.addEditVariable(v2, "strong")
assert s.hasEditVariable(v2)
with pytest.raises(UnknownEditVariable) as e3:
s.suggestValue(v1, 10)
assert e3.value.edit_variable is v1
s.reset()
assert not s.hasEditVariable(v2)
def test_suggesting_values_for_edit_variables() -> None:
"""Test suggesting values in different situations."""
# Suggest value for an edit variable entering a weak equality
s = Solver()
v1 = Variable("foo")
s.addEditVariable(v1, "medium")
s.addConstraint((v1 == 1) | "weak")
s.suggestValue(v1, 2)
s.updateVariables()
assert v1.value() == 2
# Suggest a value for an edit variable entering multiple solver rows
s.reset()
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addEditVariable(v2, "weak")
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v2 <= -1))
s.addConstraint((v2 >= 0) | "weak")
s.suggestValue(v2, 0)
s.updateVariables()
assert v2.value() <= -1
def test_managing_constraints() -> None:
"""Test adding/removing constraints."""
s = Solver()
v = Variable("foo")
c1 = v >= 1
c2 = v <= 0
with pytest.raises(TypeError):
s.hasConstraint(object()) # type: ignore
with pytest.raises(TypeError):
s.addConstraint(object()) # type: ignore
with pytest.raises(TypeError):
s.removeConstraint(object()) # type: ignore
assert not s.hasConstraint(c1)
s.addConstraint(c1)
assert s.hasConstraint(c1)
with pytest.raises(DuplicateConstraint) as e:
s.addConstraint(c1)
assert e.value.constraint is c1
with pytest.raises(UnknownConstraint) as e2:
s.removeConstraint(c2)
assert e2.value.constraint is c2
with pytest.raises(UnsatisfiableConstraint) as e3:
s.addConstraint(c2)
assert e3.value.constraint is c2
# XXX need to find how to get an invalid symbol from choose subject
# with pytest.raises(UnsatisfiableConstraint):
# s.addConstraint(c3)
s.removeConstraint(c1)
assert not s.hasConstraint(c1)
s.addConstraint(c2)
assert s.hasConstraint(c2)
s.reset()
assert not s.hasConstraint(c2)
def test_solving_under_constrained_system() -> None:
"""Test solving an under constrained system."""
s = Solver()
v = Variable("foo")
c = 2 * v + 1 >= 0
s.addEditVariable(v, "weak")
s.addConstraint(c)
s.suggestValue(v, 10)
s.updateVariables()
assert c.expression().value() == 21
assert c.expression().terms()[0].value() == 20
assert c.expression().terms()[0].variable().value() == 10
def test_solving_with_strength() -> None:
"""Test solving a system with unsatisfiable non-required constraint."""
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addConstraint(v1 + v2 == 0)
s.addConstraint(v1 == 10)
s.addConstraint((v2 >= 0) | "weak")
s.updateVariables()
assert v1.value() == 10 and v2.value() == -10
s.reset()
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v1 >= 10) | "medium")
s.addConstraint((v2 == 2) | "strong")
s.updateVariables()
assert v1.value() == -2 and v2.value() == 2
# Typical output solver.dump in the following function.
# the order is not stable.
# """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
# """
def test_dumping_solver(capsys) -> None:
"""Test dumping the solver internal to stdout."""
v1 = Variable("foo")
v2 = Variable("bar")
s = Solver()
s.addEditVariable(v2, "weak")
s.addConstraint(v1 + v2 == 0)
s.addConstraint((v2 <= -1))
s.addConstraint((v2 >= 0) | "weak")
s.updateVariables()
try:
s.addConstraint((v2 >= 1))
except Exception:
pass
# Print the solver state
s.dump()
state = s.dumps()
for header in (
"Objective",
"Tableau",
"Infeasible",
"Variables",
"Edit Variables",
"Constraints",
):
assert header in state
def test_handling_infeasible_constraints() -> None:
"""Test that we properly handle infeasible constraints.
We use the example of the cassowary paper to generate an infeasible
situation after updating an edit variable which causes the solver to use
the dual optimization.
"""
xm = Variable("xm")
xl = Variable("xl")
xr = Variable("xr")
s = Solver()
s.addEditVariable(xm, "strong")
s.addEditVariable(xl, "weak")
s.addEditVariable(xr, "weak")
s.addConstraint(2 * xm == xl + xr)
s.addConstraint(xl + 20 <= xr)
s.addConstraint(xl >= -10)
s.addConstraint(xr <= 100)
s.suggestValue(xm, 40)
s.suggestValue(xr, 50)
s.suggestValue(xl, 30)
# First update causing a normal update.
s.suggestValue(xm, 60)
# Create an infeasible condition triggering a dual optimization
s.suggestValue(xm, 90)
s.updateVariables()
assert xl.value() + xr.value() == 2 * xm.value()
assert xl.value() == 80
assert xr.value() == 100
def test_constraint_violated():
"""Test running a solver and check that constraints
report they've been violated
"""
s = Solver()
v = Variable("foo")
c1 = (v >= 10) | "required"
c2 = (v <= -5) | "weak"
s.addConstraint(c1)
s.addConstraint(c2)
s.updateVariables()
assert v.value() >= 10
assert c1.violated() is False
assert c2.violated() is True

View File

@@ -0,0 +1,27 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import pytest
from kiwisolver import strength
def test_accessing_predefined_strength() -> None:
"""Test getting the default values for the strength."""
assert strength.weak < strength.medium
assert strength.medium < strength.strong
assert strength.strong < strength.required
def test_creating_strength() -> None:
"""Test creating strength from constituent values."""
assert strength.create(0, 0, 1) < strength.create(0, 1, 0)
assert strength.create(0, 1, 0) < strength.create(1, 0, 0)
assert strength.create(1, 0, 0, 1) < strength.create(1, 0, 0, 4)
with pytest.raises(TypeError):
strength.create("", "", "") # type: ignore

202
kiwi/py/tests/test_term.py Normal file
View File

@@ -0,0 +1,202 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2021, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import gc
import math
import operator
from typing import Tuple
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_term_creation() -> None:
"""Test the Term constructor."""
v = Variable("foo")
t = Term(v)
assert t.variable() is v
assert t.coefficient() == 1
t = Term(v, 100)
assert t.variable() is v
assert t.coefficient() == 100
assert str(t) == "100 * foo"
with pytest.raises(TypeError) as excinfo:
Term("") # type: ignore
assert "Variable" in excinfo.exconly()
# ensure we test garbage collection
del t
gc.collect()
@pytest.fixture()
def terms():
"""Terms for testing."""
v = Variable("foo")
v2 = Variable("bar")
t = Term(v, 10)
t2 = Term(v2)
return t, t2, v, v2
def test_term_neg(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test neg on a term."""
t, _, v, _ = terms
neg = -t
assert isinstance(neg, Term)
assert neg.variable() is v and neg.coefficient() == -10
def test_term_mul(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term multiplications"""
t, _, v, _ = terms
for mul in (t * 2, 2.0 * t):
assert isinstance(mul, Term)
assert mul.variable() is v and mul.coefficient() == 20
with pytest.raises(TypeError):
t * v # type: ignore
def test_term_div(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term divisions."""
t, _, v, v2 = terms
div = t / 2
assert isinstance(div, Term)
assert div.variable() is v and div.coefficient() == 5
with pytest.raises(TypeError):
t / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
t / 0
def test_term_add(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term additions."""
t, t2, v, v2 = terms
for add in (t + 2, 2.0 + t):
assert isinstance(add, Expression)
assert add.constant() == 2
terms_ = add.terms()
assert (
len(terms_) == 1
and terms[0].variable() is v
and terms_[0].coefficient() == 10
)
for add2, order in zip((t + v2, v2 + t), ((0, 1), (1, 0))):
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms_ = add2.terms()
assert (
len(terms_) == 2
and terms_[order[0]].variable() is v
and terms_[order[0]].coefficient() == 10
and terms_[order[1]].variable() is v2
and terms_[order[1]].coefficient() == 1
)
add2 = t + t2
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms_ = add2.terms()
assert (
len(terms_) == 2
and terms_[0].variable() is v
and terms_[0].coefficient() == 10
and terms_[1].variable() is v2
and terms_[1].coefficient() == 1
)
def test_term_sub(terms: Tuple[Term, Term, Variable, Variable]) -> None:
"""Test term substractions."""
t, t2, v, v2 = terms
for sub, diff in zip((t - 2, 2.0 - t), (-2, 2)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms_ = sub.terms()
assert (
len(terms_) == 1
and terms[0].variable() is v
and terms_[0].coefficient() == -math.copysign(10, diff)
)
for sub2, order in zip((t - v2, v2 - t), ((0, 1), (1, 0))):
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms_ = sub2.terms()
assert (
len(terms_) == 2
and terms_[order[0]].variable() is v
and terms_[order[0]].coefficient() == 10 * (-1) ** order[0]
and terms_[order[1]].variable() is v2
and terms_[order[1]].coefficient() == -1 * (-1) ** order[0]
)
sub2 = t - t2
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms_ = sub2.terms()
assert (
len(terms_) == 2
and terms_[0].variable() is v
and terms_[0].coefficient() == 10
and terms_[1].variable() is v2
and terms_[1].coefficient() == -1
)
@pytest.mark.parametrize(
"op, symbol",
[
(operator.le, "<="),
(operator.eq, "=="),
(operator.ge, ">="),
(operator.lt, None),
(operator.ne, None),
(operator.gt, None),
],
)
def test_term_rich_compare_operations(op, symbol):
"""Test using comparison on variables."""
v = Variable("foo")
v2 = Variable("bar")
t1 = Term(v, 10)
t2 = Term(v2, 20)
if symbol is not None:
c = op(t1, t2 + 1)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v:
t = (t[1], t[0])
assert (
t[0].variable() is v
and t[0].coefficient() == 10
and t[1].variable() is v2
and t[1].coefficient() == -20
)
assert e.constant() == -1
assert c.op() == symbol and c.strength() == strength.required
else:
with pytest.raises(TypeError):
op(t1, t2)

View File

@@ -0,0 +1,163 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2014-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import math
import operator
import sys
import pytest
from kiwisolver import Constraint, Expression, Term, Variable, strength
def test_variable_methods() -> None:
"""Test the variable modification methods."""
v = Variable()
assert v.name() == ""
v.setName("Δ")
assert v.name() == "Δ"
v.setName("foo")
assert v.name() == "foo"
with pytest.raises(TypeError):
v.setName(1) # type: ignore
if sys.version_info >= (3,):
with pytest.raises(TypeError):
v.setName(b"r") # type: ignore
assert v.value() == 0.0
assert v.context() is None
ctx = object()
v.setContext(ctx)
assert v.context() is ctx
assert str(v) == "foo"
with pytest.raises(TypeError):
Variable(1) # type: ignore
def test_variable_neg() -> None:
"""Test neg on a variable."""
v = Variable("foo")
neg = -v
assert isinstance(neg, Term)
assert neg.variable() is v and neg.coefficient() == -1
def test_variable_mul() -> None:
"""Test variable multiplications."""
v = Variable("foo")
v2 = Variable("bar")
for mul in (v * 2.0, 2 * v):
assert isinstance(mul, Term)
assert mul.variable() is v and mul.coefficient() == 2
with pytest.raises(TypeError):
v * v2 # type: ignore
def test_variable_division() -> None:
"""Test variable divisions."""
v = Variable("foo")
v2 = Variable("bar")
div = v / 2.0
assert isinstance(div, Term)
assert div.variable() is v and div.coefficient() == 0.5
with pytest.raises(TypeError):
v / v2 # type: ignore
with pytest.raises(ZeroDivisionError):
v / 0
def test_variable_addition() -> None:
"""Test variable additions."""
v = Variable("foo")
v2 = Variable("bar")
for add in (v + 2, 2.0 + v):
assert isinstance(add, Expression)
assert add.constant() == 2
terms = add.terms()
assert (
len(terms) == 1 and terms[0].variable() is v and terms[0].coefficient() == 1
)
add2 = v + v2
assert isinstance(add2, Expression)
assert add2.constant() == 0
terms = add2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 1
and terms[1].variable() is v2
and terms[1].coefficient() == 1
)
with pytest.raises(TypeError):
v + "" # type: ignore
def test_variable_sub() -> None:
"""Test variable substractions."""
v = Variable("foo")
v2 = Variable("bar")
for sub, diff in zip((v - 2, 2 - v), (-2, 2)):
assert isinstance(sub, Expression)
assert sub.constant() == diff
terms = sub.terms()
assert (
len(terms) == 1
and terms[0].variable() is v
and terms[0].coefficient() == -math.copysign(1, diff)
)
sub2 = v - v2
assert isinstance(sub2, Expression)
assert sub2.constant() == 0
terms = sub2.terms()
assert (
len(terms) == 2
and terms[0].variable() is v
and terms[0].coefficient() == 1
and terms[1].variable() is v2
and terms[1].coefficient() == -1
)
def test_variable_rich_compare_operations() -> None:
"""Test using comparison on variables."""
v = Variable("foo")
v2 = Variable("Δ")
for op, symbol in ((operator.le, "<="), (operator.eq, "=="), (operator.ge, ">=")):
c = op(v, v2 + 1)
assert isinstance(c, Constraint)
e = c.expression()
t = e.terms()
assert len(t) == 2
if t[0].variable() is not v:
t = (t[1], t[0])
assert (
t[0].variable() is v
and t[0].coefficient() == 1
and t[1].variable() is v2
and t[1].coefficient() == -1
)
assert e.constant() == -1
assert c.op() == symbol and c.strength() == strength.required
for op in (operator.lt, operator.ne, operator.gt):
with pytest.raises(TypeError):
op(v, v2)

89
kiwi/pyproject.toml Normal file
View File

@@ -0,0 +1,89 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2013-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
[project]
name = "kiwisolver"
description = "A fast implementation of the Cassowary constraint solver"
readme = "README.rst"
requires-python = ">=3.7"
license = { file = "LICENSE" }
authors = [{ name = "The Nucleic Development Team", email = "sccolbert@gmail.com" }]
maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }]
classifiers = [
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = ["typing_extensions;python_version<'3.8'"]
dynamic = ["version"]
[project.urls]
homepage = "https://github.com/nucleic/kiwi"
documentation = "https://kiwisolver.readthedocs.io/en/latest/"
repository = "https://github.com/nucleic/kiwi"
changelog = "https://github.com/nucleic/kiwi/blob/main/releasenotes.rst"
[build-system]
requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3", "cppy>=1.2.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
include-package-data = false
package-data = { kiwisolver = ["py.typed", "*.pyi"] }
[tool.setuptools.packages.find]
where = ["py"]
include = ["kiwisolver"]
[tool.setuptools_scm]
write_to = "py/src/version.h"
write_to_template = """
/* ----------------------------------------------------------------------------
| Copyright (c) 2013-2023, Nucleic Development Team.
|
| Distributed under the terms of the Modified BSD License.
|
| The full license is in the file LICENSE, distributed with this software.
| ---------------------------------------------------------------------------*/
// This file is auto-generated by setuptools-scm do NOT edit it.
#pragma once
#define PY_KIWI_VERSION "{version}"
"""
[tool.ruff]
src = ["src"]
extend-exclude = ["tests/instruments/hardware/nifpga/scope_based"]
line-length = 88
[tool.ruff.lint]
select = ["C", "E", "F", "W", "I", "C90", "RUF"]
extend-ignore = ["E501", "RUF012"]
[tool.ruff.lint.isort]
combine-as-imports = true
known-first-party = ["kiwisolver"]
[tool.ruff.lint.mccabe]
max-complexity = 20
[tool.pytest.ini_options]
minversion = "6.0"
[tool.mypy]
follow_imports = "normal"
strict_optional = true

97
kiwi/releasenotes.rst Normal file
View File

@@ -0,0 +1,97 @@
Kiwi Release Notes
==================
Wrappers 1.4.5 | Solver 1.4.2 | 24/08/2023
------------------------------------------
- implement exceptions in Python PR #162
This allows to expose in a natural manner the object relevant to the exception:
constraint or edit_variable
- add missing signature of Constraint.violated for Python wrapper PR #166
- add support for Python 3.12
Wrappers 1.4.4 | Solver 1.4.2 | 15/07/2022
------------------------------------------
- fix timing in shared data release procedure PR #149
- revert use of nullpointer introduced in #142
Its use is not necessary anymore in 3.11.0-beta.4 and used to cause issues on
some platforms (see #144 ) PR #145
Wrappers 1.4.3 | Solver 1.4.1 | 13/06/2022
------------------------------------------
- add support for Python 3.11 PR #142
- do not install tests PR #143
- fix packaging for latest setuptools PR #140
Wrappers 1.4.2 | Solver 1.4.1 | 28/03/2022
------------------------------------------
- fix an issue with setuptools configuration PR #134
Wrappers 1.4.1 | Solver 1.4.1 | 27/03/2022
------------------------------------------
- add missing include PR #129
- re-organize the Python binding sources to properly ship type hints PR #131
Wrappers 1.4.0 | Solver 1.4.0 | 14/03/2022
------------------------------------------
- make installation PEP517 compliant PR #125
- add type hints PR #125
- add Constraint::violated() method PR #128
- make the the c++ part of the code c++20 compliant PR #120
- test with c++11 and c++20 PR #120
Wrappers 1.3.2 | Solver 1.3.1 | 31/08/2021
------------------------------------------
- Add support for Python 3.10, drop official support Python 3.6 PR #103
- Remove direct accesses to ob_type in C-API use Py_TYPE instead PR #103
Wrappers 1.3.1 | Solver 1.3.1 | 11/01/2020
------------------------------------------
- allow to avoid linking against VC2014_1 on windows PR #97
- do not mark move constructor / assignment operator of expression as noexcept PR #97
This is to circumvent a suspected bug in the GCC compiler in the manylinux1
image.
Wrappers 1.3.0 | Solver 1.3.0 | 10/21/2020
------------------------------------------
- add c++ benchmarks and run them on CIs PR #91
- modernize the c++ code by using more c++11 features PR #90
- introduce move semantic in some c++ constructors to improve performances PR #89
- add support for Python 3.9 PR #88
Wrappers 1.2.0 | Solver 1.2.0 | 03/26/2020
------------------------------------------
- make the the c++ part of the code c++11 compliant PR #55
- use cppy for Python/C bindings PR #55
Wrappers 1.1.0 | Solver 1.1.0 | 04/24/2019
------------------------------------------
- prevent attempting a dual optimize on a dummy row PR #56 closes #15
- add ``dump`` and ``dumps`` methods to inspect the internal state of the
solver PR #56
- test on Python 3.7 PR #51
- improvements to setup.py and tests PR #46 #50
Wrappers 1.0.1 | Solver 1.0.0 | 10/24/2017
------------------------------------------
- allow unicode strings for variable name in Python 2
- allow unicode strings as strength specifiers in Python 2
Wrappers 1.0.0 | Solver 1.0.0 | 09/06/2017
------------------------------------------
- Allow anonymous variables (solver PR #32, wrappers PR #22)
- Solver: Define binary operators as free functions (PR #23)
- Wrappers: support for Python 3 (PR #13)
- Wrappers: drop distribute dependency in favor of setuptools (PR #22)
- Wrappers: add a comprehensive test suite
Wrappers 0.1.3 | Solver 0.1.1 | 07/12/2013
------------------------------------------
- Update the build script to remove the need for build.py
Wrappers 0.1.2 | Solver 0.1.1 | 01/15/2013
------------------------------------------
- Fix issue #2. Bad handling of zero-size constraints.
Wrappers 0.1.1 | Solver 0.1.0 | 01/13/2013
------------------------------------------
- Initial public release.

49
kiwi/setup.py Normal file
View File

@@ -0,0 +1,49 @@
# --------------------------------------------------------------------------------------
# Copyright (c) 2013-2022, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import os
from setuptools import Extension, setup
try:
from cppy import CppyBuildExt
except ImportError as e:
raise RuntimeError(
"Missing setup required dependencies: cppy. "
"Installing through pip as recommended ensure one never hits this issue."
) from e
# Before releasing the version needs to be updated in kiwi/version.h, if the changes
# are not limited to the solver.
# Use the env var KIWI_DISABLE_FH4 to disable linking against VCRUNTIME140_1.dll
if "KIWI_DISABLE_FH4" in os.environ:
os.environ.setdefault("CPPY_DISABLE_FH4", "1")
ext_modules = [
Extension(
"kiwisolver._cext",
[
"py/src/kiwisolver.cpp",
"py/src/constraint.cpp",
"py/src/expression.cpp",
"py/src/solver.cpp",
"py/src/strength.cpp",
"py/src/term.cpp",
"py/src/variable.cpp",
],
include_dirs=["."],
language="c++",
),
]
setup(
ext_modules=ext_modules,
cmdclass={"build_ext": CppyBuildExt},
)