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