././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/0000755000175100017470000000000014471613541013336 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.flake80000644000175100017470000000057714471613520014517 0ustar00runnerdocker[flake8] exclude = .git, __pycache__, docs/source/conf.py, build, dist, ignore = E203, E266, E501, W503, E731, E301, E302, E305, E704, E701 # line length is intentionally set to 80 here because kiwi uses Bugbear # See https://github.com/psf/black/blob/master/README.md#line-length for more details max-line-length = 80 max-complexity = 19 select = B,C,E,F,W,T4,B9././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9433424 kiwisolver-1.4.5/.github/0000755000175100017470000000000014471613541014676 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.github/FUNDING.yml0000644000175100017470000000121514471613520016507 0ustar00runnerdocker# 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'] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9433424 kiwisolver-1.4.5/.github/workflows/0000755000175100017470000000000014471613541016733 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.github/workflows/ci.yml0000644000175100017470000000667014471613520020057 0ustar00runnerdockername: 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: Isort run: | isort py -cv - name: Black if: always() run: | black py --check - name: Flake8 if: always() run: | flake8 py - name: Run Mypy 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.github/workflows/docs.yml0000644000175100017470000000212714471613520020405 0ustar00runnerdockername: 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; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.github/workflows/release.yml0000644000175100017470000001434214471613520021077 0ustar00runnerdockername: 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 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: Store artifacts uses: actions/upload-artifact@v3 with: name: artifact path: dist/*.whl release_upload: name: Create Release and Upload Release Asset runs-on: ubuntu-latest if: github.event_name == 'push' needs: [build_wheels, build_sdist] steps: - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'a') || contains(github.ref, 'b')}} - uses: actions/download-artifact@v3 with: name: artifact path: dist - name: Upload Release Asset id: upload-release-asset uses: shogo82148/actions-upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: dist/* upload_pypi: 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@master with: user: __token__ password: ${{ secrets.pypi_password }} # To test: # repository_url: https://test.pypi.org/legacy/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.gitignore0000644000175100017470000000023014471613520015316 0ustar00runnerdocker*.pyc *.pyd *.so # auto-generated by setuptools-scm py/src/version.h benchmarks/run_bench build/ dist/ kiwisolver.egg-info/ .eggs .vscode .mypy_cache ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.pre-commit-config.yaml0000644000175100017470000000056614471613520017623 0ustar00runnerdockerrepos: - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: - id: isort - repo: https://github.com/psf/black rev: main hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.910 hooks: - id: mypy additional_dependencies: []././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.readthedocs.yaml0000644000175100017470000000111414471613520016557 0ustar00runnerdocker# .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: . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/.travis.yml0000644000175100017470000000276514471613520015456 0ustar00runnerdockerlanguage: python # newer python versions are available only on xenial # (while some older only on trusty) Ubuntu distribution dist: xenial sudo: required cache: pip addons: apt: packages: lcov branches: only: - main jobs: include: - name: "run test suite with python 3.6 on ARM64" arch: arm64 python: 3.6 - name: "run test suite with python 3.7 on ARM64" arch: arm64 python: 3.7 - name: "run test suite with python 3.8 on ARM64" arch: arm64 python: 3.8 # ARM currently does not have 3.9, should add it later # added power support ppc64le arch - name: "run test suite with python 3.6 on PPC64le" arch: ppc64le python: 3.6 - name: "run test suite with python 3.7 on PPC64le" arch: ppc64le python: 3.7 - name: "run test suite with python 3.8 on PPC64le" arch: ppc64le python: 3.8 - name: "run test suite with python 3.9 on PPC64le" arch: ppc64le python: 3.9 env: - CPPFLAGS=--coverage before_install: - pip install --upgrade pip - pip install https://github.com/nucleic/cppy/tarball/main install: - python setup.py develop script: - python -m pytest py/tests after_success: - lcov --capture --directory . --output-file coverage.info - lcov --remove coverage.info '/usr/*' --output-file coverage.info # filter system-files - lcov --list coverage.info # Uploading to CodeCov but excluding gcov reports - bash <(curl -s https://codecov.io/bash) -f "!*.gcov" -X gcov || echo "Codecov did not collect coverage reports" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/LICENSE0000644000175100017470000000631714471613520014347 0ustar00runnerdocker========================= 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. #------------------------------------------------------------------------------ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/MANIFEST.in0000644000175100017470000000064214471613520015073 0ustar00runnerdockerinclude 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/PKG-INFO0000644000175100017470000001427614471613541014445 0ustar00runnerdockerMetadata-Version: 2.1 Name: kiwisolver Version: 1.4.5 Summary: A fast implementation of the Cassowary constraint solver Author-email: The Nucleic Development Team Maintainer-email: "Matthieu C. Dartiailh" License: ========================= 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. #------------------------------------------------------------------------------ Project-URL: homepage, https://github.com/nucleic/kiwi Project-URL: documentation, https://kiwisolver.readthedocs.io/en/latest/ Project-URL: repository, https://github.com/nucleic/kiwi Project-URL: changelog, https://github.com/nucleic/kiwi/blob/main/releasenotes.rst Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE Welcome to Kiwi =============== .. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=main :target: https://travis-ci.org/nucleic/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 `_. 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+. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/README.rst0000644000175100017470000000247414471613520015031 0ustar00runnerdockerWelcome to Kiwi =============== .. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=main :target: https://travis-ci.org/nucleic/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 `_. 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+. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9433424 kiwisolver-1.4.5/benchmarks/0000755000175100017470000000000014471613541015453 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/benchmarks/README.rst0000644000175100017470000000060214471613520017135 0ustar00runnerdockerBenchmarks 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/benchmarks/build_and_run_bench.sh0000755000175100017470000000043314471613520021753 0ustar00runnerdocker#!/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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/benchmarks/enaml_like_benchmark.cpp0000644000175100017470000002322314471613520022270 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #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(); }); } } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/benchmarks/enaml_like_benchmark.py0000644000175100017470000001742314471613520022143 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/benchmarks/nanobench.h0000644000175100017470000035256414471613520017573 0ustar00runnerdocker// __ _ _______ __ _ _____ ______ _______ __ _ _______ _ _ // | \ | |_____| | \ | | | |_____] |______ | \ | | |_____| // | \_| | | | \_| |_____| |_____] |______ | \_| |_____ | | // // Microbenchmark framework for C++11/14/17/20 // https://github.com/martinus/nanobench // // Licensed under the MIT License . // SPDX-License-Identifier: MIT // Copyright (c) 2019-2021 Martin Ankerl // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #ifndef ANKERL_NANOBENCH_H_INCLUDED #define ANKERL_NANOBENCH_H_INCLUDED // see https://semver.org/ #define ANKERL_NANOBENCH_VERSION_MAJOR 4 // incompatible API changes #define ANKERL_NANOBENCH_VERSION_MINOR 3 // backwards-compatible changes #define ANKERL_NANOBENCH_VERSION_PATCH 4 // backwards-compatible bug fixes /////////////////////////////////////////////////////////////////////////////////////////////////// // public facing api - as minimal as possible /////////////////////////////////////////////////////////////////////////////////////////////////// #include // high_resolution_clock #include // memcpy #include // for std::ostream* custom output target in Config #include // all names #include // holds all results #define ANKERL_NANOBENCH(x) ANKERL_NANOBENCH_PRIVATE_##x() #define ANKERL_NANOBENCH_PRIVATE_CXX() __cplusplus #define ANKERL_NANOBENCH_PRIVATE_CXX98() 199711L #define ANKERL_NANOBENCH_PRIVATE_CXX11() 201103L #define ANKERL_NANOBENCH_PRIVATE_CXX14() 201402L #define ANKERL_NANOBENCH_PRIVATE_CXX17() 201703L #if ANKERL_NANOBENCH(CXX) >= ANKERL_NANOBENCH(CXX17) # define ANKERL_NANOBENCH_PRIVATE_NODISCARD() [[nodiscard]] #else # define ANKERL_NANOBENCH_PRIVATE_NODISCARD() #endif #if defined(__clang__) # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() \ _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpadded\"") # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() _Pragma("clang diagnostic pop") #else # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() #endif #if defined(__GNUC__) # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Weffc++\"") # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() _Pragma("GCC diagnostic pop") #else # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() #endif #if defined(ANKERL_NANOBENCH_LOG_ENABLED) # include # define ANKERL_NANOBENCH_LOG(x) \ do { \ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl; \ } while (0) #else # define ANKERL_NANOBENCH_LOG(x) \ do { \ } while (0) #endif #if defined(__linux__) && defined(PERF_EVENT_IOC_ID) && defined(PERF_COUNT_HW_REF_CPU_CYCLES) && defined(PERF_FLAG_FD_CLOEXEC) && \ !defined(ANKERL_NANOBENCH_DISABLE_PERF_COUNTERS) // only enable perf counters on kernel 3.14 which seems to have all the necessary defines. The three PERF_... defines are not in // kernel 2.6.32 (all others are). # define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 1 #else # define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 0 #endif #if defined(__clang__) # define ANKERL_NANOBENCH_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__))) #else # define ANKERL_NANOBENCH_NO_SANITIZE(...) #endif #if defined(_MSC_VER) # define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __declspec(noinline) #else # define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __attribute__((noinline)) #endif // workaround missing "is_trivially_copyable" in g++ < 5.0 // See https://stackoverflow.com/a/31798726/48181 #if defined(__GNUC__) && __GNUC__ < 5 # define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) #else # define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value #endif // declarations /////////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { using Clock = std::conditional::type; class Bench; struct Config; class Result; class Rng; class BigO; /** * @brief Renders output from a mustache-like template and benchmark results. * * The templating facility here is heavily inspired by [mustache - logic-less templates](https://mustache.github.io/). * It adds a few more features that are necessary to get all of the captured data out of nanobench. Please read the * excellent [mustache manual](https://mustache.github.io/mustache.5.html) to see what this is all about. * * nanobench output has two nested layers, *result* and *measurement*. Here is a hierarchy of the allowed tags: * * * `{{#result}}` Marks the begin of the result layer. Whatever comes after this will be instantiated as often as * a benchmark result is available. Within it, you can use these tags: * * * `{{title}}` See Bench::title(). * * * `{{name}}` Benchmark name, usually directly provided with Bench::run(), but can also be set with Bench::name(). * * * `{{unit}}` Unit, e.g. `byte`. Defaults to `op`, see Bench::title(). * * * `{{batch}}` Batch size, see Bench::batch(). * * * `{{complexityN}}` Value used for asymptotic complexity calculation. See Bench::complexityN(). * * * `{{epochs}}` Number of epochs, see Bench::epochs(). * * * `{{clockResolution}}` Accuracy of the clock, i.e. what's the smallest time possible to measure with the clock. * For modern systems, this can be around 20 ns. This value is automatically determined by nanobench at the first * benchmark that is run, and used as a static variable throughout the application's runtime. * * * `{{clockResolutionMultiple}}` Configuration multiplier for `clockResolution`. See Bench::clockResolutionMultiple(). * This is the target runtime for each measurement (epoch). That means the more accurate your clock is, the faster * will be the benchmark. Basing the measurement's runtime on the clock resolution is the main reason why nanobench is so fast. * * * `{{maxEpochTime}}` Configuration for a maximum time each measurement (epoch) is allowed to take. Note that at least * a single iteration will be performed, even when that takes longer than maxEpochTime. See Bench::maxEpochTime(). * * * `{{minEpochTime}}` Minimum epoch time, usually not set. See Bench::minEpochTime(). * * * `{{minEpochIterations}}` See Bench::minEpochIterations(). * * * `{{epochIterations}}` See Bench::epochIterations(). * * * `{{warmup}}` Number of iterations used before measuring starts. See Bench::warmup(). * * * `{{relative}}` True or false, depending on the setting you have used. See Bench::relative(). * * Apart from these tags, it is also possible to use some mathematical operations on the measurement data. The operations * are of the form `{{command(name)}}`. Currently `name` can be one of `elapsed`, `iterations`. If performance counters * are available (currently only on current Linux systems), you also have `pagefaults`, `cpucycles`, * `contextswitches`, `instructions`, `branchinstructions`, and `branchmisses`. All the measuers (except `iterations`) are * provided for a single iteration (so `elapsed` is the time a single iteration took). The following tags are available: * * * `{{median()}}` Calculate median of a measurement data set, e.g. `{{median(elapsed)}}`. * * * `{{average()}}` Average (mean) calculation. * * * `{{medianAbsolutePercentError()}}` Calculates MdAPE, the Median Absolute Percentage Error. The MdAPE is an excellent * metric for the variation of measurements. It is more robust to outliers than the * [Mean absolute percentage error (M-APE)](https://en.wikipedia.org/wiki/Mean_absolute_percentage_error). * @f[ * \mathrm{MdAPE}(e) = \mathrm{med}\{| \frac{e_i - \mathrm{med}\{e\}}{e_i}| \} * @f] * E.g. for *elapsed*: First, @f$ \mathrm{med}\{e\} @f$ calculates the median by sorting and then taking the middle element * of all *elapsed* measurements. This is used to calculate the absolute percentage * error to this median for each measurement, as in @f$ | \frac{e_i - \mathrm{med}\{e\}}{e_i}| @f$. All these results * are sorted, and the middle value is chosen as the median absolute percent error. * * This measurement is a bit hard to interpret, but it is very robust against outliers. E.g. a value of 5% means that half of the * measurements deviate less than 5% from the median, and the other deviate more than 5% from the median. * * * `{{sum()}}` Sums of all the measurements. E.g. `{{sum(iterations)}}` will give you the total number of iterations * measured in this benchmark. * * * `{{minimum()}}` Minimum of all measurements. * * * `{{maximum()}}` Maximum of all measurements. * * * `{{sumProduct(, )}}` Calculates the sum of the products of corresponding measures: * @f[ * \mathrm{sumProduct}(a,b) = \sum_{i=1}^{n}a_i\cdot b_i * @f] * E.g. to calculate total runtime of the benchmark, you multiply iterations with elapsed time for each measurement, and * sum these results up: * `{{sumProduct(iterations, elapsed)}}`. * * * `{{#measurement}}` To access individual measurement results, open the begin tag for measurements. * * * `{{elapsed}}` Average elapsed wall clock time per iteration, in seconds. * * * `{{iterations}}` Number of iterations in the measurement. The number of iterations will fluctuate due * to some applied randomness, to enhance accuracy. * * * `{{pagefaults}}` Average number of pagefaults per iteration. * * * `{{cpucycles}}` Average number of CPU cycles processed per iteration. * * * `{{contextswitches}}` Average number of context switches per iteration. * * * `{{instructions}}` Average number of retired instructions per iteration. * * * `{{branchinstructions}}` Average number of branches executed per iteration. * * * `{{branchmisses}}` Average number of branches that were missed per iteration. * * * `{{/measurement}}` Ends the measurement tag. * * * `{{/result}}` Marks the end of the result layer. This is the end marker for the template part that will be instantiated * for each benchmark result. * * * For the layer tags *result* and *measurement* you additionally can use these special markers: * * * ``{{#-first}}`` - Begin marker of a template that will be instantiated *only for the first* entry in the layer. Use is only * allowed between the begin and end marker of the layer allowed. So between ``{{#result}}`` and ``{{/result}}``, or between * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-first}}``. * * * ``{{^-first}}`` - Begin marker of a template that will be instantiated *for each except the first* entry in the layer. This, * this is basically the inversion of ``{{#-first}}``. Use is only allowed between the begin and end marker of the layer allowed. * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``. * * * ``{{/-first}}`` - End marker for either ``{{#-first}}`` or ``{{^-first}}``. * * * ``{{#-last}}`` - Begin marker of a template that will be instantiated *only for the last* entry in the layer. Use is only * allowed between the begin and end marker of the layer allowed. So between ``{{#result}}`` and ``{{/result}}``, or between * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-last}}``. * * * ``{{^-last}}`` - Begin marker of a template that will be instantiated *for each except the last* entry in the layer. This, * this is basically the inversion of ``{{#-last}}``. Use is only allowed between the begin and end marker of the layer allowed. * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``. * * * ``{{/-last}}`` - End marker for either ``{{#-last}}`` or ``{{^-last}}``. * @verbatim embed:rst For an overview of all the possible data you can get out of nanobench, please see the tutorial at :ref:`tutorial-template-json`. The templates that ship with nanobench are: * :cpp:func:`templates::csv() ` * :cpp:func:`templates::json() ` * :cpp:func:`templates::htmlBoxplot() ` * :cpp:func:`templates::pyperf() ` @endverbatim * * @param mustacheTemplate The template. * @param bench Benchmark, containing all the results. * @param out Output for the generated output. */ void render(char const* mustacheTemplate, Bench const& bench, std::ostream& out); void render(std::string const& mustacheTemplate, Bench const& bench, std::ostream& out); /** * Same as render(char const* mustacheTemplate, Bench const& bench, std::ostream& out), but for when * you only have results available. * * @param mustacheTemplate The template. * @param results All the results to be used for rendering. * @param out Output for the generated output. */ void render(char const* mustacheTemplate, std::vector const& results, std::ostream& out); void render(std::string const& mustacheTemplate, std::vector const& results, std::ostream& out); // Contains mustache-like templates namespace templates { /*! @brief CSV data for the benchmark results. Generates a comma-separated values dataset. First line is the header, each following line is a summary of each benchmark run. @verbatim embed:rst See the tutorial at :ref:`tutorial-template-csv` for an example. @endverbatim */ char const* csv() noexcept; /*! @brief HTML output that uses plotly to generate an interactive boxplot chart. See the tutorial for an example output. The output uses only the elapsed wall clock time, and displays each epoch as a single dot. @verbatim embed:rst See the tutorial at :ref:`tutorial-template-html` for an example. @endverbatim @see ankerl::nanobench::render() */ char const* htmlBoxplot() noexcept; /*! @brief Output in pyperf compatible JSON format, which can be used for more analyzations. @verbatim embed:rst See the tutorial at :ref:`tutorial-template-pyperf` for an example how to further analyze the output. @endverbatim */ char const* pyperf() noexcept; /*! @brief Template to generate JSON data. The generated JSON data contains *all* data that has been generated. All times are as double values, in seconds. The output can get quite large. @verbatim embed:rst See the tutorial at :ref:`tutorial-template-json` for an example. @endverbatim */ char const* json() noexcept; } // namespace templates namespace detail { template struct PerfCountSet; class IterationLogic; class PerformanceCounters; #if ANKERL_NANOBENCH(PERF_COUNTERS) class LinuxPerformanceCounters; #endif } // namespace detail } // namespace nanobench } // namespace ankerl // definitions //////////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { namespace detail { template struct PerfCountSet { T pageFaults{}; T cpuCycles{}; T contextSwitches{}; T instructions{}; T branchInstructions{}; T branchMisses{}; }; } // namespace detail ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) struct Config { // actual benchmark config std::string mBenchmarkTitle = "benchmark"; std::string mBenchmarkName = "noname"; std::string mUnit = "op"; double mBatch = 1.0; double mComplexityN = -1.0; size_t mNumEpochs = 11; size_t mClockResolutionMultiple = static_cast(1000); std::chrono::nanoseconds mMaxEpochTime = std::chrono::milliseconds(100); std::chrono::nanoseconds mMinEpochTime{}; uint64_t mMinEpochIterations{1}; uint64_t mEpochIterations{0}; // If not 0, run *exactly* these number of iterations per epoch. uint64_t mWarmup = 0; std::ostream* mOut = nullptr; std::chrono::duration mTimeUnit = std::chrono::nanoseconds{1}; std::string mTimeUnitName = "ns"; bool mShowPerformanceCounters = true; bool mIsRelative = false; Config(); ~Config(); Config& operator=(Config const&); Config& operator=(Config&&); Config(Config const&); Config(Config&&) noexcept; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) // Result returned after a benchmark has finished. Can be used as a baseline for relative(). ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class Result { public: enum class Measure : size_t { elapsed, iterations, pagefaults, cpucycles, contextswitches, instructions, branchinstructions, branchmisses, _size }; explicit Result(Config const& benchmarkConfig); ~Result(); Result& operator=(Result const&); Result& operator=(Result&&); Result(Result const&); Result(Result&&) noexcept; // adds new measurement results // all values are scaled by iters (except iters...) void add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc); ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept; ANKERL_NANOBENCH(NODISCARD) double median(Measure m) const; ANKERL_NANOBENCH(NODISCARD) double medianAbsolutePercentError(Measure m) const; ANKERL_NANOBENCH(NODISCARD) double average(Measure m) const; ANKERL_NANOBENCH(NODISCARD) double sum(Measure m) const noexcept; ANKERL_NANOBENCH(NODISCARD) double sumProduct(Measure m1, Measure m2) const noexcept; ANKERL_NANOBENCH(NODISCARD) double minimum(Measure m) const noexcept; ANKERL_NANOBENCH(NODISCARD) double maximum(Measure m) const noexcept; ANKERL_NANOBENCH(NODISCARD) bool has(Measure m) const noexcept; ANKERL_NANOBENCH(NODISCARD) double get(size_t idx, Measure m) const; ANKERL_NANOBENCH(NODISCARD) bool empty() const noexcept; ANKERL_NANOBENCH(NODISCARD) size_t size() const noexcept; // Finds string, if not found, returns _size. static Measure fromString(std::string const& str); private: Config mConfig{}; std::vector> mNameToMeasurements{}; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) /** * An extremely fast random generator. Currently, this implements *RomuDuoJr*, developed by Mark Overton. Source: * http://www.romu-random.org/ * * RomuDuoJr is extremely fast and provides reasonable good randomness. Not enough for large jobs, but definitely * good enough for a benchmarking framework. * * * Estimated capacity: @f$ 2^{51} @f$ bytes * * Register pressure: 4 * * State size: 128 bits * * This random generator is a drop-in replacement for the generators supplied by ````. It is not * cryptographically secure. It's intended purpose is to be very fast so that benchmarks that make use * of randomness are not distorted too much by the random generator. * * Rng also provides a few non-standard helpers, optimized for speed. */ class Rng final { public: /** * @brief This RNG provides 64bit randomness. */ using result_type = uint64_t; static constexpr uint64_t(min)(); static constexpr uint64_t(max)(); /** * As a safety precausion, we don't allow copying. Copying a PRNG would mean you would have two random generators that produce the * same sequence, which is generally not what one wants. Instead create a new rng with the default constructor Rng(), which is * automatically seeded from `std::random_device`. If you really need a copy, use copy(). */ Rng(Rng const&) = delete; /** * Same as Rng(Rng const&), we don't allow assignment. If you need a new Rng create one with the default constructor Rng(). */ Rng& operator=(Rng const&) = delete; // moving is ok Rng(Rng&&) noexcept = default; Rng& operator=(Rng&&) noexcept = default; ~Rng() noexcept = default; /** * @brief Creates a new Random generator with random seed. * * Instead of a default seed (as the random generators from the STD), this properly seeds the random generator from * `std::random_device`. It guarantees correct seeding. Note that seeding can be relatively slow, depending on the source of * randomness used. So it is best to create a Rng once and use it for all your randomness purposes. */ Rng(); /*! Creates a new Rng that is seeded with a specific seed. Each Rng created from the same seed will produce the same randomness sequence. This can be useful for deterministic behavior. @verbatim embed:rst .. note:: The random algorithm might change between nanobench releases. Whenever a faster and/or better random generator becomes available, I will switch the implementation. @endverbatim As per the Romu paper, this seeds the Rng with splitMix64 algorithm and performs 10 initial rounds for further mixing up of the internal state. @param seed The 64bit seed. All values are allowed, even 0. */ explicit Rng(uint64_t seed) noexcept; Rng(uint64_t x, uint64_t y) noexcept; Rng(std::vector const& data); /** * Creates a copy of the Rng, thus the copy provides exactly the same random sequence as the original. */ ANKERL_NANOBENCH(NODISCARD) Rng copy() const noexcept; /** * @brief Produces a 64bit random value. This should be very fast, thus it is marked as inline. In my benchmark, this is ~46 times * faster than `std::default_random_engine` for producing 64bit random values. It seems that the fastest std contender is * `std::mt19937_64`. Still, this RNG is 2-3 times as fast. * * @return uint64_t The next 64 bit random value. */ inline uint64_t operator()() noexcept; // This is slightly biased. See /** * Generates a random number between 0 and range (excluding range). * * The algorithm only produces 32bit numbers, and is slightly biased. The effect is quite small unless your range is close to the * maximum value of an integer. It is possible to correct the bias with rejection sampling (see * [here](https://lemire.me/blog/2016/06/30/fast-random-shuffling/), but this is most likely irrelevant in practices for the * purposes of this Rng. * * See Daniel Lemire's blog post [A fast alternative to the modulo * reduction](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/) * * @param range Upper exclusive range. E.g a value of 3 will generate random numbers 0, 1, 2. * @return uint32_t Generated random values in range [0, range(. */ inline uint32_t bounded(uint32_t range) noexcept; // random double in range [0, 1( // see http://prng.di.unimi.it/ /** * Provides a random uniform double value between 0 and 1. This uses the method described in [Generating uniform doubles in the * unit interval](http://prng.di.unimi.it/), and is extremely fast. * * @return double Uniformly distributed double value in range [0,1(, excluding 1. */ inline double uniform01() noexcept; /** * Shuffles all entries in the given container. Although this has a slight bias due to the implementation of bounded(), this is * preferable to `std::shuffle` because it is over 5 times faster. See Daniel Lemire's blog post [Fast random * shuffling](https://lemire.me/blog/2016/06/30/fast-random-shuffling/). * * @param container The whole container will be shuffled. */ template void shuffle(Container& container) noexcept; /** * Extracts the full state of the generator, e.g. for serialization. For this RNG this is just 2 values, but to stay API compatible * with future implementations that potentially use more state, we use a vector. * * @return Vector containing the full state: */ std::vector state() const; private: static constexpr uint64_t rotl(uint64_t x, unsigned k) noexcept; uint64_t mX; uint64_t mY; }; /** * @brief Main entry point to nanobench's benchmarking facility. * * It holds configuration and results from one or more benchmark runs. Usually it is used in a single line, where the object is * constructed, configured, and then a benchmark is run. E.g. like this: * * ankerl::nanobench::Bench().unit("byte").batch(1000).run("random fluctuations", [&] { * // here be the benchmark code * }); * * In that example Bench() constructs the benchmark, it is then configured with unit() and batch(), and after configuration a * benchmark is executed with run(). Once run() has finished, it prints the result to `std::cout`. It would also store the results * in the Bench instance, but in this case the object is immediately destroyed so it's not available any more. */ ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class Bench { public: /** * @brief Creates a new benchmark for configuration and running of benchmarks. */ Bench(); Bench(Bench&& other); Bench& operator=(Bench&& other); Bench(Bench const& other); Bench& operator=(Bench const& other); ~Bench() noexcept; /*! @brief Repeatedly calls `op()` based on the configuration, and performs measurements. This call is marked with `noinline` to prevent the compiler to optimize beyond different benchmarks. This can have quite a big effect on benchmark accuracy. @verbatim embed:rst .. note:: Each call to your lambda must have a side effect that the compiler can't possibly optimize it away. E.g. add a result to an externally defined number (like `x` in the above example), and finally call `doNotOptimizeAway` on the variables the compiler must not remove. You can also use :cpp:func:`ankerl::nanobench::doNotOptimizeAway` directly in the lambda, but be aware that this has a small overhead. @endverbatim @tparam Op The code to benchmark. */ template ANKERL_NANOBENCH(NOINLINE) Bench& run(char const* benchmarkName, Op&& op); template ANKERL_NANOBENCH(NOINLINE) Bench& run(std::string const& benchmarkName, Op&& op); /** * @brief Same as run(char const* benchmarkName, Op op), but instead uses the previously set name. * @tparam Op The code to benchmark. */ template ANKERL_NANOBENCH(NOINLINE) Bench& run(Op&& op); /** * @brief Title of the benchmark, will be shown in the table header. Changing the title will start a new markdown table. * * @param benchmarkTitle The title of the benchmark. */ Bench& title(char const* benchmarkTitle); Bench& title(std::string const& benchmarkTitle); ANKERL_NANOBENCH(NODISCARD) std::string const& title() const noexcept; /// Name of the benchmark, will be shown in the table row. Bench& name(char const* benchmarkName); Bench& name(std::string const& benchmarkName); ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept; /** * @brief Sets the batch size. * * E.g. number of processed byte, or some other metric for the size of the processed data in each iteration. If you benchmark * hashing of a 1000 byte long string and want byte/sec as a result, you can specify 1000 as the batch size. * * @tparam T Any input type is internally cast to `double`. * @param b batch size */ template Bench& batch(T b) noexcept; ANKERL_NANOBENCH(NODISCARD) double batch() const noexcept; /** * @brief Sets the operation unit. * * Defaults to "op". Could be e.g. "byte" for string processing. This is used for the table header, e.g. to show `ns/byte`. Use * singular (*byte*, not *bytes*). A change clears the currently collected results. * * @param unit The unit name. */ Bench& unit(char const* unit); Bench& unit(std::string const& unit); ANKERL_NANOBENCH(NODISCARD) std::string const& unit() const noexcept; /** * @brief Sets the time unit to be used for the default output. * * Nanobench defaults to using ns (nanoseconds) as output in the markdown. For some benchmarks this is too coarse, so it is * possible to configure this. E.g. use `timeUnit(1ms, "ms")` to show `ms/op` instead of `ns/op`. * * @param tu Time unit to display the results in, default is 1ns. * @param tuName Name for the time unit, default is "ns" */ Bench& timeUnit(std::chrono::duration const& tu, std::string const& tuName); ANKERL_NANOBENCH(NODISCARD) std::string const& timeUnitName() const noexcept; ANKERL_NANOBENCH(NODISCARD) std::chrono::duration const& timeUnit() const noexcept; /** * @brief Set the output stream where the resulting markdown table will be printed to. * * The default is `&std::cout`. You can disable all output by setting `nullptr`. * * @param outstream Pointer to output stream, can be `nullptr`. */ Bench& output(std::ostream* outstream) noexcept; ANKERL_NANOBENCH(NODISCARD) std::ostream* output() const noexcept; /** * Modern processors have a very accurate clock, being able to measure as low as 20 nanoseconds. This is the main trick nanobech to * be so fast: we find out how accurate the clock is, then run the benchmark only so often that the clock's accuracy is good enough * for accurate measurements. * * The default is to run one epoch for 1000 times the clock resolution. So for 20ns resolution and 11 epochs, this gives a total * runtime of * * @f[ * 20ns * 1000 * 11 \approx 0.2ms * @f] * * To be precise, nanobench adds a 0-20% random noise to each evaluation. This is to prevent any aliasing effects, and further * improves accuracy. * * Total runtime will be higher though: Some initial time is needed to find out the target number of iterations for each epoch, and * there is some overhead involved to start & stop timers and calculate resulting statistics and writing the output. * * @param multiple Target number of times of clock resolution. Usually 1000 is a good compromise between runtime and accuracy. */ Bench& clockResolutionMultiple(size_t multiple) noexcept; ANKERL_NANOBENCH(NODISCARD) size_t clockResolutionMultiple() const noexcept; /** * @brief Controls number of epochs, the number of measurements to perform. * * The reported result will be the median of evaluation of each epoch. The higher you choose this, the more * deterministic the result be and outliers will be more easily removed. Also the `err%` will be more accurate the higher this * number is. Note that the `err%` will not necessarily decrease when number of epochs is increased. But it will be a more accurate * representation of the benchmarked code's runtime stability. * * Choose the value wisely. In practice, 11 has been shown to be a reasonable choice between runtime performance and accuracy. * This setting goes hand in hand with minEpocIterations() (or minEpochTime()). If you are more interested in *median* runtime, you * might want to increase epochs(). If you are more interested in *mean* runtime, you might want to increase minEpochIterations() * instead. * * @param numEpochs Number of epochs. */ Bench& epochs(size_t numEpochs) noexcept; ANKERL_NANOBENCH(NODISCARD) size_t epochs() const noexcept; /** * @brief Upper limit for the runtime of each epoch. * * As a safety precausion if the clock is not very accurate, we can set an upper limit for the maximum evaluation time per * epoch. Default is 100ms. At least a single evaluation of the benchmark is performed. * * @see minEpochTime(), minEpochIterations() * * @param t Maximum target runtime for a single epoch. */ Bench& maxEpochTime(std::chrono::nanoseconds t) noexcept; ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds maxEpochTime() const noexcept; /** * @brief Minimum time each epoch should take. * * Default is zero, so we are fully relying on clockResolutionMultiple(). In most cases this is exactly what you want. If you see * that the evaluation is unreliable with a high `err%`, you can increase either minEpochTime() or minEpochIterations(). * * @see maxEpochTime(), minEpochIterations() * * @param t Minimum time each epoch should take. */ Bench& minEpochTime(std::chrono::nanoseconds t) noexcept; ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds minEpochTime() const noexcept; /** * @brief Sets the minimum number of iterations each epoch should take. * * Default is 1, and we rely on clockResolutionMultiple(). If the `err%` is high and you want a more smooth result, you might want * to increase the minimum number or iterations, or increase the minEpochTime(). * * @see minEpochTime(), maxEpochTime(), minEpochIterations() * * @param numIters Minimum number of iterations per epoch. */ Bench& minEpochIterations(uint64_t numIters) noexcept; ANKERL_NANOBENCH(NODISCARD) uint64_t minEpochIterations() const noexcept; /** * Sets exactly the number of iterations for each epoch. Ignores all other epoch limits. This forces nanobench to use exactly * the given number of iterations for each epoch, not more and not less. Default is 0 (disabled). * * @param numIters Exact number of iterations to use. Set to 0 to disable. */ Bench& epochIterations(uint64_t numIters) noexcept; ANKERL_NANOBENCH(NODISCARD) uint64_t epochIterations() const noexcept; /** * @brief Sets a number of iterations that are initially performed without any measurements. * * Some benchmarks need a few evaluations to warm up caches / database / whatever access. Normally this should not be needed, since * we show the median result so initial outliers will be filtered away automatically. If the warmup effect is large though, you * might want to set it. Default is 0. * * @param numWarmupIters Number of warmup iterations. */ Bench& warmup(uint64_t numWarmupIters) noexcept; ANKERL_NANOBENCH(NODISCARD) uint64_t warmup() const noexcept; /** * @brief Marks the next run as the baseline. * * Call `relative(true)` to mark the run as the baseline. Successive runs will be compared to this run. It is calculated by * * @f[ * 100\% * \frac{baseline}{runtime} * @f] * * * 100% means it is exactly as fast as the baseline * * >100% means it is faster than the baseline. E.g. 200% means the current run is twice as fast as the baseline. * * <100% means it is slower than the baseline. E.g. 50% means it is twice as slow as the baseline. * * See the tutorial section "Comparing Results" for example usage. * * @param isRelativeEnabled True to enable processing */ Bench& relative(bool isRelativeEnabled) noexcept; ANKERL_NANOBENCH(NODISCARD) bool relative() const noexcept; /** * @brief Enables/disables performance counters. * * On Linux nanobench has a powerful feature to use performance counters. This enables counting of retired instructions, count * number of branches, missed branches, etc. On default this is enabled, but you can disable it if you don't need that feature. * * @param showPerformanceCounters True to enable, false to disable. */ Bench& performanceCounters(bool showPerformanceCounters) noexcept; ANKERL_NANOBENCH(NODISCARD) bool performanceCounters() const noexcept; /** * @brief Retrieves all benchmark results collected by the bench object so far. * * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to * see all the nitty gritty detials. * * @return All results collected so far. */ ANKERL_NANOBENCH(NODISCARD) std::vector const& results() const noexcept; /*! @verbatim embed:rst Convenience shortcut to :cpp:func:`ankerl::nanobench::doNotOptimizeAway`. @endverbatim */ template Bench& doNotOptimizeAway(Arg&& arg); /*! @verbatim embed:rst Sets N for asymptotic complexity calculation, so it becomes possible to calculate `Big O `_ from multiple benchmark evaluations. Use :cpp:func:`ankerl::nanobench::Bench::complexityBigO` when the evaluation has finished. See the tutorial :ref:`asymptotic-complexity` for details. @endverbatim @tparam T Any type is cast to `double`. @param b Length of N for the next benchmark run, so it is possible to calculate `bigO`. */ template Bench& complexityN(T b) noexcept; ANKERL_NANOBENCH(NODISCARD) double complexityN() const noexcept; /*! Calculates [Big O](https://en.wikipedia.org/wiki/Big_O_notation>) of the results with all preconfigured complexity functions. Currently these complexity functions are fitted into the benchmark results: @f$ \mathcal{O}(1) @f$, @f$ \mathcal{O}(n) @f$, @f$ \mathcal{O}(\log{}n) @f$, @f$ \mathcal{O}(n\log{}n) @f$, @f$ \mathcal{O}(n^2) @f$, @f$ \mathcal{O}(n^3) @f$. If we e.g. evaluate the complexity of `std::sort`, this is the result of `std::cout << bench.complexityBigO()`: ``` | coefficient | err% | complexity |--------------:|-------:|------------ | 5.08935e-09 | 2.6% | O(n log n) | 6.10608e-08 | 8.0% | O(n) | 1.29307e-11 | 47.2% | O(n^2) | 2.48677e-15 | 69.6% | O(n^3) | 9.88133e-06 | 132.3% | O(log n) | 5.98793e-05 | 162.5% | O(1) ``` So in this case @f$ \mathcal{O}(n\log{}n) @f$ provides the best approximation. @verbatim embed:rst See the tutorial :ref:`asymptotic-complexity` for details. @endverbatim @return Evaluation results, which can be printed or otherwise inspected. */ std::vector complexityBigO() const; /** * @brief Calculates bigO for a custom function. * * E.g. to calculate the mean squared error for @f$ \mathcal{O}(\log{}\log{}n) @f$, which is not part of the default set of * complexityBigO(), you can do this: * * ``` * auto logLogN = bench.complexityBigO("O(log log n)", [](double n) { * return std::log2(std::log2(n)); * }); * ``` * * The resulting mean squared error can be printed with `std::cout << logLogN`. E.g. it prints something like this: * * ```text * 2.46985e-05 * O(log log n), rms=1.48121 * ``` * * @tparam Op Type of mapping operation. * @param name Name for the function, e.g. "O(log log n)" * @param op Op's operator() maps a `double` with the desired complexity function, e.g. `log2(log2(n))`. * @return BigO Error calculation, which is streamable to std::cout. */ template BigO complexityBigO(char const* name, Op op) const; template BigO complexityBigO(std::string const& name, Op op) const; /*! @verbatim embed:rst Convenience shortcut to :cpp:func:`ankerl::nanobench::render`. @endverbatim */ Bench& render(char const* templateContent, std::ostream& os); Bench& render(std::string const& templateContent, std::ostream& os); Bench& config(Config const& benchmarkConfig); ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept; private: Config mConfig{}; std::vector mResults{}; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) /** * @brief Makes sure none of the given arguments are optimized away by the compiler. * * @tparam Arg Type of the argument that shouldn't be optimized away. * @param arg The input that we mark as being used, even though we don't do anything with it. */ template void doNotOptimizeAway(Arg&& arg); namespace detail { #if defined(_MSC_VER) void doNotOptimizeAwaySink(void const*); template void doNotOptimizeAway(T const& val); #else // These assembly magic is directly from what Google Benchmark is doing. I have previously used what facebook's folly was doing, but // this seemd to have compilation problems in some cases. Google Benchmark seemed to be the most well tested anyways. // see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307 template void doNotOptimizeAway(T const& val) { // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : : "r,m"(val) : "memory"); } template void doNotOptimizeAway(T& val) { # if defined(__clang__) // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : "+r,m"(val) : : "memory"); # else // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : "+m,r"(val) : : "memory"); # endif } #endif // internally used, but visible because run() is templated. // Not movable/copy-able, so we simply use a pointer instead of unique_ptr. This saves us from // having to include , and the template instantiation overhead of unique_ptr which is unfortunately quite significant. ANKERL_NANOBENCH(IGNORE_EFFCPP_PUSH) class IterationLogic { public: explicit IterationLogic(Bench const& config) noexcept; ~IterationLogic(); ANKERL_NANOBENCH(NODISCARD) uint64_t numIters() const noexcept; void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept; void moveResultTo(std::vector& results) noexcept; private: struct Impl; Impl* mPimpl; }; ANKERL_NANOBENCH(IGNORE_EFFCPP_POP) ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class PerformanceCounters { public: PerformanceCounters(PerformanceCounters const&) = delete; PerformanceCounters& operator=(PerformanceCounters const&) = delete; PerformanceCounters(); ~PerformanceCounters(); void beginMeasure(); void endMeasure(); void updateResults(uint64_t numIters); ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& val() const noexcept; ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& has() const noexcept; private: #if ANKERL_NANOBENCH(PERF_COUNTERS) LinuxPerformanceCounters* mPc = nullptr; #endif PerfCountSet mVal{}; PerfCountSet mHas{}; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) // Gets the singleton PerformanceCounters& performanceCounters(); } // namespace detail class BigO { public: using RangeMeasure = std::vector>; template static RangeMeasure mapRangeMeasure(RangeMeasure data, Op op) { for (auto& rangeMeasure : data) { rangeMeasure.first = op(rangeMeasure.first); } return data; } static RangeMeasure collectRangeMeasure(std::vector const& results); template BigO(char const* bigOName, RangeMeasure const& rangeMeasure, Op rangeToN) : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {} template BigO(std::string const& bigOName, RangeMeasure const& rangeMeasure, Op rangeToN) : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {} BigO(char const* bigOName, RangeMeasure const& scaledRangeMeasure); BigO(std::string const& bigOName, RangeMeasure const& scaledRangeMeasure); ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept; ANKERL_NANOBENCH(NODISCARD) double constant() const noexcept; ANKERL_NANOBENCH(NODISCARD) double normalizedRootMeanSquare() const noexcept; ANKERL_NANOBENCH(NODISCARD) bool operator<(BigO const& other) const noexcept; private: std::string mName{}; double mConstant{}; double mNormalizedRootMeanSquare{}; }; std::ostream& operator<<(std::ostream& os, BigO const& bigO); std::ostream& operator<<(std::ostream& os, std::vector const& bigOs); } // namespace nanobench } // namespace ankerl // implementation ///////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { constexpr uint64_t(Rng::min)() { return 0; } constexpr uint64_t(Rng::max)() { return (std::numeric_limits::max)(); } ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") uint64_t Rng::operator()() noexcept { auto x = mX; mX = UINT64_C(15241094284759029579) * mY; mY = rotl(mY - x, 27); return x; } ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") uint32_t Rng::bounded(uint32_t range) noexcept { uint64_t r32 = static_cast(operator()()); auto multiresult = r32 * range; return static_cast(multiresult >> 32U); } double Rng::uniform01() noexcept { auto i = (UINT64_C(0x3ff) << 52U) | (operator()() >> 12U); // can't use union in c++ here for type puning, it's undefined behavior. // std::memcpy is optimized anyways. double d; std::memcpy(&d, &i, sizeof(double)); return d - 1.0; } template void Rng::shuffle(Container& container) noexcept { auto size = static_cast(container.size()); for (auto i = size; i > 1U; --i) { using std::swap; auto p = bounded(i); // number in [0, i) swap(container[i - 1], container[p]); } } constexpr uint64_t Rng::rotl(uint64_t x, unsigned k) noexcept { return (x << k) | (x >> (64U - k)); } template ANKERL_NANOBENCH_NO_SANITIZE("integer") Bench& Bench::run(Op&& op) { // It is important that this method is kept short so the compiler can do better optimizations/ inlining of op() detail::IterationLogic iterationLogic(*this); auto& pc = detail::performanceCounters(); while (auto n = iterationLogic.numIters()) { pc.beginMeasure(); Clock::time_point before = Clock::now(); while (n-- > 0) { op(); } Clock::time_point after = Clock::now(); pc.endMeasure(); pc.updateResults(iterationLogic.numIters()); iterationLogic.add(after - before, pc); } iterationLogic.moveResultTo(mResults); return *this; } // Performs all evaluations. template Bench& Bench::run(char const* benchmarkName, Op&& op) { name(benchmarkName); return run(std::forward(op)); } template Bench& Bench::run(std::string const& benchmarkName, Op&& op) { name(benchmarkName); return run(std::forward(op)); } template BigO Bench::complexityBigO(char const* benchmarkName, Op op) const { return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op); } template BigO Bench::complexityBigO(std::string const& benchmarkName, Op op) const { return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op); } // Set the batch size, e.g. number of processed bytes, or some other metric for the size of the processed data in each iteration. // Any argument is cast to double. template Bench& Bench::batch(T b) noexcept { mConfig.mBatch = static_cast(b); return *this; } // Sets the computation complexity of the next run. Any argument is cast to double. template Bench& Bench::complexityN(T n) noexcept { mConfig.mComplexityN = static_cast(n); return *this; } // Convenience: makes sure none of the given arguments are optimized away by the compiler. template Bench& Bench::doNotOptimizeAway(Arg&& arg) { detail::doNotOptimizeAway(std::forward(arg)); return *this; } // Makes sure none of the given arguments are optimized away by the compiler. template void doNotOptimizeAway(Arg&& arg) { detail::doNotOptimizeAway(std::forward(arg)); } namespace detail { #if defined(_MSC_VER) template void doNotOptimizeAway(T const& val) { doNotOptimizeAwaySink(&val); } #endif } // namespace detail } // namespace nanobench } // namespace ankerl #if defined(ANKERL_NANOBENCH_IMPLEMENT) /////////////////////////////////////////////////////////////////////////////////////////////////// // implementation part - only visible in .cpp /////////////////////////////////////////////////////////////////////////////////////////////////// # include // sort, reverse # include // compare_exchange_strong in loop overhead # include // getenv # include // strstr, strncmp # include // ifstream to parse proc files # include // setw, setprecision # include // cout # include // accumulate # include // random_device # include // to_s in Number # include // throw for rendering templates # include // std::tie # if defined(__linux__) # include //sysconf # endif # if ANKERL_NANOBENCH(PERF_COUNTERS) # include // map # include # include # include # include # endif // declarations /////////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { // helper stuff that is only intended to be used internally namespace detail { struct TableInfo; // formatting utilities namespace fmt { class NumSep; class StreamStateRestorer; class Number; class MarkDownColumn; class MarkDownCode; } // namespace fmt } // namespace detail } // namespace nanobench } // namespace ankerl // definitions //////////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { uint64_t splitMix64(uint64_t& state) noexcept; namespace detail { // helpers to get double values template inline double d(T t) noexcept { return static_cast(t); } inline double d(Clock::duration duration) noexcept { return std::chrono::duration_cast>(duration).count(); } // Calculates clock resolution once, and remembers the result inline Clock::duration clockResolution() noexcept; } // namespace detail namespace templates { char const* csv() noexcept { return R"DELIM("title";"name";"unit";"batch";"elapsed";"error %";"instructions";"branches";"branch misses";"total" {{#result}}"{{title}}";"{{name}}";"{{unit}}";{{batch}};{{median(elapsed)}};{{medianAbsolutePercentError(elapsed)}};{{median(instructions)}};{{median(branchinstructions)}};{{median(branchmisses)}};{{sumProduct(iterations, elapsed)}} {{/result}})DELIM"; } char const* htmlBoxplot() noexcept { return R"DELIM(
)DELIM"; } char const* pyperf() noexcept { return R"DELIM({ "benchmarks": [ { "runs": [ { "values": [ {{#measurement}} {{elapsed}}{{^-last}}, {{/last}}{{/measurement}} ] } ] } ], "metadata": { "loops": {{sum(iterations)}}, "inner_loops": {{batch}}, "name": "{{title}}", "unit": "second" }, "version": "1.0" })DELIM"; } char const* json() noexcept { return R"DELIM({ "results": [ {{#result}} { "title": "{{title}}", "name": "{{name}}", "unit": "{{unit}}", "batch": {{batch}}, "complexityN": {{complexityN}}, "epochs": {{epochs}}, "clockResolution": {{clockResolution}}, "clockResolutionMultiple": {{clockResolutionMultiple}}, "maxEpochTime": {{maxEpochTime}}, "minEpochTime": {{minEpochTime}}, "minEpochIterations": {{minEpochIterations}}, "epochIterations": {{epochIterations}}, "warmup": {{warmup}}, "relative": {{relative}}, "median(elapsed)": {{median(elapsed)}}, "medianAbsolutePercentError(elapsed)": {{medianAbsolutePercentError(elapsed)}}, "median(instructions)": {{median(instructions)}}, "medianAbsolutePercentError(instructions)": {{medianAbsolutePercentError(instructions)}}, "median(cpucycles)": {{median(cpucycles)}}, "median(contextswitches)": {{median(contextswitches)}}, "median(pagefaults)": {{median(pagefaults)}}, "median(branchinstructions)": {{median(branchinstructions)}}, "median(branchmisses)": {{median(branchmisses)}}, "totalTime": {{sumProduct(iterations, elapsed)}}, "measurements": [ {{#measurement}} { "iterations": {{iterations}}, "elapsed": {{elapsed}}, "pagefaults": {{pagefaults}}, "cpucycles": {{cpucycles}}, "contextswitches": {{contextswitches}}, "instructions": {{instructions}}, "branchinstructions": {{branchinstructions}}, "branchmisses": {{branchmisses}} }{{^-last}},{{/-last}} {{/measurement}} ] }{{^-last}},{{/-last}} {{/result}} ] })DELIM"; } ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) struct Node { enum class Type { tag, content, section, inverted_section }; char const* begin; char const* end; std::vector children; Type type; template // NOLINTNEXTLINE(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) bool operator==(char const (&str)[N]) const noexcept { return static_cast(std::distance(begin, end) + 1) == N && 0 == strncmp(str, begin, N - 1); } }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) static std::vector parseMustacheTemplate(char const** tpl) { std::vector nodes; while (true) { auto begin = std::strstr(*tpl, "{{"); auto end = begin; if (begin != nullptr) { begin += 2; end = std::strstr(begin, "}}"); } if (begin == nullptr || end == nullptr) { // nothing found, finish node nodes.emplace_back(Node{*tpl, *tpl + std::strlen(*tpl), std::vector{}, Node::Type::content}); return nodes; } nodes.emplace_back(Node{*tpl, begin - 2, std::vector{}, Node::Type::content}); // we found a tag *tpl = end + 2; switch (*begin) { case '/': // finished! bail out return nodes; case '#': nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::section}); break; case '^': nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::inverted_section}); break; default: nodes.emplace_back(Node{begin, end, std::vector{}, Node::Type::tag}); break; } } } static bool generateFirstLast(Node const& n, size_t idx, size_t size, std::ostream& out) { ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); bool matchFirst = n == "-first"; bool matchLast = n == "-last"; if (!matchFirst && !matchLast) { return false; } bool doWrite = false; if (n.type == Node::Type::section) { doWrite = (matchFirst && idx == 0) || (matchLast && idx == size - 1); } else if (n.type == Node::Type::inverted_section) { doWrite = (matchFirst && idx != 0) || (matchLast && idx != size - 1); } if (doWrite) { for (auto const& child : n.children) { if (child.type == Node::Type::content) { out.write(child.begin, std::distance(child.begin, child.end)); } } } return true; } static bool matchCmdArgs(std::string const& str, std::vector& matchResult) { matchResult.clear(); auto idxOpen = str.find('('); auto idxClose = str.find(')', idxOpen); if (idxClose == std::string::npos) { return false; } matchResult.emplace_back(str.substr(0, idxOpen)); // split by comma matchResult.emplace_back(std::string{}); for (size_t i = idxOpen + 1; i != idxClose; ++i) { if (str[i] == ' ' || str[i] == '\t') { // skip whitespace continue; } if (str[i] == ',') { // got a comma => new string matchResult.emplace_back(std::string{}); continue; } // no whitespace no comma, append matchResult.back() += str[i]; } return true; } static bool generateConfigTag(Node const& n, Config const& config, std::ostream& out) { using detail::d; if (n == "title") { out << config.mBenchmarkTitle; return true; } else if (n == "name") { out << config.mBenchmarkName; return true; } else if (n == "unit") { out << config.mUnit; return true; } else if (n == "batch") { out << config.mBatch; return true; } else if (n == "complexityN") { out << config.mComplexityN; return true; } else if (n == "epochs") { out << config.mNumEpochs; return true; } else if (n == "clockResolution") { out << d(detail::clockResolution()); return true; } else if (n == "clockResolutionMultiple") { out << config.mClockResolutionMultiple; return true; } else if (n == "maxEpochTime") { out << d(config.mMaxEpochTime); return true; } else if (n == "minEpochTime") { out << d(config.mMinEpochTime); return true; } else if (n == "minEpochIterations") { out << config.mMinEpochIterations; return true; } else if (n == "epochIterations") { out << config.mEpochIterations; return true; } else if (n == "warmup") { out << config.mWarmup; return true; } else if (n == "relative") { out << config.mIsRelative; return true; } return false; } static std::ostream& generateResultTag(Node const& n, Result const& r, std::ostream& out) { if (generateConfigTag(n, r.config(), out)) { return out; } // match e.g. "median(elapsed)" // g++ 4.8 doesn't implement std::regex :( // static std::regex const regOpArg1("^([a-zA-Z]+)\\(([a-zA-Z]*)\\)$"); // std::cmatch matchResult; // if (std::regex_match(n.begin, n.end, matchResult, regOpArg1)) { std::vector matchResult; if (matchCmdArgs(std::string(n.begin, n.end), matchResult)) { if (matchResult.size() == 2) { auto m = Result::fromString(matchResult[1]); if (m == Result::Measure::_size) { return out << 0.0; } if (matchResult[0] == "median") { return out << r.median(m); } if (matchResult[0] == "average") { return out << r.average(m); } if (matchResult[0] == "medianAbsolutePercentError") { return out << r.medianAbsolutePercentError(m); } if (matchResult[0] == "sum") { return out << r.sum(m); } if (matchResult[0] == "minimum") { return out << r.minimum(m); } if (matchResult[0] == "maximum") { return out << r.maximum(m); } } else if (matchResult.size() == 3) { auto m1 = Result::fromString(matchResult[1]); auto m2 = Result::fromString(matchResult[2]); if (m1 == Result::Measure::_size || m2 == Result::Measure::_size) { return out << 0.0; } if (matchResult[0] == "sumProduct") { return out << r.sumProduct(m1, m2); } } } // match e.g. "sumProduct(elapsed, iterations)" // static std::regex const regOpArg2("^([a-zA-Z]+)\\(([a-zA-Z]*)\\s*,\\s+([a-zA-Z]*)\\)$"); // nothing matches :( throw std::runtime_error("command '" + std::string(n.begin, n.end) + "' not understood"); } static void generateResultMeasurement(std::vector const& nodes, size_t idx, Result const& r, std::ostream& out) { for (auto const& n : nodes) { if (!generateFirstLast(n, idx, r.size(), out)) { ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); switch (n.type) { case Node::Type::content: out.write(n.begin, std::distance(n.begin, n.end)); break; case Node::Type::inverted_section: throw std::runtime_error("got a inverted section inside measurement"); case Node::Type::section: throw std::runtime_error("got a section inside measurement"); case Node::Type::tag: { auto m = Result::fromString(std::string(n.begin, n.end)); if (m == Result::Measure::_size || !r.has(m)) { out << 0.0; } else { out << r.get(idx, m); } break; } } } } } static void generateResult(std::vector const& nodes, size_t idx, std::vector const& results, std::ostream& out) { auto const& r = results[idx]; for (auto const& n : nodes) { if (!generateFirstLast(n, idx, results.size(), out)) { ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); switch (n.type) { case Node::Type::content: out.write(n.begin, std::distance(n.begin, n.end)); break; case Node::Type::inverted_section: throw std::runtime_error("got a inverted section inside result"); case Node::Type::section: if (n == "measurement") { for (size_t i = 0; i < r.size(); ++i) { generateResultMeasurement(n.children, i, r, out); } } else { throw std::runtime_error("got a section inside result"); } break; case Node::Type::tag: generateResultTag(n, r, out); break; } } } } } // namespace templates // helper stuff that only intended to be used internally namespace detail { char const* getEnv(char const* name); bool isEndlessRunning(std::string const& name); bool isWarningsEnabled(); template T parseFile(std::string const& filename); void gatherStabilityInformation(std::vector& warnings, std::vector& recommendations); void printStabilityInformationOnce(std::ostream* os); // remembers the last table settings used. When it changes, a new table header is automatically written for the new entry. uint64_t& singletonHeaderHash() noexcept; // determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference. Clock::duration calcClockResolution(size_t numEvaluations) noexcept; // formatting utilities namespace fmt { // adds thousands separator to numbers ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class NumSep : public std::numpunct { public: explicit NumSep(char sep); char do_thousands_sep() const override; std::string do_grouping() const override; private: char mSep; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) // RAII to save & restore a stream's state ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class StreamStateRestorer { public: explicit StreamStateRestorer(std::ostream& s); ~StreamStateRestorer(); // sets back all stream info that we remembered at construction void restore(); // don't allow copying / moving StreamStateRestorer(StreamStateRestorer const&) = delete; StreamStateRestorer& operator=(StreamStateRestorer const&) = delete; StreamStateRestorer(StreamStateRestorer&&) = delete; StreamStateRestorer& operator=(StreamStateRestorer&&) = delete; private: std::ostream& mStream; std::locale mLocale; std::streamsize const mPrecision; std::streamsize const mWidth; std::ostream::char_type const mFill; std::ostream::fmtflags const mFmtFlags; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) // Number formatter class Number { public: Number(int width, int precision, double value); Number(int width, int precision, int64_t value); std::string to_s() const; private: friend std::ostream& operator<<(std::ostream& os, Number const& n); std::ostream& write(std::ostream& os) const; int mWidth; int mPrecision; double mValue; }; // helper replacement for std::to_string of signed/unsigned numbers so we are locale independent std::string to_s(uint64_t s); std::ostream& operator<<(std::ostream& os, Number const& n); class MarkDownColumn { public: MarkDownColumn(int w, int prec, std::string const& tit, std::string const& suff, double val); std::string title() const; std::string separator() const; std::string invalid() const; std::string value() const; private: int mWidth; int mPrecision; std::string mTitle; std::string mSuffix; double mValue; }; // Formats any text as markdown code, escaping backticks. class MarkDownCode { public: explicit MarkDownCode(std::string const& what); private: friend std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode); std::ostream& write(std::ostream& os) const; std::string mWhat{}; }; std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode); } // namespace fmt } // namespace detail } // namespace nanobench } // namespace ankerl // implementation ///////////////////////////////////////////////////////////////////////////////// namespace ankerl { namespace nanobench { void render(char const* mustacheTemplate, std::vector const& results, std::ostream& out) { detail::fmt::StreamStateRestorer restorer(out); out.precision(std::numeric_limits::digits10); auto nodes = templates::parseMustacheTemplate(&mustacheTemplate); for (auto const& n : nodes) { ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); switch (n.type) { case templates::Node::Type::content: out.write(n.begin, std::distance(n.begin, n.end)); break; case templates::Node::Type::inverted_section: throw std::runtime_error("unknown list '" + std::string(n.begin, n.end) + "'"); case templates::Node::Type::section: if (n == "result") { const size_t nbResults = results.size(); for (size_t i = 0; i < nbResults; ++i) { generateResult(n.children, i, results, out); } } else if (n == "measurement") { if (results.size() != 1) { throw std::runtime_error( "render: can only use section 'measurement' here if there is a single result, but there are " + detail::fmt::to_s(results.size())); } // when we only have a single result, we can immediately go into its measurement. auto const& r = results.front(); for (size_t i = 0; i < r.size(); ++i) { generateResultMeasurement(n.children, i, r, out); } } else { throw std::runtime_error("render: unknown section '" + std::string(n.begin, n.end) + "'"); } break; case templates::Node::Type::tag: if (results.size() == 1) { // result & config are both supported there generateResultTag(n, results.front(), out); } else { // This just uses the last result's config. if (!generateConfigTag(n, results.back().config(), out)) { throw std::runtime_error("unknown tag '" + std::string(n.begin, n.end) + "'"); } } break; } } } void render(std::string const& mustacheTemplate, std::vector const& results, std::ostream& out) { render(mustacheTemplate.c_str(), results, out); } void render(char const* mustacheTemplate, const Bench& bench, std::ostream& out) { render(mustacheTemplate, bench.results(), out); } void render(std::string const& mustacheTemplate, const Bench& bench, std::ostream& out) { render(mustacheTemplate.c_str(), bench.results(), out); } namespace detail { PerformanceCounters& performanceCounters() { # if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wexit-time-destructors" # endif static PerformanceCounters pc; # if defined(__clang__) # pragma clang diagnostic pop # endif return pc; } // Windows version of doNotOptimizeAway // see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307 // see https://github.com/facebook/folly/blob/master/folly/Benchmark.h#L280 // see https://docs.microsoft.com/en-us/cpp/preprocessor/optimize # if defined(_MSC_VER) # pragma optimize("", off) void doNotOptimizeAwaySink(void const*) {} # pragma optimize("", on) # endif template T parseFile(std::string const& filename) { std::ifstream fin(filename); T num{}; fin >> num; return num; } char const* getEnv(char const* name) { # if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4996) // getenv': This function or variable may be unsafe. # endif return std::getenv(name); # if defined(_MSC_VER) # pragma warning(pop) # endif } bool isEndlessRunning(std::string const& name) { auto endless = getEnv("NANOBENCH_ENDLESS"); return nullptr != endless && endless == name; } // True when environment variable NANOBENCH_SUPPRESS_WARNINGS is either not set at all, or set to "0" bool isWarningsEnabled() { auto suppression = getEnv("NANOBENCH_SUPPRESS_WARNINGS"); return nullptr == suppression || suppression == std::string("0"); } void gatherStabilityInformation(std::vector& warnings, std::vector& recommendations) { warnings.clear(); recommendations.clear(); bool recommendCheckFlags = false; # if defined(DEBUG) warnings.emplace_back("DEBUG defined"); recommendCheckFlags = true; # endif bool recommendPyPerf = false; # if defined(__linux__) auto nprocs = sysconf(_SC_NPROCESSORS_CONF); if (nprocs <= 0) { warnings.emplace_back("couldn't figure out number of processors - no governor, turbo check possible"); } else { // check frequency scaling for (long id = 0; id < nprocs; ++id) { auto idStr = detail::fmt::to_s(static_cast(id)); auto sysCpu = "/sys/devices/system/cpu/cpu" + idStr; auto minFreq = parseFile(sysCpu + "/cpufreq/scaling_min_freq"); auto maxFreq = parseFile(sysCpu + "/cpufreq/scaling_max_freq"); if (minFreq != maxFreq) { auto minMHz = static_cast(minFreq) / 1000.0; auto maxMHz = static_cast(maxFreq) / 1000.0; warnings.emplace_back("CPU frequency scaling enabled: CPU " + idStr + " between " + detail::fmt::Number(1, 1, minMHz).to_s() + " and " + detail::fmt::Number(1, 1, maxMHz).to_s() + " MHz"); recommendPyPerf = true; break; } } auto currentGovernor = parseFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); if ("performance" != currentGovernor) { warnings.emplace_back("CPU governor is '" + currentGovernor + "' but should be 'performance'"); recommendPyPerf = true; } if (0 == parseFile("/sys/devices/system/cpu/intel_pstate/no_turbo")) { warnings.emplace_back("Turbo is enabled, CPU frequency will fluctuate"); recommendPyPerf = true; } } # endif if (recommendCheckFlags) { recommendations.emplace_back("Make sure you compile for Release"); } if (recommendPyPerf) { recommendations.emplace_back("Use 'pyperf system tune' before benchmarking. See https://github.com/psf/pyperf"); } } void printStabilityInformationOnce(std::ostream* outStream) { static bool shouldPrint = true; if (shouldPrint && outStream && isWarningsEnabled()) { auto& os = *outStream; shouldPrint = false; std::vector warnings; std::vector recommendations; gatherStabilityInformation(warnings, recommendations); if (warnings.empty()) { return; } os << "Warning, results might be unstable:" << std::endl; for (auto const& w : warnings) { os << "* " << w << std::endl; } os << std::endl << "Recommendations" << std::endl; for (auto const& r : recommendations) { os << "* " << r << std::endl; } } } // remembers the last table settings used. When it changes, a new table header is automatically written for the new entry. uint64_t& singletonHeaderHash() noexcept { static uint64_t sHeaderHash{}; return sHeaderHash; } ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") inline uint64_t hash_combine(uint64_t seed, uint64_t val) { return seed ^ (val + UINT64_C(0x9e3779b9) + (seed << 6U) + (seed >> 2U)); } // determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference. Clock::duration calcClockResolution(size_t numEvaluations) noexcept { auto bestDuration = Clock::duration::max(); Clock::time_point tBegin; Clock::time_point tEnd; for (size_t i = 0; i < numEvaluations; ++i) { tBegin = Clock::now(); do { tEnd = Clock::now(); } while (tBegin == tEnd); bestDuration = (std::min)(bestDuration, tEnd - tBegin); } return bestDuration; } // Calculates clock resolution once, and remembers the result Clock::duration clockResolution() noexcept { static Clock::duration sResolution = calcClockResolution(20); return sResolution; } ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) struct IterationLogic::Impl { enum class State { warmup, upscaling_runtime, measuring, endless }; explicit Impl(Bench const& bench) : mBench(bench) , mResult(bench.config()) { printStabilityInformationOnce(mBench.output()); // determine target runtime per epoch mTargetRuntimePerEpoch = detail::clockResolution() * mBench.clockResolutionMultiple(); if (mTargetRuntimePerEpoch > mBench.maxEpochTime()) { mTargetRuntimePerEpoch = mBench.maxEpochTime(); } if (mTargetRuntimePerEpoch < mBench.minEpochTime()) { mTargetRuntimePerEpoch = mBench.minEpochTime(); } if (isEndlessRunning(mBench.name())) { std::cerr << "NANOBENCH_ENDLESS set: running '" << mBench.name() << "' endlessly" << std::endl; mNumIters = (std::numeric_limits::max)(); mState = State::endless; } else if (0 != mBench.warmup()) { mNumIters = mBench.warmup(); mState = State::warmup; } else if (0 != mBench.epochIterations()) { // exact number of iterations mNumIters = mBench.epochIterations(); mState = State::measuring; } else { mNumIters = mBench.minEpochIterations(); mState = State::upscaling_runtime; } } // directly calculates new iters based on elapsed&iters, and adds a 10% noise. Makes sure we don't underflow. ANKERL_NANOBENCH(NODISCARD) uint64_t calcBestNumIters(std::chrono::nanoseconds elapsed, uint64_t iters) noexcept { auto doubleElapsed = d(elapsed); auto doubleTargetRuntimePerEpoch = d(mTargetRuntimePerEpoch); auto doubleNewIters = doubleTargetRuntimePerEpoch / doubleElapsed * d(iters); auto doubleMinEpochIters = d(mBench.minEpochIterations()); if (doubleNewIters < doubleMinEpochIters) { doubleNewIters = doubleMinEpochIters; } doubleNewIters *= 1.0 + 0.2 * mRng.uniform01(); // +0.5 for correct rounding when casting // NOLINTNEXTLINE(bugprone-incorrect-roundings) return static_cast(doubleNewIters + 0.5); } ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") void upscale(std::chrono::nanoseconds elapsed) { if (elapsed * 10 < mTargetRuntimePerEpoch) { // we are far below the target runtime. Multiply iterations by 10 (with overflow check) if (mNumIters * 10 < mNumIters) { // overflow :-( showResult("iterations overflow. Maybe your code got optimized away?"); mNumIters = 0; return; } mNumIters *= 10; } else { mNumIters = calcBestNumIters(elapsed, mNumIters); } } void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept { # if defined(ANKERL_NANOBENCH_LOG_ENABLED) auto oldIters = mNumIters; # endif switch (mState) { case State::warmup: if (isCloseEnoughForMeasurements(elapsed)) { // if elapsed is close enough, we can skip upscaling and go right to measurements // still, we don't add the result to the measurements. mState = State::measuring; mNumIters = calcBestNumIters(elapsed, mNumIters); } else { // not close enough: switch to upscaling mState = State::upscaling_runtime; upscale(elapsed); } break; case State::upscaling_runtime: if (isCloseEnoughForMeasurements(elapsed)) { // if we are close enough, add measurement and switch to always measuring mState = State::measuring; mTotalElapsed += elapsed; mTotalNumIters += mNumIters; mResult.add(elapsed, mNumIters, pc); mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters); } else { upscale(elapsed); } break; case State::measuring: // just add measurements - no questions asked. Even when runtime is low. But we can't ignore // that fluctuation, or else we would bias the result mTotalElapsed += elapsed; mTotalNumIters += mNumIters; mResult.add(elapsed, mNumIters, pc); if (0 != mBench.epochIterations()) { mNumIters = mBench.epochIterations(); } else { mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters); } break; case State::endless: mNumIters = (std::numeric_limits::max)(); break; } if (static_cast(mResult.size()) == mBench.epochs()) { // we got all the results that we need, finish it showResult(""); mNumIters = 0; } ANKERL_NANOBENCH_LOG(mBench.name() << ": " << detail::fmt::Number(20, 3, static_cast(elapsed.count())) << " elapsed, " << detail::fmt::Number(20, 3, static_cast(mTargetRuntimePerEpoch.count())) << " target. oldIters=" << oldIters << ", mNumIters=" << mNumIters << ", mState=" << static_cast(mState)); } void showResult(std::string const& errorMessage) const { ANKERL_NANOBENCH_LOG(errorMessage); if (mBench.output() != nullptr) { // prepare column data /////// std::vector columns; auto rMedian = mResult.median(Result::Measure::elapsed); if (mBench.relative()) { double d = 100.0; if (!mBench.results().empty()) { d = rMedian <= 0.0 ? 0.0 : mBench.results().front().median(Result::Measure::elapsed) / rMedian * 100.0; } columns.emplace_back(11, 1, "relative", "%", d); } if (mBench.complexityN() > 0) { columns.emplace_back(14, 0, "complexityN", "", mBench.complexityN()); } columns.emplace_back(22, 2, mBench.timeUnitName() + "/" + mBench.unit(), "", rMedian / (mBench.timeUnit().count() * mBench.batch())); columns.emplace_back(22, 2, mBench.unit() + "/s", "", rMedian <= 0.0 ? 0.0 : mBench.batch() / rMedian); double rErrorMedian = mResult.medianAbsolutePercentError(Result::Measure::elapsed); columns.emplace_back(10, 1, "err%", "%", rErrorMedian * 100.0); double rInsMedian = -1.0; if (mResult.has(Result::Measure::instructions)) { rInsMedian = mResult.median(Result::Measure::instructions); columns.emplace_back(18, 2, "ins/" + mBench.unit(), "", rInsMedian / mBench.batch()); } double rCycMedian = -1.0; if (mResult.has(Result::Measure::cpucycles)) { rCycMedian = mResult.median(Result::Measure::cpucycles); columns.emplace_back(18, 2, "cyc/" + mBench.unit(), "", rCycMedian / mBench.batch()); } if (rInsMedian > 0.0 && rCycMedian > 0.0) { columns.emplace_back(9, 3, "IPC", "", rCycMedian <= 0.0 ? 0.0 : rInsMedian / rCycMedian); } if (mResult.has(Result::Measure::branchinstructions)) { double rBraMedian = mResult.median(Result::Measure::branchinstructions); columns.emplace_back(17, 2, "bra/" + mBench.unit(), "", rBraMedian / mBench.batch()); if (mResult.has(Result::Measure::branchmisses)) { double p = 0.0; if (rBraMedian >= 1e-9) { p = 100.0 * mResult.median(Result::Measure::branchmisses) / rBraMedian; } columns.emplace_back(10, 1, "miss%", "%", p); } } columns.emplace_back(12, 2, "total", "", mResult.sumProduct(Result::Measure::iterations, Result::Measure::elapsed)); // write everything auto& os = *mBench.output(); // combine all elements that are relevant for printing the header uint64_t hash = 0; hash = hash_combine(std::hash{}(mBench.unit()), hash); hash = hash_combine(std::hash{}(mBench.title()), hash); hash = hash_combine(std::hash{}(mBench.timeUnitName()), hash); hash = hash_combine(std::hash{}(mBench.timeUnit().count()), hash); hash = hash_combine(std::hash{}(mBench.relative()), hash); hash = hash_combine(std::hash{}(mBench.performanceCounters()), hash); if (hash != singletonHeaderHash()) { singletonHeaderHash() = hash; // no result yet, print header os << std::endl; for (auto const& col : columns) { os << col.title(); } os << "| " << mBench.title() << std::endl; for (auto const& col : columns) { os << col.separator(); } os << "|:" << std::string(mBench.title().size() + 1U, '-') << std::endl; } if (!errorMessage.empty()) { for (auto const& col : columns) { os << col.invalid(); } os << "| :boom: " << fmt::MarkDownCode(mBench.name()) << " (" << errorMessage << ')' << std::endl; } else { for (auto const& col : columns) { os << col.value(); } os << "| "; auto showUnstable = isWarningsEnabled() && rErrorMedian >= 0.05; if (showUnstable) { os << ":wavy_dash: "; } os << fmt::MarkDownCode(mBench.name()); if (showUnstable) { auto avgIters = static_cast(mTotalNumIters) / static_cast(mBench.epochs()); // NOLINTNEXTLINE(bugprone-incorrect-roundings) auto suggestedIters = static_cast(avgIters * 10 + 0.5); os << " (Unstable with ~" << detail::fmt::Number(1, 1, avgIters) << " iters. Increase `minEpochIterations` to e.g. " << suggestedIters << ")"; } os << std::endl; } } } ANKERL_NANOBENCH(NODISCARD) bool isCloseEnoughForMeasurements(std::chrono::nanoseconds elapsed) const noexcept { return elapsed * 3 >= mTargetRuntimePerEpoch * 2; } uint64_t mNumIters = 1; Bench const& mBench; std::chrono::nanoseconds mTargetRuntimePerEpoch{}; Result mResult; Rng mRng{123}; std::chrono::nanoseconds mTotalElapsed{}; uint64_t mTotalNumIters = 0; State mState = State::upscaling_runtime; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) IterationLogic::IterationLogic(Bench const& bench) noexcept : mPimpl(new Impl(bench)) {} IterationLogic::~IterationLogic() { if (mPimpl) { delete mPimpl; } } uint64_t IterationLogic::numIters() const noexcept { ANKERL_NANOBENCH_LOG(mPimpl->mBench.name() << ": mNumIters=" << mPimpl->mNumIters); return mPimpl->mNumIters; } void IterationLogic::add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept { mPimpl->add(elapsed, pc); } void IterationLogic::moveResultTo(std::vector& results) noexcept { results.emplace_back(std::move(mPimpl->mResult)); } # if ANKERL_NANOBENCH(PERF_COUNTERS) ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) class LinuxPerformanceCounters { public: struct Target { Target(uint64_t* targetValue_, bool correctMeasuringOverhead_, bool correctLoopOverhead_) : targetValue(targetValue_) , correctMeasuringOverhead(correctMeasuringOverhead_) , correctLoopOverhead(correctLoopOverhead_) {} uint64_t* targetValue{}; bool correctMeasuringOverhead{}; bool correctLoopOverhead{}; }; ~LinuxPerformanceCounters(); // quick operation inline void start() {} inline void stop() {} bool monitor(perf_sw_ids swId, Target target); bool monitor(perf_hw_id hwId, Target target); bool hasError() const noexcept { return mHasError; } // Just reading data is faster than enable & disabling. // we subtract data ourselves. inline void beginMeasure() { if (mHasError) { return; } // NOLINTNEXTLINE(hicpp-signed-bitwise) mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP); if (mHasError) { return; } // NOLINTNEXTLINE(hicpp-signed-bitwise) mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP); } inline void endMeasure() { if (mHasError) { return; } // NOLINTNEXTLINE(hicpp-signed-bitwise) mHasError = (-1 == ioctl(mFd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP)); if (mHasError) { return; } auto const numBytes = sizeof(uint64_t) * mCounters.size(); auto ret = read(mFd, mCounters.data(), numBytes); mHasError = ret != static_cast(numBytes); } void updateResults(uint64_t numIters); // rounded integer division template static inline T divRounded(T a, T divisor) { return (a + divisor / 2) / divisor; } template ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") void calibrate(Op&& op) { // clear current calibration data, for (auto& v : mCalibratedOverhead) { v = UINT64_C(0); } // create new calibration data auto newCalibration = mCalibratedOverhead; for (auto& v : newCalibration) { v = (std::numeric_limits::max)(); } for (size_t iter = 0; iter < 100; ++iter) { beginMeasure(); op(); endMeasure(); if (mHasError) { return; } for (size_t i = 0; i < newCalibration.size(); ++i) { auto diff = mCounters[i]; if (newCalibration[i] > diff) { newCalibration[i] = diff; } } } mCalibratedOverhead = std::move(newCalibration); { // calibrate loop overhead. For branches & instructions this makes sense, not so much for everything else like cycles. // marsaglia's xorshift: mov, sal/shr, xor. Times 3. // This has the nice property that the compiler doesn't seem to be able to optimize multiple calls any further. // see https://godbolt.org/z/49RVQ5 uint64_t const numIters = 100000U + (std::random_device{}() & 3); uint64_t n = numIters; uint32_t x = 1234567; auto fn = [&]() { x ^= x << 13; x ^= x >> 17; x ^= x << 5; }; beginMeasure(); while (n-- > 0) { fn(); } endMeasure(); detail::doNotOptimizeAway(x); auto measure1 = mCounters; n = numIters; beginMeasure(); while (n-- > 0) { // we now run *twice* so we can easily calculate the overhead fn(); fn(); } endMeasure(); detail::doNotOptimizeAway(x); auto measure2 = mCounters; for (size_t i = 0; i < mCounters.size(); ++i) { // factor 2 because we have two instructions per loop auto m1 = measure1[i] > mCalibratedOverhead[i] ? measure1[i] - mCalibratedOverhead[i] : 0; auto m2 = measure2[i] > mCalibratedOverhead[i] ? measure2[i] - mCalibratedOverhead[i] : 0; auto overhead = m1 * 2 > m2 ? m1 * 2 - m2 : 0; mLoopOverhead[i] = divRounded(overhead, numIters); } } } private: bool monitor(uint32_t type, uint64_t eventid, Target target); std::map mIdToTarget{}; // start with minimum size of 3 for read_format std::vector mCounters{3}; std::vector mCalibratedOverhead{3}; std::vector mLoopOverhead{3}; uint64_t mTimeEnabledNanos = 0; uint64_t mTimeRunningNanos = 0; int mFd = -1; bool mHasError = false; }; ANKERL_NANOBENCH(IGNORE_PADDED_POP) LinuxPerformanceCounters::~LinuxPerformanceCounters() { if (-1 != mFd) { close(mFd); } } bool LinuxPerformanceCounters::monitor(perf_sw_ids swId, LinuxPerformanceCounters::Target target) { return monitor(PERF_TYPE_SOFTWARE, swId, target); } bool LinuxPerformanceCounters::monitor(perf_hw_id hwId, LinuxPerformanceCounters::Target target) { return monitor(PERF_TYPE_HARDWARE, hwId, target); } // overflow is ok, it's checked ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") void LinuxPerformanceCounters::updateResults(uint64_t numIters) { // clear old data for (auto& id_value : mIdToTarget) { *id_value.second.targetValue = UINT64_C(0); } if (mHasError) { return; } mTimeEnabledNanos = mCounters[1] - mCalibratedOverhead[1]; mTimeRunningNanos = mCounters[2] - mCalibratedOverhead[2]; for (uint64_t i = 0; i < mCounters[0]; ++i) { auto idx = static_cast(3 + i * 2 + 0); auto id = mCounters[idx + 1U]; auto it = mIdToTarget.find(id); if (it != mIdToTarget.end()) { auto& tgt = it->second; *tgt.targetValue = mCounters[idx]; if (tgt.correctMeasuringOverhead) { if (*tgt.targetValue >= mCalibratedOverhead[idx]) { *tgt.targetValue -= mCalibratedOverhead[idx]; } else { *tgt.targetValue = 0U; } } if (tgt.correctLoopOverhead) { auto correctionVal = mLoopOverhead[idx] * numIters; if (*tgt.targetValue >= correctionVal) { *tgt.targetValue -= correctionVal; } else { *tgt.targetValue = 0U; } } } } } bool LinuxPerformanceCounters::monitor(uint32_t type, uint64_t eventid, Target target) { *target.targetValue = (std::numeric_limits::max)(); if (mHasError) { return false; } auto pea = perf_event_attr(); std::memset(&pea, 0, sizeof(perf_event_attr)); pea.type = type; pea.size = sizeof(perf_event_attr); pea.config = eventid; pea.disabled = 1; // start counter as disabled pea.exclude_kernel = 1; pea.exclude_hv = 1; // NOLINTNEXTLINE(hicpp-signed-bitwise) pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; const int pid = 0; // the current process const int cpu = -1; // all CPUs # if defined(PERF_FLAG_FD_CLOEXEC) // since Linux 3.14 const unsigned long flags = PERF_FLAG_FD_CLOEXEC; # else const unsigned long flags = 0; # endif auto fd = static_cast(syscall(__NR_perf_event_open, &pea, pid, cpu, mFd, flags)); if (-1 == fd) { return false; } if (-1 == mFd) { // first call: set to fd, and use this from now on mFd = fd; } uint64_t id = 0; // NOLINTNEXTLINE(hicpp-signed-bitwise) if (-1 == ioctl(fd, PERF_EVENT_IOC_ID, &id)) { // couldn't get id return false; } // insert into map, rely on the fact that map's references are constant. mIdToTarget.emplace(id, target); // prepare readformat with the correct size (after the insert) auto size = 3 + 2 * mIdToTarget.size(); mCounters.resize(size); mCalibratedOverhead.resize(size); mLoopOverhead.resize(size); return true; } PerformanceCounters::PerformanceCounters() : mPc(new LinuxPerformanceCounters()) , mVal() , mHas() { mHas.pageFaults = mPc->monitor(PERF_COUNT_SW_PAGE_FAULTS, LinuxPerformanceCounters::Target(&mVal.pageFaults, true, false)); mHas.cpuCycles = mPc->monitor(PERF_COUNT_HW_REF_CPU_CYCLES, LinuxPerformanceCounters::Target(&mVal.cpuCycles, true, false)); mHas.contextSwitches = mPc->monitor(PERF_COUNT_SW_CONTEXT_SWITCHES, LinuxPerformanceCounters::Target(&mVal.contextSwitches, true, false)); mHas.instructions = mPc->monitor(PERF_COUNT_HW_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.instructions, true, true)); mHas.branchInstructions = mPc->monitor(PERF_COUNT_HW_BRANCH_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.branchInstructions, true, false)); mHas.branchMisses = mPc->monitor(PERF_COUNT_HW_BRANCH_MISSES, LinuxPerformanceCounters::Target(&mVal.branchMisses, true, false)); // mHas.branchMisses = false; mPc->start(); mPc->calibrate([] { auto before = ankerl::nanobench::Clock::now(); auto after = ankerl::nanobench::Clock::now(); (void)before; (void)after; }); if (mPc->hasError()) { // something failed, don't monitor anything. mHas = PerfCountSet{}; } } PerformanceCounters::~PerformanceCounters() { if (nullptr != mPc) { delete mPc; } } void PerformanceCounters::beginMeasure() { mPc->beginMeasure(); } void PerformanceCounters::endMeasure() { mPc->endMeasure(); } void PerformanceCounters::updateResults(uint64_t numIters) { mPc->updateResults(numIters); } # else PerformanceCounters::PerformanceCounters() = default; PerformanceCounters::~PerformanceCounters() = default; void PerformanceCounters::beginMeasure() {} void PerformanceCounters::endMeasure() {} void PerformanceCounters::updateResults(uint64_t) {} # endif ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& PerformanceCounters::val() const noexcept { return mVal; } ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& PerformanceCounters::has() const noexcept { return mHas; } // formatting utilities namespace fmt { // adds thousands separator to numbers NumSep::NumSep(char sep) : mSep(sep) {} char NumSep::do_thousands_sep() const { return mSep; } std::string NumSep::do_grouping() const { return "\003"; } // RAII to save & restore a stream's state StreamStateRestorer::StreamStateRestorer(std::ostream& s) : mStream(s) , mLocale(s.getloc()) , mPrecision(s.precision()) , mWidth(s.width()) , mFill(s.fill()) , mFmtFlags(s.flags()) {} StreamStateRestorer::~StreamStateRestorer() { restore(); } // sets back all stream info that we remembered at construction void StreamStateRestorer::restore() { mStream.imbue(mLocale); mStream.precision(mPrecision); mStream.width(mWidth); mStream.fill(mFill); mStream.flags(mFmtFlags); } Number::Number(int width, int precision, int64_t value) : mWidth(width) , mPrecision(precision) , mValue(static_cast(value)) {} Number::Number(int width, int precision, double value) : mWidth(width) , mPrecision(precision) , mValue(value) {} std::ostream& Number::write(std::ostream& os) const { StreamStateRestorer restorer(os); os.imbue(std::locale(os.getloc(), new NumSep(','))); os << std::setw(mWidth) << std::setprecision(mPrecision) << std::fixed << mValue; return os; } std::string Number::to_s() const { std::stringstream ss; write(ss); return ss.str(); } std::string to_s(uint64_t n) { std::string str; do { str += static_cast('0' + static_cast(n % 10)); n /= 10; } while (n != 0); std::reverse(str.begin(), str.end()); return str; } std::ostream& operator<<(std::ostream& os, Number const& n) { return n.write(os); } MarkDownColumn::MarkDownColumn(int w, int prec, std::string const& tit, std::string const& suff, double val) : mWidth(w) , mPrecision(prec) , mTitle(tit) , mSuffix(suff) , mValue(val) {} std::string MarkDownColumn::title() const { std::stringstream ss; ss << '|' << std::setw(mWidth - 2) << std::right << mTitle << ' '; return ss.str(); } std::string MarkDownColumn::separator() const { std::string sep(static_cast(mWidth), '-'); sep.front() = '|'; sep.back() = ':'; return sep; } std::string MarkDownColumn::invalid() const { std::string sep(static_cast(mWidth), ' '); sep.front() = '|'; sep[sep.size() - 2] = '-'; return sep; } std::string MarkDownColumn::value() const { std::stringstream ss; auto width = mWidth - 2 - static_cast(mSuffix.size()); ss << '|' << Number(width, mPrecision, mValue) << mSuffix << ' '; return ss.str(); } // Formats any text as markdown code, escaping backticks. MarkDownCode::MarkDownCode(std::string const& what) { mWhat.reserve(what.size() + 2); mWhat.push_back('`'); for (char c : what) { mWhat.push_back(c); if ('`' == c) { mWhat.push_back('`'); } } mWhat.push_back('`'); } std::ostream& MarkDownCode::write(std::ostream& os) const { return os << mWhat; } std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode) { return mdCode.write(os); } } // namespace fmt } // namespace detail // provide implementation here so it's only generated once Config::Config() = default; Config::~Config() = default; Config& Config::operator=(Config const&) = default; Config& Config::operator=(Config&&) = default; Config::Config(Config const&) = default; Config::Config(Config&&) noexcept = default; // provide implementation here so it's only generated once Result::~Result() = default; Result& Result::operator=(Result const&) = default; Result& Result::operator=(Result&&) = default; Result::Result(Result const&) = default; Result::Result(Result&&) noexcept = default; namespace detail { template inline constexpr typename std::underlying_type::type u(T val) noexcept { return static_cast::type>(val); } } // namespace detail // Result returned after a benchmark has finished. Can be used as a baseline for relative(). Result::Result(Config const& benchmarkConfig) : mConfig(benchmarkConfig) , mNameToMeasurements{detail::u(Result::Measure::_size)} {} void Result::add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc) { using detail::d; using detail::u; double dIters = d(iters); mNameToMeasurements[u(Result::Measure::iterations)].push_back(dIters); mNameToMeasurements[u(Result::Measure::elapsed)].push_back(d(totalElapsed) / dIters); if (pc.has().pageFaults) { mNameToMeasurements[u(Result::Measure::pagefaults)].push_back(d(pc.val().pageFaults) / dIters); } if (pc.has().cpuCycles) { mNameToMeasurements[u(Result::Measure::cpucycles)].push_back(d(pc.val().cpuCycles) / dIters); } if (pc.has().contextSwitches) { mNameToMeasurements[u(Result::Measure::contextswitches)].push_back(d(pc.val().contextSwitches) / dIters); } if (pc.has().instructions) { mNameToMeasurements[u(Result::Measure::instructions)].push_back(d(pc.val().instructions) / dIters); } if (pc.has().branchInstructions) { double branchInstructions = 0.0; // correcting branches: remove branch introduced by the while (...) loop for each iteration. if (pc.val().branchInstructions > iters + 1U) { branchInstructions = d(pc.val().branchInstructions - (iters + 1U)); } mNameToMeasurements[u(Result::Measure::branchinstructions)].push_back(branchInstructions / dIters); if (pc.has().branchMisses) { // correcting branch misses double branchMisses = d(pc.val().branchMisses); if (branchMisses > branchInstructions) { // can't have branch misses when there were branches... branchMisses = branchInstructions; } // assuming at least one missed branch for the loop branchMisses -= 1.0; if (branchMisses < 1.0) { branchMisses = 1.0; } mNameToMeasurements[u(Result::Measure::branchmisses)].push_back(branchMisses / dIters); } } } Config const& Result::config() const noexcept { return mConfig; } inline double calcMedian(std::vector& data) { if (data.empty()) { return 0.0; } std::sort(data.begin(), data.end()); auto midIdx = data.size() / 2U; if (1U == (data.size() & 1U)) { return data[midIdx]; } return (data[midIdx - 1U] + data[midIdx]) / 2U; } double Result::median(Measure m) const { // create a copy so we can sort auto data = mNameToMeasurements[detail::u(m)]; return calcMedian(data); } double Result::average(Measure m) const { using detail::d; auto const& data = mNameToMeasurements[detail::u(m)]; if (data.empty()) { return 0.0; } // create a copy so we can sort return sum(m) / d(data.size()); } double Result::medianAbsolutePercentError(Measure m) const { // create copy auto data = mNameToMeasurements[detail::u(m)]; // calculates MdAPE which is the median of percentage error // see https://www.spiderfinancial.com/support/documentation/numxl/reference-manual/forecasting-performance/mdape auto med = calcMedian(data); // transform the data to absolute error for (auto& x : data) { x = (x - med) / x; if (x < 0) { x = -x; } } return calcMedian(data); } double Result::sum(Measure m) const noexcept { auto const& data = mNameToMeasurements[detail::u(m)]; return std::accumulate(data.begin(), data.end(), 0.0); } double Result::sumProduct(Measure m1, Measure m2) const noexcept { auto const& data1 = mNameToMeasurements[detail::u(m1)]; auto const& data2 = mNameToMeasurements[detail::u(m2)]; if (data1.size() != data2.size()) { return 0.0; } double result = 0.0; for (size_t i = 0, s = data1.size(); i != s; ++i) { result += data1[i] * data2[i]; } return result; } bool Result::has(Measure m) const noexcept { return !mNameToMeasurements[detail::u(m)].empty(); } double Result::get(size_t idx, Measure m) const { auto const& data = mNameToMeasurements[detail::u(m)]; return data.at(idx); } bool Result::empty() const noexcept { return 0U == size(); } size_t Result::size() const noexcept { auto const& data = mNameToMeasurements[detail::u(Measure::elapsed)]; return data.size(); } double Result::minimum(Measure m) const noexcept { auto const& data = mNameToMeasurements[detail::u(m)]; if (data.empty()) { return 0.0; } // here its save to assume that at least one element is there return *std::min_element(data.begin(), data.end()); } double Result::maximum(Measure m) const noexcept { auto const& data = mNameToMeasurements[detail::u(m)]; if (data.empty()) { return 0.0; } // here its save to assume that at least one element is there return *std::max_element(data.begin(), data.end()); } Result::Measure Result::fromString(std::string const& str) { if (str == "elapsed") { return Measure::elapsed; } else if (str == "iterations") { return Measure::iterations; } else if (str == "pagefaults") { return Measure::pagefaults; } else if (str == "cpucycles") { return Measure::cpucycles; } else if (str == "contextswitches") { return Measure::contextswitches; } else if (str == "instructions") { return Measure::instructions; } else if (str == "branchinstructions") { return Measure::branchinstructions; } else if (str == "branchmisses") { return Measure::branchmisses; } else { // not found, return _size return Measure::_size; } } // Configuration of a microbenchmark. Bench::Bench() { mConfig.mOut = &std::cout; } Bench::Bench(Bench&&) = default; Bench& Bench::operator=(Bench&&) = default; Bench::Bench(Bench const&) = default; Bench& Bench::operator=(Bench const&) = default; Bench::~Bench() noexcept = default; double Bench::batch() const noexcept { return mConfig.mBatch; } double Bench::complexityN() const noexcept { return mConfig.mComplexityN; } // Set a baseline to compare it to. 100% it is exactly as fast as the baseline, >100% means it is faster than the baseline, <100% // means it is slower than the baseline. Bench& Bench::relative(bool isRelativeEnabled) noexcept { mConfig.mIsRelative = isRelativeEnabled; return *this; } bool Bench::relative() const noexcept { return mConfig.mIsRelative; } Bench& Bench::performanceCounters(bool showPerformanceCounters) noexcept { mConfig.mShowPerformanceCounters = showPerformanceCounters; return *this; } bool Bench::performanceCounters() const noexcept { return mConfig.mShowPerformanceCounters; } // Operation unit. Defaults to "op", could be e.g. "byte" for string processing. // If u differs from currently set unit, the stored results will be cleared. // Use singular (byte, not bytes). Bench& Bench::unit(char const* u) { if (u != mConfig.mUnit) { mResults.clear(); } mConfig.mUnit = u; return *this; } Bench& Bench::unit(std::string const& u) { return unit(u.c_str()); } std::string const& Bench::unit() const noexcept { return mConfig.mUnit; } Bench& Bench::timeUnit(std::chrono::duration const& tu, std::string const& tuName) { mConfig.mTimeUnit = tu; mConfig.mTimeUnitName = tuName; return *this; } std::string const& Bench::timeUnitName() const noexcept { return mConfig.mTimeUnitName; } std::chrono::duration const& Bench::timeUnit() const noexcept { return mConfig.mTimeUnit; } // If benchmarkTitle differs from currently set title, the stored results will be cleared. Bench& Bench::title(const char* benchmarkTitle) { if (benchmarkTitle != mConfig.mBenchmarkTitle) { mResults.clear(); } mConfig.mBenchmarkTitle = benchmarkTitle; return *this; } Bench& Bench::title(std::string const& benchmarkTitle) { if (benchmarkTitle != mConfig.mBenchmarkTitle) { mResults.clear(); } mConfig.mBenchmarkTitle = benchmarkTitle; return *this; } std::string const& Bench::title() const noexcept { return mConfig.mBenchmarkTitle; } Bench& Bench::name(const char* benchmarkName) { mConfig.mBenchmarkName = benchmarkName; return *this; } Bench& Bench::name(std::string const& benchmarkName) { mConfig.mBenchmarkName = benchmarkName; return *this; } std::string const& Bench::name() const noexcept { return mConfig.mBenchmarkName; } // Number of epochs to evaluate. The reported result will be the median of evaluation of each epoch. Bench& Bench::epochs(size_t numEpochs) noexcept { mConfig.mNumEpochs = numEpochs; return *this; } size_t Bench::epochs() const noexcept { return mConfig.mNumEpochs; } // Desired evaluation time is a multiple of clock resolution. Default is to be 1000 times above this measurement precision. Bench& Bench::clockResolutionMultiple(size_t multiple) noexcept { mConfig.mClockResolutionMultiple = multiple; return *this; } size_t Bench::clockResolutionMultiple() const noexcept { return mConfig.mClockResolutionMultiple; } // Sets the maximum time each epoch should take. Default is 100ms. Bench& Bench::maxEpochTime(std::chrono::nanoseconds t) noexcept { mConfig.mMaxEpochTime = t; return *this; } std::chrono::nanoseconds Bench::maxEpochTime() const noexcept { return mConfig.mMaxEpochTime; } // Sets the maximum time each epoch should take. Default is 100ms. Bench& Bench::minEpochTime(std::chrono::nanoseconds t) noexcept { mConfig.mMinEpochTime = t; return *this; } std::chrono::nanoseconds Bench::minEpochTime() const noexcept { return mConfig.mMinEpochTime; } Bench& Bench::minEpochIterations(uint64_t numIters) noexcept { mConfig.mMinEpochIterations = (numIters == 0) ? 1 : numIters; return *this; } uint64_t Bench::minEpochIterations() const noexcept { return mConfig.mMinEpochIterations; } Bench& Bench::epochIterations(uint64_t numIters) noexcept { mConfig.mEpochIterations = numIters; return *this; } uint64_t Bench::epochIterations() const noexcept { return mConfig.mEpochIterations; } Bench& Bench::warmup(uint64_t numWarmupIters) noexcept { mConfig.mWarmup = numWarmupIters; return *this; } uint64_t Bench::warmup() const noexcept { return mConfig.mWarmup; } Bench& Bench::config(Config const& benchmarkConfig) { mConfig = benchmarkConfig; return *this; } Config const& Bench::config() const noexcept { return mConfig; } Bench& Bench::output(std::ostream* outstream) noexcept { mConfig.mOut = outstream; return *this; } ANKERL_NANOBENCH(NODISCARD) std::ostream* Bench::output() const noexcept { return mConfig.mOut; } std::vector const& Bench::results() const noexcept { return mResults; } Bench& Bench::render(char const* templateContent, std::ostream& os) { ::ankerl::nanobench::render(templateContent, *this, os); return *this; } Bench& Bench::render(std::string const& templateContent, std::ostream& os) { ::ankerl::nanobench::render(templateContent, *this, os); return *this; } std::vector Bench::complexityBigO() const { std::vector bigOs; auto rangeMeasure = BigO::collectRangeMeasure(mResults); bigOs.emplace_back("O(1)", rangeMeasure, [](double) { return 1.0; }); bigOs.emplace_back("O(n)", rangeMeasure, [](double n) { return n; }); bigOs.emplace_back("O(log n)", rangeMeasure, [](double n) { return std::log2(n); }); bigOs.emplace_back("O(n log n)", rangeMeasure, [](double n) { return n * std::log2(n); }); bigOs.emplace_back("O(n^2)", rangeMeasure, [](double n) { return n * n; }); bigOs.emplace_back("O(n^3)", rangeMeasure, [](double n) { return n * n * n; }); std::sort(bigOs.begin(), bigOs.end()); return bigOs; } Rng::Rng() : mX(0) , mY(0) { std::random_device rd; std::uniform_int_distribution dist; do { mX = dist(rd); mY = dist(rd); } while (mX == 0 && mY == 0); } ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") uint64_t splitMix64(uint64_t& state) noexcept { uint64_t z = (state += UINT64_C(0x9e3779b97f4a7c15)); z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9); z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb); return z ^ (z >> 31U); } // Seeded as described in romu paper (update april 2020) Rng::Rng(uint64_t seed) noexcept : mX(splitMix64(seed)) , mY(splitMix64(seed)) { for (size_t i = 0; i < 10; ++i) { operator()(); } } // only internally used to copy the RNG. Rng::Rng(uint64_t x, uint64_t y) noexcept : mX(x) , mY(y) {} Rng Rng::copy() const noexcept { return Rng{mX, mY}; } Rng::Rng(std::vector const& data) : mX(0) , mY(0) { if (data.size() != 2) { throw std::runtime_error("ankerl::nanobench::Rng::Rng: needed exactly 2 entries in data, but got " + std::to_string(data.size())); } mX = data[0]; mY = data[1]; } std::vector Rng::state() const { std::vector data(2); data[0] = mX; data[1] = mY; return data; } BigO::RangeMeasure BigO::collectRangeMeasure(std::vector const& results) { BigO::RangeMeasure rangeMeasure; for (auto const& result : results) { if (result.config().mComplexityN > 0.0) { rangeMeasure.emplace_back(result.config().mComplexityN, result.median(Result::Measure::elapsed)); } } return rangeMeasure; } BigO::BigO(std::string const& bigOName, RangeMeasure const& rangeMeasure) : mName(bigOName) { // estimate the constant factor double sumRangeMeasure = 0.0; double sumRangeRange = 0.0; for (size_t i = 0; i < rangeMeasure.size(); ++i) { sumRangeMeasure += rangeMeasure[i].first * rangeMeasure[i].second; sumRangeRange += rangeMeasure[i].first * rangeMeasure[i].first; } mConstant = sumRangeMeasure / sumRangeRange; // calculate root mean square double err = 0.0; double sumMeasure = 0.0; for (size_t i = 0; i < rangeMeasure.size(); ++i) { auto diff = mConstant * rangeMeasure[i].first - rangeMeasure[i].second; err += diff * diff; sumMeasure += rangeMeasure[i].second; } auto n = static_cast(rangeMeasure.size()); auto mean = sumMeasure / n; mNormalizedRootMeanSquare = std::sqrt(err / n) / mean; } BigO::BigO(const char* bigOName, RangeMeasure const& rangeMeasure) : BigO(std::string(bigOName), rangeMeasure) {} std::string const& BigO::name() const noexcept { return mName; } double BigO::constant() const noexcept { return mConstant; } double BigO::normalizedRootMeanSquare() const noexcept { return mNormalizedRootMeanSquare; } bool BigO::operator<(BigO const& other) const noexcept { return std::tie(mNormalizedRootMeanSquare, mName) < std::tie(other.mNormalizedRootMeanSquare, other.mName); } std::ostream& operator<<(std::ostream& os, BigO const& bigO) { return os << bigO.constant() << " * " << bigO.name() << ", rms=" << bigO.normalizedRootMeanSquare(); } std::ostream& operator<<(std::ostream& os, std::vector const& bigOs) { detail::fmt::StreamStateRestorer restorer(os); os << std::endl << "| coefficient | err% | complexity" << std::endl << "|--------------:|-------:|------------" << std::endl; for (auto const& bigO : bigOs) { os << "|" << std::setw(14) << std::setprecision(7) << std::scientific << bigO.constant() << " "; os << "|" << detail::fmt::Number(6, 1, bigO.normalizedRootMeanSquare() * 100.0) << "% "; os << "| " << bigO.name(); os << std::endl; } return os; } } // namespace nanobench } // namespace ankerl #endif // ANKERL_NANOBENCH_IMPLEMENT #endif // ANKERL_NANOBENCH_H_INCLUDED ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/codecov.yml0000644000175100017470000000050214471613520015475 0ustar00runnerdockercodecov: 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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/0000755000175100017470000000000014471613541014266 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/Makefile0000644000175100017470000000113514471613520015723 0ustar00runnerdocker# 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)././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/README.md0000644000175100017470000000034714471613520015546 0ustar00runnerdockerBuilding 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`). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/make.bat0000644000175100017470000000145414471613520015674 0ustar00runnerdocker@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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/requirements.txt0000644000175100017470000000005214471613520017544 0ustar00runnerdockersphinx>=4 sphinx-rtd-theme>=1 sphinx-tabs ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/source/0000755000175100017470000000000014471613541015566 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/source/api/0000755000175100017470000000000014471613541016337 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/api/cpp.rst0000644000175100017470000000007214471613520017647 0ustar00runnerdockerKiwisolver C++ API ================== Under construction ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/api/index.rst0000644000175100017470000000014514471613520020175 0ustar00runnerdockerkiwisolver ========== .. toctree:: :maxdepth: 2 Python API C++ API ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/api/python.rst0000644000175100017470000000020114471613520020400 0ustar00runnerdockerKiwisolver Python API ===================== .. automodule:: kiwisolver :members: :undoc-members: :show-inheritance: ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/source/basis/0000755000175100017470000000000014471613541016667 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/basis/basic_systems.rst0000644000175100017470000001402714471613520022272 0ustar00runnerdocker.. _basis-basic-systems: Constraints definition and system solving ========================================= .. include:: ../substitutions.sub A system in Kiwi is defined by a set of constraints that can be either equalities or inequalities (>= and <= only, strict inequalities are not accepted), each of which can have an associated strength making more or less important to respect when solving the problem. The next sections will cover how to define those constraints and extract the result from the solver. Defining variables and constraints ---------------------------------- The first things that need to be defined are variables. Variables represent the values which the solver will be trying to determine. Variables are represented by |Variable| objects which can be created as follow: .. tabs:: .. code-tab:: python from kiwisolver import Variable x1 = Variable('x1') x2 = Variable('x2') xm = Variable('xm') .. code-tab:: c++ #include 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} }; The next step is to add them to our solver, which is 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 do not have to create all your variables before starting adding constraints to the solver. 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) supports to have redundant constraints, meaning that even if having two constraints (x == 10, x + y == 30) is equivalent to a third one (y == 20), all three can be added to the solver without issue. However, one should not add multiple times the same constraint (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 defines three standard strengths in addition of the "required" strength: strong, medium, weak. A strong constraint will always win over a medium constraints which will always win over a weak constraint [#f1]_ . Lets assume than in our example x1 would like to be at 40, but it does not have to. This is translated as follow: .. 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` lets say. This is a problem. In a real application (for 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 variable 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 gives 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 make for faster updates and allow 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 and as a consequence we get the following solution: ``xm == 90, x1 == 80, x2 == 100`` .. note:: To know if a non-required constraint was violated when solving the system, you can use the constraint ``violated`` method. .. versionadded:: 1.4 Footnotes --------- .. [#f1] Actually there are some corner cases in which this can be violated. See :ref:`basics-internals` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/basis/index.rst0000644000175100017470000000124614471613520020530 0ustar00runnerdocker.. _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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/basis/installation.rst0000644000175100017470000000514314471613520022122 0ustar00runnerdocker.. _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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/basis/solver_internals.rst0000644000175100017470000001456014471613520023015 0ustar00runnerdocker.. _basics-internals: Solver internals and tips ========================= .. include:: ../substitutions.sub Kiwi is not a simple re-writing of Cassowary and because of that Kiwi does not always perfectly reflects the original implementation. The following sections points out those "discrepancies" and give some tips on how to work 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 usually well constrained and stay constraints are hence 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 of the required strength by default: "weak", "medium", "strong". Contrary to Cassowary, which uses lexicographic ordering to ensure that strength are always respected, Kiwi strives for speed and use simple floating point numbers. .. note:: Using simple floating point, means that is some rare corner case a large number of weak constraints may overcome a medium constraint. However in practice this rarely happens. Kiwi allows to create custom strength 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 corresponds 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 differentiate 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 number, 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 constraints 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 causing large memory leaks, it is recommended to reset the solver state (using the method of the same name) and 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 speedups over std::map. Symbol representation ^^^^^^^^^^^^^^^^^^^^^ Symbol are used in Kiwi to represent the state of the solver. Since solving the system requires a large number of manipulation of the symbols the operations have to compile down to an efficient representation. In Kiwi, symbols compile down to long long meaning that a vector of them fits in a CPU cache line. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/conf.py0000644000175100017470000001214214471613520017062 0ustar00runnerdocker# -*- 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", ), ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/source/developer_notes/0000755000175100017470000000000014471613541020763 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/developer_notes/index.rst0000644000175100017470000000200014471613520022611 0ustar00runnerdocker.. _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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/index.rst0000644000175100017470000000213414471613520017424 0ustar00runnerdocker.. 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 Use cases Developer notes API Documentation Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/substitutions.sub0000644000175100017470000000110214471613520021227 0ustar00runnerdocker.. 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` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/docs/source/use_cases/0000755000175100017470000000000014471613541017540 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/use_cases/enaml.rst0000644000175100017470000001536614471613520021376 0ustar00runnerdocker.. _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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/use_cases/enaml_hbox.svg0000644000175100017470000004155414471613520022403 0ustar00runnerdocker Widget 1 Widget 2 Widget 3 top margin bottom margin left margin right margin Container ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/docs/source/use_cases/index.rst0000644000175100017470000000042014471613520021372 0ustar00runnerdocker.. _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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9473426 kiwisolver-1.4.5/kiwi/0000755000175100017470000000000014471613541014301 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/AssocVector.h0000644000175100017470000002740414471613520016711 0ustar00runnerdocker//////////////////////////////////////////////////////////////////////////////// // 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 #include #include #include namespace Loki { //////////////////////////////////////////////////////////////////////////////// // class template AssocVectorCompare // Used by AssocVector //////////////////////////////////////////////////////////////////////////////// namespace Private { template class AssocVectorCompare : public C { typedef std::pair 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 not std::pair // * iterators are random //////////////////////////////////////////////////////////////////////////////// template < class K, class V, class C = std::less, class A = std::allocator< std::pair > > class AssocVector : private std::vector< std::pair, A > , private Private::AssocVectorCompare { typedef std::vector, A> Base; typedef Private::AssocVectorCompare 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 , 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 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 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 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 equal_range(const key_type& k) { MyCompare& me = *this; return std::equal_range(begin(), end(), k, me); } std::pair equal_range( const key_type& k) const { const MyCompare& me = *this; return std::equal_range(begin(), end(), k, me); } template friend bool operator==(const AssocVector& lhs, const AssocVector& rhs); bool operator<(const AssocVector& rhs) const { const Base& me = *this; const Base& yo = rhs; return me < yo; } template friend bool operator!=(const AssocVector& lhs, const AssocVector& rhs); template friend bool operator>(const AssocVector& lhs, const AssocVector& rhs); template friend bool operator>=(const AssocVector& lhs, const AssocVector& rhs); template friend bool operator<=(const AssocVector& lhs, const AssocVector& rhs); }; template inline bool operator==(const AssocVector& lhs, const AssocVector& rhs) { const std::vector, A>& me = lhs; return me == rhs; } template inline bool operator!=(const AssocVector& lhs, const AssocVector& rhs) { return !(lhs == rhs); } template inline bool operator>(const AssocVector& lhs, const AssocVector& rhs) { return rhs < lhs; } template inline bool operator>=(const AssocVector& lhs, const AssocVector& rhs) { return !(lhs < rhs); } template inline bool operator<=(const AssocVector& lhs, const AssocVector& rhs) { return !(rhs < lhs); } // specialized algorithms: template void swap(AssocVector& lhs, AssocVector& rhs) { lhs.swap(rhs); } } // namespace Loki ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/constraint.h0000644000175100017470000000716614471613520016645 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #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 vars; for (const auto & term : expr.terms()) vars[term.variable()] += term.coefficient(); std::vector 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 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/debug.h0000644000175100017470000001064414471613520015542 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #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 &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 void dump(const T &value) { impl::DebugHelper::dump(value, std::cout); } template void dump(const T &value, std::ostream &out) { impl::DebugHelper::dump(value, out); } template std::string dumps(const T &value) { std::stringstream stream; impl::DebugHelper::dump(value, stream); return stream.str(); } } // namespace debug } // namespace kiwi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/errors.h0000644000175100017470000000635214471613520015771 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/expression.h0000644000175100017470000000277114471613520016655 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #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 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 &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 m_terms; double m_constant; }; } // namespace kiwi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/kiwi.h0000644000175100017470000000113114471613520015406 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/maptype.h0000644000175100017470000000157214471613520016133 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #include #include "AssocVector.h" namespace kiwi { namespace impl { template < typename K, typename V, typename C = std::less, typename A = std::allocator>> using MapType = Loki::AssocVector; // template< // typename K, // typename V, // typename C = std::less, // typename A = std::allocator< std::pair > > // using MapType = std::map; } // namespace impl } // namespace kiwi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/row.h0000644000175100017470000001075614471613520015267 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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; 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/shareddata.h0000644000175100017470000000663514471613520016561 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 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 &other) const { return m_data < other.m_data; } bool operator==(const SharedDataPtr &other) const { return m_data == other.m_data; } bool operator!=(const SharedDataPtr &other) const { return m_data != other.m_data; } SharedDataPtr(const SharedDataPtr &other) : m_data(other.m_data) { incref(m_data); } SharedDataPtr(SharedDataPtr&& other) noexcept : m_data(other.m_data) { other.m_data = nullptr; } SharedDataPtr &operator=(const SharedDataPtr &other) { if (m_data != other.m_data) { T *temp = m_data; m_data = other.m_data; incref(m_data); decref(temp); } return *this; } SharedDataPtr& operator=(SharedDataPtr&& 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 &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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/solver.h0000644000175100017470000000717414471613520015772 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/solverimpl.h0000644000175100017470000005316714471613520016657 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #include #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; using RowMap = MapType; using CnMap = MapType; using EditMap = MapType; 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 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 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 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 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 createRow( const Constraint& constraint, Tag& tag ) { const Expression& expr( constraint.expression() ); std::unique_ptr 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 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::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::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::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 m_infeasible_rows; std::unique_ptr m_objective; std::unique_ptr m_artificial; Symbol::Id m_id_tick; }; } // namespace impl } // namespace kiwi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/strength.h0000644000175100017470000000205314471613520016305 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/symbol.h0000644000175100017470000000177314471613520015764 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/symbolics.h0000644000175100017470000002710614471613520016461 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #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 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 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 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/term.h0000644000175100017470000000224514471613520015421 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #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& 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/util.h0000644000175100017470000000106614471613520015427 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/variable.h0000644000175100017470000000561514471613520016243 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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 m_context; double m_value; private: VariableData(const VariableData &other); VariableData &operator=(const VariableData &other); }; SharedDataPtr m_data; friend bool operator<(const Variable &lhs, const Variable &rhs) { return lhs.m_data < rhs.m_data; } }; } // namespace kiwi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/kiwi/version.h0000644000175100017470000000100314471613520016126 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/lint_requirements.txt0000644000175100017470000000007714471613520017651 0ustar00runnerdockerblack flake8 isort mypy # Allow to lint tests using mypy pytest././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9433424 kiwisolver-1.4.5/py/0000755000175100017470000000000014471613541013766 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/py/kiwisolver/0000755000175100017470000000000014471613541016164 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/kiwisolver/__init__.py0000644000175100017470000000176514471613520020303 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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__", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/kiwisolver/_cext.pyi0000644000175100017470000002073614471613520020016 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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, / # noqa ) -> 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, / # noqa ) -> 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.""" ... ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/kiwisolver/exceptions.py0000644000175100017470000000232514471613520020716 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/kiwisolver/py.typed0000644000175100017470000000000014471613520017646 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/py/kiwisolver.egg-info/0000755000175100017470000000000014471613541017656 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/kiwisolver.egg-info/PKG-INFO0000644000175100017470000001427614471613540020764 0ustar00runnerdockerMetadata-Version: 2.1 Name: kiwisolver Version: 1.4.5 Summary: A fast implementation of the Cassowary constraint solver Author-email: The Nucleic Development Team Maintainer-email: "Matthieu C. Dartiailh" License: ========================= 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. #------------------------------------------------------------------------------ Project-URL: homepage, https://github.com/nucleic/kiwi Project-URL: documentation, https://kiwisolver.readthedocs.io/en/latest/ Project-URL: repository, https://github.com/nucleic/kiwi Project-URL: changelog, https://github.com/nucleic/kiwi/blob/main/releasenotes.rst Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE Welcome to Kiwi =============== .. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=main :target: https://travis-ci.org/nucleic/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 `_. 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+. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/kiwisolver.egg-info/SOURCES.txt0000644000175100017470000000345514471613540021550 0ustar00runnerdocker.flake8 .gitignore .pre-commit-config.yaml .readthedocs.yaml .travis.yml LICENSE MANIFEST.in README.rst codecov.yml lint_requirements.txt pyproject.toml releasenotes.rst setup.py .github/FUNDING.yml .github/workflows/ci.yml .github/workflows/docs.yml .github/workflows/release.yml benchmarks/README.rst benchmarks/build_and_run_bench.sh benchmarks/enaml_like_benchmark.cpp benchmarks/enaml_like_benchmark.py benchmarks/nanobench.h docs/Makefile docs/README.md docs/make.bat docs/requirements.txt docs/source/conf.py docs/source/index.rst docs/source/substitutions.sub docs/source/api/cpp.rst docs/source/api/index.rst docs/source/api/python.rst docs/source/basis/basic_systems.rst docs/source/basis/index.rst docs/source/basis/installation.rst docs/source/basis/solver_internals.rst docs/source/developer_notes/index.rst docs/source/use_cases/enaml.rst docs/source/use_cases/enaml_hbox.svg docs/source/use_cases/index.rst kiwi/AssocVector.h kiwi/constraint.h kiwi/debug.h kiwi/errors.h kiwi/expression.h kiwi/kiwi.h kiwi/maptype.h kiwi/row.h kiwi/shareddata.h kiwi/solver.h kiwi/solverimpl.h kiwi/strength.h kiwi/symbol.h kiwi/symbolics.h kiwi/term.h kiwi/util.h kiwi/variable.h kiwi/version.h py/kiwisolver/__init__.py py/kiwisolver/_cext.pyi py/kiwisolver/exceptions.py py/kiwisolver/py.typed py/kiwisolver.egg-info/PKG-INFO py/kiwisolver.egg-info/SOURCES.txt py/kiwisolver.egg-info/dependency_links.txt py/kiwisolver.egg-info/requires.txt py/kiwisolver.egg-info/top_level.txt py/src/constraint.cpp py/src/expression.cpp py/src/kiwisolver.cpp py/src/solver.cpp py/src/strength.cpp py/src/symbolics.h py/src/term.cpp py/src/types.h py/src/util.h py/src/variable.cpp py/src/version.h py/tests/test_constraint.py py/tests/test_expression.py py/tests/test_solver.py py/tests/test_strength.py py/tests/test_term.py py/tests/test_variable.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/kiwisolver.egg-info/dependency_links.txt0000644000175100017470000000000114471613540023723 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/kiwisolver.egg-info/requires.txt0000644000175100017470000000005514471613540022255 0ustar00runnerdocker [:python_version < "3.8"] typing_extensions ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/kiwisolver.egg-info/top_level.txt0000644000175100017470000000001714471613540022405 0ustar00runnerdockerkiwisolver src ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/py/src/0000755000175100017470000000000014471613541014555 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/constraint.cpp0000644000175100017470000001464514471613520017454 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #include #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(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(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(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(item); stream << term->coefficient << " * "; stream << reinterpret_cast(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(pyoldcn); Constraint *newcn = reinterpret_cast(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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/expression.cpp0000644000175100017470000001541514471613520017463 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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( 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( 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( item ); stream << term->coefficient << " * "; stream << reinterpret_cast( 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( item ); Variable* pyvar = reinterpret_cast( term->variable ); result += term->coefficient * pyvar->variable.value(); } return PyFloat_FromDouble( result ); } PyObject* Expression_add( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Expression_sub( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Expression_mul( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Expression_div( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Expression_neg( PyObject* value ) { return UnaryInvoke()( value ); } PyObject* Expression_richcmp( PyObject* first, PyObject* second, int op ) { switch( op ) { case Py_EQ: return BinaryInvoke()( first, second ); case Py_LE: return BinaryInvoke()( first, second ); case Py_GE: return BinaryInvoke()( 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/kiwisolver.cpp0000644000175100017470000000742214471613520017461 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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( 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 ); } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/solver.cpp0000644000175100017470000002031414471613520016570 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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( 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( 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( 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( 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( 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( 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( 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( 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/strength.cpp0000644000175100017470000000644614471613520017126 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/symbolics.h0000644000175100017470000003314314471613520016733 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include "types.h" #include "util.h" namespace kiwisolver { template struct UnaryInvoke { PyObject* operator()( PyObject* value ) { return Op()( reinterpret_cast( value ) ); } }; template struct BinaryInvoke { PyObject* operator()( PyObject* first, PyObject* second ) { if( T::TypeCheck( first ) ) return invoke( reinterpret_cast( first ), second ); return invoke( reinterpret_cast( second ), first ); } struct Normal { template PyObject* operator()( T* primary, U secondary ) { return Op()( primary, secondary ); } }; struct Reverse { template PyObject* operator()( T* primary, U secondary ) { return Op()( secondary, primary ); } }; template PyObject* invoke( T* primary, PyObject* secondary ) { if( Expression::TypeCheck( secondary ) ) return Invk()( primary, reinterpret_cast( secondary ) ); if( Term::TypeCheck( secondary ) ) return Invk()( primary, reinterpret_cast( secondary ) ); if( Variable::TypeCheck( secondary ) ) return Invk()( primary, reinterpret_cast( 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 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( 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( 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( 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( 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 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 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 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( temp.get() ) ); } template 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( 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 PyObject* operator()( T first, U second ) { return makecn( first, second, kiwi::OP_EQ ); } }; struct CmpLE { template PyObject* operator()( T first, U second ) { return makecn( first, second, kiwi::OP_LE ); } }; struct CmpGE { template PyObject* operator()( T first, U second ) { return makecn( first, second, kiwi::OP_GE ); } }; } // namespace kiwisolver ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/term.cpp0000644000175100017470000001263314471613520016232 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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( 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( 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( 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( self->variable ); return PyFloat_FromDouble( self->coefficient * pyvar->variable.value() ); } PyObject* Term_add( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Term_sub( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Term_mul( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Term_div( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Term_neg( PyObject* value ) { return UnaryInvoke()( value ); } PyObject* Term_richcmp( PyObject* first, PyObject* second, int op ) { switch( op ) { case Py_EQ: return BinaryInvoke()( first, second ); case Py_LE: return BinaryInvoke()( first, second ); case Py_GE: return BinaryInvoke()( 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/types.h0000644000175100017470000000432714471613520016075 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/util.h0000644000175100017470000001240214471613520015677 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #include #include #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& coeffs ) { typedef std::map::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( 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( pyexpr ); std::map 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( 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( 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( pyexpr ); std::vector 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( item ); Variable* var = reinterpret_cast( 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/src/variable.cpp0000644000175100017470000001444214471613520017050 0ustar00runnerdocker/*----------------------------------------------------------------------------- | 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 #include #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( kwlist ), &name, &context ) ) return 0; cppy::ptr pyvar( PyType_GenericNew( type, args, kwargs ) ); if( !pyvar ) return 0; Variable* self = reinterpret_cast( 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()( first, second ); } PyObject* Variable_sub( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Variable_mul( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Variable_div( PyObject* first, PyObject* second ) { return BinaryInvoke()( first, second ); } PyObject* Variable_neg( PyObject* value ) { return UnaryInvoke()( value ); } PyObject* Variable_richcmp( PyObject* first, PyObject* second, int op ) { switch( op ) { case Py_EQ: return BinaryInvoke()( first, second ); case Py_LE: return BinaryInvoke()( first, second ); case Py_GE: return BinaryInvoke()( 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866400.0 kiwisolver-1.4.5/py/src/version.h0000644000175100017470000000072014471613540016411 0ustar00runnerdocker/* ---------------------------------------------------------------------------- | 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. | ---------------------------------------------------------------------------*/ // This file is auto-generated by setuptools-scm do NOT edit it. #pragma once #define PY_KIWI_VERSION "1.4.5" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/py/tests/0000755000175100017470000000000014471613541015130 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_constraint.py0000644000175100017470000000477114471613520020733 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_expression.py0000644000175100017470000001654614471613520020751 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_solver.py0000644000175100017470000001704714471613520020061 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_strength.py0000644000175100017470000000202414471613520020372 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_term.py0000644000175100017470000001267114471613520017514 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/py/tests/test_variable.py0000644000175100017470000001046114471613520020325 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/pyproject.toml0000644000175100017470000000531114471613520016247 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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 :: 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"] exclude = ["tests"] [tool.setuptools_scm] write_to = "py/src/version.h" write_to_template = """ /* ---------------------------------------------------------------------------- | 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. | ---------------------------------------------------------------------------*/ // This file is auto-generated by setuptools-scm do NOT edit it. #pragma once #define PY_KIWI_VERSION "{version}" """ [tool.black] line-length = 88 # Enforce the default value [tool.pytest.ini_options] minversion = "6.0" [tool.mypy] follow_imports = "normal" strict_optional = true [tool.isort] multi_line_output = 3 include_trailing_comma = true combine_as_imports = true force_grid_wrap = 0 use_parentheses = true line_length = 88 known_first_party = "kiwisolver" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/releasenotes.rst0000644000175100017470000000742714471613520016570 0ustar00runnerdockerKiwi 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1692866400.9513426 kiwisolver-1.4.5/setup.cfg0000644000175100017470000000004614471613541015157 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1692866384.0 kiwisolver-1.4.5/setup.py0000644000175100017470000000261114471613520015045 0ustar00runnerdocker# -------------------------------------------------------------------------------------- # 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}, )