Merge commit '81396a5322a7a48764fcf254d5d933ba1e57bdc5' as 'kiwi'
This commit is contained in:
12
kiwi/.github/FUNDING.yml
vendored
Normal file
12
kiwi/.github/FUNDING.yml
vendored
Normal 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
116
kiwi/.github/workflows/ci.yml
vendored
Normal 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
45
kiwi/.github/workflows/docs.yml
vendored
Normal 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
220
kiwi/.github/workflows/release.yml
vendored
Normal 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
14
kiwi/.gitignore
vendored
Normal 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
|
||||
14
kiwi/.pre-commit-config.yaml
Normal file
14
kiwi/.pre-commit-config.yaml
Normal 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
27
kiwi/.readthedocs.yaml
Normal 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
71
kiwi/LICENSE
Normal 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
17
kiwi/MANIFEST.in
Normal 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
23
kiwi/README.rst
Normal 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+.
|
||||
17
kiwi/benchmarks/README.rst
Normal file
17
kiwi/benchmarks/README.rst
Normal 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
|
||||
11
kiwi/benchmarks/build_and_run_bench.sh
Executable file
11
kiwi/benchmarks/build_and_run_bench.sh
Executable 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
|
||||
224
kiwi/benchmarks/enaml_like_benchmark.cpp
Normal file
224
kiwi/benchmarks/enaml_like_benchmark.cpp
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
206
kiwi/benchmarks/enaml_like_benchmark.py
Normal file
206
kiwi/benchmarks/enaml_like_benchmark.py
Normal 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
3359
kiwi/benchmarks/nanobench.h
Normal file
File diff suppressed because it is too large
Load Diff
24
kiwi/codecov.yml
Normal file
24
kiwi/codecov.yml
Normal 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
20
kiwi/docs/Makefile
Normal 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
6
kiwi/docs/README.md
Normal 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
36
kiwi/docs/make.bat
Normal 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
|
||||
3
kiwi/docs/requirements.txt
Normal file
3
kiwi/docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx>=4
|
||||
sphinx-rtd-theme>=1
|
||||
sphinx-tabs
|
||||
4
kiwi/docs/source/api/cpp.rst
Normal file
4
kiwi/docs/source/api/cpp.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Kiwisolver C++ API
|
||||
==================
|
||||
|
||||
Under construction
|
||||
8
kiwi/docs/source/api/index.rst
Normal file
8
kiwi/docs/source/api/index.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
kiwisolver
|
||||
==========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
Python API <python.rst>
|
||||
C++ API <cpp.rst>
|
||||
7
kiwi/docs/source/api/python.rst
Normal file
7
kiwi/docs/source/api/python.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
Kiwisolver Python API
|
||||
=====================
|
||||
|
||||
.. automodule:: kiwisolver
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
213
kiwi/docs/source/basis/basic_systems.rst
Normal file
213
kiwi/docs/source/basis/basic_systems.rst
Normal file
@@ -0,0 +1,213 @@
|
||||
.. _basis-basic-systems:
|
||||
|
||||
Constraints definition and system solving
|
||||
=========================================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
A system within Kiwi is defined by a set of constraints, which may
|
||||
be either equalities or inequalities (limited to >= and <=, as strict inequalities
|
||||
are not supported). Each constraint can be assigned a specific 'strength',
|
||||
indicating its relative importance in the problem-solving process. The subsequent
|
||||
sections will delve into the methods of defining these constraints and extracting
|
||||
results from the solver.
|
||||
|
||||
Defining variables and constraints
|
||||
----------------------------------
|
||||
|
||||
The initial step involves defining variables, which represent
|
||||
the values that the solver aims to determine. These variables are
|
||||
encapsulated by |Variable| objects. The creation of these objects
|
||||
can be accomplished as follows:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import Variable
|
||||
|
||||
x1 = Variable('x1')
|
||||
x2 = Variable('x2')
|
||||
xm = Variable('xm')
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
#include <kiwi/kiwi.h>
|
||||
|
||||
using namespace kiwi
|
||||
|
||||
Variable x1("x1");
|
||||
Variable x2("x2");
|
||||
Variable xm("xm");
|
||||
|
||||
.. note::
|
||||
|
||||
Naming your variables is not mandatory but it is recommended since it will
|
||||
help the solver in providing more meaningful error messages.
|
||||
|
||||
Now that we have some variables we can define our constraints.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
constraints = [x1 >= 0, x2 <= 100, x2 >= x1 + 10, xm == (x1 + x2) / 2]
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
Constraint constraints[] = { Constraint {x1 >= 0},
|
||||
Constraint {x2 <= 100},
|
||||
Constraint {x2 >= x1 + 20},
|
||||
Constraint {xm == (x1 + x2) / 2}
|
||||
};
|
||||
|
||||
Next, add these variables to the solver, an instance of |Solver|:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import Solver
|
||||
|
||||
solver = Solver()
|
||||
|
||||
for cn in constraints:
|
||||
solver.addConstraint(cn)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
Solver solver;
|
||||
|
||||
for(auto& constraint : constraints)
|
||||
{
|
||||
solver.addConstraint(constraint);
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
You can start adding constraints to the solver without creating all your
|
||||
variables first.
|
||||
|
||||
|
||||
So far, we have defined a system representing three points on the segment
|
||||
[0, 100], with one of them being the middle of the others, which cannot get
|
||||
closer than 10. All those constraints have to be satisfied; in the context
|
||||
of Cassowary, they are required constraints.
|
||||
|
||||
.. note::
|
||||
|
||||
Cassowary (and Kiwi) allows for redundant constraints, which means
|
||||
even with two constraints (x == 10, x + y == 30) being equivalent to a
|
||||
third one (y == 20), all three can be added to the solver without issues.
|
||||
|
||||
However, it is advisable not to add the same constraint multiple times
|
||||
in the same form to the solver.
|
||||
|
||||
|
||||
Managing constraints strength
|
||||
-----------------------------
|
||||
|
||||
Cassowary also supports constraints that are not required. Those are only
|
||||
respected on a best effort basis. To express that a constraint is not required
|
||||
we need to assign it a *strength*. Kiwi specifies three standard strengths
|
||||
besides the "required" strength: strong, medium, weak. A strong constraint
|
||||
will always win over a medium constraint, which in turn will always override
|
||||
a weak constraint [#f1]_ .
|
||||
|
||||
In our example, let's assume x1 would like to be at 40, without this being a
|
||||
requirement. This is translated as follows:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.addConstraint((x1 == 40) | "weak")
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.addConstraint(x1 == 40 | strength::weak);
|
||||
|
||||
|
||||
Adding edit variables
|
||||
---------------------
|
||||
|
||||
So far our system is pretty static; we have no way of trying to find solutions
|
||||
for a particular value of `xm`, let's say. This is a problem. In a real
|
||||
application (e.g. a GUI layout), we would like to find the size of the widgets
|
||||
based on the top window but also react to the window resizing, so actually
|
||||
adding and removing constraints all the time wouldn't be optimal. And there is
|
||||
a better way: edit variables.
|
||||
|
||||
Edit variables are variables for which you can suggest values. Edit variables
|
||||
have a strength which can be at most strong (the value of a edit variable can
|
||||
never be required).
|
||||
|
||||
For the sake of our example, we will make "xm" editable:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.addEditVariable(xm, 'strong')
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.addEditVariable(xm, strength::strong);
|
||||
|
||||
Once a variable has been added as an edit variable, you can suggest a value for
|
||||
it and the solver will try to solve the system with it.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.suggestValue(xm, 60)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.suggestValue(xm, 60);
|
||||
|
||||
This would give the following solution: ``xm == 60, x1 == 40, x2 == 80``.
|
||||
|
||||
|
||||
Solving and updating variables
|
||||
------------------------------
|
||||
|
||||
Kiwi solves the system each time a constraint is added or removed, or a new
|
||||
value is suggested for an edit variable. Solving the system each time makes for
|
||||
faster updates and allows to keep the solver in a consinstent state. However,
|
||||
the variable values are not updated automatically, and you need to ask
|
||||
the solver to perform this operation before reading the values, as illustrated
|
||||
below:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
solver.suggestValue(xm, 90)
|
||||
solver.updateVariables()
|
||||
print(xm.value(), x1.value(), x2.value())
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
solver.suggestValue(xm, 90);
|
||||
solver.updateVariables();
|
||||
std::cout << xm.value() << ", " << x1.value() << ", " << x2.value();
|
||||
|
||||
This last update creates an infeasible situation by pushing x2 further than
|
||||
100, if we keep x1 where it would like to be. As a consequence, we get the
|
||||
following solution: ``xm == 90, x1 == 80, x2 == 100``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
To determine if a non-required constraint was violated when solving the system,
|
||||
you can use the constraint's ``violated`` method.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
.. [#f1] Actually, there are some corner cases in which this can be violated.
|
||||
See :ref:`basics-internals`
|
||||
22
kiwi/docs/source/basis/index.rst
Normal file
22
kiwi/docs/source/basis/index.rst
Normal file
@@ -0,0 +1,22 @@
|
||||
.. _basis:
|
||||
|
||||
Kiwisolver usage
|
||||
================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
This section of the docs aims at getting you up and running with Kiwi. You will
|
||||
in particular learn how to install Kiwi, create a system of constraints, solve,
|
||||
update it etc... By the end of it you will know how to use the solver.
|
||||
|
||||
However if you are not familiar with Cassowary (or constraints solver in
|
||||
general) it may not be enough to get you started using it in your project.
|
||||
Hopefully the real world use cases described in :ref:`uses` will shed
|
||||
more light on how to use it in real applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation.rst
|
||||
basic_systems.rst
|
||||
solver_internals.rst
|
||||
87
kiwi/docs/source/basis/installation.rst
Normal file
87
kiwi/docs/source/basis/installation.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
.. _basis-installation:
|
||||
|
||||
Installing Kiwisolver on Python
|
||||
===============================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
Kiwisolver is supported on Python 3.7+. Installing it is a straight-forward
|
||||
process. There are three approaches to choose from.
|
||||
|
||||
The easy way: Pre-compiled packages
|
||||
-----------------------------------
|
||||
|
||||
The easiest way to install atom is through pre-compiled packages. Kiwisolver is
|
||||
distributed pre-compiled in two-forms.
|
||||
|
||||
Conda packages
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
If you use the `Anaconda`_ Python distribution platform (or `Miniconda`_, its
|
||||
lighter-weight companion), the latest release of Kiwisolver can be installed
|
||||
using conda from the default channel or the conda-forge channel::
|
||||
|
||||
$ conda install kiwisolver
|
||||
|
||||
$ conda install kiwisolver -c conda-forge
|
||||
|
||||
.. _Anaconda: https://store.continuum.io/cshop/anaconda
|
||||
.. _Miniconda: https://conda.io/miniconda.html
|
||||
|
||||
Wheels
|
||||
^^^^^^
|
||||
|
||||
If you don't use Anaconda, you can install Kiwisolver pre-compiled,
|
||||
through PIP, for most common platforms::
|
||||
|
||||
$ pip install kiwisolver
|
||||
|
||||
Compiling it yourself: The Hard Way
|
||||
-----------------------------------
|
||||
|
||||
Building Kiwisolver from scratch requires Python and a C++ compiler. On Unix
|
||||
platform getting a C++ compiler properly configured is generally
|
||||
straighforward. On Windows, starting with Python 3.6 the free version of the
|
||||
Microsoft toolchain should work out of the box. Installing Kiwisolver is then
|
||||
as simple as::
|
||||
|
||||
$ pip install .
|
||||
|
||||
.. note::
|
||||
|
||||
For MacOSX users on OSX Mojave, one needs to set MACOSX_DEPLOYMENT_TARGET
|
||||
to higher than 10.9 to force the compiler to use the new C++ stdlib::
|
||||
|
||||
$ export MACOSX_DEPLOYMENT_TARGET=10.10
|
||||
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
|
||||
Kiwisolver is known to run on Windows, OSX, and Linux; and compiles cleanly
|
||||
with MSVC, Clang, GCC, and MinGW. If you encounter a bug, please report
|
||||
it on the `Issue Tracker`_.
|
||||
|
||||
.. _Issue Tracker: http://github.com/nucleic/enaml/issues
|
||||
|
||||
|
||||
Checking your install
|
||||
---------------------
|
||||
|
||||
Once you installed kiwisolver you should be able to import it as follows:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import kiwisolver
|
||||
|
||||
.. note::
|
||||
|
||||
On Windows, the import may fail with `ImportError: DLL load failed`. If it
|
||||
does, it means your system is missing the Microsoft Visual C++
|
||||
redistributable matching your Python version. To fix the issue download
|
||||
and install the package corresponding to your Python version
|
||||
(https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads):
|
||||
|
||||
- Python 2.7: Visual C++ Redistributable 2008
|
||||
- Python 3.4: Visual C++ Redistributable 2010
|
||||
- Python 3.5+: Visual C++ Redistributable 2015 or more recent
|
||||
196
kiwi/docs/source/basis/solver_internals.rst
Normal file
196
kiwi/docs/source/basis/solver_internals.rst
Normal file
@@ -0,0 +1,196 @@
|
||||
.. _basics-internals:
|
||||
|
||||
Solver internals and tips
|
||||
=========================
|
||||
|
||||
.. include:: ../substitutions.sub
|
||||
|
||||
|
||||
Kiwi is not a mere rewriting of Cassowary, and due to this, it does not always
|
||||
perfectly reflect the original implementation. The following sections point out
|
||||
these discrepancies and provide tips on how to work effectively with Kiwi.
|
||||
|
||||
|
||||
Inspecting the solver state
|
||||
---------------------------
|
||||
|
||||
The state of the solver can be inspected by dumping a text representation of
|
||||
its state either to stdout using the ``dump`` method of the solver, or to a
|
||||
string using the ``dumps`` method. Typically, at least a basic understanding of
|
||||
the Cassowary algorithm is necessary to analyse the output.
|
||||
|
||||
A typical output is reproduced below:
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
Objective
|
||||
---------
|
||||
-2 + 2 * e2 + 1 * s8 + -2 * s10
|
||||
|
||||
Tableau
|
||||
-------
|
||||
v1 | 1 + 1 * s10
|
||||
e3 | -1 + 1 * e2 + -1 * s10
|
||||
v4 | -1 + -1 * d5 + -1 * s10
|
||||
s6 | -2 + -1 * s10
|
||||
e9 | -1 + 1 * s8 + -1 * s10
|
||||
|
||||
Infeasible
|
||||
----------
|
||||
e3
|
||||
e9
|
||||
|
||||
Variables
|
||||
---------
|
||||
bar = v1
|
||||
foo = v4
|
||||
|
||||
Edit Variables
|
||||
--------------
|
||||
bar
|
||||
|
||||
Constraints
|
||||
-----------
|
||||
1 * bar + -0 >= 0 | strength = 1
|
||||
1 * bar + 1 <= 0 | strength = 1.001e+09
|
||||
1 * foo + 1 * bar + 0 == 0 | strength = 1.001e+09
|
||||
1 * bar + 0 == 0 | strength = 1
|
||||
|
||||
In the dump, the letters have the following meaning:
|
||||
|
||||
- v: external variable, corresponds to the variable created by you, the user
|
||||
- s: slack symbol, used to represent inequalities
|
||||
- e: error symbol, used to represent non-required constraints
|
||||
- d: dummy variable, always zero, used to keep track of the impact of an
|
||||
external variable in the tableau.
|
||||
- i: invalid symbol, returned when no valid symbol can be found.
|
||||
|
||||
|
||||
Stay constraints emulation
|
||||
--------------------------
|
||||
|
||||
One feature of Cassowary that Kiwi abandoned is the notion of stay
|
||||
constraints. Stay constraints are typically used in under-constrained
|
||||
situations (drag and drop) to allow the solver to find a solution by keeping
|
||||
non-modified variable close to their original position. A typical example is
|
||||
a rectangle whose one corner is being dragged in a drawing application.
|
||||
|
||||
Kiwi does not have stay constraints mostly because in the context of widget
|
||||
placement, the system is typically well constrained, rendering stay constraints
|
||||
unnecessary.
|
||||
|
||||
If your application requires them, several workarounds can be considered:
|
||||
|
||||
- adding/removing non-required equality constraints to mimic stay constraints
|
||||
- using edit-variables to mimic stay constraints
|
||||
|
||||
The first method will require to remove the old constraints as soon as they
|
||||
stop making sense, while the second will require to update the suggested value.
|
||||
|
||||
|
||||
Creating strengths and their internal representation
|
||||
----------------------------------------------------
|
||||
|
||||
Kiwi provides three strengths in addition to the required strength by default:
|
||||
"weak", "medium", and "strong". Contrary to Cassowary, which uses lexicographic
|
||||
ordering to ensure that strength are always respected, Kiwi strives for speed
|
||||
and uses simple floating point numbers.
|
||||
|
||||
.. note::
|
||||
|
||||
Using simple floating point, means that in some rare corner cases, a large
|
||||
number of weak constraints may outweigh a medium constraint. However, in
|
||||
practice, this rarely happens.
|
||||
|
||||
Kiwi allows to create custom strengths in the following manner:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: python
|
||||
|
||||
from kiwisolver import strength
|
||||
|
||||
my_strength = strength.create(1, 1, 1)
|
||||
my_strength2 = strength.create(1, 1, 1, 2)
|
||||
|
||||
.. code-tab:: c++
|
||||
|
||||
double my_strength = strength::create(1, 1, 1);
|
||||
double my_strength = strength::create(1, 1, 1, 2);
|
||||
|
||||
The first argument is multiplied by 1 000 000, the second argument by 1 000,
|
||||
and the third by 1. No strength can be create larger than the required
|
||||
strength. The default strengths in Kiwi correspond to:
|
||||
|
||||
.. code:: python
|
||||
|
||||
weak = strength.create(0, 0, 1)
|
||||
medium = strength.create(0, 1, 0)
|
||||
strong = strength.create(1, 0, 0)
|
||||
required = strength.create(1000, 1000, 1000)
|
||||
|
||||
While Cassowary differentiates between strength and weight, those two concepts
|
||||
are fused in Kiwi: when creating a strength, one can apply a weight (the fourth
|
||||
argument) that will multiply it.
|
||||
|
||||
.. note::
|
||||
|
||||
Because strengths are implemented as floating point numbers, in order to be
|
||||
effective, strengths must be different enough from one another. The
|
||||
following is unlikely to produce any really useful result.
|
||||
|
||||
.. code:: python
|
||||
|
||||
weak1 = strength.create(0, 0, 1)
|
||||
weak2 = strength.create(0, 0, 2)
|
||||
weak3 = strength.create(0, 0, 3)
|
||||
|
||||
|
||||
Managing memory
|
||||
---------------
|
||||
|
||||
When removing a constraint, Kiwi does not check whether or not the variables
|
||||
used in the constraint are still in use in other constraints. This is mostly
|
||||
because such checks could be quite expensive. However, this means the map of
|
||||
variables can grow over time.
|
||||
|
||||
To avoid this possibly causing large memory leaks, it is recommended to reset
|
||||
the solver state (using the method of the same name) and to add back the
|
||||
constraints, that are still valid at this point.
|
||||
|
||||
|
||||
Representation of constraints
|
||||
-----------------------------
|
||||
|
||||
If you browse through the API documentation you may notice a number of classes
|
||||
that do not appear anywhere in this documentation: Term and Expression.
|
||||
|
||||
Those classes are used internally in constraints and are created automatically
|
||||
by the library. A |Term| represents a variable/symbol and the coefficient that
|
||||
multiplies it, |Expression| represents a sum of terms and a constant value and
|
||||
is used as the left hand side of a constraint.
|
||||
|
||||
|
||||
Performance implementation tricks
|
||||
---------------------------------
|
||||
|
||||
Map type
|
||||
^^^^^^^^
|
||||
|
||||
Kiwi uses maps to represent the state of the solver and to manipulate it. As a
|
||||
consequence, the map type should be fast, with a particular emphasis on
|
||||
iteration. The C++ standard library provides unordered_map and map that could
|
||||
be used in kiwi, but none of those are very friendly to the CPU cache. For
|
||||
this reason, Kiwi uses the AssocVector class implemented in Loki (slightly
|
||||
updated to respect c++11 standards). The use of this class provides a 2x
|
||||
speedup over std::map.
|
||||
|
||||
|
||||
Symbol representation
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Symbols are used in Kiwi to represent the state of the solver. Since solving the
|
||||
system requires a large number of manipulations of the symbols, the operations
|
||||
have to compile down to an efficient representation. In Kiwi, symbols compile
|
||||
down to long long meaning that a vector of them fits in a CPU cache line.
|
||||
169
kiwi/docs/source/conf.py
Normal file
169
kiwi/docs/source/conf.py
Normal 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",
|
||||
),
|
||||
]
|
||||
29
kiwi/docs/source/developer_notes/index.rst
Normal file
29
kiwi/docs/source/developer_notes/index.rst
Normal 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
|
||||
32
kiwi/docs/source/index.rst
Normal file
32
kiwi/docs/source/index.rst
Normal 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`
|
||||
16
kiwi/docs/source/substitutions.sub
Normal file
16
kiwi/docs/source/substitutions.sub
Normal 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`
|
||||
142
kiwi/docs/source/use_cases/enaml.rst
Normal file
142
kiwi/docs/source/use_cases/enaml.rst
Normal 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.
|
||||
244
kiwi/docs/source/use_cases/enaml_hbox.svg
Normal file
244
kiwi/docs/source/use_cases/enaml_hbox.svg
Normal 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 |
13
kiwi/docs/source/use_cases/index.rst
Normal file
13
kiwi/docs/source/use_cases/index.rst
Normal 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
352
kiwi/kiwi/AssocVector.h
Normal 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
140
kiwi/kiwi/constraint.h
Normal 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
184
kiwi/kiwi/debug.h
Normal 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
162
kiwi/kiwi/errors.h
Normal 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
62
kiwi/kiwi/expression.h
Normal 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
19
kiwi/kiwi/kiwi.h
Normal 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
37
kiwi/kiwi/maptype.h
Normal 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
182
kiwi/kiwi/row.h
Normal 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
181
kiwi/kiwi/shareddata.h
Normal 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
178
kiwi/kiwi/solver.h
Normal 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
825
kiwi/kiwi/solverimpl.h
Normal 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
44
kiwi/kiwi/strength.h
Normal 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
68
kiwi/kiwi/symbol.h
Normal 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
680
kiwi/kiwi/symbolics.h
Normal 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
59
kiwi/kiwi/term.h
Normal 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
24
kiwi/kiwi/util.h
Normal 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
119
kiwi/kiwi/variable.h
Normal 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
14
kiwi/kiwi/version.h
Normal 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"
|
||||
4
kiwi/lint_requirements.txt
Normal file
4
kiwi/lint_requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
ruff
|
||||
mypy
|
||||
# Allow to lint tests using mypy
|
||||
pytest
|
||||
42
kiwi/py/kiwisolver/__init__.py
Normal file
42
kiwi/py/kiwisolver/__init__.py
Normal 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__",
|
||||
]
|
||||
228
kiwi/py/kiwisolver/_cext.pyi
Normal file
228
kiwi/py/kiwisolver/_cext.pyi
Normal 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."""
|
||||
...
|
||||
51
kiwi/py/kiwisolver/exceptions.py
Normal file
51
kiwi/py/kiwisolver/exceptions.py
Normal 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
|
||||
0
kiwi/py/kiwisolver/py.typed
Normal file
0
kiwi/py/kiwisolver/py.typed
Normal file
222
kiwi/py/src/constraint.cpp
Normal file
222
kiwi/py/src/constraint.cpp
Normal 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
251
kiwi/py/src/expression.cpp
Normal 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
187
kiwi/py/src/kiwisolver.cpp
Normal 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
338
kiwi/py/src/solver.cpp
Normal 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
149
kiwi/py/src/strength.cpp
Normal 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
618
kiwi/py/src/symbolics.h
Normal 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
229
kiwi/py/src/term.cpp
Normal 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
138
kiwi/py/src/types.h
Normal 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
203
kiwi/py/src/util.h
Normal 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
270
kiwi/py/src/variable.cpp
Normal 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
|
||||
84
kiwi/py/tests/test_constraint.py
Normal file
84
kiwi/py/tests/test_constraint.py
Normal 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
|
||||
271
kiwi/py/tests/test_expression.py
Normal file
271
kiwi/py/tests/test_expression.py
Normal 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()
|
||||
295
kiwi/py/tests/test_solver.py
Normal file
295
kiwi/py/tests/test_solver.py
Normal 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
|
||||
27
kiwi/py/tests/test_strength.py
Normal file
27
kiwi/py/tests/test_strength.py
Normal 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
202
kiwi/py/tests/test_term.py
Normal 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)
|
||||
163
kiwi/py/tests/test_variable.py
Normal file
163
kiwi/py/tests/test_variable.py
Normal 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
89
kiwi/pyproject.toml
Normal 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
97
kiwi/releasenotes.rst
Normal 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
49
kiwi/setup.py
Normal 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},
|
||||
)
|
||||
Reference in New Issue
Block a user