pax_global_header00006660000000000000000000000064152145071160014514gustar00rootroot0000000000000052 comment=20002307cdff6f566b2bff87ade13e3d04e531d3 edge-addition-planarity-suite-Version_5.0.0.0/000077500000000000000000000000001521450711600212065ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/.editorconfig000066400000000000000000000003621521450711600236640ustar00rootroot00000000000000# From https://stackoverflow.com/a/42136008 # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true edge-addition-planarity-suite-Version_5.0.0.0/.gitattributes000066400000000000000000000002431521450711600241000ustar00rootroot00000000000000# From https://stackoverflow.com/a/42136008 # See also https://docs.github.com/en/get-started/git-basics/configuring-git-to-handle-line-endings * text=auto eol=lf edge-addition-planarity-suite-Version_5.0.0.0/.github/000077500000000000000000000000001521450711600225465ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001521450711600247315ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000020471521450711600274260ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' type: Bug --- ## Describe the bug _A clear and concise description of the defect._ ## Reproduction steps 1. _Update with reproduction steps_ ## Expected behavior _A clear and concise description of what you expected to happen._ ## Terminal logs _If applicable, upload a logfile of your terminal output to help explain your problem._ ## Environment - **OS** [e.g. Debian Linux Trixie 13.4] - Installation mechanism: - [ ] Via system package manager (Linux only) - Paste **`planarity` version** here: ``` > planarity -h ... ``` - [ ] Built from Source - Paste **compiler version information** here: ``` > gcc --version ... ``` - **Python Version** _(If applicable)_ [e.g. 3.14.3] ## Additional context _Add any other context about the problem here, including issues or bugs on related projects and supporting documentation._ edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341521450711600267160ustar00rootroot00000000000000blank_issues_enabled: false edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011051521450711600304530ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' type: Feature --- ## Problem statement _A clear and concise description of the problem._ ## Proposed solution _A clear and concise description of a proposed solution and how it resolves the problem._ ### Alternative solutions _A clear and concise description of any alternative solutions or features you've considered to resolve the problem._ ## Additional context _Add any other context, links to documentation, logs, or screenshots about the feature request here._ edge-addition-planarity-suite-Version_5.0.0.0/.github/dependabot.yml000066400000000000000000000007631521450711600254040ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" cooldown: default-days: 7 edge-addition-planarity-suite-Version_5.0.0.0/.github/pull_request_template.md000066400000000000000000000022471521450711600275140ustar00rootroot00000000000000Resolves #__**ISSUE NUMBER**__ ## Type of change Please indicate the type of change by deleting non-relevant list items below: - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - This change requires a documentation update ## Changes ### Added * _Replace with paths to files added, and brief summary of the purpose these new files satisfy with respect to the original Issue_ ### Updated * _Replace with paths to files updated, and brief summary of the purpose these changes satisfy with respect to the original Issue_ ### Removed * _Replace with paths to files removed, and brief summary of the why these files were removed with respect to the original Issue_ ## Testing - _Outline manual or automated tests performed and how it relates to the particular feature added or functionality changed_ - _Upload logs or provide relevant snippets of terminal output to demo functionality_
Replace with high-level description of log snippets ``` Paste terminal log snippets here ```
edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/000077500000000000000000000000001521450711600246035ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/compile-warnings.yml000066400000000000000000000016621521450711600306110ustar00rootroot00000000000000name: Compiler warnings on: workflow_dispatch: pull_request: branches: [ '**' ] paths-ignore: - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: name: ${{ matrix.compiler }} timeout-minutes: 60 runs-on: ubuntu-latest env: CC: ${{ matrix.compiler }} CFLAGS: -Werror strategy: fail-fast: false matrix: compiler: [clang-18, gcc-14] steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Compiler information . . . run: $CC --version - name: Configure . . . run: | mkdir -p m4 autoreconf -if ./configure --enable-compile-warnings CC="$CC" CFLAGS="$CFLAGS" - name: Build . . . run: make -j4 # We don't run the tests, since in this job we are only checking for # compiler warnings edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/compiler-versions.yml000066400000000000000000000034511521450711600310110ustar00rootroot00000000000000name: Compiler Version Compatibility # Currently, we are prioritizing gcc and clang for maximum version compatibility on: workflow_dispatch: pull_request: branches: [ '**' ] paths-ignore: - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: name: ${{ matrix.sys.compiler }}-${{ matrix.sys.version }} timeout-minutes: 60 runs-on: ubuntu-latest strategy: fail-fast: false matrix: sys: - { compiler: gcc, version: 9 } - { compiler: gcc, version: 10 } - { compiler: gcc, version: 11 } - { compiler: gcc, version: 12 } - { compiler: gcc, version: 13 } - { compiler: gcc, version: 14 } - { compiler: clang, version: 14 } - { compiler: clang, version: 15 } - { compiler: clang, version: 16 } - { compiler: clang, version: 17 } - { compiler: clang, version: 18 } - { compiler: clang, version: 19 } steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup compiler . . . run: | sudo add-apt-repository ppa:ubuntu-toolchain-r/ppa -y sudo apt-get --yes update sudo apt-get install ${{ matrix.sys.compiler }}-${{ matrix.sys.version }} CC=${{ matrix.sys.compiler }}-${{ matrix.sys.version }} echo "CC=$CC" >> $GITHUB_ENV - name: Compiler information run: | echo $CC $CC --version - name: Configure . . . env: CC: ${{ env.CC }} run: | mkdir -p m4 autoreconf -if ./configure CC="$CC" - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/dev-platforms.yml000066400000000000000000000146111521450711600301140ustar00rootroot00000000000000name: Dev Platforms # This comprises a non-exhaustive list of supported development platforms on: workflow_dispatch: pull_request: branches: [ '**' ] paths-ignore: - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: aarch64: name: Ubuntu-aarch64 runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0 with: arch: aarch64 distro: ubuntu22.04 run: | apt-get --yes update apt-get install autoconf automake libtool build-essential --yes gcc --version autoreconf -if && ./configure make -j4 ./test-samples.sh alpine: name: Alpine defaults: run: shell: alpine.sh {0} runs-on: ubuntu-latest steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: jirutka/setup-alpine@ae3b3ddba35054804fc4a3507b519fa7e8152050 # v1.4.1 with: branch: v3.15 - name: Alpine/musl versions . . . run: | cat /etc/alpine-release apk info musl - name: Install autotools etc . . . run: | apk add build-base autoconf automake libtool shell: alpine.sh --root {0} - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh arm: name: Ubuntu-arm runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install autotools etc . . . run: sudo apt-get install autoconf automake libtool - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh debian: name: Debian runs-on: ubuntu-latest container: image: debian:latest steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install autotools etc . . . run: apt update && apt -y upgrade && apt install -y make autoconf automake libtool - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh cygwin: name: Cygwin defaults: run: shell: C:\cygwin\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}' timeout-minutes: 60 runs-on: windows-latest steps: - run: git config --global core.autocrlf input shell: pwsh -command ". '{0}'". - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.1 with: packages: gcc-core,automake,libtool,autoconf - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh macos: name: macOS timeout-minutes: 60 runs-on: macOS-latest env: CC: clang steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install dependencies . . . run: brew install autoconf automake libtool - name: Clang version . . . run: clang --version - name: Configure . . . run: | mkdir -p m4 autoreconf -if ./configure CC="$CC" - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh msvc: name: Windows MSVC cl timeout-minutes: 60 runs-on: windows-latest strategy: fail-fast: false matrix: arch: [x64, x86, x86_amd64, amd64_x86] steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup MSVC Developer Command Prompt uses: TheMrMilchmann/setup-msvc-dev@79dac248aac9d0059f86eae9d8b5bfab4e95e97c # v4.0.0 with: arch: ${{ matrix.arch }} # NOTE: Using defaults for sdk (use default Windows SDK), toolset (use # default compiler toolset), uwp (not building for Windows Store, so no # need for Universal Windows Platform support), nor spectre (not using # VS tools with spectre mitigations) Also, not setting vs-path so we # always use the latest - name: Build using MSVC cl run: | cl /O2 /nologo /Wall /wd4464 /wd4710 /wd4711 /wd4774 /wd4996 /wd5045 ` /wd4068 /wd4100 /wd4820 /external:anglebrackets /external:W0 ` /Fe./planarity.exe ./c/planarityApp/**.c ./c/graphLib/**.c ` ./c/graphLib/extensionSystem/**.c ./c/graphLib/homeomorphSearch/**.c ` ./c/graphLib/io/**.c ./c/graphLib/lowLevelUtils/**.c ` ./c/graphLib/planarityRelated/**.c - name: Run the tests... run: ./planarity.exe -test ./c/samples/ msys: name: Windows-${{ matrix.sys }} defaults: run: shell: msys2 {0} timeout-minutes: 60 runs-on: windows-latest strategy: fail-fast: false matrix: sys: [MINGW64, MINGW32, UCRT64, CLANG64] steps: - uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1 with: update: true msystem: ${{ matrix.sys }} pacboy: toolchain autotools - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh msys-arm: name: Windows-CLANGARM64 defaults: run: shell: msys2 {0} runs-on: windows-11-arm steps: - uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1 with: update: true msystem: CLANGARM64 pacboy: toolchain autotools - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Configure . . . run: autoreconf -if && ./configure - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/runtests.yml000066400000000000000000000024441521450711600272210ustar00rootroot00000000000000name: Tests on: workflow_dispatch: pull_request: branches: [ '**' ] paths-ignore: - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: basic-tests: name: ${{ matrix.compiler }} timeout-minutes: 60 runs-on: ubuntu-latest env: CC: ${{ matrix.compiler }} strategy: fail-fast: false matrix: compiler: [clang, gcc] steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: test run: | echo ${{ github.event_name }} echo ${{ github.event_name == 'pull_request' }} - name: Configure . . . run: | mkdir -p m4 autoreconf -if ./configure CC="$CC" - name: Build . . . run: make -j4 - name: Run the tests . . . run: ./test-samples.sh distcheck: timeout-minutes: 15 runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' }} env: CC: gcc steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Configure . . . run: | mkdir -p m4 autoreconf -if ./configure CC="$CC" - name: make distcheck . . . run: make distcheck -j4 edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/sanitisers.yml000066400000000000000000000040111521450711600275060ustar00rootroot00000000000000name: Sanitisers on: workflow_dispatch: pull_request: branches: [ '**' ] paths-ignore: - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: sanitisers: timeout-minutes: 60 runs-on: ubuntu-latest env: CC: clang CFLAGS: -fno-omit-frame-pointer -g -O1 strategy: matrix: type: [address, undefined] include: - type: undefined environment_variables: UBSAN_OPTIONS=log_path=ubsan.log fail-fast: false name: ${{ matrix.type }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Configure with -fsanitize=${{ matrix.type }} . . . run: | mkdir -p m4 && autoreconf -if ./configure CC="$CC" CFLAGS="-fsanitize=${{ matrix.type }} $CFLAGS" - name: Build . . . run: make -j4 - name: Run tests . . . run: ${{ matrix.environment_variables }} ./test-samples.sh - name: Check log files if: ${{ matrix.type == 'undefined' }} run: | if [ -f ubsan.log* ]; then cat ubsan.log* $(exit 1) fi valgrind: timeout-minutes: 60 runs-on: ubuntu-latest env: CC: gcc CFLAGS: -g -O0 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install dependencies . . . run: | sudo apt-get --yes update sudo apt-get install -y expect libc6-dbg libtool-bin valgrind - name: Configure . . . run: | mkdir -p m4 && autoreconf -if ./configure CC="$CC" CFLAGS="$CFLAGS" - name: Build . . . run: make -j4 - name: Run tests with valgrind . . . run: | valgrind --version unbuffer libtool --mode=execute valgrind --leak-check=full ./test-samples.sh 2>&1 | tee valgrind.log echo ( ! grep -i "Invalid" valgrind.log ) ( ! grep -E "lost: [^0]" valgrind.log ) edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/spelling.yml000066400000000000000000000005141521450711600271430ustar00rootroot00000000000000name: Spelling on: [pull_request, workflow_dispatch] jobs: codespell: runs-on: ubuntu-latest steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: skip: ./c/samples edge-addition-planarity-suite-Version_5.0.0.0/.gitignore000066400000000000000000000027231521450711600232020ustar00rootroot00000000000000# Directories **/Release/ **/Debug/ /c/.settings/ # Ignore directories for output of Random Graphs **/random/ **/embedded/ **/adjlist/ **/obstructed/ **/error/ # Object files *.o *.ko *.obj *.elf # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Linker output *.ilk *.map *.exp # Debug files *.dSYM/ *.su *.idb *.pdb # Java *.class *.log *.jar *.war *.ear *.zip *.tar.gz *.rar # To ignore files generated by the make distribution process *.tar.gz **/.deps/ /autom4te.cache/ libtool test-samples.sh Makefile config.status config.guess* config.sub* depcomp install-sh* Makefile.in missing ltmain.sh configure configure~ aclocal.m4 compile test-driver planarity-* planarity-*/* libplanarity.pc m4/ # To ignore files generated by the make distribution process on MacOS **/.libs/ .dirstamp *.lai # Ignoring configuration for VSCode /.vscode/ # Ignoring configuration for Eclipse project .project /c/.project /c/.cproject ## MacOS excludes .DS_Store # Ignore binary produced by compiling on MacOS planarity # Ignore planaritytesting Package default output directories, pycache, # .pylintrc, and configuration file for leaks orchestrator TestSupport/planaritytesting/__pycache__/ TestSupport/results/ TestSupport/g6_diff_finder_logs/ TestSupport/planaritytesting/.pylintrc TestSupport/planaritytesting/leaksorchestrator/planarity_leaks_config.ini edge-addition-planarity-suite-Version_5.0.0.0/LICENSE.TXT000066400000000000000000000050451521450711600226750ustar00rootroot00000000000000The Edge Addition Planarity Suite Copyright (c) 1997-2026, John M. Boyer All rights reserved. Includes a reference implementation of the following: * John M. Boyer. "Subgraph Homeomorphism via the Edge Addition Planarity Algorithm". Journal of Graph Algorithms and Applications, Vol. 16, no. 2, pp. 381-410, 2012. http://dx.doi.org/10.7155/jgaa.00268 * John M. Boyer. "A New Method for Efficiently Generating Planar Graph Visibility Representations". In P. Eades and P. Healy, editors, Proceedings of the 13th International Conference on Graph Drawing 2005, Lecture Notes Comput. Sci., Volume 3843, pp. 508-511, Springer-Verlag, 2006. http://dx.doi.org/10.1007/11618058_47 * John M. Boyer and Wendy J. Myrvold. "On the Cutting Edge: Simplified O(n) Planarity by Edge Addition". Journal of Graph Algorithms and Applications, Vol. 8, No. 3, pp. 241-273, 2004. http://dx.doi.org/10.7155/jgaa.00091 * John M. Boyer. "Simplified O(n) Algorithms for Planar Graph Embedding, Kuratowski Subgraph Isolation, and Related Problems". Ph.D. Dissertation, University of Victoria, 2001. https://dspace.library.uvic.ca/handle/1828/9918 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 Edge Addition Planarity Suite 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 HOLDER 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. edge-addition-planarity-suite-Version_5.0.0.0/Makefile.am000066400000000000000000000075331521450711600232520ustar00rootroot00000000000000SUBDIRS = c/samples ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libplanarity.la AM_CFLAGS = $(WARNING_CFLAGS) libplanarity_la_SOURCES = \ c/graphLib/lowLevelUtils/apiutils.private.h \ c/graphLib/lowLevelUtils/listcoll.h \ c/graphLib/lowLevelUtils/stack.h \ c/graphLib/io/strbuf.h \ c/graphLib/io/strOrFile.h \ c/graphLib/extensionSystem/graphExtensions.h \ c/graphLib/extensionSystem/graphExtensions.private.h \ c/graphLib/extensionSystem/graphFunctionTable.h \ c/graphLib/graph.private.h \ c/graphLib/graphDFSUtils.private.h \ c/graphLib/planarityRelated/graphPlanarity.private.h \ c/graphLib/planarityRelated/graphOuterplanarity.private.h \ c/graphLib/planarityRelated/graphDrawPlanar.private.h \ c/graphLib/homeomorphSearch/graphK23Search.private.h \ c/graphLib/homeomorphSearch/graphK33Search.private.h \ c/graphLib/homeomorphSearch/graphK4Search.private.h \ c/graphLib/graphLib.c \ c/graphLib/lowLevelUtils/apiutils.c \ c/graphLib/extensionSystem/graphExtensions.c \ c/graphLib/graph.c \ c/graphLib/io/g6-api-utilities.c \ c/graphLib/io/g6-read-iterator.c \ c/graphLib/io/g6-write-iterator.c \ c/graphLib/io/graphIO.c \ c/graphLib/io/strbuf.c \ c/graphLib/io/strOrFile.c \ c/graphLib/lowLevelUtils/listcoll.c \ c/graphLib/lowLevelUtils/stack.c \ c/graphLib/graphDFSUtils.c \ c/graphLib/planarityRelated/graphPlanarity_Extensions.c \ c/graphLib/planarityRelated/graphEmbed.c \ c/graphLib/planarityRelated/graphIsolator.c \ c/graphLib/planarityRelated/graphNonplanar.c \ c/graphLib/planarityRelated/graphTests.c \ c/graphLib/planarityRelated/graphOuterplanarity_Extensions.c \ c/graphLib/planarityRelated/graphOuterplanarObstruction.c \ c/graphLib/planarityRelated/graphDrawPlanar_Extensions.c \ c/graphLib/planarityRelated/graphDrawPlanar.c \ c/graphLib/homeomorphSearch/graphK23Search_Extensions.c \ c/graphLib/homeomorphSearch/graphK23Search.c \ c/graphLib/homeomorphSearch/graphK33Search_Extensions.c \ c/graphLib/homeomorphSearch/graphK33Search.c \ c/graphLib/homeomorphSearch/graphK4Search_Extensions.c \ c/graphLib/homeomorphSearch/graphK4Search.c libplanarity_la_LDFLAGS = -no-undefined $(AM_LDFLAGS) -version-info @LT_CURRENT@:@LT_REVISION@:@LT_AGE@ # These pkgincludes are added without "nobase_" so that the files will be copied to the # root of the planarity project includedir (i.e. excluding the c subdirectory) pkginclude_HEADERS = \ c/graphLib.h # This is the full set of pkgincludes for which "nobase_" has been used to ensure that # the directory structure is retained when installing the planarity headers. nobase_pkginclude_HEADERS = \ c/graphLib/graphLib.h \ c/graphLib/lowLevelUtils/appconst.h \ c/graphLib/lowLevelUtils/apiutils.h \ c/graphLib/graph.h \ c/graphLib/io/graphIO.h \ c/graphLib/io/g6-read-iterator.h \ c/graphLib/io/g6-write-iterator.h \ c/graphLib/graphDFSUtils.h \ c/graphLib/planarityRelated/graphPlanarity.h \ c/graphLib/planarityRelated/graphOuterplanarity.h \ c/graphLib/planarityRelated/graphDrawPlanar.h \ c/graphLib/homeomorphSearch/graphK23Search.h \ c/graphLib/homeomorphSearch/graphK33Search.h \ c/graphLib/homeomorphSearch/graphK4Search.h bin_PROGRAMS = planarity planarity_LDADD = libplanarity.la planarity_SOURCES = \ c/planarityApp/planarity.h \ c/planarityApp/platformTime.h \ c/planarityApp/planarity.c \ c/planarityApp/planarityCommandLine.c \ c/planarityApp/planarityHelp.c \ c/planarityApp/planarityMenu.c \ c/planarityApp/planarityRandomGraphs.c \ c/planarityApp/planaritySpecificGraph.c \ c/planarityApp/planarityTransformGraph.c \ c/planarityApp/planarityTestAllGraphs.c \ c/planarityApp/planarityUtils.c man1_MANS = c/planarityApp/planarity.1 EXTRA_DIST=$(man1_MANS) TESTS = test-samples.sh # The @docdir@ variable will be replaced by the ./configure script. docdir = @docdir@ dist_doc_DATA = README.md LICENSE.TXT pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libplanarity.pc edge-addition-planarity-suite-Version_5.0.0.0/README.md000066400000000000000000000167501521450711600224760ustar00rootroot00000000000000# Edge Addition Planarity Suite The primary purpose of this repository is to provide implementations of the edge addition planar graph embedding algorithm and related algorithms, including a planar graph drawing method, an isolator for a minimal subgraph obstructing planarity in non-planar graphs, outerplanar graph embedder and obstruction isolator algorithms, and tester/isolator algorithms for subgraphs homeomorphic to _K2,3_, _K4_, and _K3,3_. The C implementations in this repository are the reference implementations of algorithms appearing in the following papers: * [Subgraph Homeomorphism via the Edge Addition Planarity Algorithm](http://dx.doi.org/10.7155/jgaa.00268) * [A New Method for Efficiently Generating Planar Graph Visibility Representations](http://dx.doi.org/10.1007/11618058_47) * [On the Cutting Edge: Simplified O(n) Planarity by Edge Addition](http://dx.doi.org/10.7155/jgaa.00091) * [Simplified O(n) Algorithms for Planar Graph Embedding, Kuratowski Subgraph Isolation, and Related Problems](https://dspace.library.uvic.ca/handle/1828/9918) A secondary purpose of this repository is to provide a generalized graph API that enables implementation of a very wide range of in-memory graph algorithms including basic methods for reading, writing, and manipulating graphs, intermediate methods such as for depth first search and lowpoint calculations, and advanced methods for solving planarity, outerplanarity, planar graph drawing, and selected subgraph homeomorphism problems. An extension mechanism is also provided to enable implementation of graph algorithms by overriding and augmenting data structures and methods of the core graph data structure. ## Getting Started These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. ### Pre-compiled Executable Releases for Non-Developers On several (Debian-based) distributions of Linux, you may be able to get the planarity executable with `sudo apt install planarity`, or you may already have access to its key functionality if you have SageMath or MATLAB. For non-developer users on an operating system _Platform_ supported by the release, a pre-compiled executable version of the `planarity` executable can be obtained by downloading from the release tag and then decompressing the `planarity-N.N.N.N.`_Platform_`Exe.zip` file. If you run the `planarity` executable program, it will offer an interactive, menu-driven mode that lets a user manually select algorithms to run and, where appropriate, files containing graphs on which to run the algorithms. The `planarity` executable program also supports an extensive list of command-line parameters that make it possible to automate the execution of any of the algorithms included in the application. Run `planarity` with the `-h` command-line parameter to get more information about the command line options, and use `-h -menu` for more extensive information about command-line mode. Essentially, all functionality available in the interactive, menu-driven mode is also available via the command-line parameters. ### Setting up a Development Environment Please refer to the [2. Dev Setup](https://github.com/graph-algorithms/edge-addition-planarity-suite/wiki/2.-Dev-Setup) wiki page for instructions on how to install development dependencies on various supported platforms, as well as how to get started working with the project in Visual Studio Code. ### Making the Distribution Once one has set up the development environment and is able to work with the code in the development environment, it is possible to make the distribution with the following additional steps: 1. Ensure that the `autotools`, `configure`, and `make` are available on the command-line (e.g. add `C:\msys64\usr\bin` to the system `PATH` before Windows Program Files to ensure that the `find` program is the one from `MSYS2` rather than the one from Windows (e.g. adjust the `PATH` variable as needed)). 2. Open `bash` (e.g., on Windows, open the start menu and start typing "MSYS2 UCRT64" to open the correct terminal app), then within `bash` navigate to the root of the `edge-addition-planarity-suite` repository (i.e., the directory containing `configure.ac` and the `c` subdirectory) 3. Enter the following commands: 1. `autoreconf -fi` 2. `./configure` 3. `make dist` 4. `make distcheck` The result is a validated `planarity-N.N.N.N.tar.gz` distribution, where `N.N.N.N` is the version number expressed in the `AC_INIT` line of the `configure.ac` file. ### Making and Running the Software from the Distribution If you have done the steps to set up the development environment and work with the code, then you can make and run the software using the development environment, so you don't necessarily need to make or run the software using the process below. You also don't necessarily need to `make` and `make install` the planarity software on Linux if you are able to get it using `sudo apt planarity` (e.g., using a Debian-based Linux distribution, which uses [`apt`](https://en.wikipedia.org/wiki/APT_(software)) for package management). However, you may have only downloaded the distribution (i.e., `planarity-N.N.N.N.tar.gz`) from a Release tag of this project. Once you have decompressed the distribution into a directory, you can make it by getting into `bash` (e.g., on Windows, open the start menu and start typing "MSYS2 UCRT64" to open the correct terminal app) and then entering the following commands: 1. `./configure` 2. `make` At this point, the `planarity` executable can be run from within the distribution directory. For example, on Windows, use the command-line to run `planarity -test ./c/samples` (this calls the real `planarity.exe` in the `.libs/` subdirectory). On Linux, the planarity program can also be installed by entering `sudo make install` on the command-line. Note that the `libplanarity` shared object and symlinks will be installed to `/usr/local/lib` so it will be necessary to set `LD_LIBRARY_PATH` accordingly. For one session, this can be done with `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib`. To make it more permanent, you could use: 1. Create a new file `/etc/ld.so.conf.d/planarity.conf` containing `/usr/local/lib` 2. Run `sudo ldconfig` ## Versioning The overall project and the APIs for the graph library and the planarity-related algorithm implementations are versioned using the methods documented in [`configure.ac`](configure.ac) and [`graphLib.h`](c/graphLib/graphLib.h). The overall project version adheres to a `Major.Minor.Maintenance.Tweak` numbering system, and the `libPlanarity` shared library, which contains the graph library and planarity-related algorithm implementations, is versioned using the _current:revision:age_ system from `LibTool`. The `planarity.exe` application, which provides command-line and menu-driven interfaces for the graph library and planarity-related algorithms, is versioned using the overall project version defined in [`graphLib.h`](c/graphLib/graphLib.h) (see [`planarityHelp.c`](c/planarityApp/planarityHelp.c)). ## License This project is licensed under a 3-clause BSD License appearing in [`LICENSE.TXT`](LICENSE.TXT). ## Related Works and Further Documentation There have been successful technology transfers of the implementation code and/or algorithms of this project into other projects. To see a list of the related projects and for further documentation about this project, please see the [project wiki](https://github.com/graph-algorithms/edge-addition-planarity-suite/wiki). edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/000077500000000000000000000000001521450711600235225ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/generate_graphs.bat000066400000000000000000000011301521450711600273430ustar00rootroot00000000000000@echo off REM Change to correspond to the location of the geng executable SET gengPath="C:\Users\wbkbo\git\nauty\geng.exe" REM Change to correspond to the root output directory SET graphOutputDir="E:\graphs" SET /A N=11 IF NOT "%1"=="" ( SET N=%1 ) SET /A MAXM = (%N% * (%N% - 1^)) / 2 IF NOT EXIST "%graphOutputDir%\%N%" mkdir "%graphOutputDir%\%N%" REM Make sure to update the path to the geng executable and the directory to which you wish to write the generated graphs FOR /L %%M IN (0,1,%MAXM%) DO ( START /B "geng%%M" %gengPath% %N% %%M:%%M > "%graphOutputDir%\%N%\n%N%.m%%M.g6" ) edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planarity_runner.bat000066400000000000000000000014041521450711600276050ustar00rootroot00000000000000@echo off REM Change to correspond to the location of the planarity executable SET planarityPath="C:\Users\wbkbo\git\edge-addition-planarity-suite-fork\Release\planarity.exe" REM Change to correspond to the parent of the test files directory SET graphFilesDir="E:\graphs" REM Change to correspond to the root output directory SET outputDir="C:\Users\wbkbo\git\edge-addition-planarity-suite-fork\TestSupport\results" SET /A N=11 SET C=p IF NOT "%1"=="" ( SET N=%1 IF NOT "%2"=="" ( SET C=%2 ) ) IF NOT EXIST "%outputDir%\%N%\%C%" mkdir "%outputDir%\%N%\%C%" SET /A MAXM = (%N% * (%N% - 1^)) / 2 FOR /L %%M IN (0,1,%MAXM%) DO ( CALL %planarityPath% -t -%C% "%graphFilesDir%\%N%\test.n%N%.m%%M.g6" "%outputDir%\%N%\%C%\n%N%.m%%M.%C%.out.txt" ) edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/000077500000000000000000000000001521450711600271235ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/__init__.py000066400000000000000000000015171521450711600312400ustar00rootroot00000000000000"""Tools to help investigate K_{3, 3} search graph algorithm extension bug""" __all__ = [ "planaritytesting_utils", "graph_generation_orchestrator", "planarity_testAllGraphs_orchestrator", "g6_diff_finder", "planarity_testAllGraphs_output_parsing", "graph", "edge_deletion_analysis", "test_table_generator", ] import logging from . import planaritytesting_utils from . import graph_generation_orchestrator from . import planarity_testAllGraphs_orchestrator from . import g6_diff_finder from . import planarity_testAllGraphs_output_parsing from . import graph from . import edge_deletion_analysis from . import test_table_generator # This ensures that no logging occurs by default unless a logger has been # properly configured on a per-module basis logging.getLogger(__name__).addHandler(logging.NullHandler()) edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/edge_deletion_analysis.py000066400000000000000000001337031521450711600341760ustar00rootroot00000000000000"""Automate the edge-deletion analysis on the graph(s) in a .g6 input file Classes: EdgeDeletionAnalysisError EdgeDeletionAnalyzer """ # pylint: disable=too-many-lines #!/usr/bin/env python __all__ = ["EdgeDeletionAnalyzer", "EdgeDeletionAnalysisError"] import argparse from copy import deepcopy import logging import os from pathlib import Path import re import shutil import subprocess import sys from typing import Optional from graph import Graph, GraphError from planaritytesting_utils import ( g6_header, determine_input_filetype, is_path_to_executable, EDGE_DELETION_ANALYSIS_SPECIFIERS, ) class EdgeDeletionAnalysisError(BaseException): """Signals issues encountered during edge deletion analysis For example: - Nonzero exit code from running various planarity commands """ def __init__(self, message): super().__init__(message) self.message = message class EdgeDeletionAnalyzer: """Performs edge-deletion analysis on graph(s) in a .g6 input file""" # Cap this at a much lower value, since EDA for N=10 takes days if this # threshold is high __MAX_NUM_INVALID_OKs = 500 def __init__( self, planarity_path: Path, infile_path: Path, output_dir: Optional[Path] = None, extension_choice: str = "3", ) -> None: """Validate input and set up for edge-deletion analysis If output_dir was not None and doesn't correspond to a file, self.output_dir will be set to: {output_dir}/{infile_path.stem}/{extension_choice} If output_dir was None, self.output_dir will be set to TestSupport/results/edge_deletion_analysis/{infile_path.stem}/ {extension_choice}/ If the output_dir exists, all of its contents are wiped and the directory remade. Then, the input file is copied into this directory. Args: planarity_path: Path to planarity executable infile_path: Path to graph input file output_dir: Directory under which a new subdirectory will be created to which results will be written. extension_choice: Specifies the graph algorithm extension you wish to test, either 2 for K_{2, 3} search, 3 for K_{3, 3} search or 4 for K_4 search Raises: argparse.ArgumentTypeError: If the planarity_path doesn't correspond to an executable, if the infile_path doesn't correspond to a file, or if the output_dir corresponds to a file (rather than an existing directory, or to a directory that does not yet exist) """ if not is_path_to_executable(planarity_path): raise argparse.ArgumentTypeError( f"Path for planarity executable '{planarity_path}' does not " "correspond to an executable." ) try: file_type = determine_input_filetype(infile_path) except ValueError as input_filetype_error: raise argparse.ArgumentTypeError( "Failed to determine input filetype of " f"'{infile_path}'." ) from input_filetype_error if file_type != "G6": raise argparse.ArgumentTypeError( f"Determined '{infile_path}' has filetype '{file_type}', " "which is not supported; please supply a .g6 file." ) if extension_choice not in EDGE_DELETION_ANALYSIS_SPECIFIERS(): raise ValueError( f"'{extension_choice}' is not a valid graph algorithm " "extension command specifier; expecting either 2 (K_{2, 3}), " "3 (K_{3, 3}), or 4 (K_4)." ) if not output_dir: test_support_dir = Path(sys.argv[0]).resolve().parent.parent output_dir = Path.joinpath( test_support_dir, "results", "edge_deletion_analysis" ) if output_dir.is_file(): raise argparse.ArgumentTypeError( f"Path '{output_dir}' corresponds to a file, and can't be " "used as the output directory." ) output_dir = Path.joinpath( output_dir, f"{infile_path.stem}", f"{extension_choice}" ) if Path.is_dir(output_dir): shutil.rmtree(output_dir) Path.mkdir(output_dir, parents=True, exist_ok=True) infile_copy_path = Path.joinpath( output_dir, infile_path.name ).resolve() shutil.copyfile(infile_path, infile_copy_path) self.planarity_path = planarity_path.resolve() self.orig_infile_path = infile_copy_path self.output_dir = output_dir self.extension_choice = extension_choice self._setup_logger() def _setup_logger(self) -> None: """Set up logger instance for EdgeDeletionAnalyzer Defaults to: {self.output_dir}/ edge_deletion_analysis-{graph_input_name}.{command}.log """ graph_input_name = self.orig_infile_path.stem log_path = Path.joinpath( self.output_dir, f"edge_deletion_analysis-{graph_input_name}" f".{self.extension_choice}.log", ) # logging.getLogger() returns the *same instance* of a logger # when given the same name. In order to prevent this, must either give # a unique name, or must prevent adding additional handlers to the # logger self.logger = logging.getLogger( __name__ + f"{graph_input_name}_{self.extension_choice}" ) self.logger.setLevel(logging.DEBUG) if not self.logger.handlers: # Create the Handler for logging data to a file logger_handler = logging.FileHandler(filename=log_path) logger_handler.setLevel(logging.DEBUG) # Create a Formatter for formatting the log messages logger_formatter = logging.Formatter( "%(asctime)s - [%(levelname)s] - %(module)s.%(funcName)s - " "%(message)s" ) # Add the Formatter to the Handler logger_handler.setFormatter(logger_formatter) # Add the Handler to the Logger self.logger.addHandler(logger_handler) stderr_handler = logging.StreamHandler() stderr_handler.setLevel(logging.ERROR) stderr_handler.setFormatter(logger_formatter) self.logger.addHandler(stderr_handler) def analyze_graphs(self) -> int: """Transform input graph(s) and perform edge-deletion analysis on each Iterates over contents of .g6 input file; for each line of the input file, write a new .g6 file containing only that graph. Then, run the transformation to adjacency list, and finally perform the edge-deletion analysis steps to determine if the graph contains the target homeomorph, either a graph homeomorphic to K_{2, 3} (extension choice is 2), a K_{3, 3} (extension choice is 3), or a K_4 (extension choice is 4) that was missed by the K_{2, 3} search (2), K_{3, 3} search (3) or K_4 search (4) graph algorithm extension. Returns: num_invalid_OKs: number of graphs in the infile for which contains_missed_homeomorph was true. Raises: EdgeDeletionAnalysisError: If an error occurred when trying to determine the input file type, if the input file is anything other than a .g6 file, if transformation failed for the input graph(s), or if any of the analysis steps failed for the input graph(s) """ num_invalid_OKs = 0 with open(self.orig_infile_path, "r", encoding="utf-8") as orig_infile: line_num = 0 for line in orig_infile: line_num += 1 if line_num == 1: line.replace(g6_header(), "") new_g6_name = f"{self.orig_infile_path.name}.{line_num}" new_parent_dir = Path.joinpath(self.output_dir, new_g6_name) Path.mkdir(new_parent_dir, parents=True, exist_ok=True) new_g6_path = Path.joinpath( new_parent_dir, f"{new_g6_name}.g6" ) with open(new_g6_path, "w", encoding="utf-8") as new_g6_file: new_g6_file.write(line) try: adj_list_path = self.transform_input( new_g6_path, new_parent_dir ) except EdgeDeletionAnalysisError as input_transform_error: raise EdgeDeletionAnalysisError( f"Unable to transform graph on line {line_num} in " f"'{self.orig_infile_path}'." ) from input_transform_error try: contains_missed_homeomorph = ( self.analyze_transformed_graph( adj_list_path, new_parent_dir ) ) except EdgeDeletionAnalysisError as analysis_error: raise EdgeDeletionAnalysisError( "Error encountered analyzing graph on line " f"{line_num} in '{self.orig_infile_path}'." ) from analysis_error if not contains_missed_homeomorph: self.logger.info( "Cleaning up '%s' directory.", new_parent_dir ) shutil.rmtree(new_parent_dir, ignore_errors=True) else: num_invalid_OKs += 1 if ( num_invalid_OKs >= EdgeDeletionAnalyzer.__MAX_NUM_INVALID_OKs ): self.logger.error( "Encountered more invalid OKs than supported " "(i.e. %d)", EdgeDeletionAnalyzer.__MAX_NUM_INVALID_OKs, ) break return num_invalid_OKs def transform_input(self, infile_path: Path, output_dir: Path) -> Path: """Transforms input graph to adjacency list Runs planarity -x -a {input_file} {infile_stem}.AdjList.out.txt to transform the graph to adjacency list Args: infile_path: Path to input file containing a single graph output_dir: Directory to which transformed graph is output Returns: Path to Adjacency List representation of input graph. Raises: EdgeDeletionAnalysisError: if calling planarity returned nonzero exit code """ adj_list_path = Path.joinpath( output_dir, infile_path.stem + ".AdjList.out.txt" ) planarity_transform_args = [ f"{self.planarity_path}", "-x", "-a", f"{infile_path}", f"{adj_list_path}", ] # TestGraphFunctionality() returns either OK or NOTOK, which means # commandLine() will return 0 or -1. try: subprocess.run(planarity_transform_args, check=True) except subprocess.CalledProcessError as e: error_message = ( f"Unable to transform '{infile_path}' " "to Adjacency list." ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) from e return adj_list_path def analyze_transformed_graph( self, adj_list_path: Path, output_dir: Path ) -> bool: """Perform steps of edge-deletion analysis First os.chdir() to the output_dir, since SpecificGraph() calls ConstructInputFileName(), which enforces a limit on the length of the infileName. Runs planarity -s -{self.extension_to_run} \ {infile_stem}.AdjList.out.txt \ {infile_stem}.AdjList.s.{self.extension_to_run}.out.txt and report whether a subgraph homeomorphic to K_{2, 3} (extension choice is 2), K_{3, 3} (3) or K_4 (4) was found (NONEMBEDDABLE) or not (OK). If no subgraph homeomorphic to the target homeomorph was found (i.e. previous test yielded OK), run planarity -s -[po] {infile_stem}.AdjList.out.txt \ {infile_stem}.AdjList.s.[po].out.txt \ {infile_stem}.AdjList.s.[po].obstruction.txt a. If the graph is reported as planar (3) or outerplanar (2, 4), then we can be sure there is no subgraph homeomorphic to the target homeomorph exists in the graph and execution continues without running the rest of the loop body. b. If the graph was reported as nonplanar (3) or nonouterplanar (2, 4), then examine the obstruction in the secondary output file {input_file}.AdjList.s.[po].obstruction.txt: i. If the obstruction is homeomorphic to the target homeomorph, then report that the original graph contains a subgraph homeomorphic to the target homeomorph that was not found by the respective search graph algorithm extension. ii. If the obstruction is homeomorphic to K_4 (2), K_5 (3), or K_{2, 3} (4), then perform the edge-deletion analysis to determine whether the graph doesn't contain a K_{2, 3} (2), K_{3, 3} (3), or K_4 (4) (with a high degree of confidence) Args: adj_list_path: Path to adjacency list representation of input graph output_dir: Path to output directory Return: bool indicating whether or not a K_{2, 3} (2), K_{3, 3} (3), or K_4 (4) was found by planarity (3) or by outerplanarity (2, 4) that was not found by the respective search graph algorithm extension. Raises: EdgeDeletionAnalysisError: re-raised from any step of the analysis """ contains_missed_homeomorph = False orig_dir = os.getcwd() os.chdir(output_dir) try: contains_target_homeomorph = self._run_homeomorph_search( adj_list_path ) if not contains_target_homeomorph: obstruction_name = self._run_alternate_check( adj_list_path, output_dir ) if obstruction_name: obstruction_type = self.determine_obstruction_type( obstruction_name, output_dir ) obstruction_path = Path.joinpath( output_dir, obstruction_name ) target_obstruction_type, alternate_obstruction_type = ( self._get_forbidden_minor_pair(self.extension_choice) ) _, description = ( self._get_specific_graph_alternate_check_names( self.extension_choice ) ) if obstruction_type == target_obstruction_type: self.logger.error( "In '%s', %sity found a %s that was not " "found by %s search; see '%s'.", adj_list_path, description, target_obstruction_type, target_obstruction_type, obstruction_path, ) contains_missed_homeomorph = True elif obstruction_type == alternate_obstruction_type: self.logger.info( "'%s' contains a subgraph homeomorphic to %s; " "proceeding with edge-deletion analysis." "\n=======================\n", adj_list_path, alternate_obstruction_type, ) contains_missed_homeomorph = ( self._edge_deletion_analysis( adj_list_path, output_dir ) ) self.logger.info("\n=======================\n") if contains_missed_homeomorph: self.logger.error( "In '%s', edge-deletion analysis determined " "that there is a %s that was not found by %s " "search.", adj_list_path, target_obstruction_type, target_obstruction_type, ) else: self.logger.info( "'%s' likely doesn't contain a %s.", adj_list_path, target_obstruction_type, ) except EdgeDeletionAnalysisError as e: raise EdgeDeletionAnalysisError( f"Encountered error when processing '{adj_list_path}'." ) from e else: return contains_missed_homeomorph finally: os.chdir(orig_dir) def _run_homeomorph_search( self, graph_infile_path: Path ) -> Optional[bool]: """Run homeomorph search based on extension choice Runs K_{2, 3} search for extension choice 2, K_{3, 3} search for extension choice 3, or K_4 search for extension choice 4. Args: graph_infile_path: Path to graph on which you wish to run the homeomorph search algorithm Returns: bool: indicates whether or not the input graph contained the target homeomorph Raises: EdgeDeletionAnalysisError: If calling SpecificGraph returned anything other than 0 (OK) or 1 (NONEMBEDDABLE), or if messages emitted to stdout don't align with their respective exit codes. """ homeomorph_search_output_name = Path( graph_infile_path.stem + f".s.{self.extension_choice}.out.txt" ) homeomorph_search_args = [ f"{self.planarity_path}", "-s", f"-{self.extension_choice}", f"{graph_infile_path.name}", f"{homeomorph_search_output_name}", ] # check=False since exit code will be nonzero if NONEMBEDDABLE, which # is not an error state. result = subprocess.run( homeomorph_search_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", check=False, ) target_obstruction_type, _ = self._get_forbidden_minor_pair( self.extension_choice ) _, descriptor = self._get_specific_graph_alternate_check_names( self.extension_choice ) # SpecificGraph() will return OK, NONEMBEDDABLE, or NOTOK. This means # commandLine() will return 0, 1, or -1 for those three cases; since # subprocess.run() with check=True would raise a CalledProcessError for # nonzero exit codes, this logic is required to handle when we *truly* # encounter an error, i.e. NOTOK yielding exit code -1 if result.returncode not in (0, 1): try: result.check_returncode() except ( subprocess.CalledProcessError ) as homeomorph_search_subprocess_error: error_message = ( f"Encountered an error running {target_obstruction_type} " f"search on '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError( error_message ) from homeomorph_search_subprocess_error if result.returncode == 1: if "has a subgraph homeomorphic to" not in result.stdout: error_message = ( f"{descriptor}ity SpecificGraph() exit code doesn't align " f"with stdout result for '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) self.logger.info( "'%s' contains a subgraph homeomorphic to %s; see '%s'", graph_infile_path, target_obstruction_type, homeomorph_search_output_name, ) return True if result.returncode == 0: if "has no subgraph homeomorphic to" not in result.stdout: error_message = ( f"{descriptor}ity SpecificGraph() exit code doesn't align " f"with stdout result for '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) self.logger.info( "'%s' is reported as not containing a " "subgraph homeomorphic to %s.", graph_infile_path, target_obstruction_type, ) return False return False def _run_alternate_check( self, graph_infile_path: Path, output_dir: Path ) -> Optional[Path]: """Invoke planarity (extension choice is 3) or outerplanarity (4) Args: graph_infile_path: Path to graph on which you wish to run SpecificGraph for planarity (3) or outerplanarity (4) output_dir: Path to which you wish to output results Returns: obstruction_name: Path object that only contains the stem and extension of the obstruction; expected to be a file within output_dir. Raises: EdgeDeletionAnalysisError: if calling planarity/outerplanarity returned anything other than 0 (OK) or 1 (NONEMBEDDABLE), or if messages emitted to stdout don't align with their respective exit codes """ command_specifier, descriptor = ( self._get_specific_graph_alternate_check_names( self.extension_choice ) ) output_name = Path( graph_infile_path.stem + f".s.{command_specifier}.out.txt" ) obstruction_name = Path( graph_infile_path.stem + f".s.{command_specifier}.obstruction.txt" ) core_planarity_args = [ f"{self.planarity_path}", "-s", f"-{command_specifier}", f"{graph_infile_path.name}", f"{output_name}", f"{obstruction_name}", ] # check=False since we want to handle nonzero exit code appropriately result = subprocess.run( core_planarity_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", check=False, ) # SpecificGraph() will return OK, NONEMBEDDABLE, or NOTOK. This means # commandLine() will return 0, 1, or -1 for those three cases; since # subprocess.run() with check=True would raise a CalledProcessError for # nonzero exit codes, this logic is required to handle when we *truly* # encounter an error, i.e. NOTOK yielding exit code -1 if result.returncode not in (0, 1): try: result.check_returncode() except subprocess.CalledProcessError as e: error_message = ( "Encountered an error running " f"{descriptor}rity on '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) from e if result.returncode == 1: if f"is not {descriptor}" not in result.stdout: error_message = ( f"{descriptor}ity SpecificGraph() exit code doesn't align " f"with stdout result for '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) self.logger.info( "'%s' is non%s; see '%s'", graph_infile_path, descriptor, obstruction_name, ) elif result.returncode == 0: if f"is {descriptor}" not in result.stdout: error_message = ( f"{descriptor}ity SpecificGraph() exit code doesn't align " f"with stdout result for '{graph_infile_path}':" f"\n\tOutput to stdout:\n\t\t{result.stdout}" f"\n\tOutput to stderr:\n\t\t{result.stderr}" ) self.logger.error(error_message) raise EdgeDeletionAnalysisError(error_message) output_path = Path.joinpath(output_dir, output_name) self.logger.info( "'%s' is reported as %s, as shown in '%s'.", graph_infile_path, descriptor, output_path, ) obstruction_name = None return obstruction_name def determine_obstruction_type( self, obstruction_name: Path, output_dir: Path ) -> str: """Determine obstruction type based on vertex degree counts Args: obstruction_name: name of secondary output file produced by SpecificGraph invoking planarity (extension choice is 3) or outerplanarity (extension choice is 4) output_dir: Directory containing obstruction_name Returns: - 'K_{2, 3}' if there are two vertices of degree 3, and all other vertices have degree 2 or 0 - 'K_{3, 3}' if there are six vertices of degree 3, and all other vertices have degree 2 or 0 - 'K_4' if there are four vertices of degree 3, and all other vertices have degree 2 or 0 - 'K_5' if there are 5 vertices of degree 4, and all other vertices have degree 2 or 0 Raises: EdgeDeletionAnalysisError: If error encountered when trying to read obstruction graph from file, or if the obstruction found doesn't have the expected characteristics of any of K_5, K_{3, 3}, K_{2, 3}, or K_4 """ try: obstruction_graph = EdgeDeletionAnalyzer._read_adj_list_graph_repr( obstruction_name ) except EdgeDeletionAnalysisError as read_adj_list_error: raise EdgeDeletionAnalysisError( "Encountered error when trying to read obstruction subgraph " "from " f"'{Path.joinpath(output_dir, obstruction_name)}'" ) from read_adj_list_error vertex_degree_counts = obstruction_graph.get_vertex_degree_counts() vertices_with_nonzero_degree = ( obstruction_graph.order - vertex_degree_counts.get(0, 0) ) num_vertices_of_deg_2 = vertex_degree_counts.get(2, 0) num_vertices_of_deg_3 = vertex_degree_counts.get(3, 0) num_vertices_of_deg_4 = vertex_degree_counts.get(4, 0) if ( num_vertices_of_deg_3 == 2 and num_vertices_of_deg_2 == (vertices_with_nonzero_degree - 2) and all( count == 0 for deg, count in vertex_degree_counts.items() if deg != 0 if deg != 2 if deg != 3 ) ): return "K_{2, 3}" if ( num_vertices_of_deg_3 == 6 and num_vertices_of_deg_2 == (vertices_with_nonzero_degree - 6) and all( count == 0 for deg, count in vertex_degree_counts.items() if deg != 0 if deg != 2 if deg != 3 ) ): return "K_{3, 3}" if ( num_vertices_of_deg_3 == 4 and num_vertices_of_deg_2 == (vertices_with_nonzero_degree - 4) and all( count == 0 for deg, count in vertex_degree_counts.items() if deg != 0 if deg != 2 if deg != 3 ) ): return "K_4" if ( num_vertices_of_deg_4 == 5 and num_vertices_of_deg_2 == (vertices_with_nonzero_degree - 5) and all( count == 0 for deg, count in vertex_degree_counts.items() if deg != 0 if deg != 2 if deg != 4 ) ): return "K_5" raise EdgeDeletionAnalysisError( "The obstruction found doesn't have the expected " "characteristics ofof any of K_5, K_{3, 3}, K_{2, 3}, or K_4; " f"see '{Path.joinpath(output_dir, obstruction_name)}'." ) @staticmethod def _get_forbidden_minor_pair( extension_choice: str, ) -> tuple[str, str]: """Gets pair of "forbidden minors" for given extension choice Args: extension_choice: either 2 (K_{2, 3} search), 3 (K_{3, 3} search), or 4 (K_4 search) Returns: The pair of forbidden minors for the given extension choice """ if extension_choice not in EDGE_DELETION_ANALYSIS_SPECIFIERS(): raise EdgeDeletionAnalysisError( f"Invalid extension choice: '{extension_choice}'" ) target_obstruction_correspondence = { "2": "K_{2, 3}", "3": "K_{3, 3}", "4": "K_4", } alternate_obstruction_correspondence = { "2": "K_4", "3": "K_5", "4": "K_{2, 3}", } target_obstruction_type = target_obstruction_correspondence[ extension_choice ] alternate_obstruction_type = alternate_obstruction_correspondence[ extension_choice ] return target_obstruction_type, alternate_obstruction_type @staticmethod def _get_specific_graph_alternate_check_names( extension_choice: str, ) -> tuple[str, str]: """ Args: extension_choice: either 2 (K_{2, 3} search), 3 (K_{3, 3} search), or 4 (K_4 search) Returns: The pair of command_specifier and descriptor for the given extension choice """ if extension_choice not in EDGE_DELETION_ANALYSIS_SPECIFIERS(): raise EdgeDeletionAnalysisError( f"Invalid extension choice: '{extension_choice}'" ) command_correspondence = {"2": "o", "3": "p", "4": "o"} descriptor_correspondence = { "2": "outerplanar", "3": "planar", "4": "outerplanar", } command_specifier = command_correspondence[extension_choice] descriptor = descriptor_correspondence[extension_choice] return command_specifier, descriptor @staticmethod def _read_adj_list_graph_repr(graph_path: Path) -> Graph: """Static method to read graph as adjacency list from file Args: graph_path: Path to 0-based adjacency-list representation of graph we wish to use to initialize a Graph object Returns: Graph with order and graph_adj_list_repr corresponding to file contents. Raises: EdgeDeletionAnalysisError: If file header is invalid, or if GraphError was encountered when we tried to add an edge based on file contents. """ with open(graph_path, "r", encoding="utf-8") as graph_file: header = graph_file.readline() match = re.match(r"N=(?P\d+)", header) if not match: raise EdgeDeletionAnalysisError( f"Infile '{graph_path}' doesn't contain " "an adjacency list: invalid header." ) order = int(match.group("order")) graph = Graph(order) line_num = 0 for line in graph_file: line_num += 1 line_elements = line.split(":") u = int(line_elements[0]) neighbours = [ int(v) for v in line_elements[1].split() if v if v != "-1" ] for v in neighbours: try: graph.add_arc(u, v) except GraphError as e: raise EdgeDeletionAnalysisError( f"Unable to add edge ({u}, {v}) as specified on" f"line {line_num} of '{graph_path}'." ) from e return graph def _edge_deletion_analysis( # pylint: disable=too-many-locals self, adj_list_path: Path, output_dir: Path ) -> bool: """Search for homeomorph by removing edges and running alternate check If the original graph was determined to not contain a subgraph homeomorphic to K_{2, 3} (extension choice is 2), K_{3, 3} (3), or K_4 (4), but was reported as nonplanar (3) or nonouterplanar (2, 4) and the obstruction found was homeomorphic to K_4 (2), K_5 (3) or K_{2, 3} (4), we want to see if there is no subgraph homeomorphic to K_{2, 3} (2), K_{3, 3} (3) or K_4 (4) with a high degree of confidence (not a guarantee). This is done by iterating over the graph_adj_list_repr of the original graph: - for each edge, if the head vertex label is greater than the tail vertex label, create a deepcopy of the original graph and remove that edge before running planarity/outerplanarity - if the graph-minus-one-edge is planar/outerplanar, we continue to the next loop iteration to see if removing a different edge will reveal the target obstruction - if the graph-minus-one-edge is nonplanar/nonouterplanar, we check the obstruction type: - if the obstruction is homeomorphic to the target homeomorph, we emit a message to indicate that we found a target homeomorph that was missed by their respective search - if the obstruction is homeomorphic to the K_4 (2), K_5 (3), or K_{2, 3} (4), run K_{2, 3} search (2), K_{3, 3} search (3), or K_4 search (4) on the graph-minus-one-edge; either: - we report that a subgraph homeomorphic to the target graph was missed by the original call to the graph search extension - we report that no subgraph homeomorphic to the target graph was found by the edge-deletion-analysis. It may be the case that the graph-minus-one-edge still has the same underlying structure that causes the issue with K_{2, 3} search (2), K_{3, 3} search (3), or K_4 search (4) on the original graph, so even this additional test is not a 100% guarantee of finding the target homeomorph that we should have found in the original graph. Args: adj_list_path: Path to adjacency list upon which to perform edge-deletion analysis output_dir: Directory to which you wish to output the results of edge-deletion analysis on the input graph Returns: bool indicating at least one K_{2, 3}/K_{3, 3}/K_4 was found as a result of the edge-deletion manipulation. Raises: EdgeDeletionAnalysisError is raised if: - the original adjacency list produced by the transform is 1-based rather than the expected 0-based - this could happen if rather than starting with a graph in .g6 format, your input graph is an adjacency list produced by planarity when NIL is set to 0 rather than -1, causing gp_LowerBoundVertexStorage(theGraph) to return 1 and line terminators to be set to 0 because gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO is falsy - Deleting an edge from the copied graph fails - Running planarity on the original-graph-minus-one-edge fails - Determining the type of obstruction found fails """ try: orig_graph = EdgeDeletionAnalyzer._read_adj_list_graph_repr( adj_list_path ) except EdgeDeletionAnalysisError as e: raise EdgeDeletionAnalysisError( "Unable to read original graph adjacency list representation" f"from file '{adj_list_path}'." ) from e adj_list_stem = ".".join(adj_list_path.name.split(".")[:-3]) adj_list_suffix = "".join(adj_list_path.suffixes[-3:]) orig_graph_contains_homeomorph = False u = -1 for adj_list in orig_graph.graph_adj_list_repr: u += 1 adj_list_index = -1 for v in adj_list: adj_list_index += 1 if v < u: continue orig_graph_minus_edge = deepcopy(orig_graph) try: orig_graph_minus_edge.delete_edge(u, v) except EdgeDeletionAnalysisError as delete_edge_error: raise EdgeDeletionAnalysisError( f"Unable to delete edge {{{u}, {v}}} from copied graph" ) from delete_edge_error orig_graph_minus_edge_path = Path.joinpath( adj_list_path.parent, f"{adj_list_stem}.rem{u}-{v}{adj_list_suffix}", ) with open( orig_graph_minus_edge_path, "w", encoding="utf-8" ) as outfile: outfile.write(str(orig_graph_minus_edge)) try: obstruction_name = self._run_alternate_check( orig_graph_minus_edge_path, output_dir ) except EdgeDeletionAnalysisError as e: raise EdgeDeletionAnalysisError( "Failed to run planarity on " f"'{orig_graph_minus_edge_path}'." ) from e if not obstruction_name: continue try: obstruction_type = self.determine_obstruction_type( obstruction_name, output_dir ) except EdgeDeletionAnalysisError as e: raise EdgeDeletionAnalysisError( "Failed to determine obstruction type of graph " f"produced by removing edge {{{u}, {v}}} from the " "original graph." ) from e planar_obstruction_path = Path.joinpath( output_dir, obstruction_name ) target_obstruction_type, alternate_obstruction_type = ( self._get_forbidden_minor_pair(self.extension_choice) ) if obstruction_type == target_obstruction_type: self.logger.info( "'%s' contains a %s that was not found by %s search; " "see '%s'.", orig_graph_minus_edge_path, target_obstruction_type, target_obstruction_type, planar_obstruction_path, ) orig_graph_contains_homeomorph = True elif obstruction_type == alternate_obstruction_type: self.logger.info( "'%s' contains a subgraph homeomorphic to %s; see " "'%s'. Attempting to see if edge-removal allows %s " "search to find the target homeomorph.", orig_graph_minus_edge_path, alternate_obstruction_type, planar_obstruction_path, target_obstruction_type, ) orig_graph_minus_edge_contains_homeomorph = ( self._run_homeomorph_search(orig_graph_minus_edge_path) ) if orig_graph_minus_edge_contains_homeomorph: self.logger.info( "'%s' contains a %s that was not found by %s " "search; see '%s'.", orig_graph_minus_edge_path, target_obstruction_type, target_obstruction_type, planar_obstruction_path, ) orig_graph_contains_homeomorph = True else: self.logger.info( "'%s' doesn't appear to contain a %s.", orig_graph_minus_edge_path, target_obstruction_type, ) return orig_graph_contains_homeomorph if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Edge deletion analysis tool\n\n" "When given an input file, determine the type of input. If " "the file is a not a .g6 file, an error is raised. Otherwise, " "iterate over each line and create a separate .g6 file:\n" "\t{input_file.stem}.{line_num}.g6\n" "And perform the following steps on each graph:\n" "1. Runs\n" "\tplanarity -x -a {input_file} {infile_stem}.AdjList.out" ".txt\n" "to transform the graph to adjacency list\n" "2. Runs\n" "\tplanarity -s -{extension_choice} {infile_stem}.AdjList.out.txt " "{infile_stem}.AdjList.s.{extension_choice}.out.txt\n" "and report whether a subgraph homeomorphic to K_{2, 3} (extension " "choice is 2), K_{3, 3} (extension choice is 3), or K_4 (extension " "choice is 4) was found (NONEMBEDDABLE) or not (OK).\n" "3. If no subgraph homeomorphic to the target homeomorph was found " "(i.e. previous test yielded OK), run\n" "\tplanarity -s -p {infile_stem}.AdjList.out.txt " "{infile_stem}.AdjList.s.[po].out.txt {infile_stem}.AdjList.s.[po]." "obstruction.txt\n" "Where we run planarity if the extension choice is 3 or " "outerplanarity if the extension choice is 2 or 4.\n" "\ta. If the graph is reported as planar/outerplanar, then we can be " "sure the target homeomorph will not be present in the graph and " "execution terminates.\n" "\tb. If the graph was reported as nonplanar/nonouterplanar, examine " "the obstruction in \n" "\t\t{input_file}.AdjList.s.[po].obstruction.txt:\n" "\t\ti. If the obstruction is homeomorphic to K_{2, 3}/K_{3, 3}/K_4, " "then report that the original graph contains the target homeomorph " "that was not found by the respective graph search extension.\n" "\t\tii. If the obstruction is homeomorphic to the other forbidden " "minor (K_4 for K{2, 3}, K_5 for K_{3, 3}, or K_{2, 3} for K_4) then " "perform the edge-deletion analysis to determine if the original " "graph contains a subgraph homeomorphic to the target homeomorph that" "wasn't found by the corresponding graph search extension, or if the " "original graph doesn't contain the target homeomorph (with high " "confidence).", ) parser.add_argument( "-p", "--planaritypath", type=Path, required=True, help="Path to planarity executable", metavar="PATH_TO_PLANARITY_EXECUTABLE", ) parser.add_argument( "-i", "--inputfile", type=Path, required=True, help="Path to graph(s) to analyze", metavar="PATH_TO_GRAPH(s)", ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="OUTPUT_DIR_PATH", help="Parent directory under which subdirectory named after " "{infile_stem} will be created for output results. If none " "provided, defaults to:\n" "\tTestSupport/results/edge_deletion_analysis/{infile_stem}", ) parser.add_argument( "-c", "--command", type=str, metavar="ALGORITHM_COMMAND", default="3", help="Graph algorithm command specifier, either 2 (K_{2, 3} search), " "3 (K_{3, 3} search), or 4 (K_4 search)", ) args = parser.parse_args() eda = EdgeDeletionAnalyzer( planarity_path=args.planaritypath, infile_path=args.inputfile, output_dir=args.outputdir, extension_choice=args.command, ) num_invalidOKs = eda.analyze_graphs() eda.logger.info("NUMBER OF INVALID OKs: %d", num_invalidOKs) edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/g6_diff_finder.py000066400000000000000000000574641521450711600323500ustar00rootroot00000000000000"""Tools to analyze .g6 files produced by nauty geng Classes: G6DiffFinderException G6DiffFinder """ #!/usr/bin/env python __all__ = ["G6DiffFinder", "G6DiffFinderException"] import os import sys import json import argparse import logging from pathlib import Path from typing import Optional from planaritytesting_utils import g6_header, determine_input_filetype class G6DiffFinderException(Exception): """ Custom exception for representing errors that arise when processing two .g6 files. """ def __init__(self, message): super().__init__(message) self.message = message class G6DiffFinder: """Tool to identify the diffs between two .g6 input files.""" def __init__( self, first_comparand_infile_path: Path, second_comparand_infile_path: Path, log_path: Optional[Path] = None, ) -> None: """ Initializes G6DiffFinder instance. For each .g6 path provided, validates the infile path and then populates the corresponding comparand dict. Args: first_comparand_infile_path: path to a .g6 file second_comparand_infile_path: path to a .g6 file Raises: ValueError: If either first_comparand_infile_path or second_comparand_infile_path are invalid FileNotFoundError: If _populate_comparand_dict() failed to open either input file, re-raise error """ self._setup_logger(log_path) try: first_comparand_filetype = determine_input_filetype( first_comparand_infile_path ) except ValueError as comparand_infile_error: raise comparand_infile_error if first_comparand_filetype != "G6": raise ValueError( f"'{first_comparand_infile_path}' is not a .g6 file." ) try: self._first_comparand_dict = self._populate_comparand_dict( first_comparand_infile_path ) except FileNotFoundError as comparand_infile_not_found_error: raise FileNotFoundError( "Unable to populate comparand dict: can't open first input file" ) from comparand_infile_not_found_error try: second_comparand_filetype = determine_input_filetype( second_comparand_infile_path ) except ValueError as comparand_infile_error: raise comparand_infile_error if second_comparand_filetype != "G6": raise ValueError( f"'{second_comparand_infile_path}' is not a .g6 file." ) try: self._second_comparand_dict = self._populate_comparand_dict( second_comparand_infile_path ) except FileNotFoundError as comparand_infile_not_found_error: raise FileNotFoundError( "Unable to populate comparand dict: " "can't open second input file" ) from comparand_infile_not_found_error def _setup_logger(self, log_path: Optional[Path] = None) -> None: if not log_path: test_support_dir = Path(sys.argv[0]).resolve().parent.parent g6_diff_finder_logdir = Path.joinpath( test_support_dir, "g6_diff_finder_logs" ) Path.mkdir(g6_diff_finder_logdir, parents=True, exist_ok=True) log_path = Path.joinpath(g6_diff_finder_logdir, "G6DiffFinder.log") if log_path.is_file(): os.remove(log_path) # logging.getLogger() returns the *same instance* of a logger # when given the same name. In order to prevent this, must either give # a unique name, or must prevent adding additional handlers to the # logger self.logger = logging.getLogger(__name__ + str(log_path.name)) self.logger.setLevel(logging.DEBUG) if not self.logger.handlers: # Create the Handler for logging data to a file logger_handler = logging.FileHandler(filename=log_path) logger_handler.setLevel(logging.DEBUG) # Create a Formatter for formatting the log messages logger_formatter = logging.Formatter( "%(asctime)s - [%(levelname)s] - %(module)s.%(funcName)s - " "%(message)s" ) # Add the Formatter to the Handler logger_handler.setFormatter(logger_formatter) # Add the Handler to the Logger self.logger.addHandler(logger_handler) def _populate_comparand_dict( self, comparand_infile_path: Path ) -> dict[str, Path | dict[str, int | list[int]]]: """ Opens the file corresponding to path comparand_infile_path, then iterates over the lines of the file object. If the first line contains the .g6 header, it is stripped from the line contents. Then, the line contents are stripped of whitespace, after which we check to see if the graph encoding already has appeared in the file. If so, we add the current line_num to the duplicate_line_nums list corresponding to that encoding. If not, we insert a key-value pair into the comparand dict corresponding to the graph encoding mapped to a sub-dict with the first_line on which the encoding occurs and an empty list of duplicate_line_nums in case that same encoding appears again in the file. Args: comparand_infile_path: path to a .g6 file Raises: FileNotFoundError: If open() fails on comparand_infile_path """ try: self.logger.info( "Populating comparand dict from infile path '%s'.", comparand_infile_path, ) with open( comparand_infile_path, "r", encoding="utf-8" ) as comparand_infile: comparand_dict = {} line_num = 1 for line in comparand_infile: if line_num == 1: line = line.replace(g6_header(), "") line = line.strip() if not line: continue if line in comparand_dict: if not comparand_dict[line].get("duplicate_line_nums"): comparand_dict[line]["duplicate_line_nums"] = [] comparand_dict[line]["duplicate_line_nums"].append( line_num ) else: comparand_dict[line] = { "first_line": line_num, "duplicate_line_nums": [], } line_num += 1 comparand_dict["infile_path"] = comparand_infile_path return comparand_dict except FileNotFoundError as comparand_file_not_found_error: raise FileNotFoundError( "Unable to open comparand infile with path " f"'{comparand_infile_path}'." ) from comparand_file_not_found_error def output_duplicates(self): """Output duplicates in each comparand dict Calls self._output_duplicates() for each comparand dict so that we have two separate output files containing the duplicates within their respective .g6 input files. Raises: G6DiffFinderException: Re-raises if _output_duplicates() failed on either comparand dict """ try: self._output_duplicates(self._first_comparand_dict) except G6DiffFinderException as first_comparand_find_duplicates_error: raise G6DiffFinderException( "Unable to output duplicates for first .g6 file." ) from first_comparand_find_duplicates_error try: self._output_duplicates(self._second_comparand_dict) except G6DiffFinderException as second_comparand_find_duplicates_error: raise G6DiffFinderException( "Unable to output duplicates for second .g6 file." ) from second_comparand_find_duplicates_error def _output_duplicates(self, comparand_dict: dict): """ Performs a dictionary-comprehension to get each g6_encoded_graph whose corresponding value has a non-empty duplicate_line_nums list. If this dictionary is empty, emits a log message that no duplicates were found. If the dictionary is nonempty, json.dumps() to an output file whose path is: {comparand_infile_path}.duplicates.out.txt The file is overwritten if it already exists. Args: comparand_dict: contains key-value pairs of graph encodings mapped to sub-dicts, which contain the first_line on which the encoding occurred and the list of duplicate_line_nums on which the graph recurred in the file. Raises: G6DiffFinderException: If KeyError encountered when trying to get value corresponding to key infile_path from the comparand dict """ try: comparand_infile_path = Path( comparand_dict["infile_path"] ).resolve() except KeyError as no_infile_path_error: raise G6DiffFinderException( "Invalid dict structure: missing 'infile_path'." ) from no_infile_path_error else: comparand_outfile_results_directory_path = Path.joinpath( comparand_infile_path.parent, "results" ) Path.mkdir( comparand_outfile_results_directory_path, parents=True, exist_ok=True, ) comparand_outfile_path = Path.joinpath( comparand_outfile_results_directory_path, comparand_infile_path.parts[-1] + ".duplicates.out.txt", ) duplicated_g6_encodings = { g6_encoded_graph: comparand_dict[g6_encoded_graph] for g6_encoded_graph in comparand_dict if g6_encoded_graph != "infile_path" if len(comparand_dict[g6_encoded_graph]["duplicate_line_nums"]) > 0 } if not duplicated_g6_encodings: self.logger.info( "No duplicates present in '%s'.", comparand_infile_path ) else: with open( comparand_outfile_path, "w", encoding="utf-8" ) as comparand_outfile: self.logger.info( "Outputting duplicates present in '%s'", comparand_infile_path, ) comparand_outfile.write( "Comparand infile name: " f"'{comparand_infile_path.name}'\n" ) comparand_outfile.write( json.dumps(duplicated_g6_encodings, indent=4) ) def graph_set_diff(self): """ Calls self._graph_set_diff() for both the self._first_comparand_dict against the second self._second_comparand_dict and vice versa to output which graphs are in the first .g6 file that are absent from the second, and in the second .g6 file that are absent from the first. """ self._graph_set_diff( self._first_comparand_dict, self._second_comparand_dict ) self._graph_set_diff( self._second_comparand_dict, self._first_comparand_dict ) def _graph_set_diff( self, first_comparand_dict: dict, second_comparand_dict: dict ): """ Gets the first_comparand_infile_dir (which is where the output file will be written to, if the result is nonempty) and the infile names that were used to populate the respective dicts; these are used to construct the outfile path. Performs a list-comprehension to determine which graph encodings appear in the first file that don't appear in the second. If the graphs_in_first_but_not_second is empty, emits a log message indicating that no graphs were found in the first dict that were not in the second. If graphs_in_first_but_not_second is nonempty, writes each graph encoding followed by a newline so that the output constitutes a valid .g6 file. The output filepath will be of the form {first_comparand_infile_dir}/results/ graphs_in_{first_comparand_infile_name}_not_in_{second_comparand_infile_name}.g6 Args: first_comparand_dict: contains key-value pairs of graph encodings mapped to sub-dicts, which contain the first_line on which the encoding occurred and the list of duplicate_line_nums on which the graph recurred in the file. second_comparand_dict: Same structure as first_comparand_dict. Raises: G6DiffFinderException: Re-raises if exception encountered when we _get_infile_names() from the two comparand dicts """ try: ( first_comparand_infile_dir, first_comparand_infile_name, second_comparand_infile_name, ) = self._get_infile_names( first_comparand_dict, second_comparand_dict ) except G6DiffFinderException as get_infile_names_error: raise G6DiffFinderException( "Unable to extract infile_names from comparand dicts." ) from get_infile_names_error graphs_in_first_but_not_second = [ g6_encoding for g6_encoding in first_comparand_dict if g6_encoding != "infile_path" if g6_encoding not in second_comparand_dict ] if not graphs_in_first_but_not_second: self.logger.info( "No graphs present in '%s' that aren't in '%s'", first_comparand_infile_name, second_comparand_infile_name, ) else: comparand_outfile_results_directory_path = Path.joinpath( first_comparand_infile_dir, "results" ) Path.mkdir( comparand_outfile_results_directory_path, parents=True, exist_ok=True, ) outfile_path = Path.joinpath( comparand_outfile_results_directory_path, f"graphs_in_{first_comparand_infile_name}_not_in_" + f"{second_comparand_infile_name}.g6", ) self.logger.info( "Outputting graphs present in '%s' that aren't in '%s' to " "'%s'.", first_comparand_infile_name, second_comparand_infile_name, outfile_path, ) with open( outfile_path, "w", encoding="utf-8" ) as graph_set_diff_outfile: for g6_encoding in graphs_in_first_but_not_second: graph_set_diff_outfile.write(g6_encoding) graph_set_diff_outfile.write("\n") def graph_set_intersection_with_different_line_nums(self): """ Takes the two comparand dicts associated with the G6DiffFinder and gets the first_comparand_infile_dir (which is where the output file will be written to, if the result is nonempty) and the infile names corresponding to the self._first_comparand_dict and self._second_comparand_dict; these are used to construct the outfile path. Performs a dictionary comprehension to produce key-value pairs of graph encoding mapped to a tuple containing the first_line on which the encoding occurred in the first .g6 infile and the first_line on which the encoding occurred in the second .g6 infile. If the graphs_in_first_and_second is empty, emits a log message indicating that the intersection is empty. If graphs_in_first_and_second is nonempty, json.dumps() the dict to the output file of the form: {first_comparand_infile_dir}/results/graphs_in_{first_comparand_infile_name}_and_{second_comparand_infile_name}.txt # pylint: disable=line-too-long Raises: G6DiffFinderException: Re-raises if exception encountered when we _get_infile_names() from the two comparand dicts """ try: ( first_comparand_infile_dir, first_comparand_infile_name, second_comparand_infile_name, ) = self._get_infile_names( self._first_comparand_dict, self._second_comparand_dict ) except G6DiffFinderException as get_infile_names_error: raise G6DiffFinderException( "Unable to extract infile_names from comparand dicts." ) from get_infile_names_error graphs_in_first_and_second = { g6_encoding: ( self._first_comparand_dict[g6_encoding]["first_line"], # type: ignore self._second_comparand_dict[g6_encoding]["first_line"], # type: ignore ) for g6_encoding in self._first_comparand_dict if g6_encoding != "infile_path" if g6_encoding in self._second_comparand_dict if ( self._first_comparand_dict[g6_encoding]["first_line"] # type: ignore != self._second_comparand_dict[g6_encoding][ # type: ignore "first_line" ] # type: ignore ) } if not graphs_in_first_and_second: self.logger.info( "No graphs present in both '%s' and '%s' that appear on " "different lines.", first_comparand_infile_name, second_comparand_infile_name, ) else: comparand_outfile_results_directory_path = Path.joinpath( first_comparand_infile_dir, "results" ) Path.mkdir( comparand_outfile_results_directory_path, parents=True, exist_ok=True, ) outfile_path = Path.joinpath( comparand_outfile_results_directory_path, f"graphs_in_{first_comparand_infile_name}_and_" + f"{second_comparand_infile_name}.txt", ) self.logger.info( "Outputting graphs present in both '%s' and '%s' that " "appear on different lines to '%s'.", first_comparand_infile_name, second_comparand_infile_name, outfile_path, ) with open( outfile_path, "w", encoding="utf-8" ) as graph_set_intersection_outfile: graph_set_intersection_outfile.write( json.dumps(graphs_in_first_and_second, indent=4) ) def _get_infile_names( self, first_comparand_dict: dict, second_comparand_dict: dict ): """ Uses pathlib.Path object's .parent attribute to get the directory of the first .g6 input file, stored in the first_comparand_dict's infile_path attribute. Then, gets the first_comparand_infile_name by stripping the .g6 extension, and likewise for the second_comparand_infile_name. Args: first_comparand_dict: Dict containing the key infile_name, in addition to the key-value pairs of graph encodings mapped to their first_line and the list of duplicate_line_nums second_comparand_dict: Same structure as first_comparand_dict Returns: first_comparand_infile_dir: Parent directory of the first .g6 file first_comparand_infile_name: base name of the first .g6 file second_comparand_infile_name: base name of the second .g6 file Raises: G6DiffFinderException: If KeyError encountered when trying to get value corresponding to key infile_path from either of the two comparand dicts """ try: first_comparand_infile_path = Path( first_comparand_dict["infile_path"] ).resolve() except KeyError as key_error: raise G6DiffFinderException( "Invalid dict structure: missing key 'infile_path'." ) from key_error first_comparand_infile_dir = first_comparand_infile_path.parent first_comparand_infile_name = first_comparand_infile_path.with_suffix( "" ).name try: second_comparand_infile_name = ( Path(second_comparand_dict["infile_path"]).with_suffix("").name ) except KeyError as key_error: raise G6DiffFinderException( "Invalid dict structure: missing key 'infile_path'." ) from key_error return ( first_comparand_infile_dir, first_comparand_infile_name, second_comparand_infile_name, ) if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Tool to help interrogate and compare two .g6 files.\n" "- Determines if there are duplicates in the first and second " "comparand .g6 files; outputs to files with paths:\n" "\t{first_comparand_infile_dir}/results/" "{first_comparand_infile_name}.duplicates.out.txt\n" "\t{second_comparand_infile_dir}/results/" "{second_comparand_infile_name}.duplicates.out.txt\n" "- Determines if there any graphs that appear in the first .g6 " "file that do not appear in the second .g6 file, and vice versa; " "outputs to files with paths:\n" "\t{first_comparand_infile_dir}/results/graphs_in_" "{first_comparand_infile_name}_not_in_" "{second_comparand_infile_name}.g6\n" "\t{second_comparand_infile_dir}/results/graphs_in_" "{second_comparand_infile_name}_not_in_" "{first_comparand_infile_name}.g6\n" "- Records graphs that occur in both files but which appear on " "different line numbers; outputs to a file with path:\n" "\t{first_comparand_infile_dir}/results/graphs_in_" "{first_comparand_infile_name}_and_" "{second_comparand_infile_name}.txt", ) parser.add_argument( "--first_comparand", "-f", type=Path, help="The first .g6 file to compare.", metavar="FIRST_COMPARAND.g6", required=True, ) parser.add_argument( "--second_comparand", "-s", type=Path, help="The second .g6 file to compare.", metavar="SECOND_COMPARAND.g6", required=True, ) args = parser.parse_args() try: g6_diff_finder = G6DiffFinder( args.first_comparand, args.second_comparand ) except Exception as e: raise G6DiffFinderException( "Unable to initialize G6DiffFinder with given input files." ) from e try: g6_diff_finder.output_duplicates() except Exception as e: raise G6DiffFinderException( "Unable to output duplicates for given input files." ) from e try: g6_diff_finder.graph_set_diff() except Exception as e: raise G6DiffFinderException( "Failed to discern diff between two .g6 input files." ) from e try: g6_diff_finder.graph_set_intersection_with_different_line_nums() except Exception as e: raise G6DiffFinderException( "Failed to determine set intersection of two .g6 input files." ) from e g6_generation_and_comparison_driver.py000066400000000000000000001256661521450711600366140ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting"""Generate .g6 files, run planarity, identify discrepancies, and perform diffs Classes: G6GenerationAndComparisonDriver Functions: parse_range(value: str) -> tuple """ # pylint: disable=too-many-lines #!/usr/bin/env python __all__ = [] import sys import os import shutil import argparse from pathlib import Path import subprocess from typing import Optional import json from graph_generation_orchestrator import distribute_geng_workload from planarity_testAllGraphs_orchestrator import ( distribute_planarity_testAllGraphs_workload, ) from g6_diff_finder import G6DiffFinder, G6DiffFinderException from planaritytesting_utils import ( PLANARITY_ALGORITHM_SPECIFIERS, max_num_edges_for_order, is_path_to_executable, parse_range, ) from planarity_testAllGraphs_output_parsing import process_file_contents class G6GenerationAndComparisonDriver: """Generates .g6 files, runs planarity, and identifies discrepancies""" def __init__( self, geng_path: Path, orders: tuple, planarity_path: Optional[Path] = None, planarity_backup_path: Optional[Path] = None, output_parent_dir: Optional[Path] = None, ): # pylint: disable=too-many-arguments """Initializes G6GenerationAndComparisonDriver instance. Args: geng_path: The path to the nauty geng executable used to generate .g6 graph files for each order and edge-count orders: A tuple of ints corresponding to the graph orders for which we wish to perform the graph generation and comparison planarity_path: The path to the edge-addition-planarity-suite executable planarity_backup_path: The path to the planarity-backup executable (optional; used to generate the makeg .g6 files) output_parent_dir: Directory to which you wish to output results Raises: argparse.ArgumentError: Re-raises if any of the arguments were invalid """ try: ( geng_path, orders, planarity_path, planarity_backup_path, output_parent_dir, ) = self._validate_and_normalize_input( geng_path, orders, planarity_path, planarity_backup_path, output_parent_dir, ) except argparse.ArgumentError as e: raise argparse.ArgumentError( argument=None, message="Invalid parameters; unable to proceed with " "generation and comparison.", ) from e self.geng_path = geng_path self.orders = orders self.planarity_path = planarity_path self.planarity_backup_path = planarity_backup_path self.output_parent_dir = output_parent_dir self.planarity_discrepancies = {} def _validate_and_normalize_input( # pylint: disable=too-many-arguments self, geng_path: Path, orders: tuple, planarity_path: Optional[Path] = None, planarity_backup_path: Optional[Path] = None, output_parent_dir: Optional[Path] = None, ) -> tuple[Path, tuple[int, ...], Path, Optional[Path], Path]: """Validates G6GenerationAndComparisonDriver initialization values Args: geng_path: Path to geng executable orders: Tuple containing orders of graphs for which you wish to generate the .g6 files planarity_path: Path to planarity executable; defaults to: Release/planarity planarity_backup_path: Path to planarity-backup executable, or None output_parent_dir: Path to directory to which you wish to output .g6 files and comparison results; defaults to: TestSupport/results/g6_generation_and_comparison_driver/ Returns: A tuple comprised of the valid and normalized input in their original order. Raises: argparse.ArgumentTypeError: If any of the arguments were invalid """ if not is_path_to_executable(geng_path): raise argparse.ArgumentTypeError( f"Path for geng executable '{geng_path}' does not correspond " "to an executable." ) if not orders: raise argparse.ArgumentTypeError("No graph orders given.") if any( order for order in orders if order < 2 if order > 12 if not isinstance(order, int) ): raise argparse.ArgumentTypeError( "Desired graph orders must be integers between 2 an 12 " "inclusive." ) planarity_project_root = ( Path(sys.argv[0]).resolve().parent.parent.parent ) if not planarity_path: planarity_path = Path.joinpath( planarity_project_root, "Release", "planarity" ) if not is_path_to_executable(planarity_path): raise argparse.ArgumentTypeError( f"Path for planarity executable '{planarity_path}' does not " "correspond to an executable." ) if planarity_backup_path and not is_path_to_executable( planarity_backup_path ): raise argparse.ArgumentTypeError( "Path for planarity_backup executable " f"'{planarity_backup_path}' does not correspond to an " "executable." ) if not output_parent_dir: output_parent_dir = Path.joinpath( planarity_project_root, "TestSupport", "results", "g6_generation_and_comparison_driver", ) Path.mkdir(output_parent_dir, parents=True, exist_ok=True) for order in orders: candidate_output_dir = Path.joinpath( output_parent_dir, f"{order}" ) if Path.is_dir(candidate_output_dir): shutil.rmtree(candidate_output_dir) if ( not isinstance(output_parent_dir, Path) or not output_parent_dir.is_dir() ): raise argparse.ArgumentTypeError( "Output directory path is invalid." ) return ( geng_path, orders, planarity_path, planarity_backup_path, output_parent_dir, ) def generate_g6_files(self): """Generate .g6 files using nauty geng and planarity-backup's makeg For each graph order in self.orders, uses the graph generation orchestrator to generate the geng .g6 and geng canonical .g6 files (all graphs of a single edge-count per file), then if the path to the planarity-backup executable was provided, calls this executable for each edge-count from 0 to (N * (N - 1)) / 2 to generate the makeg .g6 and makeg canonical .g6 files. """ for order in self.orders: g6_output_dir_for_order = Path.joinpath( self.output_parent_dir, f"{order}" ) self._generate_geng_g6_files_for_order( order, g6_output_dir_for_order ) if self.planarity_backup_path: for num_edges in range(max_num_edges_for_order(order) + 1): self._generate_makeg_g6_files_for_order_and_num_edges( order, num_edges ) def _generate_geng_g6_files_for_order( self, order: int, geng_g6_output_dir_for_order: Path ): """Parallelize geng calls Args: order: The graph order for which we wish to generate all graphs for each edge-count geng_g6_output_dir_for_order: Directory to which we wish to output the geng .g6 and geng canonical .g6 graph files """ distribute_geng_workload( geng_path=self.geng_path, canonical_files=False, order=order, output_dir=geng_g6_output_dir_for_order, ) distribute_geng_workload( geng_path=self.geng_path, canonical_files=True, order=order, output_dir=geng_g6_output_dir_for_order, ) def _generate_makeg_g6_files_for_order_and_num_edges( self, order: int, num_edges: int, command: str = "3" ): """Generate makeg .g6 and makeg canonical .g6 files The makeg .g6 and makeg canonical .g6 files generated have paths: {self.output_parent_dir}/{order}/ n{order}.m{num_edges}.makeg(.canonical)?.g6 Which is an intermediate directory required for the use of the planarity_testAllGraphs_orchestrator (which runs on all .g6 files in the given root directory matching a specific pattern indicated by the canonical_files and makeg_g6 flags) The output files of planarity-backup's Test All Graphs for the given algorithm command specifier have paths: {self.output_parent_dir}/{order}/planarity_results/{command}/ {num_edges}/planarity-backup-output_n{order}.m{num_edges}.makeg(.canonical)?.{command}.out.txt Args: order: Order of graphs to generate num_edges: Restricts .g6 output file to contain only graphs with the desired edge-count command: Algorithm command specifier to use when testing all graphs of the given order and edge-count (Defaults to '3' corresponding to K_{3, 3} search) """ # pylint: disable=line-too-long g6_output_dir_for_order_and_edgecount = Path.joinpath( self.output_parent_dir, f"{order}" ) Path.mkdir( g6_output_dir_for_order_and_edgecount, parents=True, exist_ok=True ) planarity_backup_outfile_dir = Path.joinpath( self.output_parent_dir, f"{order}", "planarity_results", f"{command}", f"{num_edges}", ) Path.mkdir(planarity_backup_outfile_dir, parents=True, exist_ok=True) self._generate_makeg_g6_file_for_order_and_num_edges( g6_output_dir_for_order_and_edgecount, planarity_backup_outfile_dir, order, num_edges, False, command, ) self._generate_makeg_g6_file_for_order_and_num_edges( g6_output_dir_for_order_and_edgecount, planarity_backup_outfile_dir, order, num_edges, True, command, ) def _generate_makeg_g6_file_for_order_and_num_edges( # pylint: disable=too-many-arguments self, g6_output_dir_for_order_and_edgecount: Path, planarity_backup_outfile_dir: Path, order: int, num_edges: int, canonical_files: bool, command: str = "3", ): """Run planarity-backup executable to generate makeg .g6 files Runs the old planarity-backup code with the given algorithm command specifier (defaults to K_{3, 3} search) to test all graphs of a given order and edge-count to determine the number of OKs (no K_{3, 3}) vs. number of NONEMBEDDABLEs (contains a K_{3, 3}) The graphs generated by makeg are written to file: {g6_output_dir_for_order_and_edgecount}/ n{order}.m{num_edges}.makeg(.canonical)?.g6 Args: g6_output_dir_for_order_and_edgecount: (Intermediate) directory to which you wish to output makeg .g6 files for the given order and edge-count planarity_backup_outfile_dir: (Intermediate) directory to which you wish to output the results of running the planarity-backup executable for the given command specifier on all graphs of the given order and edge-count order: Order of graphs to generate num_edges: Restricts .g6 output file to contain only graphs with the desired edge-count canonical_files: Indicates whether or not makeg should generate canonical .g6 graphs command: Algorithm command specifier to use when testing all graphs of the given order and edge-count (Defaults to '3' corresponding to K_{3, 3} search) """ canonical_ext = ".canonical" if canonical_files else "" planarity_backup_outfile_name = Path.joinpath( planarity_backup_outfile_dir, f"planarity-backup-output_n{order}.m{num_edges}." + f"makeg{canonical_ext}.{command}.out.txt", ) with open( planarity_backup_outfile_name, "w", encoding="utf-8" ) as makeg_outfile: planarity_backup_args = [ f"{self.planarity_backup_path}", "-gen", f"{g6_output_dir_for_order_and_edgecount}", f"-{command}", f"{order}", f"{num_edges}", f"{num_edges}", ] if canonical_files: planarity_backup_args.insert(4, "-l") subprocess.run( planarity_backup_args, stdout=makeg_outfile, stderr=subprocess.PIPE, check=False, ) def run_planarity(self): """Runs planarity on all .g6 files For each graph order in self.orders, runs planarity Test All Graphs for every algorithm command specifier on all geng .g6 and geng canonical .g6 files: {self.output_parent_dir}/{order}/ n{order}.m{num_edges}(.canonical)?.g6 If a path to the planarity-backup executable was provided, runs planarity Test All Graphs for every algorithm command specifier on all makeg .g6 and makeg canonical .g6 files: {self.output_parent_dir}/{order}/ n{order}.m{num_edges}(.makeg)?(.canonical)?.g6 """ for order in self.orders: geng_g6_output_dir_for_order = Path.joinpath( self.output_parent_dir, f"{order}" ) planarity_output_dir_for_order = Path.joinpath( self.output_parent_dir, "results", f"{order}" ) self._run_planarity_on_g6_files_for_order( order, geng_g6_output_dir_for_order, planarity_output_dir_for_order, makeg_g6=False, ) if self.planarity_backup_path: self._run_planarity_on_g6_files_for_order( order, geng_g6_output_dir_for_order, planarity_output_dir_for_order, makeg_g6=True, ) def _run_planarity_on_g6_files_for_order( self, order: int, g6_output_dir_for_order: Path, planarity_output_dir_for_order: Path, makeg_g6: bool, ): """Parallelize running planarity TestAllGraphs for all commands Args: order: Order of graphs in .g6 files on which planarity's Test All Graphs shall be run for the given command g6_output_dir_for_order: Directory containing the input .g6 files, one file per num_edges for a given graph order. planarity_output_dir_for_order: Directory to which results of running planarity on each .g6 file containing all graphs of a specific num_edges for the given graph order shall be output makeg_g6: Flag to indicate that the .g6 files taken as input were generated using planarity-backup's makeg """ distribute_planarity_testAllGraphs_workload( planarity_path=self.planarity_path, canonical_files=False, makeg_g6=makeg_g6, order=order, input_dir=g6_output_dir_for_order, output_dir=planarity_output_dir_for_order, ) distribute_planarity_testAllGraphs_workload( planarity_path=self.planarity_path, canonical_files=True, makeg_g6=makeg_g6, order=order, input_dir=g6_output_dir_for_order, output_dir=planarity_output_dir_for_order, ) def reorganize_files(self): """Reorganize .g6 and planarity Test All Graphs output files After run_planarity(), the various .g6 files and planarity Test All Graphs output files must be reorganized to make subsequent processing easier. """ for order in self.orders: orig_geng_g6_output_dir_for_order = Path.joinpath( self.output_parent_dir, f"{order}" ) for num_edges in range(max_num_edges_for_order(order) + 1): self._move_g6_files( order, num_edges, orig_geng_g6_output_dir_for_order ) self._move_planarity_output_files( order, num_edges, orig_geng_g6_output_dir_for_order ) shutil.rmtree(Path.joinpath(self.output_parent_dir, "results")) def _move_g6_files( self, order: int, num_edges: int, g6_output_dir_for_order: Path ): """Sort generated .g6 files into sub-dirs by graph edge-count After moving, {self.output_parent_dir}/{order}/graphs/{num_edges}/ contains: n{order}.m{num_edges}.g6 n{order}.m{num_edges}.canonical.g6 If planarity-backup path provided, also shall include: n{order}.m{num_edges}.makeg.g6 n{order}.m{num_edges}.makeg.canonical.g6 Args: order: Order of graphs contained in the .g6 files to move num_edges: Only move the .g6 files that contain all graphs of a specific edge-count g6_output_dir_for_order: Original directory containing the .g6 files produced by generate_g6_files() """ g6_output_dir_for_order_and_edgecount = Path.joinpath( g6_output_dir_for_order, "graphs", f"{num_edges}" ) Path.mkdir( g6_output_dir_for_order_and_edgecount, parents=True, exist_ok=True ) geng_g6_outfile_name = f"n{order}.m{num_edges}.g6" self._move_file( g6_output_dir_for_order, geng_g6_outfile_name, g6_output_dir_for_order_and_edgecount, ) geng_canonical_g6_outfile_name = f"n{order}.m{num_edges}.canonical.g6" self._move_file( g6_output_dir_for_order, geng_canonical_g6_outfile_name, g6_output_dir_for_order_and_edgecount, ) if self.planarity_backup_path: makeg_g6_outfile_name = f"n{order}.m{num_edges}.makeg.g6" self._move_file( g6_output_dir_for_order, makeg_g6_outfile_name, g6_output_dir_for_order_and_edgecount, ) makeg_canonical_g6_outfile_name = ( f"n{order}.m{num_edges}.makeg.canonical.g6" ) self._move_file( g6_output_dir_for_order, makeg_canonical_g6_outfile_name, g6_output_dir_for_order_and_edgecount, ) def _move_planarity_output_files( self, order: int, num_edges: int, new_output_dir_for_order: Path ): """Sort planarity output files into sub-dirs by command and edge-count After moving, {order}/planarity_results/{command}/{num_edges}/ contains: n{order}.m{num_edges}.{command}.out.txt n{order}.m{num_edges}.canonical.{command}.out.txt If planarity-backup path provided, also shall include: n{order}.m{num_edges}.makeg.{command}.out.txt n{order}.m{num_edges}.makeg.canonical.{command}.out.txt Args: order: Order of graphs contained in the .g6 files taken as input to planarity Test All Graphs num_edges: Only move the planarity output files corresponding to specific edge-count new_output_dir_for_order: Directory under which new subdirectory, planarity_results/, should be created """ for command in PLANARITY_ALGORITHM_SPECIFIERS(): orig_planarity_output_dir = Path.joinpath( self.output_parent_dir, "results", f"{order}", f"{command}" ) new_planarity_output_dir_for_order_and_edgecount = Path.joinpath( new_output_dir_for_order, "planarity_results", f"{command}", f"{num_edges}", ) Path.mkdir( new_planarity_output_dir_for_order_and_edgecount, parents=True, exist_ok=True, ) g6_planarity_outfile_name = ( f"n{order}.m{num_edges}.{command}.out.txt" ) self._move_file( orig_planarity_output_dir, g6_planarity_outfile_name, new_planarity_output_dir_for_order_and_edgecount, ) canonical_g6_planarity_outfile_name = ( f"n{order}.m{num_edges}.canonical.{command}.out.txt" ) self._move_file( orig_planarity_output_dir, canonical_g6_planarity_outfile_name, new_planarity_output_dir_for_order_and_edgecount, ) if self.planarity_backup_path: makeg_g6_planarity_outfile_name = ( f"n{order}.m{num_edges}.makeg.{command}.out.txt" ) self._move_file( orig_planarity_output_dir, makeg_g6_planarity_outfile_name, new_planarity_output_dir_for_order_and_edgecount, ) canonical_makeg_g6_planarity_outfile_name = ( f"n{order}.m{num_edges}.makeg.canonical.{command}.out.txt" ) self._move_file( orig_planarity_output_dir, canonical_makeg_g6_planarity_outfile_name, new_planarity_output_dir_for_order_and_edgecount, ) def _move_file(self, src_dir: Path, filename: str, dest_dir: Path): """Moves {src_dir}/{filename} to {dest_dir}/ If a file with name {filename} occurs in {dest_dir}, we os.remove() it before we shutil.move() the file to the {dest_dir}. Args: src_dir: Directory containing the file to move filename: Name of the file to be moved dest_dir: Directory to which we wish to move the file """ src_path = Path.joinpath(src_dir, filename) dest_path = Path.joinpath(dest_dir, filename) if Path.is_file(dest_path): os.remove(dest_path) shutil.move(src_path, dest_dir) def find_planarity_discrepancies(self): """Find graph order, edge-count, and command w/ planarity disagreements If the sub-dicts for geng_g6, makeg_g6, geng_canonical_g6, and makeg_canonical_g6 disagree for a given order, num_edges, and command, then the structure will be as follows: { f"{order}": { f"{num_edges}" : { f"{command}: { "geng_g6": { 'numGraphs': numGraphs_from_file, 'numOK': numOK_from_file, 'numNONEMBEDDABLE': numNONEMBEDDABLE_from_file, }, "makeg_g6": { 'numGraphs': numGraphs_from_file, 'numOK': numOK_from_file, 'numNONEMBEDDABLE': numNONEMBEDDABLE_from_file, }, "geng_canonical_g6": { 'numGraphs': numGraphs_from_file, 'numOK': numOK_from_file, 'numNONEMBEDDABLE': numNONEMBEDDABLE_from_file, }, "makeg_canonical_g6": { 'numGraphs': numGraphs_from_file, 'numOK': numOK_from_file, 'numNONEMBEDDABLE': numNONEMBEDDABLE_from_file, } } ... } ... } ... } """ for order in self.orders: planarity_output_dir = Path.joinpath( self.output_parent_dir, f"{order}", "planarity_results" ) self.planarity_discrepancies[order] = {} for num_edges in range(max_num_edges_for_order(order) + 1): self.planarity_discrepancies[order][num_edges] = {} for command in PLANARITY_ALGORITHM_SPECIFIERS(): self.planarity_discrepancies[order][num_edges][ command ] = {} planarity_output_dir_for_order_and_edgecount = ( Path.joinpath( planarity_output_dir, f"{command}", f"{num_edges}" ) ) geng_g6_output = Path.joinpath( planarity_output_dir_for_order_and_edgecount, f"n{order}.m{num_edges}.{command}.out.txt", ) self._extract_planarity_results( geng_g6_output, order, num_edges, command, "geng_g6" ) geng_canonical_g6_output = Path.joinpath( planarity_output_dir_for_order_and_edgecount, f"n{order}.m{num_edges}.canonical.{command}.out.txt", ) self._extract_planarity_results( geng_canonical_g6_output, order, num_edges, command, "geng_canonical_g6", ) if self.planarity_backup_path: makeg_g6_output = Path.joinpath( planarity_output_dir_for_order_and_edgecount, f"n{order}.m{num_edges}.makeg.{command}.out.txt", ) self._extract_planarity_results( makeg_g6_output, order, num_edges, command, "makeg_g6", ) makeg_canonical_g6_output = Path.joinpath( planarity_output_dir_for_order_and_edgecount, f"n{order}.m{num_edges}.makeg.canonical." + f"{command}.out.txt", ) self._extract_planarity_results( makeg_canonical_g6_output, order, num_edges, command, "makeg_canonical_g6", ) if ( self.planarity_discrepancies[order][num_edges][ command ]["geng_g6"] == self.planarity_discrepancies[order][num_edges][ command ]["makeg_g6"] == self.planarity_discrepancies[order][num_edges][ command ]["geng_canonical_g6"] == self.planarity_discrepancies[order][num_edges][ command ]["makeg_canonical_g6"] ): del self.planarity_discrepancies[order][num_edges][ command ] else: if ( self.planarity_discrepancies[order][num_edges][ command ]["geng_g6"] == self.planarity_discrepancies[order][num_edges][ command ]["geng_canonical_g6"] ): del self.planarity_discrepancies[order][num_edges][ command ] if not self.planarity_discrepancies[order][num_edges]: del self.planarity_discrepancies[order][num_edges] planarity_discrepancies_for_order_outfile_path = Path.joinpath( planarity_output_dir, f"n{order}.planarity_discrepancies.txt" ) with open( planarity_discrepancies_for_order_outfile_path, "w", encoding="utf-8", ) as planarity_discrepancies_for_order_outfile: planarity_discrepancies_for_order_outfile.write( "Discrepancies in planarity output results for order " f"{order} by edge-count and algorithm specifier:\n" "======\n" ) planarity_discrepancies_for_order_outfile.write( json.dumps(self.planarity_discrepancies[order], indent=4) ) def _extract_planarity_results( self, planarity_outfile: Path, order: int, num_edges: int, command: str, file_type: str, ): """Use planarity_testAllGraphs_output_parsing to parse planarity output Args: planarity_outfile: The output of having run planarity Test All Graphs for the given algorithm command specifier for all graphs of a specific order and num_edges order: Order of graphs in .g6 file on which planarity's Test All Graphs was run for the given command to produce the outfile num_edges: Number of edges for graphs in the .g6 file command: Algorithm command specifier file_type: One of geng_g6, makeg_g6, geng_canonical_g6, or makeg_canonical_g6, indicating the type of the original .g6 input file """ ( _, _, numGraphs_from_file, numOK_from_file, numNONEMBEDDABLE_from_file, _, ) = process_file_contents(planarity_outfile, command) self.planarity_discrepancies[order][num_edges][command][file_type] = { "numGraphs": numGraphs_from_file, "numOK": numOK_from_file, "numNONEMBEDDABLE": numNONEMBEDDABLE_from_file, } def get_planarity_discrepancy_g6_diffs( self, ): # pylint: disable=too-many-locals """Get .g6 file diffs only if discrepancies exist in planarity output Since discrepancies in planarity results for all graphs of a given order and edge-count must only occur on graphs that appear in one .g6 file but not the other, it was useful to use the G6DiffFinder to narrow down the set of graphs for which there occurred more OKs vs. NONEMBEDDABLEs. The dictionary self.planarity_discrepancies is populated during the course of find_planarity_discrepancies(). To avoid duplicating work, the dictionary diffs_performed maps graph orders to a set of edge-counts for which we have already performed the diffs between the various .g6 file combinations: { f"{order}": { e_0, e_1, ..., e_k } ... } The logs in {self.output_parent_dir}/{order}/g6_diff_finder_logs/ contain log.info messages emitted by the G6DiffFinder when comparing: - geng .g6 vs. geng canonical .g6 - G6DiffFinder.n{order}.geng_vs_geng-canonical.log - geng .g6 vs. makeg .g6 - G6DiffFinder.n{order}.geng_vs_makeg.log - geng .g6 vs. makeg canonical .g6 - G6DiffFinder.n{order}.geng_vs_makeg-canonical.log - geng canonical .g6 vs. makeg canonical .g6 - G6DiffFinder.n{order}.geng-canonical_vs_makeg-canonical.log - makeg .g6 vs. makeg canonical .g6 - G6DiffFinder.n{order}.makeg_vs_makeg-canonical.log Up to 10 diff files will be output to the directory {self.output_parent_dir}/{order}/{graphs}/{num_edges}/{results}/ - geng .g6 vs. geng canonical .g6 corresponds to the files: - graphs_in_n{order}.m{num_edges}_not_in_n{order}.m{num_edges}.canonical.g6 - graphs_in_n{order}.m{num_edges}.canonical_not_in_n{order}.m{num_edges}.g6 - geng .g6 vs. makeg .g6 corresponds to the files: - graphs_in_n{order}.m{num_edges}_not_in_n{order}.m{num_edges}.makeg.g6 - graphs_in_n{order}.m{num_edges}.makeg_not_in_n{order}.m{num_edges}.g6 - geng .g6 vs. makeg canonical .g6 corresponds to the files: - graphs_in_n{order}.m{num_edges}_not_in_n{order}.m{num_edges}.makeg.canonical.g6 - graphs_in_n{order}.m{num_edges}.makeg.canonical_not_in_n{order}.m{num_edges}.g6 - geng canonical .g6 vs. makeg canonical .g6 corresponds to the files: - graphs_in_n{order}.m{num_edges}.canonical_not_in_n{order}.m{num_edges}.makeg.canonical.g6 - graphs_in_n{order}.m{num_edges}.makeg.canonical_not_in_n{order}.m{num_edges}.canonical.g6 - makeg .g6 vs. makeg canonical .g6 corresponds to the files: - graphs_in_n{order}.m{num_edges}.makeg_not_in_n{order}.m{num_edges}.makeg.canonical.g6 - graphs_in_n{order}.m{num_edges}.makeg.canonical_not_in_n{order}.m{num_edges}.makeg.g6 """ # pylint: disable=line-too-long diffs_performed = {} for ( order, discrepancies_for_order, ) in self.planarity_discrepancies.items(): if not diffs_performed.get(order): diffs_performed[order] = set() output_dir_for_order = Path.joinpath( self.output_parent_dir, f"{order}" ) log_dir_for_order = Path.joinpath( output_dir_for_order, "g6_diff_finder_logs", ) Path.mkdir(log_dir_for_order, parents=True, exist_ok=True) log_path_for_geng_g6_vs_geng_canonical_g6 = Path.joinpath( log_dir_for_order, f"G6DiffFinder.n{order}.geng_vs_geng-canonical.log", ) log_path_for_makeg_g6_vs_makeg_canonical_g6 = Path.joinpath( log_dir_for_order, f"G6DiffFinder.n{order}.makeg_vs_makeg-canonical.log", ) log_path_for_geng_g6_vs_makeg_g6 = Path.joinpath( log_dir_for_order, f"G6DiffFinder.n{order}.geng_vs_makeg.log" ) log_path_for_geng_canonical_g6_vs_makeg_canonical_g6 = ( Path.joinpath( log_dir_for_order, f"G6DiffFinder.n{order}." + "geng-canonical_vs_makeg-canonical.log", ) ) log_path_for_geng_g6_vs_makeg_canonical_g6 = Path.joinpath( log_dir_for_order, f"G6DiffFinder.n{order}.geng_vs_makeg-canonical.log", ) for num_edges in discrepancies_for_order.keys(): if num_edges in diffs_performed[order]: continue diffs_performed[order].add(num_edges) g6_files_for_order_and_edgecount = Path.joinpath( output_dir_for_order, "graphs", f"{num_edges}" ) geng_g6_path = Path.joinpath( g6_files_for_order_and_edgecount, f"n{order}.m{num_edges}.g6", ) geng_canonical_g6_path = Path.joinpath( g6_files_for_order_and_edgecount, f"n{order}.m{num_edges}.canonical.g6", ) makeg_g6_path = Path.joinpath( g6_files_for_order_and_edgecount, f"n{order}.m{num_edges}.makeg.g6", ) makeg_canonical_g6_path = Path.joinpath( g6_files_for_order_and_edgecount, f"n{order}.m{num_edges}.makeg.canonical.g6", ) self._get_diffs( geng_g6_path, geng_canonical_g6_path, log_path_for_geng_g6_vs_geng_canonical_g6, ) if self.planarity_backup_path: self._get_diffs( geng_g6_path, makeg_g6_path, log_path_for_geng_g6_vs_makeg_g6, ) self._get_diffs( geng_g6_path, makeg_canonical_g6_path, log_path_for_geng_g6_vs_makeg_canonical_g6, ) # Don't bother with geng canonical vs. makeg, because makeg # and geng are so similar; only do the other 4 possible # pairings (4C2 - 1 = 5 cases, which each produce 2 files) self._get_diffs( geng_canonical_g6_path, makeg_canonical_g6_path, log_path_for_geng_canonical_g6_vs_makeg_canonical_g6, ) self._get_diffs( makeg_g6_path, makeg_canonical_g6_path, log_path_for_makeg_g6_vs_makeg_canonical_g6, ) def _get_diffs( self, first_comparand_infile: Path, second_comparand_infile: Path, log_path: Path, ): """Uses G6DiffFinder to output set differences of two .g6 infiles Args: first_comparand_infile: Path to first .g6 comparand infile second_comparand_infile: Path to second .g6 comparand infile log_path: Path to which logs should be written Raises: G6DiffFinderException: If an error occurred during the processing of the two .g6 comparand infiles """ try: g6_diff_finder = G6DiffFinder( first_comparand_infile, second_comparand_infile, log_path ) except Exception as g6_diff_finder_error: raise G6DiffFinderException( "Unable to initialize G6DiffFinder with given input files: " f"'{first_comparand_infile}', '{second_comparand_infile}'" ) from g6_diff_finder_error try: g6_diff_finder.graph_set_diff() except BaseException as e: raise G6DiffFinderException( "Failed to discern diff between two .g6 input files." ) from e if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="G6 File Generation and Comparison Tool\n\n" "For each graph order in the specified range, a child directory " "of the output directory will be created named 'n{order}'. This " "directory will contain subdirectories:\n" "- graphs/\n" "\t- For each edge-count from 0 to (N * (N - 1)) / 2, a " "directory is created to contain:\n" "\tn{order}.m{num_edges}.g6\n" "\tn{order}.m{num_edges}.canonical.g6\n" "\tn{order}.m{num_edges}.makeg.g6 (req. planarity-backup " "\tpath)\n" "\tn{order}.m{num_edges}.makeg.canonical.g6 (req. " "planarity-backup path)\n" "\t- results/ which will contain the diffs of these .g6 " " files, pairs of files:\n" "\t\t- graphs_in_{first_infile}_not_in_{second_infile}.g6\n" "\t\t- graphs_in_{second_infile}_not_in_{first_infile}.g6\n" "\tThese pairs should contain the same number of graphs\n" "- planarity_results/\n" "\t- For each graph algorithm command specifier, there will be " "a subdirectory containing one subdirectory for each edge-count " "(0 to (N * (N - 1)) / 2), which will contain files:\n" "\tn{order}.m{num_edges}.{command}.out.txt\n" "\tn{order}.m{num_edges}.canonical.{command}.out.txt\n" "\tn{order}.m{num_edges}.makeg.{command}.out.txt\n" "\tn{order}.m{num_edges}.makeg.canonical.{command}.out.txt\n" "- g6_diff_finder_logs/\n" "\t- Will contain logs for each of the following comparisons:\n" "\tG6DiffFinder.n{order}.geng_vs_geng-canonical.log\n" "\tG6DiffFinder.n{order}.geng_vs_makeg.log\n" "\tG6DiffFinder.n{order}.geng_vs_makeg-canonical.log\n" "\tG6DiffFinder.n{order}.geng-canonical_vs_makeg-canonical.log\n" "\tG6DiffFinder.n{order}.makeg_vs_makeg-canonical.log\n", ) parser.add_argument( "-g", "--gengpath", type=Path, required=True, help="Path to nauty geng executable", metavar="PATH_TO_GENG_EXECUTABLE", ) parser.add_argument( "-n", "--orders", type=parse_range, required=True, help="Order(s) of graphs for which you wish to generate .g6 files", metavar="X[,Y]", ) parser.add_argument( "-p", "--planaritypath", type=Path, required=False, help="Path to planarity executable", metavar="PATH_TO_PLANARITY_EXECUTABLE", ) parser.add_argument( "-b", "--planaritybackuppath", type=Path, required=False, help="Path to planarity-backup executable (optional; not public!)", metavar="PATH_TO_PLANARITY_BACKUP_EXECUTABLE", ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="OUTPUT_DIR_PATH", help="Parent directory under which subdirectories will be created " "for output results. If none provided, defaults to:\n" "\tTestSupport/results/g6_generation_and_comparison_driver/", ) args = parser.parse_args() g6_generation_and_comparison_driver = G6GenerationAndComparisonDriver( geng_path=args.gengpath, orders=args.orders, planarity_path=args.planaritypath, planarity_backup_path=args.planaritybackuppath, output_parent_dir=args.outputdir, ) g6_generation_and_comparison_driver.generate_g6_files() g6_generation_and_comparison_driver.run_planarity() g6_generation_and_comparison_driver.reorganize_files() g6_generation_and_comparison_driver.find_planarity_discrepancies() g6_generation_and_comparison_driver.get_planarity_discrepancy_g6_diffs() edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/graph.py000066400000000000000000000255351521450711600306100ustar00rootroot00000000000000"""Pure Python module containing classes to represent graphs as adjacency lists Classes: GraphError Graph """ __all__ = ["Graph", "GraphError"] from copy import deepcopy from typing import Optional class GraphError(BaseException): """Signals issues encountered while managing a Graph object""" def __init__(self, message): super().__init__(message) self.message = message class Graph: """A simple graph datastructure containing the order graph adjacency list""" def __init__( self, order: int = 0, graph_adj_list_repr: Optional[list[list[int]]] = None, ) -> None: """Constructor for Graph class Had to do some fancy footwork to support deepcopy() machinery: if you try to create an instance with only the graph_adj_list_repr and don't supply the order, the order is inferred from the number of elements of the outer list, and then the graph adjacency-list representation is validated. If you supply both the order and adjacency list, then we ensure the order and graph adjacency list representation make sense. If only the graph order is supplied, then a list of lists is initialized, which will be subsequently populated by repeated calls to self.add_edge(). Args: order: The order of the graph graph_adj_list_repr: A list of lists of integers comprising the graph's adjacency list representation Raises: GraphError: If supplied order and graph_adj_list_repr do not agree, re-raises from Graph._validate_adj_list(), or if graph order is not in closed interval [2, 12]. """ if order == 0: if graph_adj_list_repr: order_implied_by_adj_list = len(graph_adj_list_repr) try: Graph._validate_adj_list( order_implied_by_adj_list, graph_adj_list_repr ) except GraphError as e: raise GraphError( "Unable to initialize graph from given adjacency list." ) from e self.order = order_implied_by_adj_list self.graph_adj_list_repr = graph_adj_list_repr else: if order < 2 or order > 12: raise GraphError( f"Graph order '{order}' is not in closed interval [2, 12]." ) if graph_adj_list_repr: try: Graph._validate_adj_list(order, graph_adj_list_repr) except GraphError as e: raise GraphError( "Unable to initialize Graph with given order and " "adjacency list" ) from e self.order = order self.graph_adj_list_repr = graph_adj_list_repr else: self.graph_adj_list_repr = [[] for _ in range(order)] self.order = order @staticmethod def _validate_adj_list( order: int, graph_adj_list_repr: list[list[int]] ) -> None: """Static method to ensure constructor arguments make sense Args: order: The order of the graph graph_adj_list_repr: A list of lists of integers comprising the graph's adjacency list representation Raises: GraphError: If the number of vertices in the adjacency list doesn't correspond to the given graph order, if the adjacency list is not a list of lists of integers, if any of the vertex labels in the list of lists of integers are outside the expected range, or if there are duplicates within a vertex's adjacency list. """ if len(graph_adj_list_repr) != order: raise GraphError( "Number of vertices in adjacency list, " f"{len(graph_adj_list_repr)}, doesn't correspond to " f"desired graph order {order}." ) acceptable_vertex_labels = range(order) u = -1 for adj_list in graph_adj_list_repr: u += 1 adj_list_index = -1 neighbours_seen = set() for v in adj_list: adj_list_index += 1 if not isinstance(v, int): raise GraphError( "Adjacency list representation must be a list of " f"lists of integers; list for vertex {u} contains " f"{v}, which is not an integer." ) if v == -1: if len(graph_adj_list_repr[adj_list_index + 1]) > 0: raise GraphError( f"Adjacency list for vertex {u} is malformed: " "there must be no elements after -1, since this " "is used to terminate the 0-based adjacency list." ) elif v not in acceptable_vertex_labels: raise GraphError( f"Adjacency list for vertex {u} includes vertex {v}, " "which is outside the range " f"[{acceptable_vertex_labels[0]}, " f"{acceptable_vertex_labels[-1]}]." ) if v in neighbours_seen: raise GraphError( "Duplicate neighbours present in adjacency list for " f"vertex {u}." ) neighbours_seen.add(v) def __copy__(self): raise NotImplementedError( "Shallow copy is not supported for Graph objects, since the " "adjacency list is a list of a mutable type, lists of integers." ) def __deepcopy__(self, memo): """Deepcopy Graph object The Graph class has a mutable member, graph_adj_list_repr, which is a list of lists of integers. Therefore, it's necessary to implement deepcopy so that when we delete_edge() from a copy of a graph, it doesn't change the original graph's graph_adj_list_repr. """ if id(self) in memo: return memo[id(self)] graph_deepcopy = Graph( self.order, deepcopy(self.graph_adj_list_repr, memo) ) memo[id(self)] = graph_deepcopy return graph_deepcopy def __eq__(self, other) -> bool: """Graph equality Implementing the simplest form of equality from a representational standpoint. There are other definitions of equality of graphs that are relevant in other applications, but originally this was here to help debugging __deepcopy__: after creating a deepcopy of a graph, they were equal, but then after changing one of the adjacency lists, they were no longer equal. Returns: If you're comparing any other object to the Graph, if the orders of two graphs don't agree, if the number of elements in the outer list disagree (i.e. two different vertex sets), or if the corresponding adjacency lists disagree for any of the vertices, then return False """ if ( isinstance(other, Graph) and self.order == other.order and len(self.graph_adj_list_repr) == len(other.graph_adj_list_repr) and all( u_list == v_list for (u_list, v_list) in zip( self.graph_adj_list_repr, other.graph_adj_list_repr ) ) ): return True return False def __str__(self) -> str: """Get Graph's string representation Returns: Returns str of graph's adjacency list representation that matches what's produced by _WriteAdjList() in edge-addition-planarity-suite e.g. N={order} 0: X Y Z -1 ... """ adj_list_str = f"N={self.order}\n" u = -1 for adj_list in self.graph_adj_list_repr: u += 1 line = f"{u}: " for v in adj_list: line += f"{v} " line += "-1\n" adj_list_str += line return adj_list_str def _vertex_validation(self, u: int): """Ensure vertex has label in range for 0-based graphs Args: u: Vertex index Raises: GraphError: If endpoints are out of bounds for 0-based labelling """ if u >= self.order or u < 0: raise GraphError( "Invalid vertex label; must be in closed interval " f"[0, {self.order - 1}]." ) def add_arc(self, u: int, v: int): """Add arc to graph's adjacency list representation Args: u: Index of tail vertex of arc to add v: Index head vertex of arc to add Raises: GraphError: If arc endpoints are invalid """ try: self._vertex_validation(u) self._vertex_validation(v) except GraphError as e: raise GraphError(f"Unable to add arc ({u}, {v}).") from e self.graph_adj_list_repr[u].append(v) def get_max_degree(self) -> int: """Infer max degree from graph adjacency list representation Returns: The length of the longest adjacency list in the graph adjacency list representation (i.e. the list of lists of integers) """ return max(len(x) for x in self.graph_adj_list_repr) def get_vertex_degree_counts(self) -> dict[int, int]: """Determine number of vertices of each degree in graph Returns: Dict mapping integers (degree) to integers (number of vertices of of that degree in graph) """ vertex_degree_counts = {} for adj_list in self.graph_adj_list_repr: deg_of_u = len(adj_list) try: vertex_degree_counts[deg_of_u] += 1 except KeyError: vertex_degree_counts[deg_of_u] = 1 return vertex_degree_counts def delete_edge(self, u: int, v: int): """Delete edge Removes both arcs from the respective vertex adjacency lists Args: u: Index of first vertex incident on edge to delete v: Index of second vertex incident on edge to delete Raises: GraphError: If edge endpoints are invalid """ try: self._vertex_validation(u) self._vertex_validation(v) except GraphError as e: raise GraphError(f"Unable to delete edge {{{u}, {v}}}.") from e self.graph_adj_list_repr[u].remove(v) self.graph_adj_list_repr[v].remove(u) graph_generation_orchestrator.py000066400000000000000000000157641521450711600355460ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting"""Use multithreading and subprocess modules to parallelize geng calls Functions: _call_geng( geng_path: Path, canonical_files: bool, order: int, num_edges: int, output_dir: Path, ) -> None _validate_and_normalize_geng_workload_args( geng_path: Path, order: int, output_dir: Path ) -> tuple[Path, int, Path] distribute_geng_workload( geng_path: Path, canonical_files: bool, order: int, output_dir: Path ) -> None """ #!/usr/bin/env python __all__ = ["distribute_geng_workload"] import sys import multiprocessing import subprocess import argparse import shutil from pathlib import Path from planaritytesting_utils import ( max_num_edges_for_order, is_path_to_executable, ) def _call_geng( geng_path: Path, canonical_files: bool, order: int, num_edges: int, output_dir: Path, ) -> None: """Call nauty geng as blocking process on multiprocessing thread Opens a file for write (overwrites file if it exists) within the output_dir and uses subprocess.run() to start a blocking process on the multiprocessing pool thread to call the nauty geng executable with the desired order and number of edges, with stdout redirected to the output file object. The resulting .g6 output file will contain all graphs of the desired order for a single edge count. Args: geng_path: Path to the nauty geng executable canonical_files: Bool to indicate whether or not to generate graphs with canonical labelling order: Desired number of vertices num_edges: Desired number of edges output_dir: Directory to which you wish to write the resulting .g6 file """ filename = Path.joinpath( output_dir, f"n{order}.m{num_edges}{'.canonical' if canonical_files else ''}.g6", ) with open(filename, "w", encoding="utf-8") as outfile: command = [f"{geng_path}", f"{order}", f"{num_edges}:{num_edges}"] if canonical_files: command.insert(1, "-l") subprocess.run( command, stdout=outfile, stderr=subprocess.PIPE, check=False ) def _validate_and_normalize_geng_workload_args( geng_path: Path, order: int, output_dir: Path ) -> tuple[Path, int, Path]: """Validates and normalizes args provided to distribute_geng_workload Ensures geng_path corresponds to an executable, that order is an integer in closed interval [2, 12], and that output_dir is a valid Path specifying where results from executing geng should be output. Args: geng_path: Path to the nauty geng executable order: Desired number of vertices output_dir: Directory to which you wish to write the resulting .g6 file If none provided, defaults to: TestSupport/results/graph_generation_orchestrator/{order} Raises: argparse.ArgumentTypeError: If any of the args passed from the command line are determined invalid under more specific scrutiny Returns: A tuple comprised of the geng_path, order, and output_dir """ if not is_path_to_executable(geng_path): raise argparse.ArgumentTypeError( f"Path for geng executable '{geng_path}' does not correspond to " "an executable." ) if not order or order < 2 or order > 12: raise argparse.ArgumentTypeError( "Graph order must be between 2 and 12." ) if not output_dir: test_support_dir = Path(sys.argv[0]).resolve().parent.parent output_parent_dir = Path.joinpath( test_support_dir, "results", "graph_generation_orchestrator" ) candidate_output_dir = Path.joinpath(output_parent_dir, f"{order}") output_dir = candidate_output_dir elif not isinstance(output_dir, Path) or output_dir.is_file(): raise argparse.ArgumentTypeError("Output directory path is invalid.") output_dir = output_dir.resolve() try: candidate_order_from_path = (int)(output_dir.parts[-1]) except ValueError: output_dir = Path.joinpath(output_dir, str(order)) except IndexError as e: raise argparse.ArgumentTypeError( f"Unable to extract parts from output dir path '{output_dir}'." ) from e else: if candidate_order_from_path != order: raise argparse.ArgumentTypeError( f"Output directory '{output_dir}' seems to indicate " f"graph order should be '{candidate_order_from_path}'" f", which does not mach order from command line args " f"'{order}'. Please verify your command line args and retry." ) Path.mkdir(output_dir, parents=True, exist_ok=True) return geng_path, order, output_dir def distribute_geng_workload( geng_path: Path, canonical_files: bool, order: int, output_dir: Path ) -> None: """Use starmap_async on multiprocessingn pool to _call_geng Args: geng_path: Path to the nauty geng executable order: Desired number of vertices generate_canonical: Bool to indicate whether or not to generate graphs with canonical labelling output_dir: Directory to which you wish to write the resulting .g6 file """ geng_path, order, output_dir = _validate_and_normalize_geng_workload_args( geng_path, order, output_dir ) Path.mkdir(output_dir, parents=True, exist_ok=True) call_geng_args = [ (geng_path, canonical_files, order, edge_count, output_dir) for edge_count in range(max_num_edges_for_order(order) + 1) ] with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool: _ = pool.starmap_async(_call_geng, call_geng_args) pool.close() pool.join() if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Graph Generation Orchestrator\n" "Orchestrates calls to nauty's geng to generate graphs for a " "given order, separated out into files for each edge count. The " "output files will have paths:\n" "\t{output_dir}/{order}/n{order}.m{num_edges}(.canonical)?.g6", ) parser.add_argument( "-g", "--gengpath", type=Path, metavar="PATH_TO_GENG_EXECUTABLE" ) parser.add_argument( "-l", "--canonicalfiles", action="store_true", help="Generate canonical .g6 files", ) parser.add_argument("-n", "--order", type=int, default=11, metavar="N") parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="G6_OUTPUT_DIR", help="If no output directory provided, defaults to\n" "\tTestSupport/results/graph_generation_orchestrator/{order}", ) args = parser.parse_args() distribute_geng_workload( geng_path=args.gengpath, canonical_files=args.canonicalfiles, order=args.order, output_dir=args.outputdir, ) edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/leaksorchestrator/000077500000000000000000000000001521450711600326625ustar00rootroot00000000000000planarity_leaks_config_manager.py000066400000000000000000000154121521450711600413610ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/leaksorchestrator"""Create default config file for values required by PlanarityLeaksOrchestrator Functions: initialize_planarity_leaks_orchestrator_config(config_path: Path) -> None initialize_test_random_graphs_config(config_path: Path) -> None initialize_test_random_max_planar_graph_generator_config( config_path: Path, ) -> None: initialize_test_random_nonplanar_graph_generator_config( config_path: Path, ) -> None: initialize_test_specific_graph_config(config_path: Path) -> None initialize_test_transform_graph_config(config_path: Path) -> None initialize_test_test_all_graphs_config(config_path: Path) -> None """ #!/usr/bin/env python __all__ = [] import configparser from pathlib import Path import sys def initialize_planarity_leaks_orchestrator_config(config_path: Path) -> None: """Initialize planarity leaks orchestrator config file with defaults Args: config_path: Path to config file to initialize with defaults """ initialize_test_random_graphs_config(config_path) initialize_test_random_max_planar_graph_generator_config(config_path) initialize_test_random_nonplanar_graph_generator_config(config_path) initialize_test_specific_graph_config(config_path) initialize_test_transform_graph_config(config_path) initialize_test_test_all_graphs_config(config_path) def initialize_test_random_graphs_config(config_path: Path) -> None: """Initialize planarity leaks orchestrator RandomGraphs config Args: config_path: Path to config file to initialize with defaults for test_random_graphs() """ test_random_graphs_config = configparser.ConfigParser() test_random_graphs_config.read(config_path) test_random_graphs_config["RandomGraphs"] = { "enabled": "False", "perform_full_analysis": "True", "num_graphs": "500", "order": "1000", "commands_to_run": "", } with open(config_path, "w", encoding="utf-8") as config_file: test_random_graphs_config.write(config_file) def initialize_test_random_max_planar_graph_generator_config( config_path: Path, ) -> None: """Initialize planarity leaks orchestrator callRandomMaxPlanarGraph config Args: config_path: Path to config file to initialize with defaults for PlanarityLeaksOrchestrator.test_random_max_planar_graph_generator() """ test_random_max_planar_graph_generator_config = configparser.ConfigParser() test_random_max_planar_graph_generator_config.read(config_path) test_random_max_planar_graph_generator_config[ "RandomMaxPlanarGraphGenerator" ] = { "enabled": "False", "perform_full_analysis": "True", "order": "1000", } with open(config_path, "w", encoding="utf-8") as config_file: test_random_max_planar_graph_generator_config.write(config_file) def initialize_test_random_nonplanar_graph_generator_config( config_path: Path, ) -> None: """Initialize planarity leaks orchestrator callRandomNonplanarGraph config Args: config_path: Path to config file to initialize with defaults for PlanarityLeaksOrchestrator.test_random_nonplanar_graph_generator() """ test_random_nonplanar_graph_generator_config = configparser.ConfigParser() test_random_nonplanar_graph_generator_config.read(config_path) test_random_nonplanar_graph_generator_config[ "RandomNonplanarGraphGenerator" ] = { "enabled": "False", "perform_full_analysis": "True", "order": "1000", } with open(config_path, "w", encoding="utf-8") as config_file: test_random_nonplanar_graph_generator_config.write(config_file) def initialize_test_specific_graph_config(config_path: Path) -> None: """Initialize planarity leaks orchestrator SpecificGraph config Args: config_path: Path to config file to initialize with defaults for PlanarityLeaksOrchestrator.test_specific_graph() """ test_specific_graph_config = configparser.ConfigParser() test_specific_graph_config.read(config_path) test_specific_graph_config["SpecificGraph"] = { "enabled": "False", "perform_full_analysis": "True", "infile_path": ">>CHANGEME<<", "commands_to_run": "", } with open(config_path, "w", encoding="utf-8") as config_file: test_specific_graph_config.write(config_file) def initialize_test_transform_graph_config(config_path: Path) -> None: """Initialize planarity leaks orchestrator Graph Transformation config Args: config_path: Path to config file to initialize with defaults for PlanarityLeaksOrchestrator.test_transform_graph() """ test_transform_graph_config = configparser.ConfigParser() test_transform_graph_config.read(config_path) test_transform_graph_config["TransformGraph"] = { "enabled": "False", "perform_full_analysis": "True", "infile_path": ">>CHANGEME<<", "output_formats_to_test": "", } with open(config_path, "w", encoding="utf-8") as config_file: test_transform_graph_config.write(config_file) def initialize_test_test_all_graphs_config(config_path: Path) -> None: """Initialize planarity leaks orchestrator TestAllGraphs config Args: config_path: Path to config file to initialize with defaults for PlanarityLeaksOrchestrator.test_test_all_graphs() """ test_test_all_graphs_config = configparser.ConfigParser() test_test_all_graphs_config.read(config_path) test_test_all_graphs_config["TestAllGraphs"] = { "enabled": "False", "perform_full_analysis": "True", "infile_path": ">>CHANGEME<<", "commands_to_run": "", } with open(config_path, "w", encoding="utf-8") as config_file: test_test_all_graphs_config.write(config_file) if __name__ == "__main__": config = configparser.ConfigParser( converters={ "list": lambda x: ( [i.strip() for i in x.split(",") if i] if len(x) > 0 else [] ) } ) leaks_orchestrator_package_root = Path(sys.argv[0]).resolve().parent default_config_path = Path.joinpath( leaks_orchestrator_package_root, "planarity_leaks_config.ini", ) config.read(default_config_path) if not config.sections(): initialize_planarity_leaks_orchestrator_config(default_config_path) else: response = input( "Do you wish to re-initialize the default config file? [Y/n]" ) if response.lower() == "y": initialize_planarity_leaks_orchestrator_config(default_config_path) else: print( "No changes have been made to planarity leaks default " f"configuration file at '{default_config_path}'." ) planarity_leaks_orchestrator.py000066400000000000000000001105201521450711600411350ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/leaksorchestrator"""Run leaks on MacOS to test for memory issues, e.g. leaks or overruns""" #!/usr/bin/env python __all__ = [] import argparse from datetime import datetime import configparser import multiprocessing import os from pathlib import Path import platform import shutil import subprocess import sys from typing import Optional # This is required to import from modules in the parent package sys.path.append(str(Path(sys.argv[0]).resolve().parent.parent)) # pylint: disable=wrong-import-position from planaritytesting_utils import ( PLANARITY_ALGORITHM_SPECIFIERS, GRAPH_FORMAT_SPECIFIERS, determine_input_filetype, is_path_to_executable, ) # pylint: enable=wrong-import-position class PlanarityLeaksOrchestratorError(Exception): """ Custom exception for representing errors that arise when performing memory checks on a planarity executable. """ def __init__(self, message): super().__init__(message) self.message = message class PlanarityLeaksOrchestrator: """Driver for planarity memory leak analysis on MacOS using leaks util""" def __init__( self, planarity_path: Optional[Path] = None, output_dir: Optional[Path] = None, ) -> None: """Initialize PlanarityLeaksOrchestrator instance Args: planarity_path: Path to planarity executable output_dir: Directory under which subdirectories will be created for each of the jobs specified in planarity_leaks_config.ini Raises: argparse.ArgumentTypeError: If the planarity_path doesn't correspond to an executable, if the output_dir corresponds to a file, OSError: If run on any platform other than MacOS, or if the leaks utility is not found """ if platform.system() != "Darwin": raise OSError("This utility is only for use on MacOS.") if not shutil.which("leaks"): raise OSError( "leaks is not installed; please install leaks and ensure your " "path variable contains the directory to which it is " "installed." ) planarity_project_root = ( Path(sys.argv[0]).resolve().parent.parent.parent.parent ) if not planarity_path: planarity_path = Path.joinpath( planarity_project_root, "Release", "planarity" ) else: planarity_path = planarity_path.resolve() if not is_path_to_executable(planarity_path): raise argparse.ArgumentTypeError( f"'Path for planarity executable {planarity_path}' doesn't " "correspond to an executable." ) self.curr_timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") if not output_dir: output_dir = Path.joinpath( planarity_project_root, "TestSupport", "results", "planarity_leaks_orchestrator", ) if output_dir.is_file(): raise argparse.ArgumentTypeError( f"Path '{output_dir}' corresponds to a file, and can't be " "used as the output directory." ) output_dir = Path.joinpath(output_dir, f"{self.curr_timestamp}") if Path.is_dir(output_dir): shutil.rmtree(output_dir) Path.mkdir(output_dir, parents=True, exist_ok=True) self.planarity_path = planarity_path self.output_dir = output_dir @staticmethod def setup_leaks_environment_variables( perform_full_analysis: bool = False, ) -> dict[str, str]: """Sets up environment variables to configure granularity of leaks runs Args: perform_full_analysis: bool to indicate whether additional environment variables should be set for the leaks run; if True, sets the following additional environment variables: MallocStackLoggingNoCompact - This option is similar to MallocStackLogging but makes sure that all allocations are logged, no matter how small or how short lived the buffer may be. MallocScribble - If set, free sets each byte of every released block to the value 0x55. MallocPreScribble - If set, malloc sets each byte of a newly allocated block to the value 0xAA. This increases the likelihood that a program making assumptions about freshly allocated memory fails. MallocGuardEdges - If set, malloc adds guard pages before and after large allocations. MallocCheckHeapStart = - If set, specifies the number of allocations to wait before beginning periodic heap checks every as specified by MallocCheckHeapEach. If MallocCheckHeapStart is set but MallocCheckHeapEach is not specified, the default check repetition is 1000. MallocCheckHeapEach = - If set, run a consistency check on the heap every operations. MallocCheckHeapEach is only meaningful if MallocCheckHeapStart is also set. """ leaks_environment_variables = os.environ.copy() leaks_environment_variables["MallocStackLogging"] = "1" if perform_full_analysis: leaks_environment_variables = dict( leaks_environment_variables, **{ "MallocStackLoggingNoCompact": "1", "MallocScribble": "1", "MallocPreScribble": "1", "MallocGuardEdges": "1", "MallocCheckHeapStart": "1", "MallocCheckHeapEach": "6", # "MallocErrorAbort": "1", # "MallocCheckHeapAbort": "1", }, ) return leaks_environment_variables def _run_leaks( self, command_args: list[str], leaks_outfile_basename: Path, leaks_env: dict, cwd: Optional[Path] = None, ) -> None: """Run leaks utility for given args Args: command_args: List of strings comprised of executable name and command line arguments leaks_outfile_name: name of file to which you wish to write leaks output leaks_env: dictionary containing all relevant environment variables for leaks execution (e.g. os.environ() + MallocStackLogging=1) cwd: If not None, indicates to subprocess.run() that we wish to change the working directory to cwd before executing the child """ outfile_path = leaks_outfile_basename.with_suffix( leaks_outfile_basename.suffix + ".log" ) if cwd: outfile_path = Path.joinpath(cwd, outfile_path) full_args = [ # Use script utility to wrap leaks because there is some issue # capturing *all* terminal output when you invoke leaks directly # and redirect stdout and stderr to file "script", f"{outfile_path}", "leaks", "-atExit", "--", ] + command_args subprocess.run( full_args, # Must default to None for stdout and stdin so that script utility # will capture *all* leaks output from running planarity; see #59 # for investigation env=leaks_env, check=False, cwd=cwd, ) @staticmethod def _valid_commands_to_run(commands_to_run: tuple[str, ...]) -> bool: """Ensures all algorithm command specifiers in tuple are valid Args: commands_to_run: tuple of strings which we wish to ensure are valid algorithm command specifiers Returns: bool indicating whether or not the commands_to_run are valid """ return set(commands_to_run) <= set(PLANARITY_ALGORITHM_SPECIFIERS()) @staticmethod def _valid_graph_output_formats( output_formats_to_run: tuple[str, ...], ) -> bool: """Ensures all graph output format specifiers in tuple are valid Args: output_formats_to_run: tuple of strings which we wish to ensure are valid graph output format specifiers Returns: bool indicating whether or not the output_formats_to_run are valid """ return set(output_formats_to_run) <= set(GRAPH_FORMAT_SPECIFIERS()) def test_random_graphs( self, num_graphs_to_generate: int, order: int, commands_to_run: tuple[str, ...] = (), perform_full_analysis: bool = False, ) -> None: """Check RandomGraphs() for memory issues 'planarity -r [-q] C K N': Random graphs Args: num_graphs_to_generate: Number of graphs to randomly generate order: Number of vertices in each randomly generated graph commands_to_run: Tuple containing algorithm command specifiers which we wish to use to test random graphs. perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) Raises: PlanarityLeaksOrchestratorError: If commands_to_run contains [an] invalid algorithm command specifier(s) """ if not commands_to_run: commands_to_run = PLANARITY_ALGORITHM_SPECIFIERS() elif not self._valid_commands_to_run(commands_to_run): raise PlanarityLeaksOrchestratorError( "commands_to_run param contains invalid command specifiers: " f"'{commands_to_run}'." ) leaks_env = self.setup_leaks_environment_variables( perform_full_analysis ) test_random_graphs_args = [ (command, num_graphs_to_generate, order, leaks_env) for command in commands_to_run ] with multiprocessing.Pool( processes=multiprocessing.cpu_count() ) as pool: _ = pool.starmap_async( self._test_random_graphs, test_random_graphs_args ) pool.close() pool.join() def _test_random_graphs( self, command: str, num_graphs_to_generate: int, order: int, leaks_env: dict, ) -> None: """Runs RandomGraphs() for a given algorithm command specifier Args: command: algorithm command specifier num_graphs_to_generate: Number of graphs to randomly generate order: Number of vertices in each randomly generated graph leaks_env: dictionary containing all relevant environment variables for leaks execution (e.g. os.environ() + MallocStackLogging=1) """ random_graphs_args = [ f"{self.planarity_path}", "-r", f"-{command}", f"{num_graphs_to_generate}", f"{order}", ] random_graphs_outfile_parent_dir = Path.joinpath( self.output_dir, "RandomGraphs", ) Path.mkdir( random_graphs_outfile_parent_dir, parents=True, exist_ok=True ) random_graphs_leaks_outfile_basename = Path.joinpath( random_graphs_outfile_parent_dir, f"RandomGraphs.{command}.{num_graphs_to_generate}.{order}", ) self._run_leaks( random_graphs_args, random_graphs_leaks_outfile_basename, leaks_env ) def test_random_max_planar_graph_generator( self, order: int, perform_full_analysis: bool = False, ) -> None: """Check callRandomMaxPlanarGraph for memory issues 'planarity -rm [-q] N O [O2]': Maximal planar random graph Args: order: Desired number of vertices for randomly generated maximal planar graph perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) """ leaks_env = ( PlanarityLeaksOrchestrator.setup_leaks_environment_variables( perform_full_analysis ) ) max_planar_graph_generator_outfile_parent_dir = Path.joinpath( self.output_dir, "RandomMaxPlanarGraphGenerator", ) Path.mkdir( max_planar_graph_generator_outfile_parent_dir, parents=True, exist_ok=True, ) leaks_outfile_basename = Path.joinpath( max_planar_graph_generator_outfile_parent_dir, f"RandomMaxPlanarGraphGenerator.{order}", ) adjlist_before_processing = leaks_outfile_basename.with_suffix( leaks_outfile_basename.suffix + ".AdjList.BEFORE.out.txt" ) adjlist_after_processing = leaks_outfile_basename.with_suffix( leaks_outfile_basename.suffix + ".AdjList.AFTER.out.txt" ) random_max_planar_graph_args = [ f"{self.planarity_path}", "-rm", # planarityRandomGraphs.c line 432 - we don't want to # saveEdgeListFormat, since we're only testing what's available # from command line "-q", f"{order}", f"{adjlist_after_processing}", f"{adjlist_before_processing}", ] self._run_leaks( random_max_planar_graph_args, leaks_outfile_basename, leaks_env, ) def test_random_nonplanar_graph_generator( self, order: int, perform_full_analysis: bool = False, ) -> None: """Check callRandomNonplanarGraph for memory issues 'planarity -rn [-q] N O [O2]': Non-planar random graph (maximal planar plus edge) Args: order: Desired number of vertices for randomly generated nonplanar graph perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) """ leaks_env = ( PlanarityLeaksOrchestrator.setup_leaks_environment_variables( perform_full_analysis ) ) random_nonplanar_graph_generator_outfile_parent_dir = Path.joinpath( self.output_dir, "RandomNonplanarGraphGenerator", ) Path.mkdir( random_nonplanar_graph_generator_outfile_parent_dir, parents=True, exist_ok=True, ) leaks_outfile_basename = Path.joinpath( random_nonplanar_graph_generator_outfile_parent_dir, f"RandomNonplanarGraphGenerator.{order}", ) adjlist_before_processing = leaks_outfile_basename.with_suffix( leaks_outfile_basename.suffix + ".AdjList.BEFORE.out.txt" ) adjlist_after_processing = leaks_outfile_basename.with_suffix( leaks_outfile_basename.suffix + ".AdjList.AFTER.out.txt" ) random_nonplanar_graph_generator_args = [ f"{self.planarity_path}", "-rn", "-q", f"{order}", f"{adjlist_after_processing}", f"{adjlist_before_processing}", ] self._run_leaks( random_nonplanar_graph_generator_args, leaks_outfile_basename, leaks_env, ) def test_specific_graph( self, infile_path: Path, commands_to_run: tuple[str, ...] = (), perform_full_analysis: bool = False, ) -> None: """Check SpecificGraph() for memory issues for given commands Args: infile_path: Path to .g6 infile containing single graph on which we wish to run the algorithms specified in commands_to_run commands_to_run: Tuple containing algorithm command specifiers which we wish to use to test specific graph. perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) Raises: PlanarityLeaksOrchestratorError: If input_file is not a valid .g6 file, or if commands_to_run contains at least one invalid algorithm command specifier. """ try: file_type = determine_input_filetype(infile_path) except ValueError as input_filetype_error: raise PlanarityLeaksOrchestratorError( "Failed to determine input filetype of " f"'{infile_path}'." ) from input_filetype_error if not commands_to_run: commands_to_run = PLANARITY_ALGORITHM_SPECIFIERS() elif not self._valid_commands_to_run(commands_to_run): raise PlanarityLeaksOrchestratorError( "commands_to_run param contains invalid contains at least one " f"invalid algorithm command specifier: '{commands_to_run}'." ) specific_graph_outfile_parent_dir = Path.joinpath( self.output_dir, "SpecificGraph", ) Path.mkdir( specific_graph_outfile_parent_dir, parents=True, exist_ok=True, ) infile_copy_path = Path.joinpath( specific_graph_outfile_parent_dir, infile_path.name ).resolve() shutil.copyfile(infile_path, infile_copy_path) infile_path = infile_copy_path leaks_env = ( PlanarityLeaksOrchestrator.setup_leaks_environment_variables( perform_full_analysis ) ) test_specific_graph_args = [ ( specific_graph_outfile_parent_dir, infile_path, command, leaks_env, ) for command in commands_to_run ] with multiprocessing.Pool( processes=multiprocessing.cpu_count() ) as pool: _ = pool.starmap_async( self._test_specific_graph, test_specific_graph_args ) pool.close() pool.join() def _test_specific_graph( self, specific_graph_outfile_parent_dir: Path, infile_path: Path, command: str, leaks_env: dict, ) -> None: """Check SpecificGraph() for memory issues for given command 'planarity -s [-q] C I O [O2]': Specific graph Args: specific_graph_outfile_parent_dir: Directory to which results for testing SpecificGraph() will be output infile_path: Path to .g6 infile containing single graph on which we wish to run the algorithm specified by command command: Algorithm command specifier to indicate what algorithm you wish to run SpecificGraph() with leaks_env: dictionary containing all relevant environment variables for leaks execution (e.g. os.environ() + MallocStackLogging=1) """ specific_graph_leaks_outfile_basename = Path( f"SpecificGraph.{infile_path.with_suffix('').name}.{command}", ) specific_graph_primary_output = ( specific_graph_leaks_outfile_basename.with_suffix( specific_graph_leaks_outfile_basename.suffix + ".PRIMARY.out.txt" ) ) specific_graph_secondary_output = ( specific_graph_leaks_outfile_basename.with_suffix( specific_graph_leaks_outfile_basename.suffix + ".SECONDARY.out.txt" ) ) specific_graph_args = [ f"{self.planarity_path}", "-s", f"-{command}", f"{infile_path.name}", f"{specific_graph_primary_output}", f"{specific_graph_secondary_output}", ] self._run_leaks( command_args=specific_graph_args, leaks_outfile_basename=specific_graph_leaks_outfile_basename, leaks_env=leaks_env, cwd=specific_graph_outfile_parent_dir, ) def test_transform_graph( self, infile_path: Path, output_formats_to_test: tuple[str, ...] = (), perform_full_analysis: bool = False, ) -> None: """Check Graph Transformation for memory issues for given formats Args: infile_path: Path to graph input file containing single graph to be transformed to each of the desired output_formats_to_test output_formats_to_test: Tuple containing graph output formats to to test. perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) Raises: PlanarityLeaksOrchestratorError: If input_file is not a valid graph input file, or if output_formats_to_test contains at least one invalid graph output format specifier. """ try: _ = determine_input_filetype(infile_path) except ValueError as input_filetype_error: raise PlanarityLeaksOrchestratorError( "Failed to determine input filetype of " f"'{infile_path}'." ) from input_filetype_error if not output_formats_to_test: output_formats_to_test = tuple(GRAPH_FORMAT_SPECIFIERS().keys()) elif not self._valid_graph_output_formats(output_formats_to_test): raise PlanarityLeaksOrchestratorError( "output_formats_to_test param contains at least one invalid " f"graph output format specifier: '{output_formats_to_test}'." ) transform_graph_outfile_parent_dir = Path.joinpath( self.output_dir, "TransformGraph", ) Path.mkdir( transform_graph_outfile_parent_dir, parents=True, exist_ok=True, ) infile_copy_path = Path.joinpath( transform_graph_outfile_parent_dir, infile_path.name ).resolve() shutil.copyfile(infile_path, infile_copy_path) infile_path = infile_copy_path leaks_env = self.setup_leaks_environment_variables( perform_full_analysis ) test_transform_graph_args = [ ( transform_graph_outfile_parent_dir, infile_path, output_format, leaks_env, ) for output_format in output_formats_to_test ] with multiprocessing.Pool( processes=multiprocessing.cpu_count() ) as pool: _ = pool.starmap_async( self._test_transform_graph, test_transform_graph_args ) pool.close() pool.join() def _test_transform_graph( self, transform_graph_outfile_parent_dir: Path, infile_path: Path, output_format: str, leaks_env: dict, ) -> None: """Check Graph Transformation for memory issues for given output format 'planarity -t [-q] -t(gam) I O ': Transform graph Args: transform_graph_outfile_parent_dir: Directory to which results for testing graph transformation to desired format will be output infile_path: Path to graph input file containing single graph to be transformed to the desired output_format output_format: Graph output format specifier to indicate the target graph output format for which you wish to test graph transform leaks_env: dictionary containing all relevant environment variables for leaks execution (e.g. os.environ() + MallocStackLogging=1) """ graph_format_specifiers = GRAPH_FORMAT_SPECIFIERS() transform_graph_leaks_outfile_basename = Path( f"{infile_path.with_suffix('').name}" f".{graph_format_specifiers[output_format]}", ) transformed_graph_output = ( transform_graph_leaks_outfile_basename.with_suffix( transform_graph_leaks_outfile_basename.suffix + ".out.txt" ) ) transform_graph_leaks_outfile_basename = Path( "TransformGraph" + f".{transform_graph_leaks_outfile_basename}" ) specific_graph_args = [ f"{self.planarity_path}", "-t", f"-t{output_format}", f"{infile_path.name}", f"{transformed_graph_output}", ] self._run_leaks( command_args=specific_graph_args, leaks_outfile_basename=transform_graph_leaks_outfile_basename, leaks_env=leaks_env, cwd=transform_graph_outfile_parent_dir, ) def test_test_all_graphs( self, infile_path: Path, commands_to_run: tuple[str, ...] = (), perform_full_analysis: bool = False, ) -> None: """Check Test All Graphs for memory issues for given commands Args: infile_path: Path to .g6 infile containing at least one graph on which we wish to run the algorithms specified in commands_to_run commands_to_run: Tuple containing algorithm command specifiers which we wish to use to test test all graphs. perform_full_analysis: bool to determine what environment variables leaks_env will hold (to be sent to subprocess.run()) Raises: PlanarityLeaksOrchestratorError: If input_file is not a valid .g6 file, or if commands_to_run contains at least one invalid algorithm command specifier. """ try: file_type = determine_input_filetype(infile_path) except ValueError as input_filetype_error: raise PlanarityLeaksOrchestratorError( "Failed to determine input filetype of " f"'{infile_path}'." ) from input_filetype_error if file_type != "G6": raise PlanarityLeaksOrchestratorError( f"Determined '{infile_path}' has filetype '{file_type}', " "which is not supported; please supply a .g6 file." ) if not commands_to_run: commands_to_run = PLANARITY_ALGORITHM_SPECIFIERS() elif not self._valid_commands_to_run(commands_to_run): raise PlanarityLeaksOrchestratorError( "commands_to_run param contains invalid contains at least one " f"invalid algorithm command specifier: '{commands_to_run}'." ) test_all_graphs_outfile_parent_dir = Path.joinpath( self.output_dir, "TestAllGraphs", ) Path.mkdir( test_all_graphs_outfile_parent_dir, parents=True, exist_ok=True, ) infile_copy_path = Path.joinpath( test_all_graphs_outfile_parent_dir, infile_path.name ).resolve() shutil.copyfile(infile_path, infile_copy_path) infile_path = infile_copy_path leaks_env = ( PlanarityLeaksOrchestrator.setup_leaks_environment_variables( perform_full_analysis ) ) test_specific_graph_args = [ ( test_all_graphs_outfile_parent_dir, infile_path, command, leaks_env, ) for command in commands_to_run ] with multiprocessing.Pool( processes=multiprocessing.cpu_count() ) as pool: _ = pool.starmap_async( self._test_test_all_graphs, test_specific_graph_args ) pool.close() pool.join() def _test_test_all_graphs( self, test_all_graphs_outfile_parent_dir: Path, infile_path: Path, command: str, leaks_env: dict, ) -> None: """Check Test All Graphs for memory issues for given command 'planarity -t [-q] C I O': Test All Graphs Args: test_all_graphs_outfile_parent_dir: Directory to which results for testing Test All Graphs functionality will be output infile_path: Path to .g6 infile containing single graph on which we wish to run the algorithm specified by command command: Algorithm command specifier to indicate what algorithm you wish to run Test All Graphs with leaks_env: dictionary containing all relevant environment variables for leaks execution (e.g. os.environ() + MallocStackLogging=1) """ test_all_graphs_leaks_outfile_basename = Path( f"TestAllGraphs.{infile_path.with_suffix('').name}.{command}", ) test_all_graphs_output = ( test_all_graphs_leaks_outfile_basename.with_suffix( test_all_graphs_leaks_outfile_basename.suffix + ".out.txt" ) ) test_all_graphs_args = [ f"{self.planarity_path}", "-t", f"-{command}", f"{infile_path.name}", f"{test_all_graphs_output}", ] self._run_leaks( command_args=test_all_graphs_args, leaks_outfile_basename=test_all_graphs_leaks_outfile_basename, leaks_env=leaks_env, cwd=test_all_graphs_outfile_parent_dir, ) if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options] > /dev/null 2>&1", description="Planarity leaks analysis\n" "Automates runs of leaks on MacOS to identify memory issues for the " "following:\n" "- Random graphs with one small graph,\n" "- Random max planar graph generator,\n" "- Random nonplanar graph generator,\n" "- Specific Graph (will only run on first graph in input file)\n" "- Graph Transformation tool\n" "- Test All Graphs\n" "\n\tN.B. One must run the planarity_leaks_config_manager.py script, " "and update default configuration values. For example, you must " "provide an infile_name for the SpecificGraph job, and any jobs you " "wish to run must have 'enabled' set to True.\n\n" "If an output directory is specified, a subdirectory will be created " "to contain the results:\n" "\t{output_dir}/{test_timestamp}/\n" "If an output directory is not specified, defaults to:\n" "\tTestSupport/results/planarity_leaks_orchestrator/" "{test_timestamp}/\n", ) parser.add_argument( "-p", "--planaritypath", type=Path, default=None, help="Must be a path to a planarity executable that was compiled " "with -g; defaults to:\n" "\t{planarity_project_root}/Release/planarity", metavar="PATH_TO_PLANARITY_EXECUTABLE", ) parser.add_argument( "-c", "--configfile", type=Path, default=None, metavar="PATH_TO_CONFIG_FILE", help="Optional path to planarity leaks orchestrator config file;" "defaults to:\n" "\t{planarity_project_root}/TestSupport/planaritytesting/" "leaksorchestrator/planarity_leaks_config.ini", ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="DIR_FOR_RESULTS", help="If no output directory provided, defaults to\n" "\t{planarity_project_root}/TestSupport/results/" "planarity_leaks_orchestrator/", ) args = parser.parse_args() leaks_orchestrator_root = Path(sys.argv[0]).resolve().parent config_file = args.configfile if not config_file: config_file = Path.joinpath( leaks_orchestrator_root, "planarity_leaks_config.ini", ) config = configparser.ConfigParser( converters={ "list": lambda x: ( [i.strip() for i in x.split(",") if i] if len(x) > 0 else [] ) } ) config.read(config_file) if not config.sections(): raise argparse.ArgumentTypeError( f"'{config_file}' doesn't correspond to a planarity leaks config " "file. Please run planarity leaks config manager to generate " "default config and modify values as necessary for the jobs you " "wish to run." ) planarity_leaks_orchestrator = PlanarityLeaksOrchestrator( planarity_path=args.planaritypath, output_dir=args.outputdir, ) for section in config: if not config[section].getboolean("enabled"): continue if section == "RandomGraphs": num_graphs_from_config = int(config[section]["num_graphs"]) order_from_config = int(config[section]["order"]) commands_to_run_from_config = config.getlist( # type: ignore section, "commands_to_run" ) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) planarity_leaks_orchestrator.test_random_graphs( num_graphs_to_generate=num_graphs_from_config, order=order_from_config, commands_to_run=commands_to_run_from_config, perform_full_analysis=perform_full_analysis_from_config, ) elif section == "RandomMaxPlanarGraphGenerator": order_from_config = int(config[section]["order"]) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) planarity_leaks_orchestrator.test_random_max_planar_graph_generator( order=order_from_config, perform_full_analysis=perform_full_analysis_from_config, ) elif section == "RandomNonplanarGraphGenerator": order_from_config = int(config[section]["order"]) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) planarity_leaks_orchestrator.test_random_nonplanar_graph_generator( order=order_from_config, perform_full_analysis=perform_full_analysis_from_config, ) elif section == "SpecificGraph": infile_path_from_config = Path(config[section]["infile_path"]) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) commands_to_run_from_config = config.getlist( # type: ignore section, "commands_to_run" ) planarity_leaks_orchestrator.test_specific_graph( infile_path=infile_path_from_config, commands_to_run=commands_to_run_from_config, perform_full_analysis=perform_full_analysis_from_config, ) elif section == "TransformGraph": infile_path_from_config = Path(config[section]["infile_path"]) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) output_formats_to_test_from_config = config.getlist( # type: ignore section, "output_formats_to_test" ) planarity_leaks_orchestrator.test_transform_graph( infile_path=infile_path_from_config, output_formats_to_test=output_formats_to_test_from_config, perform_full_analysis=perform_full_analysis_from_config, ) elif section == "TestAllGraphs": infile_path_from_config = Path(config[section]["infile_path"]) perform_full_analysis_from_config = config[section].getboolean( "perform_full_analysis", fallback=False ) commands_to_run_from_config = config.getlist( # type: ignore section, "commands_to_run" ) planarity_leaks_orchestrator.test_test_all_graphs( infile_path=infile_path_from_config, commands_to_run=commands_to_run_from_config, perform_full_analysis=perform_full_analysis_from_config, ) planarity_testAllGraphs_orchestrator.py000066400000000000000000000322201521450711600370540ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting"""Orchestrate calls to planarity's testAllGraphs() functionality Functions: call_planarity_testAllGraphs( planarity_path: Path, canonical_files: bool, makeg_g6: bool, command: str, order: int, num_edges: int, input_dir: Path, output_dir: Path, ) -> None _validate_and_normalize_planarity_testAllGraphs_workload_args( planarity_path: Path, order: int, input_dir: Path, output_dir: Path ) -> tuple[Path, int, Path, Path] distribute_planarity_testAllGraphs_workload( planarity_path: Path, canonical_files: bool, makeg_g6: bool, order: int, input_dir: Path, output_dir: Path, ) -> None """ #!/usr/bin/env python __all__ = ["distribute_planarity_testAllGraphs_workload"] import sys import multiprocessing import subprocess import argparse from pathlib import Path from typing import Optional from planaritytesting_utils import ( PLANARITY_ALGORITHM_SPECIFIERS, max_num_edges_for_order, is_path_to_executable, ) def call_planarity_testAllGraphs( planarity_path: Path, canonical_files: bool, makeg_g6: bool, command: str, order: int, num_edges: int, input_dir: Path, output_dir: Path, log_root_path: Optional[Path] = None, ) -> None: """Call planarity as blocking process on multiprocessing thread Uses subprocess.run() to start a blocking process on the multiprocessing pool thread to call the planarity executable so that it applies the algorithm specified by the command to all graphs in the input file and outputs results to the output file, i.e. calls planarity -t {command} {infile_path} {outfile_path} Args: planarity_path: Path to the planarity executable canonical_files: Bool to indicate whether or not the .g6 input files are in canonical form makeg_g6: Bool to indicate whether or not the .g6 input files were generated by planarity-backup's embedded makeg command: Algorithm specifier character order: Desired number of vertices num_edges: Desired number of edges input_dir: Directory containing the .g6 file with all graphs of the given order and num_edges output_dir: Directory to which you wish to write the output of applying the algorithm corresponding to the command to all graphs in the input .g6 file log_root_path: Optional path to directory under which logs containing output stream results shall be written """ canonical_ext = ".canonical" if canonical_files else "" makeg_ext = ".makeg" if makeg_g6 else "" stem = f"n{order}.m{num_edges}{makeg_ext}{canonical_ext}" infile_path = Path.joinpath( input_dir, f"{stem}.g6" ) outfile_path = Path.joinpath( output_dir, f"{command}", f"{stem}.{command}.out.txt", ) if log_root_path is not None: logfile_path = Path.joinpath(log_root_path, f"{command}", f"{stem}.{command}.log") with open(logfile_path, "w") as logfile: subprocess.run( [ f"{planarity_path}", "-t", f"-{command}", f"{infile_path}", f"{outfile_path}", ], stdout=logfile, stderr=subprocess.STDOUT, check=False, ) else: subprocess.run( [ f"{planarity_path}", "-t", f"-{command}", f"{infile_path}", f"{outfile_path}", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, ) def _validate_and_normalize_planarity_testAllGraphs_workload_args( # pylint: disable=too-many-branches planarity_path: Path, order: int, input_dir: Path, output_dir: Path ) -> tuple[Path, int, Path, Path]: """Validates and normalizes args provided to distribute_planarity_workload Ensures planarity_path corresponds to an executable, that order is an integer in closed interval [2, 12], that input_dir is a valid Path that corresponds to a directory containing .g6 files, and that output_dir is a valid Path specifying where results from executing planarity should be output. Args: planarity_path: Path to the planarity executable order: Desired number of vertices input_dir: Directory containing all graphs of the desired order, with each file containing all graphs of a specific edge-count. If none provided, defaults to: TestSupport/results/graph_generation_orchestrator/{order} output_dir: Directory to which the results of executing planarity Test All Graphs for the respective command on each .g6 file will be written. If none provided, defaults to: TestSupport/results/planarity_testAllGraphs_orchestrator/{order} Raises: argparse.ArgumentTypeError: If any of the args passed from the command line are determined invalid under more specific scrutiny Returns: A tuple comprised of the planarity_path, order, input_dir, and output_dir """ if not is_path_to_executable(planarity_path): raise argparse.ArgumentTypeError( f"Path for planarity executable '{planarity_path}' does not " "correspond to an executable." ) if not order or not isinstance(order, int) or order < 2 or order > 12: raise argparse.ArgumentTypeError( "Graph order must be an integer between 2 and 12." ) if not input_dir: test_support_dir = Path(sys.argv[0]).resolve().parent.parent input_dir = Path.joinpath( test_support_dir, "results", "graph_generation_orchestrator", f"{order}", ) if not isinstance(input_dir, Path) or not input_dir.is_dir(): raise argparse.ArgumentTypeError("Input directory path is invalid.") if input_dir.is_dir() and not any(Path(input_dir).iterdir()): raise argparse.ArgumentTypeError("Input dir exists, but is empty.") input_dir = input_dir.resolve() try: candidate_order_from_path = (int)(input_dir.parts[-1]) except ValueError: pass except IndexError as e: raise argparse.ArgumentTypeError( f"Unable to extract parts from input dir path '{input_dir}'." ) from e else: if candidate_order_from_path != order: raise argparse.ArgumentTypeError( f"Input directory '{input_dir}' seems to indicate " f"graph order should be '{candidate_order_from_path}'" f", which does not mach order from command line args " f"'{order}'. Please verify your command line args and retry." ) if not output_dir: test_support_dir = Path(sys.argv[0]).resolve().parent.parent output_parent_dir = Path.joinpath( test_support_dir, "results", "planarity_testAllGraphs_orchestrator" ) candidate_output_dir = Path.joinpath(output_parent_dir, f"{order}") output_dir = candidate_output_dir elif not isinstance(output_dir, Path) or output_dir.is_file(): raise argparse.ArgumentTypeError("Output directory path is invalid.") output_dir = output_dir.resolve() try: candidate_order_from_path = (int)(output_dir.parts[-1]) except ValueError: output_dir = Path.joinpath(output_dir, str(order)) except IndexError as e: raise argparse.ArgumentTypeError( f"Unable to extract parts from output dir path '{output_dir}'." ) from e else: if candidate_order_from_path != order: raise argparse.ArgumentTypeError( f"Output directory '{output_dir}' seems to indicate " f"graph order should be '{candidate_order_from_path}'" f", which does not mach order from command line args " f"'{order}'. Please verify your command line args and retry." ) Path.mkdir(output_dir, parents=True, exist_ok=True) return planarity_path, order, input_dir, output_dir def distribute_planarity_testAllGraphs_workload( # pylint: disable=too-many-arguments planarity_path: Path, canonical_files: bool, makeg_g6: bool, order: int, input_dir: Path, output_dir: Path, log_output_streams: bool = False, ) -> None: """Use starmap_async on multiprocessing pool to _call_planarity Args: planarity_path: Path to the planarity executable canonical_files: Bool to indicate whether or not the .g6 input files are in canonical form makeg_g6: Bool to indicate whether or not the .g6 input files were generated by planarity-backup's embedded makeg order: Desired number of vertices input_dir: Directory containing all graphs of the desired order, with each file containing all graphs of a specific edge-count output_dir: Directory in which we will create one subdirectory per graph algorithm command, where the results of executing planarity Test All Graphs for the respective command on each .g6 file will be written log_output_streams: determines whether we throw away output streams or write to file """ planarity_path, order, input_dir, output_dir = ( _validate_and_normalize_planarity_testAllGraphs_workload_args( planarity_path, order, input_dir, output_dir ) ) log_root_path = Path.joinpath(output_dir, "logs") if log_output_streams else None for command in PLANARITY_ALGORITHM_SPECIFIERS(): path_to_make = Path.joinpath(output_dir, f"{command}") Path.mkdir(path_to_make, parents=True, exist_ok=True) if log_root_path is not None: path_to_make = Path.joinpath(log_root_path, f"{command}") Path.mkdir(path_to_make, parents=True, exist_ok=True) call_planarity_args = [ ( planarity_path, canonical_files, makeg_g6, command, order, num_edges, input_dir, output_dir, log_root_path, ) for num_edges in range(max_num_edges_for_order(order) + 1) for command in PLANARITY_ALGORITHM_SPECIFIERS() ] with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool: _ = pool.starmap_async( call_planarity_testAllGraphs, call_planarity_args ) pool.close() pool.join() if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Planarity testAllGraphs execution orchestrator\n\n" "Orchestrates calls to planarity's Test All Graphs " "functionality.\n" "Expects input directory to contain a subdirectory whose name is " "the order containing .g6 files to be tested. Each .g6 file " "contains all graphs of the given order with a specific number of " "edges:\n" "\t{input_dir}/{order}/n{order}.m{num_edges}(.makeg)?" "(.canonical)?.g6\n\n" "Output files will have paths:\n" "\t{output_dir}/{order}/{command}/n{order}.m{num_edges}(.makeg)?" "(.canonical)?.{command}.out.txt", ) parser.add_argument( "-p", "--planaritypath", type=Path, metavar="PATH_TO_PLANARITY_EXECUTABLE", ) parser.add_argument( "-l", "--canonicalfiles", action="store_true", help="Indicates .g6 input files are in canonical form", ) parser.add_argument( "-m", "--makegfiles", action="store_true", help="Indicates .g6 input files were generated by makeg", ) parser.add_argument("-n", "--order", type=int, metavar="N", default=11) parser.add_argument( "-i", "--inputdir", type=Path, default=None, metavar="DIR_CONTAINING_G6_FILES", help="If no input directory provided, defaults to\n" "\tTestSupport/results/graph_generation_orchestrator/{order}", ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="DIR_FOR_RESULTS", help="If no output directory provided, defaults to\n" "\tTestSupport/results/planarity_testAllGraphs_orchestrator/" "{order}", ) parser.add_argument( "-w", "--writestreams", action="store_true", help="Write stdout/stderr to logfile. Defaults to False, but if true," "writes to:" "\tTestSupport/results/planarity_testAllGraphs_orchestrator/{order}/logs", ) args = parser.parse_args() distribute_planarity_testAllGraphs_workload( planarity_path=args.planaritypath, canonical_files=args.canonicalfiles, makeg_g6=args.makegfiles, order=args.order, input_dir=args.inputdir, output_dir=args.outputdir, log_output_streams=args.writestreams, ) planarity_testAllGraphs_output_parsing.py000066400000000000000000000237561521450711600374360ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting"""Pure Python module for processing output from planarity testAllGraphs() Classes: TestAllGraphsPathError TestAllGraphsOutputFileContentsError Functions: validate_infile_name( infile_path:Path, order: Optional[int] = 0, command: Optional[str] = '' ) -> tuple[int, int, str] process_file_contents( infile_path:Path, command: str ) -> tuple[str, float, int, int, int, str] """ __all__ = [ "TestAllGraphsOutputFileContentsError", "TestAllGraphsPathError", "validate_infile_name", "process_file_contents", ] from pathlib import Path import re from typing import Optional from planaritytesting_utils import ( PLANARITY_ALGORITHM_SPECIFIERS, max_num_edges_for_order, ) class TestAllGraphsPathError(BaseException): """ Custom exception signalling issues with the paths of files that purportedly correspond to Test All Graphs output files. """ def __init__(self, message): super().__init__(message) self.message = message class TestAllGraphsOutputFileContentsError(BaseException): """ Custom exception for representing errors that arise when processing files which purportedly contain the output produced by running the planarity Test All Graphs functionality for a single algorithm command """ def __init__(self, message): super().__init__(message) self.message = message def validate_infile_name( infile_path: Path, order: Optional[int] = 0, command: Optional[str] = "" ) -> tuple[int, int, str]: """Checks that infile_path corresponds to output of running planarity Args: infile_path: pathlib.Path object indicating the input file whose name should be validated before processing order: If other files have been processed previously, this value is used for cross-validation (i.e. new infile should contain results for graphs of the same order as was previously incorporated into the processed_data); defaults to 0. command: If other files have been processed previously, this value is used for cross-validation (i.e. new infile should contain results for running the same command on other .g6 files that were previously incorporated into the processed_data); defaults to the empty string. Returns: order_from_filename, num_edges_from_filename, command_from_filename Raises: TestAllGraphsPathError: If infile_name doesn't match the expected pattern for an output file from planarity Test All Graphs, if the graph order indicated by the infile_name doesn't match previously processed files, if the num edges in the input graph doesn't make sense (greater than max_num_edges), if the algorithm command specifier isn't one of the supported values, or if the algorithm command specifier doesn't match previously processed files. """ infile_name = infile_path.parts[-1] match = re.match( r"n(?P\d+)\.m(?P\d+)(?:\.makeg)?(?:\.canonical)?" r"(?:\.g6)?\.(?P[pdo234])\.out\.txt", infile_name, ) if not match: raise TestAllGraphsPathError( f"Infile name '{infile_name}' doesn't match pattern." ) order_from_filename = int(match.group("order")) num_edges_from_filename = int(match.group("num_edges")) command_from_filename = match.group("command") if order and order != order_from_filename: raise TestAllGraphsPathError( f"Infile name '{infile_name}' indicates graph order doesn't" " equal previously derived order." ) if order and num_edges_from_filename > max_num_edges_for_order(order): raise TestAllGraphsPathError( f"Infile name '{infile_name}' indicates graph num_edges is" " greater than possible for a simple graph." ) if command_from_filename not in PLANARITY_ALGORITHM_SPECIFIERS(): raise TestAllGraphsPathError( f"Infile name '{infile_name}' contains invalid algorithm " f"command, '{command_from_filename}'." ) if command and command != command_from_filename: raise TestAllGraphsPathError( f"Command specified in input filename, '{command_from_filename}', " f"doesn't match previously derived algorithm command, '{command}'." ) return order_from_filename, num_edges_from_filename, command_from_filename def process_file_contents( infile_path: Path, command: str ) -> tuple[str, float, int, int, int, str]: """Processes and validates input file contents Uses re.match() to determine whether the file contents are of the expected form and attempts to extract the values produced by running planarity Test All Graphs for a given algorithm command on a specific .g6 file containing all graphs of a given order and single edge-count. Args: infile_path: pathlib.Path object indicating the input file whose contents are validated and processed command: expected algorithm command specifier derived from planarity Test All Graphs output filename Returns: planarity_infile_name: extracted from infile_path.parts duration: How long it took to run the chosen graph algorithm on all graphs of the given order for the given number of edges numGraphs: total number of graphs processed in the .g6 infile numOK: number of graphs for which running the planarity algorithm specified by the command returned OK (i.e. gp_Embed() with embedFlags corresponding to the command returned OK and gp_TestEmbedResultIntegrity() also returned OK) numNONEMBEDDABLE: number of graphs for which running the planarity algorithm specified by the command returned NONEMBEDDABLE (i.e. gp_Embed() with embedFlags corresponding to the command returned NONEMBEDDABLE and gp_TestEmbedResultIntegrity() also returned NONEMBEDDABLE) errorFlag: either SUCCESS (if all graphs reported OK or NONEMBEDDABLE) or ERROR (if an error was encountered allocating memory for or managing the graph datastructures, if an error was raised by the G6ReadIterator, or if the Result from gp_Embed() doesn't concur with gp_TestEmbedResultIntegrity()) Raises: TestAllGraphsOutputFileContentsError: If the input file's header doesn't have the expected format or values for those fields, if the body of the input file doesn't have the expected format, if the command derived doesn't match the expected algorithm command specifier, or if the error flag is something other than ERROR or SUCCESS. """ with open(infile_path, "r", encoding="utf-8") as infile: line = infile.readline() match = re.match( r'FILENAME="(?Pn\d+\.m\d+(\.makeg)?(\.canonical)?\.g6)"' r' DURATION="(?P\d+\.\d{3})"', line, ) if not match: raise TestAllGraphsOutputFileContentsError( f"Invalid file header in '{infile_path}'." ) planarity_infile_name_from_file = match.group("filename") if not planarity_infile_name_from_file: raise TestAllGraphsOutputFileContentsError( "Header doesn't contain input filename." ) duration_from_file = match.group("duration") if not duration_from_file: raise TestAllGraphsOutputFileContentsError( "Unable to extract duration from input file." ) duration_from_file = float(duration_from_file) line = infile.readline() match = re.match( r"-(?P\w) (?P\d+) " r"(?P\d+) (?P\d+) " r"(?PSUCCESS|ERROR)", line, ) if not match: raise TestAllGraphsOutputFileContentsError( "Invalid file contents." ) command_from_file = match.group("command") if command != command_from_file: raise TestAllGraphsOutputFileContentsError( f"Command specified in input file, '{command_from_file}', " "doesn't match command " f"given in input filename, '{command}'." ) if command_from_file not in PLANARITY_ALGORITHM_SPECIFIERS(): raise TestAllGraphsOutputFileContentsError( f"Command specifier '{command_from_file}' in input file " f"'{infile_path}' is not valid" ) numGraphs_from_file = match.group("numGraphs") if not numGraphs_from_file: raise TestAllGraphsOutputFileContentsError( f"Unable to extract numGraphs from input file '{infile_path}'." ) numOK_from_file = match.group("numOK") if not numOK_from_file: raise TestAllGraphsOutputFileContentsError( f"Unable to extract numOK from input file '{infile_path}'." ) numNONEMBEDDABLE_from_file = match.group("numNONEMBEDDABLE") if not numOK_from_file: raise TestAllGraphsOutputFileContentsError( "Unable to extract numNONEMBEDDABLE from input file " f"'{infile_path}'." ) errorFlag_from_file = match.group("errorFlag") if not errorFlag_from_file or errorFlag_from_file not in ( "SUCCESS", "ERROR", ): raise TestAllGraphsOutputFileContentsError( "Invalid errorFlag; must be SUCCESS or ERROR" ) return ( planarity_infile_name_from_file, duration_from_file, numGraphs_from_file, numOK_from_file, numNONEMBEDDABLE_from_file, errorFlag_from_file, ) # type: ignore edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/planaritytesting_utils.py000066400000000000000000000127441521450711600343260ustar00rootroot00000000000000"""Shared utilities for planarity testing Functions: PLANARITY_ALGORITHM_SPECIFIERS() -> tuple[str, ...] EDGE_DELETION_ANALYSIS_SPECIFIERS() -> tuple[str, ...] GRAPH_FORMAT_SPECIFIERS() -> dict[str, str] max_num_edges_for_order(order: int) -> int g6_header() -> str g6_suffix() -> str LEDA_header() -> str determine_input_filetype(infile_path: Path) -> str is_path_to_executable(executable_path: Path) -> bool parse_range(value: str) -> tuple[int, ...] raise_on_duplicates(ordered_pairs: list[tuple[Any, Any]]) -> Optional[Any] """ __all__ = [ "PLANARITY_ALGORITHM_SPECIFIERS", "EDGE_DELETION_ANALYSIS_SPECIFIERS", "GRAPH_FORMAT_SPECIFIERS", "max_num_edges_for_order", "g6_header", "g6_suffix", "LEDA_header", "determine_input_filetype", "is_path_to_executable", "parse_range", "raise_on_duplicates", ] import re from pathlib import Path import shutil from typing import Any, Optional def PLANARITY_ALGORITHM_SPECIFIERS() -> tuple[str, ...]: """Returns immutable tuple containing algorithm command specifiers""" return ("p", "d", "o", "2", "3", "4") def EDGE_DELETION_ANALYSIS_SPECIFIERS() -> tuple[str, ...]: """Allowed algorithm command specifiers for edge-deletion analysis""" return ("2", "3", "4") def GRAPH_FORMAT_SPECIFIERS() -> dict[str, str]: """Returns dict containing graph format specifiers mapped to extensions""" return {"g": "G6", "a": "AdjList", "m": "AdjMat"} def max_num_edges_for_order(order: int) -> int: """Returns max number of edges possible for given graph order""" return (int)((order * (order - 1)) / 2) def g6_header() -> str: """Returns expected .g6 file header string""" return ">>graph6<<" def g6_suffix() -> str: """Returns expected .g6 file suffix""" return ".g6" def LEDA_header() -> str: """Returns expected LEDA file header string""" return "LEDA.GRAPH" def determine_input_filetype(infile_path: Path) -> str: """Determine input filetype Args: infile_path: Path to graph input file Returns: str: One of 'LEDA', 'AdjList', 'AdjMat', or 'G6' Raises: ValueError: If infile doesn't exist, if it is empty, or if unable to determine the input file encoding """ if not infile_path.is_file(): raise ValueError(f"'{infile_path}' doesn't exist.") with open(infile_path, "r", encoding="utf-8") as infile: first_line = infile.readline() if not first_line: raise ValueError(f"'{infile_path}' is empty.") if LEDA_header() in first_line: return "LEDA" if re.match(r"N=(\d+)", first_line): return "AdjList" if first_line[0].isdigit(): return "AdjMat" if infile_path.suffix == g6_suffix() and ( (first_line.find(g6_header()) == 0) or (ord(first_line[0]) >= 63 and ord(first_line[0]) <= 126) ): return "G6" raise ValueError(f"Unable to determine filetype of '{infile_path}'.") def is_path_to_executable(executable_path: Path) -> bool: """Determine if Path corresponds to an executable Args: executable_path: Path for which we wish to determine whether it corresponds to an executable Returns: bool indicating whether (True) or not (False) the executable_path corresponds to an executable. """ if ( not executable_path or not isinstance(executable_path, Path) or not shutil.which(str(executable_path.resolve())) ): return False return True def parse_range(value: str) -> tuple[int, ...]: """Parse a single integer or a range of integers. Args: value: A string of the form 'X[,Y]', i.e. either a single value for the desired order X, or an interval inclusive of the endpoints [X, Y] Returns: A tuple containing either a single element, X, or every integer from X up to and including Y Raises: ValueError: if input string does not correspond to range notation, or if range endpoints (or singleton) aren't integers """ separator = "," if separator in value: if value.count(separator) > 1: raise ValueError( f"Invalid range '{value}' contains multiple commas; range " "should be of the form 'X,Y'" ) start, end = value.split(separator) try: start, end = int(start), int(end) except ValueError as int_cast_error: raise ValueError( f"Invalid range '{value}': both start and end values must be " "integers." ) from int_cast_error if start > end: raise ValueError( f"Invalid range '{value}': start value must not be greater " "than end value." ) # Transforms to interval that includes endpoints: '5,8' corresponds to # the tuple (5, 6, 7, 8) return tuple(range(start, end + 1)) try: return (int(value),) except ValueError as int_cast_error: raise ValueError( f"Invalid order specifier '{value}': should be a single " "integer 'X'" ) from int_cast_error def raise_on_duplicates(ordered_pairs: list[tuple[Any, Any]]) -> Optional[Any]: """Reject duplicate keys.""" d = {} for k, v in ordered_pairs: if k in d: raise ValueError(f"Duplicate key: {k}") d[k] = v return d test_table_generation_with_numInvalidOK.py000066400000000000000000001372361521450711600374460ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting"""Incorporate numInvalidOKs into Test Table Classes: TTGWithEDAPathConfig TTGwithEDAOrchestrator MultiOrderOrCommandTTGwithEDA ResumableTTGwithEDA """ __all__ = [] from abc import ABC, abstractmethod import argparse from dataclasses import dataclass import json from os import walk from pathlib import Path import platform import re import sys import time from typing import Optional from edge_deletion_analysis import ( EdgeDeletionAnalyzer, ) from planaritytesting_utils import ( parse_range, is_path_to_executable, PLANARITY_ALGORITHM_SPECIFIERS, EDGE_DELETION_ANALYSIS_SPECIFIERS, max_num_edges_for_order, raise_on_duplicates, ) from test_table_generator import ( TestTableGenerator, ) @dataclass class TTGWithEDAPathConfig: """Dataclass storing attributes pertaining to path config for TTA with EDA Args: planarity_path: Path to planarity executable to use when running edge-deletion analysis. If none provided, defaults to: {planarity_project_root}/Release/planarity graph_dir: Path to parent directory containing subdirectories for each graph order, the contents of which are .g6 graph files, one for each edgecount from 0 to (N * (N - 1)) / 2. If none provided, defaults to: TestSupport/results/graph_generation_orchestrator/ planarity_output_dir: Path to parent directory containing subdirectories for each graph order, the contents of which are subdirectories for each graph algorithm extension. Each of these directories contains the results of running planarity Test All Graphs for the given command on all graphs of a given order and number of edges. If none provided, defaults to: TestSupport/results/planarity_testAllGraphs_orchestrator/ test_table_output_dir: Parent directory under which subdirectory named after each {order} will be created for output results. If none provided, defaults to: TestSupport/tables/ canonical_files: Whether or not the .g6 files are in canonical form makeg_files: Whether or not the .g6 files were generated by makeg Raises: ValueError: - planarity_path doesn't correspond to an executable - the graph_dir is not a directory - the planarity_output_dir is not a directory - the test_table_output_dir is not a directory """ planarity_executable_path: Optional[Path] = None graph_dir: Optional[Path] = None planarity_output_dir: Optional[Path] = None test_table_output_dir: Optional[Path] = None canonical_files: bool = False makeg_files: bool = False def __post_init__(self): """Custom post-init logic to normalize and validate path config""" self.planarity_project_root: Path = ( Path(sys.argv[0]).resolve().parent.parent.parent ) self._normalize_and_validate_planarity_executable_path() self._normalize_and_validate_graph_dir() self._normalize_and_validate_planarity_output_dir() self._normalize_and_validate_test_table_output_dir() def _normalize_and_validate_planarity_executable_path(self) -> None: """Ensure planarity path refers to a valid executable Raises: ValueError: - planarity_path cannot be resolved - planarity_path doesn't correspond to an executable """ if not self.planarity_executable_path: extension = ".exe" if platform.system() == "Windows" else "" self.planarity_executable_path = Path.joinpath( self.planarity_project_root, "Release", f"planarity{extension}" ) try: self.planarity_executable_path = ( self.planarity_executable_path.resolve() ) except OSError as planarity_path_resolve_error: raise ValueError( "Path to planarity executable " f"'{self.planarity_executable_path}' cannot be resolved." ) from planarity_path_resolve_error if not is_path_to_executable(self.planarity_executable_path): raise ValueError( "Path for planarity executable " f"'{self.planarity_executable_path}' does not correspond to " "an executable." ) def _normalize_and_validate_graph_dir(self) -> None: """Set default for graph_dir and ensure directory exists Raises: ValueError: if self.graph_dir.is_dir() is false """ if not self.graph_dir: self.graph_dir = Path.joinpath( self.planarity_project_root, "TestSupport", "results", "graph_generation_orchestrator", ) if not self.graph_dir.is_dir(): raise ValueError( f"graph_dir = '{self.graph_dir}' does not correspond to a " "directory." ) def _normalize_and_validate_planarity_output_dir(self) -> None: """Set default for planarity_output_dir and ensure directory exists Raises: ValueError: if self.planarity_output_dir.is_dir() is false """ if not self.planarity_output_dir: self.planarity_output_dir = Path.joinpath( self.planarity_project_root, "TestSupport", "results", "planarity_testAllGraphs_orchestrator", ) if not self.planarity_output_dir.is_dir(): raise ValueError( f"planarity_output_dir = '{self.planarity_output_dir}' does " "not correspond to a directory." ) def _normalize_and_validate_test_table_output_dir(self) -> None: """Set default for test_table_output_dir and ensure directory exists Raises: ValueError: if self.test_table_output_dir.is_dir() is false """ if not self.test_table_output_dir: self.test_table_output_dir = Path.joinpath( self.planarity_project_root, "TestSupport", "tables", ) if not self.test_table_output_dir.is_dir(): raise ValueError( f"test_table_output_dir = '{self.test_table_output_dir}' " "does not correspond to a directory." ) def _input_dir_contains_expected_files( self, order: int, input_dir: Path ) -> bool: """Determines if files in input_dir are named in the expected scheme Args: order: Files in input_dir are expected to pertain to graphs of the given order input_dir: Directory containing files to check Returns: boolean indicating whether (True) or not (False) all files match the expected naming scheme, and all expected files are present """ infile_candidate_prefixes = sorted( ".".join(infile_candidate.name.split(".")[:2]) for infile_candidate in input_dir.iterdir() if (self.canonical_files == ("canonical" in infile_candidate.name)) if (self.makeg_files == ("makeg" in infile_candidate.name)) ) expected_infile_prefixes = sorted( f"n{order}.m{num_edges}" for num_edges in range(max_num_edges_for_order(order) + 1) ) return infile_candidate_prefixes == expected_infile_prefixes def check_graph_dir_contents(self, orders: tuple[int, ...]) -> None: """Ensure graph_dir contains subdirectories for each order graph_dir must be a Path to parent directory containing subdirectories for each graph order, the contents of which are .g6 graph files, one for each edgecount from 0 to (N * (N - 1)) / 2 Args: orders: The graph orders for which you wish to perform EDA and compile tables Raises: ValueError: If the graph_dir 1. contains files, 2. contains no subdirectories 3. is missing a subdirectory for one of the desired orders Or if the files in the subdirectories are not named in the expected scheme Returns: Path object graph_dir """ assert self.graph_dir is not None for dirpath, dirnames, filenames in walk(self.graph_dir): if len(filenames) > 0: raise ValueError( f"graph_dir = '{self.graph_dir}' has unexpected " "structure: should only contain subdirectories, one for " f"each graph order in {orders}." ) if len(dirnames) == 0: raise ValueError( f"graph_dir = '{self.graph_dir}' has no subdirectories: " "expected one subdirectory for each graph order in " f"{orders}." ) for order in orders: if str(order) not in dirnames: raise ValueError( f"graph_dir = '{self.graph_dir}' is missing " f"subdirectory for graph order N={order}." ) graph_dir_for_order = Path.joinpath( Path(dirpath).resolve(), f"{order}" ) if not self._input_dir_contains_expected_files( order, graph_dir_for_order ): raise ValueError( f"Graph dir for order '{graph_dir_for_order}' doesn't " "contain the expected .g6 infiles (i.e. one per each " f"num_edges for the given order {order})." ) # Don't recurse into subdirectories break def check_planarity_output_dir_contents( self, orders: tuple[int, ...], generate_tables_for_commands: tuple[str, ...], ) -> None: """Check planarity output dir has expected subdirectory structure planarity_output_dir should be a Path object corresponding to a parent directory containing subdirectories for each graph order, the contents of which are subdirectories for each graph algorithm extension. Each of these directories contains the results of running planarity Test All Graphs for the given command on all graphs of a given order and number of edges Args: orders: The graph orders for which you wish to perform EDA and compile tables generate_tables_for_commands: The graph algorithm commands for which you wish to perform EDA (if applicable) and generate tables Raises: ValueError: If the planarity_output_dir: 1. contains files, 2. contains no subdirectories 3. is missing a subdirectory for one of the desired orders If, for each of the subdirectories of planarity_output_dir containing the results for a specific graph order, the directory: 1. contains files, 2. contains no subdirectories 3. is missing a subdirectory for one of the graph algorithm command specifiers Or if the files in the sub-sub directories are not named in the expected scheme """ assert self.planarity_output_dir is not None for dirpath, dirnames, filenames in walk(self.planarity_output_dir): if len(filenames) > 0: raise ValueError( f"planarity_output_dir = '{self.planarity_output_dir}' " "has unexpected structure: should only contain " "subdirectories, one for each graph order in " f"{orders}." ) if len(dirnames) == 0: raise ValueError( f"planarity_output_dir = '{self.planarity_output_dir}' " "has no subdirectories: expected one subdirectory for " f"each graph order in {orders}." ) for order in orders: if str(order) not in dirnames: raise ValueError( f"planarity_output_dir = '{self.planarity_output_dir}'" f" is missing subdirectory for graph order N={order}." ) for dirname in dirnames: if int(dirname) not in orders: continue planarity_output_dir_for_order = Path.joinpath( Path(dirpath).resolve(), dirname ) for _, subdirs, subdir_filenames in walk( planarity_output_dir_for_order ): if len(subdir_filenames) > 0: raise ValueError( "Planarity output subdirectory " f"'{planarity_output_dir_for_order}' should only " "contain subdirectories." ) if len(dirnames) == 0: raise ValueError( "Planarity output subdirectory " f"'{planarity_output_dir_for_order}' has no " "subdirectories: expected one subdirectory for " "each command specifier in " f"{generate_tables_for_commands}." ) for command in generate_tables_for_commands: if command not in subdirs: raise ValueError( "Planarity output subdirectory " f"'{planarity_output_dir_for_order}' is " "missing subdirectory for command " f"'{command}'." ) output_dir_for_order_and_command = Path.joinpath( planarity_output_dir_for_order, f"{command}" ) if not self._input_dir_contains_expected_files( int(dirname), output_dir_for_order_and_command, ): raise ValueError( f"Planarity output dir for order and command " f"'{output_dir_for_order_and_command}' " "doesn't contain the expected planarity " "output files (i.e. one per each " f"num_edges for the given order {dirname})." ) # Don't recurse into subdirectories break # Don't recurse into subdirectories break class TTGwithEDAOrchestrator: """Orchestrate test table generation with results of edge-deletion analysis Validate command-line parameters and, based on their values, instantiate MultiOrderTTGwithEDA or ResumableTTGwithEDA. """ def __init__( self, path_config: TTGWithEDAPathConfig, orders: Optional[tuple[int, ...]] = None, edgecount_limit: Optional[int] = None, generate_tables_for_commands: Optional[tuple[str, ...]] = None, ) -> None: """Initializes TTGwithEDAOrchestrator instance Args: path_config: An instance of TTGWithEDAPathConfig that contains the validated and normalized Paths for planarity_path, graph_dir, planarity_output_dir, and test_table_output_dir, as well as the boolean flags representing whether we are performing the work on canonical_files and/or makeg_files. orders: Graph orders for which to perform edge-deletion analysis for chosen graph algorithm extensions and to tabulate results of running planarity Test All Graphs edgecount_limit: When generating table for single graph order and command specifier, indicates the maximum edgecount for which you wish to perform EDA; must be less than or equal to (N * (N - 1)) / 2, and if a resume-file exists, must be strictly greater than the highest previously processed edge- count generate_tables_for_commands: Command specifiers for which you wish to generate test tables; defaults to tuple returned by PLANARITY_ALGORITHM_SPECIFIERS() Raises: ValueError: - the specified graph orders are invalid - the command specifiers for which you wish to perform EDA (if applicable) and generate the respective test tables are invalid - the edgecount limit is invalid, in light of the chosen graph order(s) and command specifier(s) - the graph_dir does not have the correct structure - the planarity_output_dir does not have the expected structure """ self.orders = self._validate_orders(orders) self.generate_tables_for_commands = self._validate_command_specifiers( generate_tables_for_commands ) self.edgecount_limit = self._validate_edgecount_limit(edgecount_limit) path_config.check_graph_dir_contents(self.orders) path_config.check_planarity_output_dir_contents( self.orders, self.generate_tables_for_commands ) self.path_config = path_config self.ttg_with_eda_driver: Optional[TTGwithEDA] = None def _validate_orders( self, orders: Optional[tuple[int, ...]] ) -> tuple[int, ...]: """Validating graph orders for which you wish to run EDA and do TTG Raises: ValueError: - orders tuple contains non-integer values - the largest graph order specified in a range > 9 Returns: tuple containing user-specified graph order(s) """ if orders is None or len(orders) == 0: orders = tuple(range(6, 10)) # orders defaults to (6, 7, 8, 9) if not all(isinstance(x, int) for x in orders): raise ValueError("Orders tuple contains non-integer values.") if len(orders) > 1 and max(orders) > 9: raise ValueError( "When specifying a range of orders, the maximum order must " "not exceed 9, otherwise the system will run out of memory." ) return orders def _validate_command_specifiers( self, generate_tables_for_commands: Optional[tuple[str, ...]] ) -> tuple[str, ...]: """Validating commands for which you wish to generate test tables. Args: generate_tables_for_commands: tuple of str indicating the graph algorithm extensions for which you wish to generate tables Raises: ValueError: - self.orders is None or empty (falsy) - invalid command specifiers given - multiple command specifiers are given when graph order > 9 Returns: tuple of valid command specifiers """ if self.orders is None or len(self.orders) == 0: raise ValueError("Invalid self.orders tuple.") if ( generate_tables_for_commands is None or len(generate_tables_for_commands) == 0 ): generate_tables_for_commands = PLANARITY_ALGORITHM_SPECIFIERS() if not all( (c in PLANARITY_ALGORITHM_SPECIFIERS()) for c in generate_tables_for_commands ): raise ValueError( "Invalid command specifier(s) given when indicating which " "algorithms for which you wish to generate test tables." ) # NOTE: if (max(self.orders) > 9) holds, self.orders should only # contain one element, since self._validate_orders() would throw an # error if the user specified a range of graph orders with the max > 9 if max(self.orders) > 9 and len(generate_tables_for_commands) > 1: raise ValueError( "When the specified graph order is greater than 9, must only " "choose one algorithm command specifier for which you wish to " "perform edge-deletion analysis and generate table." ) return generate_tables_for_commands def _orders_and_commands_are_valid(self) -> bool: """Ensure orders and generate_tables_for_commands are nonempty""" if ( self.orders is None or len(self.orders) == 0 or self.generate_tables_for_commands is None or len(self.generate_tables_for_commands) == 0 ): return False return True def _validate_edgecount_limit( self, edgecount_limit: Optional[int] ) -> Optional[int]: """Ensure edgecount limit is valid relative to other options Args: edgecount_limit: max edge-count up to which you wish to perform EDA Raises: ValueError: - self.orders or self.generate_tables_for_commands are somehow None or empty tuples (falsy) - edgecount_limit is given when multiple graph orders and/or commands are specified - edgecount_limit exceeds the maximum possible for the given graph order Returns: int edgecount_limit (for single order and command) or None (if not applicable, i.e. when running multiple orders/commands) """ if not self._orders_and_commands_are_valid(): raise ValueError( "Unable to validate edgecount limit: invalid self.orders or " "self.generate_tables_for_commands tuples." ) if len(self.orders) > 1 or len(self.generate_tables_for_commands) > 1: if edgecount_limit is not None: raise ValueError( "Cannot specify an edgecount limit when wanting to " "perform EDA and generate tables for multiple graph " "orders and/or graph algorithm commands." ) else: assert self.orders is not None and len(self.orders) == 1 edgecount_limit = self._normalize_edgecount_limit( self.orders[0], edgecount_limit ) return edgecount_limit def _normalize_edgecount_limit( self, order: int, edgecount_limit: Optional[int] ) -> int: """For a given graph order, normalize and validate the edgecount_limit Args: order: The graph order against which you wish to check whether the edgecount_limit is valid edgecount_limit: either None (will be set to (N*(N-1))/2) or some int we wish to verify is within the accepted range Raises: ValueError: - invalid graph order supplied - edgecount_limit exceeds (N*(N-1))/2 Returns: Either the original int edgecount_limit if it is within the accepted range, or (N*(N-1))/2) for the given order """ if order < 1: raise ValueError( "Unable to normalize edgecount limit given invalid graph " f"order {order}" ) max_edgecount_for_order = max_num_edges_for_order(order) edgecount_limit = ( edgecount_limit if (edgecount_limit is not None and edgecount_limit > -1) else max_edgecount_for_order ) if edgecount_limit > max_edgecount_for_order: raise ValueError( f"Given edgecount argument {edgecount_limit} exceeds the " f"maximum edgecount for order {order}; please " "choose an edgecount limit less than or equal to " f"{max_edgecount_for_order}." ) return edgecount_limit def initialize_driver(self) -> None: """Initialize correct driver for TTG with EDA based on init parameters""" if not self._orders_and_commands_are_valid(): raise ValueError( "Unable to initialize driver for test table generation with " "numInvalidOKs due to invalid order or command tuples." ) if len(self.orders) > 1 or len(self.generate_tables_for_commands) > 1: self.ttg_with_eda_driver = MultiOrderOrCommandTTGwithEDA( path_config=self.path_config, orders=self.orders, generate_tables_for_commands=self.generate_tables_for_commands, ) elif ( len(self.orders) == 1 and len(self.generate_tables_for_commands) == 1 ): assert self.edgecount_limit is not None self.ttg_with_eda_driver = ResumableTTGwithEDA( path_config=self.path_config, order=self.orders[0], generate_tables_for_command=self.generate_tables_for_commands[ 0 ], edgecount_limit=self.edgecount_limit, ) def generate_test_tables_with_invalidOKs(self) -> None: """Leverage polymorphism to invoke correct behaviour based on params""" assert self.ttg_with_eda_driver is not None self.ttg_with_eda_driver.determine_numInvalidOKs() self.ttg_with_eda_driver.generate_tables_with_numInvalidOKs() class TTGwithEDA(ABC): """Abstract base class defining common behaviour of TTG with EDA""" @abstractmethod def __init__( self, path_config: TTGWithEDAPathConfig, ) -> None: self.path_config = path_config self._numInvalidOKs = {} @abstractmethod def determine_numInvalidOKs(self) -> None: """Override in subclass to define logic for running EDA""" def _determine_numInvalidOKs( self, order: int, num_edges: int, command: str ) -> None: """Set up correct infile name and call EDA Args: order: The graph order for which we wish to perform EDA num_edges: The number of edges of the graphs in the .g6 file command: The command specifier for which we wish to perform EDA """ canonical_ext = ( ".canonical" if self.path_config.canonical_files else "" ) makeg_ext = ".makeg" if self.path_config.makeg_files else "" assert self.path_config.graph_dir is not None g6_infile_path = Path.joinpath( self.path_config.graph_dir, f"{order}", f"n{order}.m{num_edges}{makeg_ext}{canonical_ext}.g6", ) self._call_edge_deletion_analysis(order, command, g6_infile_path) def _call_edge_deletion_analysis( self, order: int, command: str, infile_path: Path ) -> None: """For given command, get numInvalidOKs for each order and edge-count Args: order: The order of the graphs in the input file command: string indicating graph search algorithm command specifier for which we wish to determine the number of invalid OKs using the edge-deletion analysis tool. infile_path: The Path of the .g6 input file on which you wish to perform the edge-deletion analysis """ assert self.path_config.planarity_executable_path is not None eda = EdgeDeletionAnalyzer( planarity_path=self.path_config.planarity_executable_path, infile_path=infile_path, output_dir=None, extension_choice=command, ) start_proc_time = time.process_time_ns() start_time = time.perf_counter_ns() numInvalidOK_for_infile = eda.analyze_graphs() end_proc_time = time.process_time_ns() end_time = time.perf_counter_ns() proc_time_s = (end_proc_time - start_proc_time) / 1e9 tot_time_s = (end_time - start_time) / 1e9 infile_name = infile_path.name eda.logger.info( "NUMBER OF INVALID OKs in '%s' is %d; took %fs process time and " "%fs total time", infile_name, numInvalidOK_for_infile, proc_time_s, tot_time_s, ) # NOTE: Since the json spec disallows int as keys, one must cast the # order to a str order_key = str(order) if not self._numInvalidOKs.get(order_key): self._numInvalidOKs[order_key] = {} if not self._numInvalidOKs[order_key].get(command): self._numInvalidOKs[order_key][command] = {} self._numInvalidOKs[order_key][command][infile_name] = { "numInvalidOK": numInvalidOK_for_infile, "proc_time_s": proc_time_s, "tot_time_s": tot_time_s, } @abstractmethod def generate_tables_with_numInvalidOKs(self) -> None: """Override in subclass to define logic for generating tables""" def _generate_table_with_numInvalidOKs_for_order_and_command( self, order: int, command: str ) -> None: """For an order and command, compile planarity output and EDA results Args: order: The graph order for which you wish to generate test table with numInvalidOKs command: The algorithm command specifier for which testing was done """ assert self.path_config.planarity_output_dir is not None input_dir_for_order_and_command = Path.joinpath( self.path_config.planarity_output_dir, f"{order}", f"{command}" ) assert self.path_config.test_table_output_dir is not None ttg = TestTableGenerator( test_table_input_dir=input_dir_for_order_and_command, test_table_output_dir=self.path_config.test_table_output_dir, canonical_files=self.path_config.canonical_files, makeg_files=self.path_config.makeg_files, edge_deletion_analysis_results=self._numInvalidOKs.get( str(order), {} ).get(command, {}), ) ttg.get_order_and_command_from_input_dir() ttg.process_files() ttg.write_table_formatted_data_to_file() class MultiOrderOrCommandTTGwithEDA(TTGwithEDA): """Handles when user specifies a range of orders and/or commands""" def __init__( self, path_config: TTGWithEDAPathConfig, orders: tuple[int, ...], generate_tables_for_commands: tuple[str, ...], ) -> None: super().__init__(path_config) self.orders = orders self.generate_tables_for_commands = generate_tables_for_commands def determine_numInvalidOKs(self) -> None: """Run EdgeDeletionAnalyzer on all .g6 input files for the given order""" perform_eda_for_commands = tuple( c for c in EDGE_DELETION_ANALYSIS_SPECIFIERS() if c in self.generate_tables_for_commands ) for order in self.orders: for command in perform_eda_for_commands: for num_edges in range(max_num_edges_for_order(order) + 1): self._determine_numInvalidOKs(order, num_edges, command) def generate_tables_with_numInvalidOKs(self) -> None: """Incorporate edge-deletion analysis results into Test Tables""" for order in self.orders: for command in self.generate_tables_for_commands: self._generate_table_with_numInvalidOKs_for_order_and_command( order, command ) class ResumableTTGwithEDA(TTGwithEDA): """When a user specifies a single graph order and a single algorithm command specifier (one of (2, 3, 4) corresponding to K_{2, 3}, K_{3, 3}, or K_4 search), we are able to leverage a resume file to save state after processing each edge-count. """ def __init__( self, path_config: TTGWithEDAPathConfig, order: int, generate_tables_for_command: str, edgecount_limit: int, ) -> None: super().__init__(path_config) self.order = order self.command = generate_tables_for_command self.edgecount_limit = edgecount_limit ( self._resume_file_path, self.highest_processed_edgecount, self._numInvalidOKs, ) = self._initialize_numInvalidOKs() def _initialize_numInvalidOKs( self, ) -> tuple[Path, int, dict[int, dict[str, dict[str, dict]]]]: """Initialize numInvalidOKs based on parameterization and resume file Raises: TypeError: resume_file_path exists but corresponds to a directory ValueError: json.load() fails when trying to load the resume file Returns: Path to resume file for order and command, the highest processed edge-count, and the initialized numInvalidOKs dict. This dict either is empty if the resume file doesn't exist, or otherwise has been populated with the validated contents of the resume file. """ numInvalidOKs = {} highest_processed_edgecount = 0 resume_file_path = self._determine_resume_file_path() if resume_file_path.exists(): if resume_file_path.is_dir(): raise TypeError( f"Path to resume file for order {self.order} and command " f"{self.command} corresponds to a directory. Please " f"delete '{resume_file_path}' and retry." ) with open(resume_file_path, "r", encoding="utf-8") as resume_file: try: resume_file_contents = json.load( resume_file, object_pairs_hook=raise_on_duplicates ) except json.JSONDecodeError as invalid_resume_file_error: raise ValueError( "Unable to load resume file contents from " f"path '{resume_file_path}'; please delete and " "retry." ) from invalid_resume_file_error highest_processed_edgecount = self._validate_resume_file( resume_file_contents ) numInvalidOKs = resume_file_contents return resume_file_path, highest_processed_edgecount, numInvalidOKs def _determine_resume_file_path(self) -> Path: """If command line parameters allow, what resume file path to use?""" resume_file_dir = Path.joinpath( self.path_config.planarity_project_root, "TestSupport", "results", "test_table_generation_with_numInvalidOK", ) Path.mkdir(resume_file_dir, parents=True, exist_ok=True) return resume_file_dir / (f"N_{self.order}-C_{self.command}" ".json") def _validate_resume_file( self, resume_file_contents: dict[str, dict[str, dict[str, dict]]] ) -> int: """Determine if resume file contents are valid for specified params Args: resume_file_contents: the results of json.load() on the resume file for the current order and command Raises: ValueError: if the resume file contents don't match: { : { "": { } } } Returns: The highest_processed_edgecount determined when validating the dict """ highest_processed_edgecount = 0 if len(resume_file_contents) > 1: raise ValueError( f"Resume file '{self._resume_file_path}' is invalid; " "contains multiple top-level keys: " f"'{resume_file_contents.keys()}'. Should only contain one " "graph order." ) if len(resume_file_contents) == 1: contents_for_order = resume_file_contents.get(str(self.order), {}) if len(contents_for_order) > 1: raise ValueError( f"Resume file '{self._resume_file_path}' is invalid; " "contains multiple secondary keys: " f"'{contents_for_order.keys()}'. Should only contain one " "command specifier." ) if len(contents_for_order) == 1: contents_for_command = contents_for_order.get(self.command, {}) if contents_for_command: highest_processed_edgecount = ( self._validate_numInvalidOK_data(contents_for_command) ) return highest_processed_edgecount def _validate_numInvalidOK_data( self, contents_for_order_and_command: dict[str, dict] ) -> int: """Check data for each previously processed edge-count in resume file Args: contents_for_order_and_command: dict; maps infile name to EDA data Raises: ValueError: If the contents for the given order and command don't match: { "": { "numInvalidOK": numInvalidOK, "proc_time_s": proc_time_s, "tot_time_s": tot_time_s }, "": { "numInvalidOK": numInvalidOK, "proc_time_s": proc_time_s, "tot_time_s": tot_time_s }, ... "": { "numInvalidOK": numInvalidOK, "proc_time_s": proc_time_s, "tot_time_s": tot_time_s } } Returns: The highest_processed_edgecount determined when validating the dict """ highest_processed_edgecount = 0 processed_edgecounts = [] for infile_name in contents_for_order_and_command: match = re.match( r"n(?P\d+)\.m(?P\d+)", infile_name, ) if not match: raise ValueError( "Invalid infile name in resume file: " f"'{infile_name}'." ) order_from_infile = match.group("order") if not order_from_infile: raise ValueError( "Invalid infile name in resume file: " f"'{infile_name}' is missing graph order." ) try: order_from_infile = int(order_from_infile) except TypeError as order_from_infile_conversion_error: raise ValueError( "Unable to convert order extracted from " "infile to int; received " f"'{order_from_infile}'" ) from order_from_infile_conversion_error if order_from_infile != self.order: raise ValueError( "Invalid infile name in resume file: " f"'{infile_name}' corresponds to the wrong " "graph order." ) edgecount_from_infile = match.group("edgecount") if not edgecount_from_infile: raise ValueError( "Invalid infile name in resume file: " f"'{infile_name}' is missing edgecount." ) try: edgecount_from_infile = int(edgecount_from_infile) except TypeError as edgecount_from_infile_conversion_error: raise ValueError( "Unable to convert edgecount extracted from " "infile to int; received " f"'{edgecount_from_infile}'" ) from edgecount_from_infile_conversion_error if ( edgecount_from_infile < 0 or edgecount_from_infile > self.edgecount_limit ): raise ValueError( "Invalid infile name in resume file: " f"'{infile_name}' contains edgecount " f"{edgecount_from_infile} which is outside of range " f"[0, {self.edgecount_limit}]; either delete resume file " "or adjust edge-count limit." ) highest_processed_edgecount = max( highest_processed_edgecount, edgecount_from_infile ) processed_edgecounts.append(edgecount_from_infile) processed_edgecounts.sort() for x, y in zip( processed_edgecounts, range(0, max(processed_edgecounts) + 1), ): if x != y: raise ValueError( "Invalid resume file contents: missing data " f"for edgecount {y}; delete the resume file " "and retry." ) if highest_processed_edgecount == self.edgecount_limit: response = input( "Highest processed edge-count is equal to specified edge-" "count limit; do you wish to proceed (yY)?\n" "\t(If not, press any other key to abort, then delete the " "existing resume file)\n" ) if response.lower() != "y": sys.exit(0) return highest_processed_edgecount def determine_numInvalidOKs(self) -> None: """Define logic for running EDA while interacting with resume file""" starting_edgecount = ( self.highest_processed_edgecount + 1 if self.highest_processed_edgecount > 0 else 0 ) for num_edges in range(starting_edgecount, self.edgecount_limit + 1): self._determine_numInvalidOKs(self.order, num_edges, self.command) assert self._resume_file_path is not None Path.mkdir( self._resume_file_path.parent, exist_ok=True, parents=True ) with open( self._resume_file_path, "w", encoding="utf-8" ) as resume_file: json.dump(self._numInvalidOKs, resume_file, indent=4) def generate_tables_with_numInvalidOKs(self) -> None: """Define logic for generating table for single order and command""" self._generate_table_with_numInvalidOKs_for_order_and_command( order=self.order, command=self.command ) Path.unlink(self._resume_file_path, missing_ok=True) if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Test Table Generation including numInvalidOKs\n\n" "For each specified graph order:\n" "1. For each edgecount from 0 to (N * (N - 1))/2, runs the " "EdgeDeletionAnalyzer for K_{2, 3} search, K_{3, 3} search, and K_4 " "search to determine the numInvalidOKs for those graph algorithm " "extensions.\n" "2. For each of the supported graph algorithms, Tabulates results " "from running planarity's Test All Graphs functionality, " "incorporating the numInvalidOKs if applicable.\n", ) parser.add_argument( "-p", "--planaritypath", type=Path, required=False, help="Path to planarity executable. Defaults to:\n" "\t{planarity_project_root}/Release/planarity", metavar="PATH_TO_PLANARITY_EXECUTABLE", ) parser.add_argument( "-n", "--orders", type=parse_range, required=False, help="Order(s) of graphs for which you wish to generate Test Tables; " f"for commands {EDGE_DELETION_ANALYSIS_SPECIFIERS()}, will include " "numInvalidOKs derived by edge-deletion analysis.", metavar="X[,Y]", ) parser.add_argument( "-e", "--edgecountlimit", type=int, required=False, help="If only a single graph order and graph search algorithm are " "specified, perform EDA for edge-counts up to and including this " "value.", metavar="M", ) parser.add_argument( "-c", "--gentablecommands", nargs="+", default=[], required=False, help="List of algorithm command specifiers for which you wish to " "generate test tables. Defaults to " f"{PLANARITY_ALGORITHM_SPECIFIERS()}", ) parser.add_argument( "-g", "--graphdir", type=Path, required=False, help="Path to parent directory containing subdirectories for each " "graph order, the contents of which are assumed to be .g6 files (one " "for each number of edges from 0 to (N * (N - 1))/2). Defaults to:\n" "\tTestSupport/results/graph_generation_orchestrator/", metavar="PATH_TO_GRAPH_PARENT_DIR", ) parser.add_argument( "-t", "--planarityoutputdir", type=Path, required=False, help="Path to parent directory containing subdirectories for each " "graph order, the contents of which are subdirectories for each graph " f"algorithm extension (i.e. {PLANARITY_ALGORITHM_SPECIFIERS()}). Each " "of these directories contains the results of running planarity Test " "All Graphs for the given command on all graphs of a given order and " "number of edges. Defaults to:\n" "\tTestSupport/results/planarity_testAllGraphs_orchestrator/\n" "The directories will have the form:\n" "\t{planarity_output_dir}/{order}/{command}/\n" "And will contain files with names of the form:\n" "\tn{order}.m{num_edges}(.makeg)?(.canonical)?.{command}.out.txt", metavar="PATH_TO_PLANARITY_OUTPUT", ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="OUTPUT_DIR_PATH", help="Parent directory under which subdirectory named after each" "{order} will be created for output results. If none provided, " "defaults to:\n" "\tTestSupport/tables/\n" "For each chosen order, output file paths have the form:\n" "\t{output_dir}/{order}/n{order}.mALL.{command}.out.txt", ) parser.add_argument( "-l", "--canonicalfiles", action="store_true", help="Indicates .g6 input files are in canonical form", ) parser.add_argument( "-m", "--makegfiles", action="store_true", help="Indicates .g6 input files were generated by makeg", ) args = parser.parse_args() path_config_from_args = TTGWithEDAPathConfig( planarity_executable_path=args.planaritypath, graph_dir=args.graphdir, planarity_output_dir=args.planarityoutputdir, test_table_output_dir=args.outputdir, canonical_files=args.canonicalfiles, makeg_files=args.makegfiles, ) eda_ttg_orchestrator = TTGwithEDAOrchestrator( path_config=path_config_from_args, orders=args.orders, edgecount_limit=args.edgecountlimit, generate_tables_for_commands=args.gentablecommands, ) eda_ttg_orchestrator.initialize_driver() eda_ttg_orchestrator.generate_test_tables_with_invalidOKs() edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/test_table_generator.py000066400000000000000000000560141521450711600336770ustar00rootroot00000000000000"""Compile planarity testAllGraphs() output into table Classes: TestTableGeneratorPathError TestTableGeneratorFileProcessingError TestTableGenerator """ #!/usr/bin/env python __all__ = [ "TestTableGeneratorPathError", "TestTableGeneratorFileProcessingError", "TestTableGenerator", ] import sys import argparse from pathlib import Path from typing import Optional from planaritytesting_utils import PLANARITY_ALGORITHM_SPECIFIERS from planarity_testAllGraphs_output_parsing import ( TestAllGraphsPathError, TestAllGraphsOutputFileContentsError, validate_infile_name, process_file_contents, ) class TestTableGeneratorPathError(BaseException): """ Custom exception signalling issues with the paths of input for the Test Table Generator. For example: - input_dir or output_dir don't correspond to directories that exist - input filename contains an order or command that doesn't align with the order and command extracted from the parts of the parent directory """ def __init__(self, message): super().__init__(message) self.message = message class TestTableGeneratorFileProcessingError(BaseException): """ Custom exception signalling issues with the file contents of input for the Test Table Generator. """ def __init__(self, message): super().__init__(message) self.message = message class TestTableGenerator: # pylint: disable=too-many-instance-attributes """Process output from running planarity testAllGraphs for a single command""" __COL_WIDTH_TOLERANCE = 2 def __init__( self, test_table_input_dir: Path, test_table_output_dir: Path, canonical_files: bool = False, makeg_files: bool = False, edge_deletion_analysis_results: Optional[dict[str, dict]] = None, ): """Initializes TestTableGenerator instance. Checks that input_dir exists and is a directory, and that it contains input files. Also checks that the output_dir exists and is a directory. Args: input_dir: Directory containing the files output by running the planarity Test All Graphs functionality for a given algorithm command on .g6 input files containing all graphs of a given order and edge count. output_dir: Directory to which you wish to write the output file containing the tabulated results compiled from the input files in the input_dir. canonical_files: Optional bool to indicate planarity output corresponds to canonical format makeg_files: Optional bool to indicate planarity output corresponds to makeg .g6 edge_deletion_analysis_results: (Optional) When generating the test table for K_{2, 3} search, K_{3, 3} search, or K_4 search, one may optionally pass in a dictionary mapping input filenames to a sub-dict containing the numInvalidOK count derived by running the EdgeDeletionAnalyzer for this graph algorithm extension, the sum of the system and user CPU time of the current process (proc_time_s), and the total time elapsed during measurement (tot_time_s). Raises: TestTableGeneratorPathError: If the input_dir doesn't correspond to a directory, or if it is empty; or if the output_dir doesn't correspond to a directory. """ self.order = None self.command = None # According to PEP-8, one must use one leading underscore only for # non-public methods and instance variables. self._processed_data = {} self._totals = {} if not Path.is_dir(test_table_input_dir): raise TestTableGeneratorPathError( f"'{test_table_input_dir}' is not a valid directory." ) if not any(test_table_input_dir.iterdir()): raise TestTableGeneratorPathError( f"'{test_table_input_dir}' contains no files." ) self.input_dir = test_table_input_dir if Path.is_file(test_table_output_dir): raise TestTableGeneratorPathError( f"'{test_table_output_dir}' is not a valid directory." ) self.output_dir = test_table_output_dir self.canonical_files = canonical_files self.makeg_files = makeg_files self._edge_deletion_analysis_results = ( edge_deletion_analysis_results if edge_deletion_analysis_results else {} ) def get_order_and_command_from_input_dir(self): """Extract order and command from input_dir if possible Takes the self.input_dir pathlib.Path object's parts and attempts to extract the order and command from the directory structure. If the input directory is such that the path is of the form {parent_dir}/{order}/{command}/ Then we can extract these values early, allowing us to validate individual input files later in the process. Otherwise, sets self.order and self.command to None. """ parts = self.input_dir.parts try: order_from_input_dir = int(parts[-2]) except ValueError: order_from_input_dir = None command_from_input_dir = parts[-1] # You may reference class attributes either by the name of the class, # seen here, or by using "self" if command_from_input_dir not in PLANARITY_ALGORITHM_SPECIFIERS(): command_from_input_dir = None self.order = order_from_input_dir self.command = command_from_input_dir def process_files(self): """Process input files to populate _processed_data and _totals dicts Iterates over the filenames within the input_dir and constructs the full infile_path, then calls self._process_file() on this infile_path. After all files have been processed, constructs the self._totals dict containing the compiled results for the numGraphs, numOK, numNONEMBEDDABLE, numErrors, and duration. Their values are generated by summing the results of a list-comprehension, which extracts the values of the same key name in the list of dicts within self._processed_data.values(). If edge_deletion_analysis_results is nonempty, then the values in the self._processed_data dict for the numInvalidOK count derived by running the EdgeDeletionAnalyzer for this graph algorithm extension, the sum of the system and user CPU time of the current process (proc_time_s), and the total time elapsed during measurement (tot_time_s) will be summed and added to the self._totals dictionary. Raises: TestTableGeneratorFileProcessingError: If an error occurred processing the input file corresponding to path infile_path """ for dirpath, _, filenames in Path.walk(self.input_dir): for filename in filenames: if ( (self.canonical_files and "canonical" not in filename) or (not self.canonical_files and "canonical" in filename) or (self.makeg_files and "makeg" not in filename) or (not self.makeg_files and "makeg" in filename) ): continue infile_path = Path.joinpath(dirpath, filename) try: self._process_file(infile_path) except ( TestAllGraphsPathError, TestAllGraphsOutputFileContentsError, ) as e: raise TestTableGeneratorFileProcessingError( f"Error processing '{infile_path}'." ) from e if not self._processed_data: raise TestTableGeneratorFileProcessingError( f"No files in input directory '{self.input_dir}' matched the " "patterns indicated by the input flags:" f"\n\tcanonical_files={self.canonical_files}" f"\n\tmakeg_files={self.makeg_files}" ) self._totals = { "numGraphs": sum( int(x.get("numGraphs")) for x in self._processed_data.values() ), "numOK": sum( int(x.get("numOK")) for x in self._processed_data.values() ), "numNONEMBEDDABLE": sum( int(x.get("numNONEMBEDDABLE")) for x in self._processed_data.values() ), "numErrors": sum( 1 for x in self._processed_data.values() if x.get("errorFlag") == "SUCCESS" ), "duration": sum( float(x.get("duration")) for x in self._processed_data.values() ), "numInvalidOK": sum( int(x.get("numInvalidOK")) for x in self._processed_data.values() ), "proc_time_s": sum( float(x.get("proc_time_s")) for x in self._processed_data.values() ), "tot_time_s": sum( float(x.get("tot_time_s")) for x in self._processed_data.values() ), } def _process_file(self, infile_path: Path): """Process infile and integrate into self._processed_data dict Validates the infile name, then processes file contents and adds them to the self._processed_data dict by mapping the num_edges to a sub-dict containing fields for the infilename, numGraphs, numOK, numNONEMBEDDABLE, errorFlag, and duration. If edge_deletion_analysis_results is nonempty, those results (i.e. the numInvalidOK count, the proc_time_s, and the "wall time" tot_time_s) are integrated into the self._processed_data dict. Args: infile_path: Corresponds to a file within the input_dir processed_data: Dict to which we wish to add information parsed from the infile Raises: TestAllGraphsPathError: If invalid infile_path TestAllGraphsOutputFileContentsError: If input file corresponds to results that have already been processed, or re-raises exception thrown by process_file_contents() """ try: self.order, num_edges, self.command = validate_infile_name( infile_path, self.order, self.command ) except TestAllGraphsPathError as e: raise TestAllGraphsPathError( "Unable to process file when given invalid infile name." ) from e if num_edges in self._processed_data: raise TestAllGraphsOutputFileContentsError( "Already processed a file corresponding to " f"{num_edges} edges." ) try: ( planarity_infile_name, duration, numGraphs, numOK, numNONEMBEDDABLE, errorFlag, ) = process_file_contents(infile_path, self.command) except TestAllGraphsOutputFileContentsError as e: raise TestAllGraphsOutputFileContentsError( f"Unable to process contents of '{infile_path}'." ) from e self._processed_data[num_edges] = { "infilename": planarity_infile_name, "numGraphs": numGraphs, "numOK": numOK, "numNONEMBEDDABLE": numNONEMBEDDABLE, "errorFlag": errorFlag, "duration": duration, } self._processed_data[num_edges].update( { "numInvalidOK": self._edge_deletion_analysis_results.get( planarity_infile_name, {} ).get("numInvalidOK", 0), "proc_time_s": self._edge_deletion_analysis_results.get( planarity_infile_name, {} ).get("proc_time_s", 0.0), "tot_time_s": self._edge_deletion_analysis_results.get( planarity_infile_name, {} ).get("tot_time_s", 0.0), } ) def write_table_formatted_data_to_file( self, ): # pylint: disable=too-many-locals disable=too-many-statements """Writes the data extracted from the input files and totals to table""" self.output_dir = Path.joinpath(self.output_dir, f"{self.order}") Path.mkdir(self.output_dir, parents=True, exist_ok=True) makeg_ext = ".makeg" if self.makeg_files else "" canonical_ext = ".canonical" if self.canonical_files else "" output_path = Path.joinpath( self.output_dir, f"n{self.order}.mALL{makeg_ext}{canonical_ext}.{self.command}" + ".out.txt", ) infilename_heading = "Input filename" num_edges_heading = "# Edges" num_graphs_heading = "# Graphs" numOK_heading = "# OK" num_NONEMBEDDABLE_heading = "# NONEMBEDDABLE" errorFlag_heading = "Error flag" duration_heading = "Duration" numInvalidOK_heading = "# Invalid OK" proc_time_s_heading = "EDA Proc Time" tot_time_s_heading = "EDA Total Time" infilename_len = ( max( len(infilename_heading), *[ len(x.get("infilename")) for x in self._processed_data.values() ], ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) num_edges_len = ( max( len(num_edges_heading), *[len(str(num_edges)) for num_edges in self._processed_data], ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) numGraphs_len = ( max( len(str(self._totals.get("numGraphs"))), len(num_graphs_heading), ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) numOK_len = ( max(len(str(self._totals.get("numOK"))), len(numOK_heading)) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) numNONEMBEDDABLE_len = ( max( len(str(self._totals.get("numNONEMBEDDABLE"))), len(num_NONEMBEDDABLE_heading), ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) errorFlag_len = len(errorFlag_heading) duration_len = ( max(len(str(self._totals.get("duration"))), len(duration_heading)) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) nonempty_edge_deletion_analysis_results = bool( self._edge_deletion_analysis_results ) numInvalidOK_len = 0 proc_time_s_len = 0 tot_time_s_len = 0 if nonempty_edge_deletion_analysis_results: numInvalidOK_len = ( max( len(str(self._totals.get("numInvalidOK"))), len(numInvalidOK_heading), ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) proc_time_s_len = ( max( len(str(self._totals.get("proc_time_s"))), len(proc_time_s_heading), ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) tot_time_s_len = ( max( len(str(self._totals.get("tot_time_s"))), len(tot_time_s_heading), ) + TestTableGenerator.__COL_WIDTH_TOLERANCE ) with open(output_path, "w", encoding="utf-8") as outfile: # Print the table header outfile.write( f"|{infilename_heading:^{infilename_len}}" f"|{num_edges_heading:^{num_edges_len}}" f"|{num_graphs_heading:^{numGraphs_len}}" f"|{numOK_heading:^{numOK_len}}" f"|{num_NONEMBEDDABLE_heading:^{numNONEMBEDDABLE_len}}" f"|{errorFlag_heading:^{errorFlag_len}}" f"|{duration_heading:^{duration_len}}" ) if self._edge_deletion_analysis_results: outfile.write(f"|{numInvalidOK_heading:^{numInvalidOK_len}}") outfile.write(f"|{proc_time_s_heading:^{proc_time_s_len}}") outfile.write(f"|{tot_time_s_heading:^{tot_time_s_len}}") outfile.write("|\n") # Print the table header separator outfile.write( f"|{'='*infilename_len}" f"|{'='*num_edges_len}" f"|{'='*numGraphs_len}" f"|{'='*numOK_len}" f"|{'='*numNONEMBEDDABLE_len}" f"|{'='*len('Error flag')}" f"|{'='*duration_len}" ) if nonempty_edge_deletion_analysis_results: outfile.write(f"|{'='*numInvalidOK_len}") outfile.write(f"|{'='*proc_time_s_len}") outfile.write(f"|{'='*tot_time_s_len}") outfile.write("|\n") # Print the table rows for num_edges in sorted(self._processed_data.keys()): data = self._processed_data.get(num_edges) if data is None: raise TestTableGeneratorFileProcessingError( f"Data for M = {num_edges} is missing from processed " "data dict." ) outfile.write( f"|{data.get('infilename'):<{infilename_len}}" f"|{num_edges:<{num_edges_len}}" f"|{data.get('numGraphs'):<{numGraphs_len}}" f"|{data.get('numOK'):<{numOK_len}}" f"|{data.get('numNONEMBEDDABLE'):<{numNONEMBEDDABLE_len}}" f"|{data.get('errorFlag'):<{errorFlag_len}}" f"|{data.get('duration'):<{duration_len}}" ) if nonempty_edge_deletion_analysis_results: outfile.write( f"|{data.get('numInvalidOK'):<{numInvalidOK_len}}" ) outfile.write( f"|{data.get('proc_time_s'):<{proc_time_s_len}.{3}f}" ) outfile.write( f"|{data.get('tot_time_s'):<{tot_time_s_len}.{3}f}" ) outfile.write("|\n") # Print the table footer separator outfile.write( f"|{'='*infilename_len}" f"|{'='*num_edges_len}" f"|{'='*numGraphs_len}" f"|{'='*numOK_len}" f"|{'='*numNONEMBEDDABLE_len}" f"|{'='*len('Error flag')}" f"|{'='*duration_len}" ) if nonempty_edge_deletion_analysis_results: outfile.write(f"|{'='*numInvalidOK_len}") outfile.write(f"|{'='*proc_time_s_len}") outfile.write(f"|{'='*tot_time_s_len}") outfile.write("|\n") # Print the totals footer data = self._totals outfile.write( f"|{'TOTALS':<{infilename_len + num_edges_len + 1}}" f"|{data.get('numGraphs'):<{numGraphs_len}}" f"|{data.get('numOK'):<{numOK_len}}" f"|{data.get('numNONEMBEDDABLE'):<{numNONEMBEDDABLE_len}}" f"|{data.get('numErrors'):<{errorFlag_len}}" f"|{data.get('duration'):<{duration_len}}" ) if nonempty_edge_deletion_analysis_results: outfile.write( f"|{data.get('numInvalidOK'):<{numInvalidOK_len}}" ) outfile.write( f"|{data.get('proc_time_s'):<{proc_time_s_len}.{3}f}" ) outfile.write( f"|{data.get('tot_time_s'):<{tot_time_s_len}.{3}f}" ) outfile.write("|\n") if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, usage="python %(prog)s [options]", description="Test Table Generator\n" "Tabulates results from output files produced by planarity's " "Test All Graphs functionality.\n\n" "Expects to be given an input directory containing only the " "output files produced by running planarity's Test All Graphs " "for a specific algorithm.\n\n" "Preferably, this will be the output from having run the " "Planarity Orchestrator, and will have a path of the form:\n" "\t{parent_dir}/{order}/{command}/\n" "And will contain files with the full path:\n" "\t{parent_dir}/{order}/{command}/n{order}.m{numEdges}(.makeg)?" "(.canonical)?.{command}.out.txt\n\n" "Will output one file per graph algorithm containing the " "tabulated data compiled from the planarity Test All Graphs " "output files:\n" "\t{output_dir}/n{order}.mALL(.makeg)?(.canonical)?.{command}.out" ".txt", ) parser.add_argument( "-i", "--inputdir", type=Path, default=None, metavar="INPUT_DIR" ) parser.add_argument( "-o", "--outputdir", type=Path, default=None, metavar="OUTPUT_DIR" ) parser.add_argument( "-c", "--command", type=str, default="3", metavar="ALGORITHM_COMMAND", help="One of (pdo234); defaults to '%(default)s'", ) parser.add_argument( "-n", "--order", type=int, default=11, metavar="ORDER", help="Order of graphs for which you wish to compile results of " "having run planarity with given command specifier. Defaults to " "N = %(default)s", ) parser.add_argument( "-l", "--canonicalfiles", action="store_true", help="Planarity output files must contain 'canonical' in name", ) parser.add_argument( "-m", "--makegfiles", action="store_true", help="Planarity output files must contain 'makeg' in name", ) args = parser.parse_args() command = args.command order = args.order test_support_dir = Path(sys.argv[0]).resolve().parent.parent if not args.inputdir: input_dir = Path.joinpath( test_support_dir, "results", "planarity_testAllGraphs_orchestrator", f"{order}", f"{command}", ) else: input_dir = Path(args.inputdir).resolve() if not args.outputdir: output_dir = Path.joinpath(test_support_dir, "tables") else: output_dir = Path(args.outputdir).resolve() ttg = TestTableGenerator( test_table_input_dir=input_dir, test_table_output_dir=output_dir, canonical_files=args.canonicalfiles, makeg_files=args.makegfiles, ) ttg.get_order_and_command_from_input_dir() ttg.process_files() ttg.write_table_formatted_data_to_file() edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/000077500000000000000000000000001521450711600247745ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/000077500000000000000000000000001521450711600252145ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.2.out.txt000066400000000000000000000152201521450711600301050ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|=======|=================|==========|==========|==============|===============|================| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.028 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.023 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.046 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.016 |0.113 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.016 |0.245 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 |0 |0.141 |0.575 | |n10.m6.g6 |6 |66 |65 |1 |SUCCESS |0.016 |0 |0.328 |1.493 | |n10.m7.g6 |7 |165 |159 |6 |SUCCESS |0.0 |0 |0.812 |3.698 | |n10.m8.g6 |8 |428 |396 |32 |SUCCESS |0.0 |0 |2.141 |9.608 | |n10.m9.g6 |9 |1103 |952 |151 |SUCCESS |0.0 |0 |6.406 |24.577 | |n10.m10.g6 |10 |2769 |2094 |675 |SUCCESS |0.0 |0 |14.797 |61.081 | |n10.m11.g6 |11 |6759 |3982 |2777 |SUCCESS |0.015 |0 |35.500 |144.775 | |n10.m12.g6 |12 |15772 |5982 |9790 |SUCCESS |0.047 |0 |80.453 |313.361 | |n10.m13.g6 |13 |34663 |6640 |28023 |SUCCESS |0.156 |0 |162.281 |625.576 | |n10.m14.g6 |14 |71318 |5132 |66186 |SUCCESS |0.454 |0 |300.062 |1178.832 | |n10.m15.g6 |15 |136433 |2633 |133800 |SUCCESS |0.75 |0 |541.000 |2124.373 | |n10.m16.g6 |16 |241577 |778 |240799 |SUCCESS |1.546 |0 |915.984 |3640.435 | |n10.m17.g6 |17 |395166 |111 |395055 |SUCCESS |2.344 |0 |1483.109 |5907.152 | |n10.m18.g6 |18 |596191 |2 |596189 |SUCCESS |3.125 |0 |2246.109 |8926.891 | |n10.m19.g6 |19 |828728 |0 |828728 |SUCCESS |4.703 |0 |3116.219 |12335.211 | |n10.m20.g6 |20 |1061159 |0 |1061159 |SUCCESS |6.296 |0 |3990.125 |15770.964 | |n10.m21.g6 |21 |1251389 |0 |1251389 |SUCCESS |6.968 |0 |4709.016 |18517.044 | |n10.m22.g6 |22 |1358852 |0 |1358852 |SUCCESS |7.485 |0 |5117.688 |20420.178 | |n10.m23.g6 |23 |1358852 |0 |1358852 |SUCCESS |7.188 |0 |5142.891 |20676.227 | |n10.m24.g6 |24 |1251389 |0 |1251389 |SUCCESS |7.172 |0 |4739.688 |19040.693 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |6.375 |0 |4034.609 |16137.771 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |5.125 |0 |3140.531 |12598.999 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |3.656 |0 |2262.547 |8957.622 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |2.204 |0 |1510.672 |5980.732 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |1.5 |0 |916.531 |3622.344 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |0.844 |0 |515.797 |2047.351 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.437 |0 |272.203 |1073.593 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.219 |0 |131.172 |520.604 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.093 |0 |58.844 |237.001 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.047 |0 |25.734 |101.399 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.016 |0 |10.453 |41.394 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.015 |0 |3.844 |16.723 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.016 |0 |1.359 |6.511 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.0 |0 |0.625 |2.519 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.0 |0 |0.297 |0.991 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.0 |0 |0.094 |0.389 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 |0 |0.062 |0.168 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 |0 |0.016 |0.077 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 |0 |0.016 |0.033 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.016 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.016 | |================|=========|==========|=======|=================|==========|==========|==============|===============|================| |TOTALS |12005168 |28972 |11976196 |46 |68.812 |0 |45490.219 |181069.453 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.3.out.txt000066400000000000000000000153641521450711600301170ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|=========|=================|==========|==========|==============|===============|================| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.028 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.022 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.044 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.016 |0.112 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.031 |0.246 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 |0 |0.078 |0.581 | |n10.m6.g6 |6 |66 |66 |0 |SUCCESS |0.0 |0 |0.422 |1.521 | |n10.m7.g6 |7 |165 |165 |0 |SUCCESS |0.0 |0 |0.750 |3.662 | |n10.m8.g6 |8 |428 |428 |0 |SUCCESS |0.0 |0 |2.344 |9.626 | |n10.m9.g6 |9 |1103 |1102 |1 |SUCCESS |0.0 |0 |5.969 |24.540 | |n10.m10.g6 |10 |2769 |2765 |4 |SUCCESS |0.0 |0 |13.891 |62.625 | |n10.m11.g6 |11 |6759 |6734 |25 |SUCCESS |0.016 |0 |33.766 |152.775 | |n10.m12.g6 |12 |15772 |15624 |148 |SUCCESS |0.062 |0 |84.234 |366.992 | |n10.m13.g6 |13 |34663 |33786 |877 |SUCCESS |0.235 |0 |189.094 |775.691 | |n10.m14.g6 |14 |71318 |66731 |4587 |SUCCESS |0.594 |0 |382.969 |1588.006 | |n10.m15.g6 |15 |136433 |116600 |19833 |SUCCESS |1.266 |0 |743.984 |3062.843 | |n10.m16.g6 |16 |241577 |173940 |67637 |SUCCESS |2.047 |0 |1319.594 |5348.371 | |n10.m17.g6 |17 |395166 |214173 |180993 |SUCCESS |3.391 |0 |2077.844 |8384.106 | |n10.m18.g6 |18 |596191 |212226 |383965 |SUCCESS |5.422 |0 |2915.906 |11769.708 | |n10.m19.g6 |19 |828728 |164883 |663845 |SUCCESS |8.704 |0 |3716.219 |15085.622 | |n10.m20.g6 |20 |1061159 |98169 |962990 |SUCCESS |9.234 |0 |4522.375 |18452.354 | |n10.m21.g6 |21 |1251389 |43038 |1208351 |SUCCESS |9.672 |0 |4995.078 |20360.865 | |n10.m22.g6 |22 |1358852 |13226 |1345626 |SUCCESS |10.36 |0 |5290.391 |21441.333 | |n10.m23.g6 |23 |1358852 |2525 |1356327 |SUCCESS |10.14 |0 |5238.922 |21103.484 | |n10.m24.g6 |24 |1251389 |262 |1251127 |SUCCESS |9.672 |0 |4807.688 |19219.316 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |8.828 |0 |4090.641 |16148.043 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |7.843 |0 |3179.875 |12741.354 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |6.125 |0 |2276.953 |9118.045 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |4.109 |0 |1536.906 |6076.586 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |2.594 |0 |927.906 |3691.713 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |1.406 |0 |523.438 |2091.619 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.828 |0 |280.047 |1108.699 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.422 |0 |137.547 |536.981 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.171 |0 |60.469 |245.496 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.094 |0 |26.531 |105.616 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.031 |0 |10.922 |43.053 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.0 |0 |3.375 |17.185 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.016 |0 |1.906 |6.704 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.016 |0 |0.375 |2.580 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.0 |0 |0.516 |1.051 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.0 |0 |0.094 |0.415 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 |0 |0.031 |0.184 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.082 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.035 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.016 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.016 | |================|=========|==========|=========|=================|==========|==========|==============|===============|================| |TOTALS |12005168 |1166489 |10838679 |46 |103.298 |0 |49399.109 |199149.947 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.4.out.txt000066400000000000000000000152201521450711600301070ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|=======|=================|==========|==========|==============|===============|================| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.027 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.022 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.043 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.031 |0.109 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.031 |0.239 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 |0 |0.125 |0.551 | |n10.m6.g6 |6 |66 |65 |1 |SUCCESS |0.0 |0 |0.438 |1.436 | |n10.m7.g6 |7 |165 |162 |3 |SUCCESS |0.0 |0 |1.016 |3.812 | |n10.m8.g6 |8 |428 |413 |15 |SUCCESS |0.0 |0 |3.156 |10.807 | |n10.m9.g6 |9 |1103 |1034 |69 |SUCCESS |0.0 |0 |8.062 |32.094 | |n10.m10.g6 |10 |2769 |2451 |318 |SUCCESS |0.0 |0 |26.094 |100.080 | |n10.m11.g6 |11 |6759 |5321 |1438 |SUCCESS |0.015 |0 |83.109 |313.874 | |n10.m12.g6 |12 |15772 |9582 |6190 |SUCCESS |0.047 |0 |228.875 |835.269 | |n10.m13.g6 |13 |34663 |13220 |21443 |SUCCESS |0.234 |0 |478.188 |1719.888 | |n10.m14.g6 |14 |71318 |12881 |58437 |SUCCESS |0.5 |0 |717.797 |2633.099 | |n10.m15.g6 |15 |136433 |8283 |128150 |SUCCESS |1.078 |0 |870.594 |3316.733 | |n10.m16.g6 |16 |241577 |3098 |238479 |SUCCESS |1.766 |0 |1060.141 |4179.919 | |n10.m17.g6 |17 |395166 |529 |394637 |SUCCESS |2.719 |0 |1501.562 |6039.649 | |n10.m18.g6 |18 |596191 |0 |596191 |SUCCESS |3.766 |0 |2218.984 |8950.877 | |n10.m19.g6 |19 |828728 |0 |828728 |SUCCESS |4.266 |0 |3071.172 |12416.174 | |n10.m20.g6 |20 |1061159 |0 |1061159 |SUCCESS |4.812 |0 |3942.969 |15889.900 | |n10.m21.g6 |21 |1251389 |0 |1251389 |SUCCESS |5.093 |0 |4652.734 |18707.072 | |n10.m22.g6 |22 |1358852 |0 |1358852 |SUCCESS |5.422 |0 |5072.594 |20401.920 | |n10.m23.g6 |23 |1358852 |0 |1358852 |SUCCESS |5.312 |0 |5064.969 |20385.797 | |n10.m24.g6 |24 |1251389 |0 |1251389 |SUCCESS |5.266 |0 |4682.953 |18832.656 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |4.859 |0 |3970.844 |15977.488 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |4.141 |0 |3099.625 |12450.143 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |3.782 |0 |2225.125 |8962.403 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |2.656 |0 |1474.875 |5930.086 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |1.641 |0 |897.156 |3608.632 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |0.984 |0 |510.547 |2044.120 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.547 |0 |266.938 |1063.809 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.266 |0 |128.828 |517.777 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.109 |0 |59.062 |235.389 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.031 |0 |25.969 |100.604 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.016 |0 |10.172 |41.365 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.0 |0 |4.219 |16.522 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.0 |0 |1.375 |6.400 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.0 |0 |0.719 |2.431 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.0 |0 |0.281 |0.992 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.0 |0 |0.109 |0.393 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 |0 |0.125 |0.168 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.077 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.031 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.018 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.016 | |================|=========|==========|=======|=================|==========|==========|==============|===============|================| |TOTALS |12005168 |57085 |11948083 |46 |59.328 |0 |46361.578 |185730.912 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.d.out.txt000066400000000000000000000106241521450711600301720ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|=========|=================|==========|==========| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n10.m6.g6 |6 |66 |66 |0 |SUCCESS |0.0 | |n10.m7.g6 |7 |165 |165 |0 |SUCCESS |0.0 | |n10.m8.g6 |8 |428 |428 |0 |SUCCESS |0.015 | |n10.m9.g6 |9 |1103 |1102 |1 |SUCCESS |0.0 | |n10.m10.g6 |10 |2769 |2764 |5 |SUCCESS |0.032 | |n10.m11.g6 |11 |6759 |6731 |28 |SUCCESS |0.078 | |n10.m12.g6 |12 |15772 |15610 |162 |SUCCESS |0.157 | |n10.m13.g6 |13 |34663 |33724 |939 |SUCCESS |0.359 | |n10.m14.g6 |14 |71318 |66450 |4868 |SUCCESS |0.687 | |n10.m15.g6 |15 |136433 |115473 |20960 |SUCCESS |1.422 | |n10.m16.g6 |16 |241577 |171162 |70415 |SUCCESS |2.453 | |n10.m17.g6 |17 |395166 |209599 |185567 |SUCCESS |3.609 | |n10.m18.g6 |18 |596191 |206850 |389341 |SUCCESS |4.14 | |n10.m19.g6 |19 |828728 |160060 |668668 |SUCCESS |5.031 | |n10.m20.g6 |20 |1061159 |94699 |966460 |SUCCESS |5.453 | |n10.m21.g6 |21 |1251389 |41054 |1210335 |SUCCESS |6.422 | |n10.m22.g6 |22 |1358852 |12394 |1346458 |SUCCESS |6.797 | |n10.m23.g6 |23 |1358852 |2306 |1356546 |SUCCESS |5.953 | |n10.m24.g6 |24 |1251389 |233 |1251156 |SUCCESS |6.047 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |5.312 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |3.469 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |3.407 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |2.265 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |1.281 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |0.765 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.406 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.156 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.078 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.031 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.016 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.016 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.0 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.0 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.0 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.0 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|=========|=================|==========|==========| |TOTALS |12005168 |1140916 |10864252 |46 |65.857 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.o.out.txt000066400000000000000000000104601521450711600302030ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|=======|=================|==========|==========| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.016 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n10.m6.g6 |6 |66 |64 |2 |SUCCESS |0.0 | |n10.m7.g6 |7 |165 |157 |8 |SUCCESS |0.0 | |n10.m8.g6 |8 |428 |390 |38 |SUCCESS |0.0 | |n10.m9.g6 |9 |1103 |934 |169 |SUCCESS |0.016 | |n10.m10.g6 |10 |2769 |2043 |726 |SUCCESS |0.016 | |n10.m11.g6 |11 |6759 |3849 |2910 |SUCCESS |0.016 | |n10.m12.g6 |12 |15772 |5686 |10086 |SUCCESS |0.078 | |n10.m13.g6 |13 |34663 |6187 |28476 |SUCCESS |0.203 | |n10.m14.g6 |14 |71318 |4612 |66706 |SUCCESS |0.484 | |n10.m15.g6 |15 |136433 |2250 |134183 |SUCCESS |0.671 | |n10.m16.g6 |16 |241577 |618 |240959 |SUCCESS |1.329 | |n10.m17.g6 |17 |395166 |82 |395084 |SUCCESS |1.984 | |n10.m18.g6 |18 |596191 |0 |596191 |SUCCESS |3.157 | |n10.m19.g6 |19 |828728 |0 |828728 |SUCCESS |4.766 | |n10.m20.g6 |20 |1061159 |0 |1061159 |SUCCESS |5.703 | |n10.m21.g6 |21 |1251389 |0 |1251389 |SUCCESS |6.516 | |n10.m22.g6 |22 |1358852 |0 |1358852 |SUCCESS |7.969 | |n10.m23.g6 |23 |1358852 |0 |1358852 |SUCCESS |7.297 | |n10.m24.g6 |24 |1251389 |0 |1251389 |SUCCESS |6.641 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |6.203 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |4.75 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |3.641 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |2.422 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |1.672 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |0.969 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.5 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.266 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.093 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.047 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.016 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.0 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.0 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.0 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.0 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.0 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|=======|=================|==========|==========| |TOTALS |12005168 |26918 |11978250 |46 |67.441 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.p.out.txt000066400000000000000000000106241521450711600302060ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|=========|=================|==========|==========| |n10.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n10.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n10.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n10.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n10.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n10.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n10.m6.g6 |6 |66 |66 |0 |SUCCESS |0.015 | |n10.m7.g6 |7 |165 |165 |0 |SUCCESS |0.016 | |n10.m8.g6 |8 |428 |428 |0 |SUCCESS |0.0 | |n10.m9.g6 |9 |1103 |1102 |1 |SUCCESS |0.016 | |n10.m10.g6 |10 |2769 |2764 |5 |SUCCESS |0.016 | |n10.m11.g6 |11 |6759 |6731 |28 |SUCCESS |0.047 | |n10.m12.g6 |12 |15772 |15610 |162 |SUCCESS |0.094 | |n10.m13.g6 |13 |34663 |33724 |939 |SUCCESS |0.187 | |n10.m14.g6 |14 |71318 |66450 |4868 |SUCCESS |0.359 | |n10.m15.g6 |15 |136433 |115473 |20960 |SUCCESS |0.75 | |n10.m16.g6 |16 |241577 |171162 |70415 |SUCCESS |1.5 | |n10.m17.g6 |17 |395166 |209599 |185567 |SUCCESS |2.281 | |n10.m18.g6 |18 |596191 |206850 |389341 |SUCCESS |3.765 | |n10.m19.g6 |19 |828728 |160060 |668668 |SUCCESS |4.579 | |n10.m20.g6 |20 |1061159 |94699 |966460 |SUCCESS |6.266 | |n10.m21.g6 |21 |1251389 |41054 |1210335 |SUCCESS |7.219 | |n10.m22.g6 |22 |1358852 |12394 |1346458 |SUCCESS |7.781 | |n10.m23.g6 |23 |1358852 |2306 |1356546 |SUCCESS |8.219 | |n10.m24.g6 |24 |1251389 |233 |1251156 |SUCCESS |7.907 | |n10.m25.g6 |25 |1061159 |0 |1061159 |SUCCESS |6.281 | |n10.m26.g6 |26 |828728 |0 |828728 |SUCCESS |5.109 | |n10.m27.g6 |27 |596191 |0 |596191 |SUCCESS |3.516 | |n10.m28.g6 |28 |395166 |0 |395166 |SUCCESS |2.516 | |n10.m29.g6 |29 |241577 |0 |241577 |SUCCESS |1.735 | |n10.m30.g6 |30 |136433 |0 |136433 |SUCCESS |1.047 | |n10.m31.g6 |31 |71318 |0 |71318 |SUCCESS |0.516 | |n10.m32.g6 |32 |34663 |0 |34663 |SUCCESS |0.25 | |n10.m33.g6 |33 |15772 |0 |15772 |SUCCESS |0.14 | |n10.m34.g6 |34 |6759 |0 |6759 |SUCCESS |0.063 | |n10.m35.g6 |35 |2769 |0 |2769 |SUCCESS |0.032 | |n10.m36.g6 |36 |1103 |0 |1103 |SUCCESS |0.015 | |n10.m37.g6 |37 |428 |0 |428 |SUCCESS |0.016 | |n10.m38.g6 |38 |165 |0 |165 |SUCCESS |0.0 | |n10.m39.g6 |39 |66 |0 |66 |SUCCESS |0.016 | |n10.m40.g6 |40 |26 |0 |26 |SUCCESS |0.016 | |n10.m41.g6 |41 |11 |0 |11 |SUCCESS |0.0 | |n10.m42.g6 |42 |5 |0 |5 |SUCCESS |0.0 | |n10.m43.g6 |43 |2 |0 |2 |SUCCESS |0.0 | |n10.m44.g6 |44 |1 |0 |1 |SUCCESS |0.0 | |n10.m45.g6 |45 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|=========|=================|==========|==========| |TOTALS |12005168 |1140916 |10864252 |46 |72.285 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/000077500000000000000000000000001521450711600252155ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.2.out.txt000066400000000000000000000125241521450711600301130ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|========|=================|==========|==========| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n11.m6.g6 |6 |67 |66 |1 |SUCCESS |0.0 | |n11.m7.g6 |7 |172 |166 |6 |SUCCESS |0.0 | |n11.m8.g6 |8 |467 |435 |32 |SUCCESS |0.0 | |n11.m9.g6 |9 |1305 |1153 |152 |SUCCESS |0.016 | |n11.m10.g6 |10 |3664 |2969 |695 |SUCCESS |0.094 | |n11.m11.g6 |11 |10250 |7169 |3081 |SUCCESS |0.016 | |n11.m12.g6 |12 |28259 |15193 |13066 |SUCCESS |0.047 | |n11.m13.g6 |13 |75415 |26181 |49234 |SUCCESS |0.171 | |n11.m14.g6 |14 |192788 |34459 |158329 |SUCCESS |0.454 | |n11.m15.g6 |15 |467807 |33188 |434619 |SUCCESS |1.172 | |n11.m16.g6 |16 |1069890 |22391 |1047499 |SUCCESS |3.063 | |n11.m17.g6 |17 |2295898 |9967 |2285931 |SUCCESS |32.188 | |n11.m18.g6 |18 |4609179 |2629 |4606550 |SUCCESS |12.125 | |n11.m19.g6 |19 |8640134 |318 |8639816 |SUCCESS |21.719 | |n11.m20.g6 |20 |15108047 |0 |15108047 |SUCCESS |43.0 | |n11.m21.g6 |21 |24630887 |0 |24630887 |SUCCESS |69.094 | |n11.m22.g6 |22 |37433760 |0 |37433760 |SUCCESS |104.953 | |n11.m23.g6 |23 |53037356 |0 |53037356 |SUCCESS |172.89 | |n11.m24.g6 |24 |70065437 |0 |70065437 |SUCCESS |822.484 | |n11.m25.g6 |25 |86318670 |0 |86318670 |SUCCESS |261.0 | |n11.m26.g6 |26 |99187806 |0 |99187806 |SUCCESS |289.265 | |n11.m27.g6 |27 |106321628 |0 |106321628 |SUCCESS |344.812 | |n11.m28.g6 |28 |106321628 |0 |106321628 |SUCCESS |398.922 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |400.156 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |400.297 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |854.437 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |178.5 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |145.0 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |117.546 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |74.438 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |41.266 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |21.734 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |11.453 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |5.453 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |2.328 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |0.922 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.375 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.141 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.062 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.016 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.0 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.0 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.0 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.0 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.0 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|========|=================|==========|==========| |TOTALS |1018997864 |156330 |1018841534 |56 |4831.609 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.3.out.txt000066400000000000000000000140441521450711600301130ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|==========|=================|==========|====================| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.016 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n11.m6.g6 |6 |67 |67 |0 |SUCCESS |0.0 | |n11.m7.g6 |7 |172 |172 |0 |SUCCESS |0.0 | |n11.m8.g6 |8 |467 |467 |0 |SUCCESS |0.0 | |n11.m9.g6 |9 |1305 |1304 |1 |SUCCESS |0.0 | |n11.m10.g6 |10 |3664 |3660 |4 |SUCCESS |0.016 | |n11.m11.g6 |11 |10250 |10225 |25 |SUCCESS |0.203 | |n11.m12.g6 |12 |28259 |28108 |151 |SUCCESS |0.063 | |n11.m13.g6 |13 |75415 |74470 |945 |SUCCESS |0.204 | |n11.m14.g6 |14 |192788 |186959 |5829 |SUCCESS |0.547 | |n11.m15.g6 |15 |467807 |434657 |33150 |SUCCESS |1.594 | |n11.m16.g6 |16 |1069890 |909335 |160555 |SUCCESS |3.719 | |n11.m17.g6 |17 |2295898 |1659413 |636485 |SUCCESS |8.906 | |n11.m18.g6 |18 |4609179 |2569009 |2040170 |SUCCESS |68.219 | |n11.m19.g6 |19 |8640134 |3303167 |5336967 |SUCCESS |36.562 | |n11.m20.g6 |20 |15108047 |3467016 |11641031 |SUCCESS |76.641 | |n11.m21.g6 |21 |24630887 |2924802 |21706085 |SUCCESS |129.734 | |n11.m22.g6 |22 |37433760 |1945584 |35488176 |SUCCESS |219.797 | |n11.m23.g6 |23 |53037356 |996057 |52041299 |SUCCESS |389.25 | |n11.m24.g6 |24 |70065437 |377673 |69687764 |SUCCESS |483.078 | |n11.m25.g6 |25 |86318670 |100224 |86218446 |SUCCESS |1105.954 | |n11.m26.g6 |26 |99187806 |16576 |99171230 |SUCCESS |479.25 | |n11.m27.g6 |27 |106321628 |1385 |106320243 |SUCCESS |532.938 | |n11.m28.g6 |28 |106321628 |3 |106321625 |SUCCESS |596.141 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |610.672 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |625.313 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |531.047 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |958.5 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |254.625 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |203.047 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |125.172 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |73.375 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |37.5 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |18.078 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |9.219 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |3.953 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |1.578 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.609 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.235 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.094 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.031 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.015 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.0 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.0 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.0 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.0 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|==========|=================|==========|====================| |TOTALS |1018997864 |19010379 |999987485 |56 |7585.8949999999995 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.4.out.txt000066400000000000000000000125241521450711600301150ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|========|=================|==========|==========| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.015 | |n11.m6.g6 |6 |67 |66 |1 |SUCCESS |0.0 | |n11.m7.g6 |7 |172 |169 |3 |SUCCESS |0.0 | |n11.m8.g6 |8 |467 |452 |15 |SUCCESS |0.0 | |n11.m9.g6 |9 |1305 |1236 |69 |SUCCESS |0.0 | |n11.m10.g6 |10 |3664 |3343 |321 |SUCCESS |0.0 | |n11.m11.g6 |11 |10250 |8756 |1494 |SUCCESS |0.016 | |n11.m12.g6 |12 |28259 |21227 |7032 |SUCCESS |0.219 | |n11.m13.g6 |13 |75415 |43998 |31417 |SUCCESS |0.187 | |n11.m14.g6 |14 |192788 |72306 |120482 |SUCCESS |0.641 | |n11.m15.g6 |15 |467807 |88368 |379439 |SUCCESS |1.766 | |n11.m16.g6 |16 |1069890 |75977 |993913 |SUCCESS |3.515 | |n11.m17.g6 |17 |2295898 |43085 |2252813 |SUCCESS |8.078 | |n11.m18.g6 |18 |4609179 |14397 |4594782 |SUCCESS |14.375 | |n11.m19.g6 |19 |8640134 |2171 |8637963 |SUCCESS |119.812 | |n11.m20.g6 |20 |15108047 |0 |15108047 |SUCCESS |57.297 | |n11.m21.g6 |21 |24630887 |0 |24630887 |SUCCESS |92.172 | |n11.m22.g6 |22 |37433760 |0 |37433760 |SUCCESS |151.578 | |n11.m23.g6 |23 |53037356 |0 |53037356 |SUCCESS |273.484 | |n11.m24.g6 |24 |70065437 |0 |70065437 |SUCCESS |355.235 | |n11.m25.g6 |25 |86318670 |0 |86318670 |SUCCESS |434.61 | |n11.m26.g6 |26 |99187806 |0 |99187806 |SUCCESS |1058.375 | |n11.m27.g6 |27 |106321628 |0 |106321628 |SUCCESS |309.765 | |n11.m28.g6 |28 |106321628 |0 |106321628 |SUCCESS |324.843 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |322.531 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |343.312 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |359.015 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |276.625 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |270.422 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |115.078 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |81.157 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |45.062 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |24.469 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |11.922 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |5.406 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |2.546 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |1.0 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.39 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.141 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.063 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.015 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.015 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.0 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.0 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.0 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.0 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|========|=================|==========|==========| |TOTALS |1018997864 |375597 |1018622267 |56 |5065.152 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.d.out.txt000066400000000000000000000127141521450711600301760ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|==========|=================|==========|==========| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.016 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n11.m6.g6 |6 |67 |67 |0 |SUCCESS |0.0 | |n11.m7.g6 |7 |172 |172 |0 |SUCCESS |0.0 | |n11.m8.g6 |8 |467 |467 |0 |SUCCESS |0.046 | |n11.m9.g6 |9 |1305 |1304 |1 |SUCCESS |0.016 | |n11.m10.g6 |10 |3664 |3659 |5 |SUCCESS |0.031 | |n11.m11.g6 |11 |10250 |10222 |28 |SUCCESS |0.047 | |n11.m12.g6 |12 |28259 |28094 |165 |SUCCESS |0.141 | |n11.m13.g6 |13 |75415 |74407 |1008 |SUCCESS |0.375 | |n11.m14.g6 |14 |192788 |186664 |6124 |SUCCESS |0.953 | |n11.m15.g6 |15 |467807 |433303 |34504 |SUCCESS |5.704 | |n11.m16.g6 |16 |1069890 |903573 |166317 |SUCCESS |5.359 | |n11.m17.g6 |17 |2295898 |1642808 |653090 |SUCCESS |10.468 | |n11.m18.g6 |18 |4609179 |2535353 |2073826 |SUCCESS |17.985 | |n11.m19.g6 |19 |8640134 |3252691 |5387443 |SUCCESS |27.36 | |n11.m20.g6 |20 |15108047 |3407101 |11700946 |SUCCESS |45.641 | |n11.m21.g6 |21 |24630887 |2866079 |21764808 |SUCCESS |55.906 | |n11.m22.g6 |22 |37433760 |1897492 |35536268 |SUCCESS |496.953 | |n11.m23.g6 |23 |53037356 |964363 |52072993 |SUCCESS |180.437 | |n11.m24.g6 |24 |70065437 |361848 |69703589 |SUCCESS |191.782 | |n11.m25.g6 |25 |86318670 |94684 |86223986 |SUCCESS |234.563 | |n11.m26.g6 |26 |99187806 |15362 |99172444 |SUCCESS |269.313 | |n11.m27.g6 |27 |106321628 |1249 |106320379 |SUCCESS |328.125 | |n11.m28.g6 |28 |106321628 |0 |106321628 |SUCCESS |346.141 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |1196.266 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |155.516 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |140.766 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |145.578 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |126.281 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |82.734 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |48.297 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |30.016 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |15.469 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |8.172 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |3.875 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |1.515 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |0.64 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.25 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.094 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.047 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.0 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.0 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.0 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.0 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.0 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.0 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|==========|=================|==========|==========| |TOTALS |1018997864 |18681008 |1000316856 |56 |4172.878 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.o.out.txt000066400000000000000000000125241521450711600302100ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|========|=================|==========|==========| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n11.m6.g6 |6 |67 |65 |2 |SUCCESS |0.0 | |n11.m7.g6 |7 |172 |164 |8 |SUCCESS |0.0 | |n11.m8.g6 |8 |467 |429 |38 |SUCCESS |0.0 | |n11.m9.g6 |9 |1305 |1135 |170 |SUCCESS |0.062 | |n11.m10.g6 |10 |3664 |2916 |748 |SUCCESS |0.0 | |n11.m11.g6 |11 |10250 |7017 |3233 |SUCCESS |0.015 | |n11.m12.g6 |12 |28259 |14771 |13488 |SUCCESS |0.062 | |n11.m13.g6 |13 |75415 |25183 |50232 |SUCCESS |0.172 | |n11.m14.g6 |14 |192788 |32642 |160146 |SUCCESS |0.515 | |n11.m15.g6 |15 |467807 |30656 |437151 |SUCCESS |1.609 | |n11.m16.g6 |16 |1069890 |19952 |1049938 |SUCCESS |11.516 | |n11.m17.g6 |17 |2295898 |8450 |2287448 |SUCCESS |6.563 | |n11.m18.g6 |18 |4609179 |2090 |4607089 |SUCCESS |12.109 | |n11.m19.g6 |19 |8640134 |228 |8639906 |SUCCESS |21.157 | |n11.m20.g6 |20 |15108047 |0 |15108047 |SUCCESS |41.688 | |n11.m21.g6 |21 |24630887 |0 |24630887 |SUCCESS |63.407 | |n11.m22.g6 |22 |37433760 |0 |37433760 |SUCCESS |98.344 | |n11.m23.g6 |23 |53037356 |0 |53037356 |SUCCESS |761.703 | |n11.m24.g6 |24 |70065437 |0 |70065437 |SUCCESS |246.0 | |n11.m25.g6 |25 |86318670 |0 |86318670 |SUCCESS |278.234 | |n11.m26.g6 |26 |99187806 |0 |99187806 |SUCCESS |319.954 | |n11.m27.g6 |27 |106321628 |0 |106321628 |SUCCESS |381.344 | |n11.m28.g6 |28 |106321628 |0 |106321628 |SUCCESS |448.921 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |437.375 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |1098.344 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |191.61 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |187.703 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |146.812 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |115.687 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |70.453 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |42.125 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |21.953 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |10.625 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |4.828 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |2.282 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |0.891 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.344 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.141 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.047 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.016 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.0 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.016 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.0 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.0 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.0 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|========|=================|==========|==========| |TOTALS |1018997864 |145744 |1018852120 |56 |5024.627 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.p.out.txt000066400000000000000000000127141521450711600302120ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|============|==========|=================|==========|==========| |n11.m0.g6 |0 |1 |1 |0 |SUCCESS |0.016 | |n11.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n11.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n11.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n11.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n11.m5.g6 |5 |26 |26 |0 |SUCCESS |0.0 | |n11.m6.g6 |6 |67 |67 |0 |SUCCESS |0.063 | |n11.m7.g6 |7 |172 |172 |0 |SUCCESS |0.078 | |n11.m8.g6 |8 |467 |467 |0 |SUCCESS |0.0 | |n11.m9.g6 |9 |1305 |1304 |1 |SUCCESS |0.015 | |n11.m10.g6 |10 |3664 |3659 |5 |SUCCESS |0.016 | |n11.m11.g6 |11 |10250 |10222 |28 |SUCCESS |0.078 | |n11.m12.g6 |12 |28259 |28094 |165 |SUCCESS |0.078 | |n11.m13.g6 |13 |75415 |74407 |1008 |SUCCESS |0.672 | |n11.m14.g6 |14 |192788 |186664 |6124 |SUCCESS |1.641 | |n11.m15.g6 |15 |467807 |433303 |34504 |SUCCESS |1.703 | |n11.m16.g6 |16 |1069890 |903573 |166317 |SUCCESS |3.203 | |n11.m17.g6 |17 |2295898 |1642808 |653090 |SUCCESS |10.328 | |n11.m18.g6 |18 |4609179 |2535353 |2073826 |SUCCESS |19.047 | |n11.m19.g6 |19 |8640134 |3252691 |5387443 |SUCCESS |37.265 | |n11.m20.g6 |20 |15108047 |3407101 |11700946 |SUCCESS |434.625 | |n11.m21.g6 |21 |24630887 |2866079 |21764808 |SUCCESS |444.641 | |n11.m22.g6 |22 |37433760 |1897492 |35536268 |SUCCESS |143.328 | |n11.m23.g6 |23 |53037356 |964363 |52072993 |SUCCESS |244.187 | |n11.m24.g6 |24 |70065437 |361848 |69703589 |SUCCESS |322.0 | |n11.m25.g6 |25 |86318670 |94684 |86223986 |SUCCESS |373.094 | |n11.m26.g6 |26 |99187806 |15362 |99172444 |SUCCESS |448.531 | |n11.m27.g6 |27 |106321628 |1249 |106320379 |SUCCESS |539.531 | |n11.m28.g6 |28 |106321628 |0 |106321628 |SUCCESS |1089.5 | |n11.m29.g6 |29 |99187806 |0 |99187806 |SUCCESS |281.813 | |n11.m30.g6 |30 |86318670 |0 |86318670 |SUCCESS |254.719 | |n11.m31.g6 |31 |70065437 |0 |70065437 |SUCCESS |239.375 | |n11.m32.g6 |32 |53037356 |0 |53037356 |SUCCESS |219.969 | |n11.m33.g6 |33 |37433760 |0 |37433760 |SUCCESS |190.484 | |n11.m34.g6 |34 |24630887 |0 |24630887 |SUCCESS |163.063 | |n11.m35.g6 |35 |15108047 |0 |15108047 |SUCCESS |115.515 | |n11.m36.g6 |36 |8640134 |0 |8640134 |SUCCESS |44.719 | |n11.m37.g6 |37 |4609179 |0 |4609179 |SUCCESS |24.141 | |n11.m38.g6 |38 |2295898 |0 |2295898 |SUCCESS |11.469 | |n11.m39.g6 |39 |1069890 |0 |1069890 |SUCCESS |5.5 | |n11.m40.g6 |40 |467807 |0 |467807 |SUCCESS |2.562 | |n11.m41.g6 |41 |192788 |0 |192788 |SUCCESS |1.063 | |n11.m42.g6 |42 |75415 |0 |75415 |SUCCESS |0.422 | |n11.m43.g6 |43 |28259 |0 |28259 |SUCCESS |0.187 | |n11.m44.g6 |44 |10250 |0 |10250 |SUCCESS |0.094 | |n11.m45.g6 |45 |3664 |0 |3664 |SUCCESS |0.047 | |n11.m46.g6 |46 |1305 |0 |1305 |SUCCESS |0.015 | |n11.m47.g6 |47 |467 |0 |467 |SUCCESS |0.016 | |n11.m48.g6 |48 |172 |0 |172 |SUCCESS |0.015 | |n11.m49.g6 |49 |67 |0 |67 |SUCCESS |0.015 | |n11.m50.g6 |50 |26 |0 |26 |SUCCESS |0.0 | |n11.m51.g6 |51 |11 |0 |11 |SUCCESS |0.015 | |n11.m52.g6 |52 |5 |0 |5 |SUCCESS |0.0 | |n11.m53.g6 |53 |2 |0 |2 |SUCCESS |0.0 | |n11.m54.g6 |54 |1 |0 |1 |SUCCESS |0.0 | |n11.m55.g6 |55 |1 |0 |1 |SUCCESS |0.0 | |================|=========|============|==========|=================|==========|==========| |TOTALS |1018997864 |18681008 |1000316856 |56 |5668.858 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/000077500000000000000000000000001521450711600251405ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.2.out.txt000066400000000000000000000037511521450711600277630ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.021 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.021 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.031 |0.040 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.0 |0 |0.031 |0.077 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.0 |0 |0.031 |0.115 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.0 |0 |0.078 |0.112 | |n5.m6.g6 |6 |6 |5 |1 |SUCCESS |0.0 |0 |0.000 |0.152 | |n5.m7.g6 |7 |4 |2 |2 |SUCCESS |0.0 |0 |0.031 |0.124 | |n5.m8.g6 |8 |2 |0 |2 |SUCCESS |0.016 |0 |0.000 |0.029 | |n5.m9.g6 |9 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |n5.m10.g6 |10 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |34 |27 |7 |11 |0.016 |0 |0.219 |0.718 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.3.out.txt000066400000000000000000000040451521450711600277610ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|====================| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.039 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.0 |0 |0.016 |0.073 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.016 |0 |0.047 |0.117 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.0 |0 |0.031 |0.118 | |n5.m6.g6 |6 |6 |6 |0 |SUCCESS |0.0 |0 |0.031 |0.115 | |n5.m7.g6 |7 |4 |4 |0 |SUCCESS |0.0 |0 |0.000 |0.077 | |n5.m8.g6 |8 |2 |2 |0 |SUCCESS |0.016 |0 |0.031 |0.040 | |n5.m9.g6 |9 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.021 | |n5.m10.g6 |10 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.089 | |================|=========|==========|======|=================|==========|==========|==============|===============|====================| |TOTALS |34 |34 |0 |11 |0.032 |0 |0.172 |0.728 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.4.out.txt000066400000000000000000000037511521450711600277650ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.020 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.035 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.0 |0 |0.016 |0.071 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.0 |0 |0.031 |0.111 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.0 |0 |0.031 |0.110 | |n5.m6.g6 |6 |6 |5 |1 |SUCCESS |0.0 |0 |0.062 |0.146 | |n5.m7.g6 |7 |4 |2 |2 |SUCCESS |0.0 |0 |0.062 |0.120 | |n5.m8.g6 |8 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.027 | |n5.m9.g6 |9 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |n5.m10.g6 |10 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |34 |27 |7 |11 |0.0 |0 |0.234 |0.689 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.d.out.txt000066400000000000000000000024311521450711600300370ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.0 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.0 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.0 | |n5.m6.g6 |6 |6 |6 |0 |SUCCESS |0.0 | |n5.m7.g6 |7 |4 |4 |0 |SUCCESS |0.0 | |n5.m8.g6 |8 |2 |2 |0 |SUCCESS |0.0 | |n5.m9.g6 |9 |1 |1 |0 |SUCCESS |0.0 | |n5.m10.g6 |10 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |34 |33 |1 |11 |0.0 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.o.out.txt000066400000000000000000000024311521450711600300520ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.015 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.016 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.016 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.0 | |n5.m6.g6 |6 |6 |4 |2 |SUCCESS |0.015 | |n5.m7.g6 |7 |4 |1 |3 |SUCCESS |0.0 | |n5.m8.g6 |8 |2 |0 |2 |SUCCESS |0.016 | |n5.m9.g6 |9 |1 |0 |1 |SUCCESS |0.0 | |n5.m10.g6 |10 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |34 |25 |9 |11 |0.078 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.p.out.txt000066400000000000000000000024311521450711600300530ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n5.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n5.m1.g6 |1 |1 |1 |0 |SUCCESS |0.015 | |n5.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n5.m3.g6 |3 |4 |4 |0 |SUCCESS |0.016 | |n5.m4.g6 |4 |6 |6 |0 |SUCCESS |0.016 | |n5.m5.g6 |5 |6 |6 |0 |SUCCESS |0.016 | |n5.m6.g6 |6 |6 |6 |0 |SUCCESS |0.015 | |n5.m7.g6 |7 |4 |4 |0 |SUCCESS |0.0 | |n5.m8.g6 |8 |2 |2 |0 |SUCCESS |0.016 | |n5.m9.g6 |9 |1 |1 |0 |SUCCESS |0.0 | |n5.m10.g6 |10 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |34 |33 |1 |11 |0.094 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/000077500000000000000000000000001521450711600251415ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.2.out.txt000066400000000000000000000052141521450711600277610ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.040 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.000 |0.095 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 |0 |0.078 |0.168 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.0 |0 |0.078 |0.288 | |n6.m6.g6 |6 |21 |20 |1 |SUCCESS |0.0 |0 |0.109 |0.437 | |n6.m7.g6 |7 |24 |19 |5 |SUCCESS |0.0 |0 |0.188 |0.537 | |n6.m8.g6 |8 |24 |11 |13 |SUCCESS |0.0 |0 |0.156 |0.580 | |n6.m9.g6 |9 |21 |4 |17 |SUCCESS |0.0 |0 |0.141 |0.389 | |n6.m10.g6 |10 |15 |0 |15 |SUCCESS |0.0 |0 |0.031 |0.203 | |n6.m11.g6 |11 |9 |0 |9 |SUCCESS |0.0 |0 |0.047 |0.121 | |n6.m12.g6 |12 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.065 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.028 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.014 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.015 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |156 |87 |69 |16 |0.0 |0 |0.844 |3.019 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.3.out.txt000066400000000000000000000052141521450711600277620ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.039 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.016 |0.094 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 |0 |0.047 |0.171 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.0 |0 |0.047 |0.286 | |n6.m6.g6 |6 |21 |21 |0 |SUCCESS |0.0 |0 |0.156 |0.405 | |n6.m7.g6 |7 |24 |24 |0 |SUCCESS |0.0 |0 |0.109 |0.457 | |n6.m8.g6 |8 |24 |24 |0 |SUCCESS |0.0 |0 |0.094 |0.457 | |n6.m9.g6 |9 |21 |20 |1 |SUCCESS |0.016 |0 |0.047 |0.407 | |n6.m10.g6 |10 |15 |14 |1 |SUCCESS |0.0 |0 |0.094 |0.352 | |n6.m11.g6 |11 |9 |7 |2 |SUCCESS |0.0 |0 |0.125 |0.318 | |n6.m12.g6 |12 |5 |3 |2 |SUCCESS |0.0 |0 |0.078 |0.184 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.016 |0 |0.000 |0.028 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.014 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |156 |146 |10 |16 |0.032 |0 |0.844 |3.264 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.4.out.txt000066400000000000000000000052141521450711600277630ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.018 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.035 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.016 |0 |0.031 |0.089 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 |0 |0.031 |0.162 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.0 |0 |0.078 |0.276 | |n6.m6.g6 |6 |21 |20 |1 |SUCCESS |0.0 |0 |0.172 |0.421 | |n6.m7.g6 |7 |24 |21 |3 |SUCCESS |0.0 |0 |0.203 |0.642 | |n6.m8.g6 |8 |24 |13 |11 |SUCCESS |0.0 |0 |0.250 |0.796 | |n6.m9.g6 |9 |21 |5 |16 |SUCCESS |0.0 |0 |0.109 |0.528 | |n6.m10.g6 |10 |15 |0 |15 |SUCCESS |0.0 |0 |0.016 |0.208 | |n6.m11.g6 |11 |9 |0 |9 |SUCCESS |0.0 |0 |0.047 |0.125 | |n6.m12.g6 |12 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.070 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.0 |0 |0.016 |0.030 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.015 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.016 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |156 |92 |64 |16 |0.016 |0 |0.984 |3.447 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.d.out.txt000066400000000000000000000033141521450711600300420ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.0 | |n6.m6.g6 |6 |21 |21 |0 |SUCCESS |0.0 | |n6.m7.g6 |7 |24 |24 |0 |SUCCESS |0.0 | |n6.m8.g6 |8 |24 |24 |0 |SUCCESS |0.0 | |n6.m9.g6 |9 |21 |20 |1 |SUCCESS |0.0 | |n6.m10.g6 |10 |15 |13 |2 |SUCCESS |0.0 | |n6.m11.g6 |11 |9 |5 |4 |SUCCESS |0.0 | |n6.m12.g6 |12 |5 |2 |3 |SUCCESS |0.0 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.0 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |156 |142 |14 |16 |0.0 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.o.out.txt000066400000000000000000000033141521450711600300550ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.0 | |n6.m6.g6 |6 |21 |19 |2 |SUCCESS |0.0 | |n6.m7.g6 |7 |24 |17 |7 |SUCCESS |0.015 | |n6.m8.g6 |8 |24 |8 |16 |SUCCESS |0.0 | |n6.m9.g6 |9 |21 |3 |18 |SUCCESS |0.016 | |n6.m10.g6 |10 |15 |0 |15 |SUCCESS |0.0 | |n6.m11.g6 |11 |9 |0 |9 |SUCCESS |0.015 | |n6.m12.g6 |12 |5 |0 |5 |SUCCESS |0.0 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.016 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |156 |80 |76 |16 |0.062 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.p.out.txt000066400000000000000000000033141521450711600300560ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n6.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n6.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n6.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n6.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n6.m4.g6 |4 |9 |9 |0 |SUCCESS |0.0 | |n6.m5.g6 |5 |15 |15 |0 |SUCCESS |0.016 | |n6.m6.g6 |6 |21 |21 |0 |SUCCESS |0.0 | |n6.m7.g6 |7 |24 |24 |0 |SUCCESS |0.015 | |n6.m8.g6 |8 |24 |24 |0 |SUCCESS |0.0 | |n6.m9.g6 |9 |21 |20 |1 |SUCCESS |0.016 | |n6.m10.g6 |10 |15 |13 |2 |SUCCESS |0.0 | |n6.m11.g6 |11 |9 |5 |4 |SUCCESS |0.015 | |n6.m12.g6 |12 |5 |2 |3 |SUCCESS |0.0 | |n6.m13.g6 |13 |2 |0 |2 |SUCCESS |0.016 | |n6.m14.g6 |14 |1 |0 |1 |SUCCESS |0.0 | |n6.m15.g6 |15 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |156 |142 |14 |16 |0.078 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/000077500000000000000000000000001521450711600251425ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.2.out.txt000066400000000000000000000066661521450711600277770ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.021 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.016 |0 |0.000 |0.021 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.040 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.000 |0.101 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.015 |0 |0.047 |0.196 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.015 |0 |0.094 |0.415 | |n7.m6.g6 |6 |41 |40 |1 |SUCCESS |0.0 |0 |0.234 |0.867 | |n7.m7.g6 |7 |65 |59 |6 |SUCCESS |0.016 |0 |0.297 |1.350 | |n7.m8.g6 |8 |97 |70 |27 |SUCCESS |0.0 |0 |0.500 |2.106 | |n7.m9.g6 |9 |131 |59 |72 |SUCCESS |0.0 |0 |0.844 |3.879 | |n7.m10.g6 |10 |148 |27 |121 |SUCCESS |0.015 |0 |0.734 |2.560 | |n7.m11.g6 |11 |148 |6 |142 |SUCCESS |0.016 |0 |0.562 |2.189 | |n7.m12.g6 |12 |131 |1 |130 |SUCCESS |0.016 |0 |0.438 |1.874 | |n7.m13.g6 |13 |97 |0 |97 |SUCCESS |0.0 |0 |0.359 |1.261 | |n7.m14.g6 |14 |65 |0 |65 |SUCCESS |0.016 |0 |0.234 |0.863 | |n7.m15.g6 |15 |41 |0 |41 |SUCCESS |0.0 |0 |0.109 |0.587 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.016 |0 |0.047 |0.288 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.0 |0 |0.047 |0.133 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.016 |0 |0.031 |0.067 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.016 |0 |0.000 |0.027 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.015 |0 |0.000 |0.013 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |1044 |302 |742 |22 |0.188 |0 |4.578 |18.870 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.3.out.txt000066400000000000000000000066661521450711600300000ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.031 |0.019 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.040 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.047 |0.095 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.0 |0 |0.078 |0.188 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.0 |0 |0.062 |0.389 | |n7.m6.g6 |6 |41 |41 |0 |SUCCESS |0.0 |0 |0.281 |0.802 | |n7.m7.g6 |7 |65 |65 |0 |SUCCESS |0.0 |0 |0.375 |1.272 | |n7.m8.g6 |8 |97 |97 |0 |SUCCESS |0.0 |0 |0.531 |1.910 | |n7.m9.g6 |9 |131 |130 |1 |SUCCESS |0.0 |0 |0.688 |2.544 | |n7.m10.g6 |10 |148 |145 |3 |SUCCESS |0.0 |0 |0.734 |2.954 | |n7.m11.g6 |11 |148 |138 |10 |SUCCESS |0.0 |0 |0.625 |3.077 | |n7.m12.g6 |12 |131 |108 |23 |SUCCESS |0.0 |0 |0.875 |3.276 | |n7.m13.g6 |13 |97 |60 |37 |SUCCESS |0.0 |0 |0.656 |2.708 | |n7.m14.g6 |14 |65 |21 |44 |SUCCESS |0.0 |0 |0.375 |1.672 | |n7.m15.g6 |15 |41 |6 |35 |SUCCESS |0.0 |0 |0.203 |0.749 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.0 |0 |0.078 |0.289 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.0 |0 |0.016 |0.137 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.0 |0 |0.016 |0.070 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.029 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.016 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |1044 |851 |193 |22 |0.0 |0 |5.719 |22.269 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.4.out.txt000066400000000000000000000066661521450711600300010ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.036 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.031 |0.090 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.0 |0 |0.031 |0.179 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.0 |0 |0.078 |0.368 | |n7.m6.g6 |6 |41 |40 |1 |SUCCESS |0.0 |0 |0.203 |0.757 | |n7.m7.g6 |7 |65 |62 |3 |SUCCESS |0.0 |0 |0.328 |1.402 | |n7.m8.g6 |8 |97 |83 |14 |SUCCESS |0.0 |0 |0.656 |2.848 | |n7.m9.g6 |9 |131 |79 |52 |SUCCESS |0.0 |0 |1.375 |4.576 | |n7.m10.g6 |10 |148 |44 |104 |SUCCESS |0.0 |0 |1.469 |4.495 | |n7.m11.g6 |11 |148 |12 |136 |SUCCESS |0.0 |0 |0.953 |3.008 | |n7.m12.g6 |12 |131 |0 |131 |SUCCESS |0.0 |0 |0.438 |1.721 | |n7.m13.g6 |13 |97 |0 |97 |SUCCESS |0.0 |0 |0.281 |1.266 | |n7.m14.g6 |14 |65 |0 |65 |SUCCESS |0.0 |0 |0.234 |0.860 | |n7.m15.g6 |15 |41 |0 |41 |SUCCESS |0.0 |0 |0.219 |0.539 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.0 |0 |0.062 |0.277 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.0 |0 |0.000 |0.129 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.0 |0 |0.031 |0.067 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.0 |0 |0.016 |0.026 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |1044 |360 |684 |22 |0.0 |0 |6.406 |22.709 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.d.out.txt000066400000000000000000000043261521450711600300500ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.0 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.0 | |n7.m6.g6 |6 |41 |41 |0 |SUCCESS |0.0 | |n7.m7.g6 |7 |65 |65 |0 |SUCCESS |0.0 | |n7.m8.g6 |8 |97 |97 |0 |SUCCESS |0.0 | |n7.m9.g6 |9 |131 |130 |1 |SUCCESS |0.0 | |n7.m10.g6 |10 |148 |144 |4 |SUCCESS |0.0 | |n7.m11.g6 |11 |148 |135 |13 |SUCCESS |0.0 | |n7.m12.g6 |12 |131 |98 |33 |SUCCESS |0.0 | |n7.m13.g6 |13 |97 |51 |46 |SUCCESS |0.0 | |n7.m14.g6 |14 |65 |16 |49 |SUCCESS |0.0 | |n7.m15.g6 |15 |41 |5 |36 |SUCCESS |0.0 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.0 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.0 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.0 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.0 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |1044 |822 |222 |22 |0.0 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.o.out.txt000066400000000000000000000043261521450711600300630ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.0 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.0 | |n7.m6.g6 |6 |41 |39 |2 |SUCCESS |0.0 | |n7.m7.g6 |7 |65 |57 |8 |SUCCESS |0.0 | |n7.m8.g6 |8 |97 |65 |32 |SUCCESS |0.0 | |n7.m9.g6 |9 |131 |50 |81 |SUCCESS |0.0 | |n7.m10.g6 |10 |148 |22 |126 |SUCCESS |0.0 | |n7.m11.g6 |11 |148 |4 |144 |SUCCESS |0.0 | |n7.m12.g6 |12 |131 |0 |131 |SUCCESS |0.0 | |n7.m13.g6 |13 |97 |0 |97 |SUCCESS |0.0 | |n7.m14.g6 |14 |65 |0 |65 |SUCCESS |0.0 | |n7.m15.g6 |15 |41 |0 |41 |SUCCESS |0.0 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.0 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.0 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.0 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.0 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |1044 |277 |767 |22 |0.0 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.p.out.txt000066400000000000000000000043261521450711600300640ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n7.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n7.m1.g6 |1 |1 |1 |0 |SUCCESS |0.016 | |n7.m2.g6 |2 |2 |2 |0 |SUCCESS |0.016 | |n7.m3.g6 |3 |5 |5 |0 |SUCCESS |0.016 | |n7.m4.g6 |4 |10 |10 |0 |SUCCESS |0.015 | |n7.m5.g6 |5 |21 |21 |0 |SUCCESS |0.015 | |n7.m6.g6 |6 |41 |41 |0 |SUCCESS |0.0 | |n7.m7.g6 |7 |65 |65 |0 |SUCCESS |0.016 | |n7.m8.g6 |8 |97 |97 |0 |SUCCESS |0.0 | |n7.m9.g6 |9 |131 |130 |1 |SUCCESS |0.016 | |n7.m10.g6 |10 |148 |144 |4 |SUCCESS |0.015 | |n7.m11.g6 |11 |148 |135 |13 |SUCCESS |0.016 | |n7.m12.g6 |12 |131 |98 |33 |SUCCESS |0.016 | |n7.m13.g6 |13 |97 |51 |46 |SUCCESS |0.0 | |n7.m14.g6 |14 |65 |16 |49 |SUCCESS |0.016 | |n7.m15.g6 |15 |41 |5 |36 |SUCCESS |0.0 | |n7.m16.g6 |16 |21 |0 |21 |SUCCESS |0.016 | |n7.m17.g6 |17 |10 |0 |10 |SUCCESS |0.016 | |n7.m18.g6 |18 |5 |0 |5 |SUCCESS |0.016 | |n7.m19.g6 |19 |2 |0 |2 |SUCCESS |0.016 | |n7.m20.g6 |20 |1 |0 |1 |SUCCESS |0.0 | |n7.m21.g6 |21 |1 |0 |1 |SUCCESS |0.015 | |================|=========|==========|======|=================|==========|==========| |TOTALS |1044 |822 |222 |22 |0.252 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/000077500000000000000000000000001521450711600251435ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.2.out.txt000066400000000000000000000105471521450711600277720ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.020 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.039 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.031 |0.094 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.016 |0.207 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 |0 |0.094 |0.446 | |n8.m6.g6 |6 |56 |55 |1 |SUCCESS |0.0 |0 |0.297 |1.080 | |n8.m7.g6 |7 |115 |109 |6 |SUCCESS |0.0 |0 |0.656 |2.325 | |n8.m8.g6 |8 |221 |190 |31 |SUCCESS |0.0 |0 |1.438 |4.536 | |n8.m9.g6 |9 |402 |274 |128 |SUCCESS |0.0 |0 |1.984 |31.633 | |n8.m10.g6 |10 |663 |285 |378 |SUCCESS |0.0 |0 |3.938 |13.422 | |n8.m11.g6 |11 |980 |197 |783 |SUCCESS |0.0 |0 |4.531 |16.640 | |n8.m12.g6 |12 |1312 |79 |1233 |SUCCESS |0.0 |0 |5.562 |20.562 | |n8.m13.g6 |13 |1557 |18 |1539 |SUCCESS |0.016 |0 |5.766 |22.289 | |n8.m14.g6 |14 |1646 |0 |1646 |SUCCESS |0.015 |0 |6.094 |22.081 | |n8.m15.g6 |15 |1557 |0 |1557 |SUCCESS |0.0 |0 |5.469 |20.910 | |n8.m16.g6 |16 |1312 |0 |1312 |SUCCESS |0.0 |0 |4.672 |18.096 | |n8.m17.g6 |17 |980 |0 |980 |SUCCESS |0.016 |0 |3.547 |13.880 | |n8.m18.g6 |18 |663 |0 |663 |SUCCESS |0.0 |0 |2.453 |12.722 | |n8.m19.g6 |19 |402 |0 |402 |SUCCESS |0.0 |0 |1.406 |5.643 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.0 |0 |0.797 |3.129 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.0 |0 |0.453 |1.607 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 |0 |0.234 |0.784 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.0 |0 |0.094 |0.338 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.0 |0 |0.047 |0.153 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.0 |0 |0.016 |0.067 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.028 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |12346 |1251 |11095 |29 |0.047 |0 |49.609 |212.776 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.3.out.txt000066400000000000000000000105471521450711600277730ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.021 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.039 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.047 |0.100 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.016 |0 |0.047 |0.223 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 |0 |0.125 |0.484 | |n8.m6.g6 |6 |56 |56 |0 |SUCCESS |0.0 |0 |0.219 |1.116 | |n8.m7.g6 |7 |115 |115 |0 |SUCCESS |0.0 |0 |0.516 |2.388 | |n8.m8.g6 |8 |221 |221 |0 |SUCCESS |0.015 |0 |1.188 |4.601 | |n8.m9.g6 |9 |402 |401 |1 |SUCCESS |0.0 |0 |1.938 |8.423 | |n8.m10.g6 |10 |663 |659 |4 |SUCCESS |0.031 |0 |3.781 |14.562 | |n8.m11.g6 |11 |980 |959 |21 |SUCCESS |0.0 |0 |5.406 |20.674 | |n8.m12.g6 |12 |1312 |1230 |82 |SUCCESS |0.032 |0 |6.750 |28.116 | |n8.m13.g6 |13 |1557 |1310 |247 |SUCCESS |0.015 |0 |9.109 |35.457 | |n8.m14.g6 |14 |1646 |1109 |537 |SUCCESS |0.032 |0 |10.047 |39.394 | |n8.m15.g6 |15 |1557 |689 |868 |SUCCESS |0.015 |0 |8.875 |34.850 | |n8.m16.g6 |16 |1312 |307 |1005 |SUCCESS |0.031 |0 |7.125 |25.652 | |n8.m17.g6 |17 |980 |82 |898 |SUCCESS |0.0 |0 |4.422 |16.156 | |n8.m18.g6 |18 |663 |17 |646 |SUCCESS |0.016 |0 |2.438 |10.103 | |n8.m19.g6 |19 |402 |1 |401 |SUCCESS |0.0 |0 |1.516 |5.922 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.016 |0 |0.797 |3.134 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.0 |0 |0.484 |1.608 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 |0 |0.172 |0.787 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.0 |0 |0.109 |0.335 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.0 |0 |0.062 |0.217 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.0 |0 |0.047 |0.073 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.0 |0 |0.016 |0.029 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.014 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 |0 |0.016 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |12346 |7200 |5146 |29 |0.219 |0 |65.281 |254.515 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.4.out.txt000066400000000000000000000105471521450711600277740ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.020 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.020 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.000 |0.040 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.047 |0.103 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.047 |0.228 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 |0 |0.109 |0.493 | |n8.m6.g6 |6 |56 |55 |1 |SUCCESS |0.0 |0 |0.234 |1.194 | |n8.m7.g6 |7 |115 |112 |3 |SUCCESS |0.0 |0 |0.609 |2.667 | |n8.m8.g6 |8 |221 |206 |15 |SUCCESS |0.0 |0 |1.297 |5.948 | |n8.m9.g6 |9 |402 |337 |65 |SUCCESS |0.0 |0 |3.953 |14.052 | |n8.m10.g6 |10 |663 |411 |252 |SUCCESS |0.016 |0 |7.250 |27.221 | |n8.m11.g6 |11 |980 |345 |635 |SUCCESS |0.0 |0 |10.484 |35.927 | |n8.m12.g6 |12 |1312 |166 |1146 |SUCCESS |0.015 |0 |9.156 |32.939 | |n8.m13.g6 |13 |1557 |39 |1518 |SUCCESS |0.0 |0 |6.688 |26.015 | |n8.m14.g6 |14 |1646 |0 |1646 |SUCCESS |0.0 |0 |5.578 |23.025 | |n8.m15.g6 |15 |1557 |0 |1557 |SUCCESS |0.0 |0 |5.375 |21.793 | |n8.m16.g6 |16 |1312 |0 |1312 |SUCCESS |0.0 |0 |4.312 |18.348 | |n8.m17.g6 |17 |980 |0 |980 |SUCCESS |0.0 |0 |3.469 |13.709 | |n8.m18.g6 |18 |663 |0 |663 |SUCCESS |0.016 |0 |2.312 |9.270 | |n8.m19.g6 |19 |402 |0 |402 |SUCCESS |0.0 |0 |1.234 |5.652 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.015 |0 |0.703 |3.080 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.0 |0 |0.359 |1.603 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 |0 |0.156 |0.781 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.0 |0 |0.078 |0.333 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.0 |0 |0.047 |0.151 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.0 |0 |0.016 |0.066 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.027 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |12346 |1715 |10631 |29 |0.062 |0 |63.531 |244.733 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.d.out.txt000066400000000000000000000054671521450711600300610ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.016 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 | |n8.m6.g6 |6 |56 |56 |0 |SUCCESS |0.0 | |n8.m7.g6 |7 |115 |115 |0 |SUCCESS |0.0 | |n8.m8.g6 |8 |221 |221 |0 |SUCCESS |0.0 | |n8.m9.g6 |9 |402 |401 |1 |SUCCESS |0.0 | |n8.m10.g6 |10 |663 |658 |5 |SUCCESS |0.016 | |n8.m11.g6 |11 |980 |956 |24 |SUCCESS |0.0 | |n8.m12.g6 |12 |1312 |1217 |95 |SUCCESS |0.015 | |n8.m13.g6 |13 |1557 |1264 |293 |SUCCESS |0.016 | |n8.m14.g6 |14 |1646 |1042 |604 |SUCCESS |0.0 | |n8.m15.g6 |15 |1557 |631 |926 |SUCCESS |0.015 | |n8.m16.g6 |16 |1312 |275 |1037 |SUCCESS |0.016 | |n8.m17.g6 |17 |980 |72 |908 |SUCCESS |0.0 | |n8.m18.g6 |18 |663 |14 |649 |SUCCESS |0.0 | |n8.m19.g6 |19 |402 |0 |402 |SUCCESS |0.0 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.0 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.016 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.0 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.0 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.0 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.0 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.0 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |12346 |6966 |5380 |29 |0.11 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.o.out.txt000066400000000000000000000054671521450711600300740ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 | |n8.m6.g6 |6 |56 |54 |2 |SUCCESS |0.0 | |n8.m7.g6 |7 |115 |107 |8 |SUCCESS |0.016 | |n8.m8.g6 |8 |221 |184 |37 |SUCCESS |0.0 | |n8.m9.g6 |9 |402 |259 |143 |SUCCESS |0.015 | |n8.m10.g6 |10 |663 |257 |406 |SUCCESS |0.0 | |n8.m11.g6 |11 |980 |172 |808 |SUCCESS |0.015 | |n8.m12.g6 |12 |1312 |61 |1251 |SUCCESS |0.0 | |n8.m13.g6 |13 |1557 |12 |1545 |SUCCESS |0.047 | |n8.m14.g6 |14 |1646 |0 |1646 |SUCCESS |0.0 | |n8.m15.g6 |15 |1557 |0 |1557 |SUCCESS |0.031 | |n8.m16.g6 |16 |1312 |0 |1312 |SUCCESS |0.0 | |n8.m17.g6 |17 |980 |0 |980 |SUCCESS |0.0 | |n8.m18.g6 |18 |663 |0 |663 |SUCCESS |0.015 | |n8.m19.g6 |19 |402 |0 |402 |SUCCESS |0.015 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.0 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.016 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.016 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.0 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.016 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.0 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.015 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |12346 |1150 |11196 |29 |0.217 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.p.out.txt000066400000000000000000000062421521450711600300650ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|=====================| |n8.m0.g6 |0 |1 |1 |0 |SUCCESS |0.015 | |n8.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n8.m2.g6 |2 |2 |2 |0 |SUCCESS |0.016 | |n8.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n8.m4.g6 |4 |11 |11 |0 |SUCCESS |0.016 | |n8.m5.g6 |5 |24 |24 |0 |SUCCESS |0.0 | |n8.m6.g6 |6 |56 |56 |0 |SUCCESS |0.0 | |n8.m7.g6 |7 |115 |115 |0 |SUCCESS |0.0 | |n8.m8.g6 |8 |221 |221 |0 |SUCCESS |0.015 | |n8.m9.g6 |9 |402 |401 |1 |SUCCESS |0.0 | |n8.m10.g6 |10 |663 |658 |5 |SUCCESS |0.031 | |n8.m11.g6 |11 |980 |956 |24 |SUCCESS |0.015 | |n8.m12.g6 |12 |1312 |1217 |95 |SUCCESS |0.016 | |n8.m13.g6 |13 |1557 |1264 |293 |SUCCESS |0.016 | |n8.m14.g6 |14 |1646 |1042 |604 |SUCCESS |0.032 | |n8.m15.g6 |15 |1557 |631 |926 |SUCCESS |0.0 | |n8.m16.g6 |16 |1312 |275 |1037 |SUCCESS |0.031 | |n8.m17.g6 |17 |980 |72 |908 |SUCCESS |0.016 | |n8.m18.g6 |18 |663 |14 |649 |SUCCESS |0.016 | |n8.m19.g6 |19 |402 |0 |402 |SUCCESS |0.0 | |n8.m20.g6 |20 |221 |0 |221 |SUCCESS |0.016 | |n8.m21.g6 |21 |115 |0 |115 |SUCCESS |0.0 | |n8.m22.g6 |22 |56 |0 |56 |SUCCESS |0.0 | |n8.m23.g6 |23 |24 |0 |24 |SUCCESS |0.0 | |n8.m24.g6 |24 |11 |0 |11 |SUCCESS |0.016 | |n8.m25.g6 |25 |5 |0 |5 |SUCCESS |0.0 | |n8.m26.g6 |26 |2 |0 |2 |SUCCESS |0.016 | |n8.m27.g6 |27 |1 |0 |1 |SUCCESS |0.0 | |n8.m28.g6 |28 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|=====================| |TOTALS |12346 |6966 |5380 |29 |0.28300000000000003 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/000077500000000000000000000000001521450711600251445ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.2.out.txt000066400000000000000000000126371521450711600277760ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.031 |0.027 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.021 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.031 |0.041 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.031 |0.101 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.062 |0.216 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.0 |0 |0.125 |0.494 | |n9.m6.g6 |6 |63 |62 |1 |SUCCESS |0.0 |0 |0.328 |1.222 | |n9.m7.g6 |7 |148 |142 |6 |SUCCESS |0.0 |0 |0.641 |2.991 | |n9.m8.g6 |8 |345 |313 |32 |SUCCESS |0.0 |0 |1.859 |6.844 | |n9.m9.g6 |9 |771 |624 |147 |SUCCESS |0.0 |0 |4.641 |14.866 | |n9.m10.g6 |10 |1637 |1039 |598 |SUCCESS |0.0 |0 |9.047 |31.701 | |n9.m11.g6 |11 |3252 |1326 |1926 |SUCCESS |0.016 |0 |16.547 |60.376 | |n9.m12.g6 |12 |5995 |1190 |4805 |SUCCESS |0.125 |0 |27.312 |98.291 | |n9.m13.g6 |13 |10120 |698 |9422 |SUCCESS |0.032 |0 |40.984 |148.088 | |n9.m14.g6 |14 |15615 |242 |15373 |SUCCESS |0.032 |0 |55.594 |204.946 | |n9.m15.g6 |15 |21933 |39 |21894 |SUCCESS |0.094 |0 |72.844 |275.623 | |n9.m16.g6 |16 |27987 |0 |27987 |SUCCESS |0.11 |0 |93.359 |349.813 | |n9.m17.g6 |17 |32403 |0 |32403 |SUCCESS |0.172 |0 |110.359 |405.828 | |n9.m18.g6 |18 |34040 |0 |34040 |SUCCESS |0.125 |0 |115.688 |427.330 | |n9.m19.g6 |19 |32403 |0 |32403 |SUCCESS |0.125 |0 |107.531 |403.245 | |n9.m20.g6 |20 |27987 |0 |27987 |SUCCESS |0.109 |0 |95.531 |352.095 | |n9.m21.g6 |21 |21933 |0 |21933 |SUCCESS |0.093 |0 |75.734 |286.352 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.109 |0 |53.109 |197.246 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.031 |0 |34.078 |151.998 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.031 |0 |20.094 |75.166 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.0 |0 |10.438 |40.512 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.0 |0 |5.578 |20.693 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.0 |0 |2.422 |9.702 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.0 |0 |1.094 |4.269 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.0 |0 |0.422 |1.968 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.0 |0 |0.297 |0.808 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.0 |0 |0.062 |0.313 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.015 |0 |0.047 |0.139 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.067 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.0 |0 |0.000 |0.026 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |274668 |5720 |268948 |37 |1.219 |0 |955.938 |3573.447 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.3.out.txt000066400000000000000000000135421521450711600277730ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|=======|=================|==========|====================|==============|===============|================| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.016 |0.018 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.031 |0.019 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.016 |0.037 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.031 |0.090 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.031 |0.195 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.0 |0 |0.125 |0.450 | |n9.m6.g6 |6 |63 |63 |0 |SUCCESS |0.015 |0 |0.312 |1.147 | |n9.m7.g6 |7 |148 |148 |0 |SUCCESS |0.0 |0 |0.766 |2.683 | |n9.m8.g6 |8 |345 |345 |0 |SUCCESS |0.016 |0 |1.719 |6.160 | |n9.m9.g6 |9 |771 |770 |1 |SUCCESS |0.0 |0 |3.750 |13.773 | |n9.m10.g6 |10 |1637 |1633 |4 |SUCCESS |0.016 |0 |8.438 |30.089 | |n9.m11.g6 |11 |3252 |3228 |24 |SUCCESS |0.218 |0 |15.375 |59.072 | |n9.m12.g6 |12 |5995 |5862 |133 |SUCCESS |0.015 |0 |28.875 |108.522 | |n9.m13.g6 |13 |10120 |9498 |622 |SUCCESS |0.047 |0 |49.781 |184.834 | |n9.m14.g6 |14 |15615 |13321 |2294 |SUCCESS |0.078 |0 |81.391 |297.621 | |n9.m15.g6 |15 |21933 |15473 |6460 |SUCCESS |0.125 |0 |114.484 |421.163 | |n9.m16.g6 |16 |27987 |14226 |13761 |SUCCESS |0.5 |0 |139.500 |513.852 | |n9.m17.g6 |17 |32403 |10013 |22390 |SUCCESS |0.203 |0 |147.750 |543.008 | |n9.m18.g6 |18 |34040 |5154 |28886 |SUCCESS |0.25 |0 |139.703 |510.675 | |n9.m19.g6 |19 |32403 |1871 |30532 |SUCCESS |0.25 |0 |121.312 |451.452 | |n9.m20.g6 |20 |27987 |427 |27560 |SUCCESS |0.25 |0 |99.203 |363.865 | |n9.m21.g6 |21 |21933 |60 |21873 |SUCCESS |0.203 |0 |75.109 |282.210 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.125 |0 |53.656 |196.976 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.079 |0 |33.766 |126.653 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.063 |0 |19.688 |76.219 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.031 |0 |11.406 |41.425 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.015 |0 |5.453 |21.029 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.0 |0 |2.562 |9.671 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.0 |0 |1.328 |4.399 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.0 |0 |0.391 |1.908 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.0 |0 |0.203 |0.796 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.015 |0 |0.109 |0.313 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.0 |0 |0.047 |0.141 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 |0 |0.000 |0.065 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.0 |0 |0.016 |0.025 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |================|=========|==========|=======|=================|==========|====================|==============|===============|================| |TOTALS |274668 |82137 |192531 |37 |2.5140000000000002 |0 |1156.344 |4270.584 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.4.out.txt000066400000000000000000000126371521450711600300000ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | # Invalid OK | EDA Proc Time | EDA Total Time | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.018 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 |0 |0.000 |0.019 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 |0 |0.031 |0.036 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 |0 |0.047 |0.090 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 |0 |0.047 |0.196 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.0 |0 |0.156 |0.446 | |n9.m6.g6 |6 |63 |62 |1 |SUCCESS |0.0 |0 |0.203 |1.213 | |n9.m7.g6 |7 |148 |145 |3 |SUCCESS |0.0 |0 |0.719 |2.887 | |n9.m8.g6 |8 |345 |330 |15 |SUCCESS |0.0 |0 |1.953 |7.506 | |n9.m9.g6 |9 |771 |703 |68 |SUCCESS |0.0 |0 |6.062 |20.905 | |n9.m10.g6 |10 |1637 |1333 |304 |SUCCESS |0.015 |0 |16.594 |57.654 | |n9.m11.g6 |11 |3252 |2026 |1226 |SUCCESS |0.0 |0 |39.688 |135.331 | |n9.m12.g6 |12 |5995 |2243 |3752 |SUCCESS |0.016 |0 |70.031 |231.341 | |n9.m13.g6 |13 |10120 |1637 |8483 |SUCCESS |0.047 |0 |81.953 |281.337 | |n9.m14.g6 |14 |15615 |696 |14919 |SUCCESS |0.063 |0 |79.250 |281.962 | |n9.m15.g6 |15 |21933 |136 |21797 |SUCCESS |0.25 |0 |79.156 |299.984 | |n9.m16.g6 |16 |27987 |0 |27987 |SUCCESS |0.125 |0 |93.516 |354.317 | |n9.m17.g6 |17 |32403 |0 |32403 |SUCCESS |0.14 |0 |108.391 |408.340 | |n9.m18.g6 |18 |34040 |0 |34040 |SUCCESS |0.172 |0 |115.578 |428.094 | |n9.m19.g6 |19 |32403 |0 |32403 |SUCCESS |0.141 |0 |111.953 |407.300 | |n9.m20.g6 |20 |27987 |0 |27987 |SUCCESS |0.125 |0 |94.422 |357.247 | |n9.m21.g6 |21 |21933 |0 |21933 |SUCCESS |0.094 |0 |75.531 |278.112 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.078 |0 |52.125 |195.842 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.046 |0 |33.922 |126.369 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.016 |0 |20.344 |74.988 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.016 |0 |10.125 |40.800 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.015 |0 |5.609 |20.510 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.0 |0 |2.641 |9.694 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.0 |0 |1.172 |4.409 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.015 |0 |0.469 |1.879 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.0 |0 |0.250 |0.799 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.0 |0 |0.047 |0.321 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.0 |0 |0.016 |0.144 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 |0 |0.031 |0.067 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.016 |0 |0.016 |0.026 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.014 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 |0 |0.000 |0.013 | |================|=========|==========|======|=================|==========|==========|==============|===============|================| |TOTALS |274668 |9356 |265312 |37 |1.39 |0 |1102.047 |4030.209 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.d.out.txt000066400000000000000000000070301521450711600300470ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|=======|=================|==========|==========| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.016 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.0 | |n9.m6.g6 |6 |63 |63 |0 |SUCCESS |0.0 | |n9.m7.g6 |7 |148 |148 |0 |SUCCESS |0.0 | |n9.m8.g6 |8 |345 |345 |0 |SUCCESS |0.0 | |n9.m9.g6 |9 |771 |770 |1 |SUCCESS |0.031 | |n9.m10.g6 |10 |1637 |1632 |5 |SUCCESS |0.016 | |n9.m11.g6 |11 |3252 |3225 |27 |SUCCESS |0.032 | |n9.m12.g6 |12 |5995 |5848 |147 |SUCCESS |0.047 | |n9.m13.g6 |13 |10120 |9439 |681 |SUCCESS |0.063 | |n9.m14.g6 |14 |15615 |13093 |2522 |SUCCESS |0.11 | |n9.m15.g6 |15 |21933 |15029 |6904 |SUCCESS |0.125 | |n9.m16.g6 |16 |27987 |13660 |14327 |SUCCESS |0.172 | |n9.m17.g6 |17 |32403 |9536 |22867 |SUCCESS |0.156 | |n9.m18.g6 |18 |34040 |4858 |29182 |SUCCESS |0.141 | |n9.m19.g6 |19 |32403 |1734 |30669 |SUCCESS |0.266 | |n9.m20.g6 |20 |27987 |378 |27609 |SUCCESS |0.078 | |n9.m21.g6 |21 |21933 |50 |21883 |SUCCESS |0.078 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.046 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.031 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.063 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.015 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.0 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.0 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.0 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.0 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.0 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.0 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.0 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.016 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.0 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|=======|=================|==========|==========| |TOTALS |274668 |79853 |194815 |37 |1.502 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.o.out.txt000066400000000000000000000067571521450711600301010ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|======|=================|==========|==========| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.016 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.0 | |n9.m6.g6 |6 |63 |61 |2 |SUCCESS |0.0 | |n9.m7.g6 |7 |148 |140 |8 |SUCCESS |0.0 | |n9.m8.g6 |8 |345 |307 |38 |SUCCESS |0.015 | |n9.m9.g6 |9 |771 |607 |164 |SUCCESS |0.0 | |n9.m10.g6 |10 |1637 |995 |642 |SUCCESS |0.0 | |n9.m11.g6 |11 |3252 |1239 |2013 |SUCCESS |0.015 | |n9.m12.g6 |12 |5995 |1080 |4915 |SUCCESS |0.016 | |n9.m13.g6 |13 |10120 |599 |9521 |SUCCESS |0.281 | |n9.m14.g6 |14 |15615 |191 |15424 |SUCCESS |0.046 | |n9.m15.g6 |15 |21933 |27 |21906 |SUCCESS |0.078 | |n9.m16.g6 |16 |27987 |0 |27987 |SUCCESS |0.11 | |n9.m17.g6 |17 |32403 |0 |32403 |SUCCESS |0.062 | |n9.m18.g6 |18 |34040 |0 |34040 |SUCCESS |0.36 | |n9.m19.g6 |19 |32403 |0 |32403 |SUCCESS |0.125 | |n9.m20.g6 |20 |27987 |0 |27987 |SUCCESS |0.11 | |n9.m21.g6 |21 |21933 |0 |21933 |SUCCESS |0.094 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.063 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.109 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.031 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.015 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.0 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.015 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.047 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.0 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.0 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.0 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.0 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.0 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.0 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|======|=================|==========|==========| |TOTALS |274668 |5291 |269377 |37 |1.608 | edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.p.out.txt000066400000000000000000000070301521450711600300630ustar00rootroot00000000000000| Input filename | # Edges | # Graphs | # OK | # NONEMBEDDABLE |Error flag| Duration | |================|=========|==========|=======|=================|==========|==========| |n9.m0.g6 |0 |1 |1 |0 |SUCCESS |0.0 | |n9.m1.g6 |1 |1 |1 |0 |SUCCESS |0.0 | |n9.m2.g6 |2 |2 |2 |0 |SUCCESS |0.0 | |n9.m3.g6 |3 |5 |5 |0 |SUCCESS |0.0 | |n9.m4.g6 |4 |11 |11 |0 |SUCCESS |0.0 | |n9.m5.g6 |5 |25 |25 |0 |SUCCESS |0.016 | |n9.m6.g6 |6 |63 |63 |0 |SUCCESS |0.0 | |n9.m7.g6 |7 |148 |148 |0 |SUCCESS |0.0 | |n9.m8.g6 |8 |345 |345 |0 |SUCCESS |0.0 | |n9.m9.g6 |9 |771 |770 |1 |SUCCESS |0.0 | |n9.m10.g6 |10 |1637 |1632 |5 |SUCCESS |0.031 | |n9.m11.g6 |11 |3252 |3225 |27 |SUCCESS |0.203 | |n9.m12.g6 |12 |5995 |5848 |147 |SUCCESS |0.015 | |n9.m13.g6 |13 |10120 |9439 |681 |SUCCESS |0.11 | |n9.m14.g6 |14 |15615 |13093 |2522 |SUCCESS |0.062 | |n9.m15.g6 |15 |21933 |15029 |6904 |SUCCESS |0.235 | |n9.m16.g6 |16 |27987 |13660 |14327 |SUCCESS |0.172 | |n9.m17.g6 |17 |32403 |9536 |22867 |SUCCESS |0.156 | |n9.m18.g6 |18 |34040 |4858 |29182 |SUCCESS |0.141 | |n9.m19.g6 |19 |32403 |1734 |30669 |SUCCESS |0.078 | |n9.m20.g6 |20 |27987 |378 |27609 |SUCCESS |0.297 | |n9.m21.g6 |21 |21933 |50 |21883 |SUCCESS |0.094 | |n9.m22.g6 |22 |15615 |0 |15615 |SUCCESS |0.063 | |n9.m23.g6 |23 |10120 |0 |10120 |SUCCESS |0.047 | |n9.m24.g6 |24 |5995 |0 |5995 |SUCCESS |0.031 | |n9.m25.g6 |25 |3252 |0 |3252 |SUCCESS |0.032 | |n9.m26.g6 |26 |1637 |0 |1637 |SUCCESS |0.015 | |n9.m27.g6 |27 |771 |0 |771 |SUCCESS |0.016 | |n9.m28.g6 |28 |345 |0 |345 |SUCCESS |0.0 | |n9.m29.g6 |29 |148 |0 |148 |SUCCESS |0.0 | |n9.m30.g6 |30 |63 |0 |63 |SUCCESS |0.016 | |n9.m31.g6 |31 |25 |0 |25 |SUCCESS |0.015 | |n9.m32.g6 |32 |11 |0 |11 |SUCCESS |0.0 | |n9.m33.g6 |33 |5 |0 |5 |SUCCESS |0.0 | |n9.m34.g6 |34 |2 |0 |2 |SUCCESS |0.0 | |n9.m35.g6 |35 |1 |0 |1 |SUCCESS |0.016 | |n9.m36.g6 |36 |1 |0 |1 |SUCCESS |0.0 | |================|=========|==========|=======|=================|==========|==========| |TOTALS |274668 |79853 |194815 |37 |1.861 | edge-addition-planarity-suite-Version_5.0.0.0/c/000077500000000000000000000000001521450711600214305ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/.gdbinit000066400000000000000000000000231521450711600230440ustar00rootroot00000000000000set new-console on edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib.h000066400000000000000000000012341521450711600233310ustar00rootroot00000000000000#ifndef HELPERSTUB_GRAPHLIB_H #define HELPERSTUB_GRAPHLIB_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ // NOTE: This helper stub has been added to make it easier for downstream // consumers to obtain access to all features in the graph library of // the planarity project. #ifdef __cplusplus extern "C" { #endif // The "c" directory appears in this include directive because this // header file gets installed into the root of the planarity headers // directory, so the directive must first enter the "c" subdirectory. #include "c/graphLib/graphLib.h" #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/000077500000000000000000000000001521450711600231605ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/000077500000000000000000000000001521450711600264015ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/graphExtensions.c000066400000000000000000000534321521450711600317350ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include "../lowLevelUtils/appconst.h" #include "graphExtensions.private.h" #include "graphExtensions.h" #include "graphFunctionTable.h" /* Imported functions */ extern void _InitFunctionTable(graphP theGraph); /* Private function */ void _FreeExtension(graphExtensionP extension); void _OverloadFunctions(graphP theGraph, graphFunctionTableP functions); void _FixupFunctionTables(graphP theGraph, graphExtensionP curr); graphExtensionP _FindNearestOverload(graphP theGraph, graphExtensionP target, int functionIndex); /******************************************************************** * The moduleIDGenerator is used to help ensure that all extensions * added during a run-time have a different integer identifier. * An ID identifies an extension, which may be added to multiple * graphs. It is used in lieu of identifying extensions by a string * name, which is noticeably expensive when a frequently called * overload function seeks the extension context for a graph. ********************************************************************/ static int moduleIDGenerator = 0; /******************************************************************** The extension mechanism allows new modules to equip a graph with the data structures and functions needed to implement new algorithms without impeding the performance of the core graph planar embedding algorithms on graphs that have not been so equipped. The following steps must be used to create a graph extension: 1) Create a moduleID variable initialized to zero that will be assigned a positive integer the first time the extension is added to a graph by gp_AddExtension() 2) Define an extension context structure to contain all of the data and function pointers that extend the graph. The context must include a graphFunctionTableStruct to allow overloading of functions. An instance of this context structure is passed to the "context" parameter of gp_AddExtension(). 3) Define a function capable of copying your context data. It receives void pointers to destination and source contexts, and then copies the extension-specific data from source to destination. This function's pointer is passed to the "copyData" parameter of gp_AddExtension() 4) Define a function that can free the memory used by your context data structure. It will receive a void pointer indicating the instance of your context data structure that you passed as the "context" parameter to gp_AddExtension(). The free function pointer should be passed as the "freeContext" parameter to gp_AddExtension() 5) The expected method of attaching your feature to a graph is to create a function called gp_ExtendWith_Feature(), where 'Feature' is the name of your module. The attach function allocates your context data structure, initializes the extension data, assigns overload function pointers, and invokes gp_AddExtension(). NOTE: It is advisable to use memset on the context function table before assigning any function overloads because any function not being overloaded must have a NULL pointer. NOTE: The gp_AddExtension() method puts the overload function pointers into the graph's function table, and the base function pointers that were overloaded are placed in the context function table. This allows the extension's functions to have access to base function behaviors, since many extension functions will extend rather than replace the base behavior. 6) There are a few functions that you must overload in order to successfully manage data structures that are parallel to the main graph data structures. The core graph data structure has function pointers to functions that can be overloaded. In addition to invoking gp_AddExtension(), you need to set pointers to your own versions of the functions you are overloading. You will also need to store a copy of the prior pointer in your feature's context data structure so that you can invoke the "base" behavior from your function overload, e.g. if your feature is attached but not active or if your feature augments the base behavior rather than replacing it. a) If extra data must be maintained at the graph, vertex or edge levels, then fpEnsureVertexCapacity(), fpEnsureEdgeCapacity(), and fpResetGraphStorage() must be overloaded. b) For a parallel array of edge data, the extension must currently provides its own _Feature_DeleteEdge() that initializes its edge extension data along with calling gp_DeleteEdge(). c) If any data must be persisted in the file format, then overloads of fpReadPostprocess() and fpWritePostprocess() are needed. 7) Define internal functions for _Feature_ClearStructures(), _Feature_CreateStructures(), _Feature_InitStructures(), and _Feature_CopyData(); a) The _Feature_ClearStructures() should simply null out pointers to extra structures on its first invocation, but thereafter it should free them and then null them. Since the null-only step is done only once in gp_ExtendWith_Feature(), it seems reasonable to not bother with a more complicated _Feature_ClearStructures(). But, as an extension is developed, the data structures change, so it is best to keep all this logic in one place. b) The _Feature_CreateStructures() should just allocate memory for but not initialize any vertex level and edge level data structures. Data structures maintained at the graph level, such as a stack or a list collection, should be created _and_ initialized. c) The _Feature_InitStructures() should invoke just the functions needed to initialize the custom VertexRec, VertexInfo and EdgeRec data members, if any. d) The _Feature_CopyData() should invoke just the functions needed to copy the custom VertexRec, VertexInfo and EdgeRec data members, if any, from a source extension context to a destination extension context. For custom EdgeRec arrays, if the destination has more capacitiy than the source, then the implementation should also ensure the extra EdgeRecs are initialized in the destination. 8) Define a function gp_Detach_Feature() that invokes gp_RemoveExtension() This should be done for consistency, so that users of a feature do not attach it with gp_ExtendWith_Feature() and remove it with gp_RemoveExtension(). However, it may sometimes be necessary to run more code than just gp_RemoveExtension() when detaching a feature, e.g., some final result values of a feature may be saved to data available in the core graph or in other features. ********************************************************************/ /******************************************************************** gp_AddExtension() @param theGraph - pointer to the graph to which the extension is being added @param pModuleID - address of the variable that contains the feature's extension identifier. If the variable is equal to zero, it is assigned a positive number. Thereafter, the variable value can be used to find and remove the extension from any graph @param context - the data storage for the extension being added The context is owned by the extension and freed with freeContext() @param copyData - a function capable of copying the context data @param freeContext - a function capable of freeing the context data @param functions - pointer to a table of functions stored in the data context. The table of functions is an input and output parameter. On input, the table consists of new function pointers for functions being overloaded. Any function not being overloaded must be NULL. The non-NULL function pointers are used to overload the functions in the graph, and the prior pointer values in the graph are stored in the function table as output. The context data therefore has the pointer to the base function corresponding to any function its extension module overloaded. The new extension is created and added to the graph. ********************************************************************/ int gp_AddExtension(graphP theGraph, int *pModuleID, void *context, void *(*dupContext)(void *, void *), int (*copyData)(void *, void *), void (*freeContext)(void *), graphFunctionTableP functions) { graphExtensionP newExtension = NULL; if (theGraph == NULL || pModuleID == NULL || context == NULL || copyData == NULL || freeContext == NULL || functions == NULL) { return NOTOK; } // If the extension already exists, then don't redefine it. if (gp_FindExtension(theGraph, *pModuleID, NULL) == TRUE) { return NOTOK; } // Assign a unique ID to the extension if it does not already have one if (*pModuleID == 0) { *pModuleID = ++moduleIDGenerator; } // Allocate the new extension if ((newExtension = (graphExtensionP)malloc(sizeof(graphExtensionStruct))) == NULL) { return NOTOK; } // Assign the data payload of the extension newExtension->moduleID = *pModuleID; newExtension->context = context; newExtension->dupContext = dupContext; newExtension->copyData = copyData; newExtension->freeContext = freeContext; newExtension->functions = functions; _OverloadFunctions(theGraph, functions); // Make the new linkages newExtension->next = (struct graphExtensionStruct *)theGraph->extensions; theGraph->extensions = newExtension; // The new extension was successfully added return OK; } /******************************************************************** _OverloadFunctions() For each non-NULL function pointer, the pointer becomes the new value for the function in the graph, and the old function pointer in the graph is placed in the overload table. This way, when an extension function is invoked, it can choose to invoke the base function before or after whatever extension behavior it provides. Also, when it comes time to remove an extension, this extension system has access to the overload tables of all extensions so that it can unhook the functions of the module being removed from the chains of calls for each overloaded function. This will involve some pointer changes in the overload tables of extensions other than the one being removed. ********************************************************************/ void _OverloadFunctions(graphP theGraph, graphFunctionTableP functions) { void **currFunctionTable = (void **)theGraph->functions; void **newFunctionTable = (void **)functions; int numFunctions = sizeof(graphFunctionTableStruct) / sizeof(void *); int K; for (K = 0; K < numFunctions; K++) { if (newFunctionTable[K] != NULL) { void *fp = currFunctionTable[K]; currFunctionTable[K] = newFunctionTable[K]; newFunctionTable[K] = fp; } } } /******************************************************************** gp_FindExtension() @param theGraph - the graph whose extension list is to be searched @param moduleID - the identifier of the module whose extension context is desired @param pContext - the return parameter that receives the value of the extension, if found. This may be NULL if the extension was not found or if the extension context value was NULL. @return TRUE if the extension was found, NOTOK if not found If FALSE is returned, then the context returned is guaranteed to be NULL If TRUE is returned, the context returned may be NULL if that is the current value of the module extension ********************************************************************/ int gp_FindExtension(graphP theGraph, int moduleID, void **pContext) { graphExtensionP first = NULL, next = NULL; if (pContext != NULL) { *pContext = NULL; } if (theGraph == NULL || moduleID == 0) { return FALSE; } first = theGraph->extensions; while (first != NULL) { next = (graphExtensionP)first->next; if (first->moduleID == moduleID) { if (pContext != NULL) { *pContext = first->context; } return TRUE; } first = next; } return FALSE; } /******************************************************************** gp_GetExtension() Calling this function is equivalent to invoking gp_FindExtension() except that some debuggers have difficulty stepping into a function that (properly) start by setting a local variable pointer to NULL when the debugger has watch expressions that dereference a pointer of the same name. In such cases, MyContext *context = NULL; gp_FindExtension(theGraph, MYEXTENSION_ID, &context); can be replaced by MyContext *context = gp_GetExtension(theGraph, MYEXTENSION_ID); @param theGraph - the graph whose extension list is to be searched @param moduleID - the identifier of the module whose extension context is desired @return void pointer to the extension if found, or NULL if not found. ********************************************************************/ void *gp_GetExtension(graphP theGraph, int moduleID) { void *context = NULL; int result = gp_FindExtension(theGraph, moduleID, &context); return result ? context : NULL; } /******************************************************************** gp_RemoveExtension() @param theGraph - the graph from which to remove an extension @param moduleID - the ID of the module whose extension context is to be removed @return OK if the module is successfully removed or not in the list NOTOK for internal errors, such as invalid parameters ********************************************************************/ int gp_RemoveExtension(graphP theGraph, int moduleID) { graphExtensionP prev = NULL, curr = NULL, next = NULL; if (theGraph == NULL || moduleID == 0) return NOTOK; curr = theGraph->extensions; while (curr != NULL) { next = (graphExtensionP)curr->next; if (curr->moduleID == moduleID) break; prev = curr; curr = next; } // An extension can only be removed if it is found. Otherwise, // we return OK because the extension degenerately removed // (since it is already gone) if (curr != NULL) { _FixupFunctionTables(theGraph, curr); // Unhook the curr extension if (prev != NULL) prev->next = (struct graphExtensionStruct *)next; else theGraph->extensions = next; // Free the curr extension _FreeExtension(curr); } return OK; } /******************************************************************** _FixupFunctionTables() Removes the functions in the curr function table from the function call lists established by the function tables of all extensions and theGraph. Since new extensions are prepended, extensions before curr may have further overloaded the functions in the curr function table. For a non-NULL function pointer in the curr table, if there is a preceding extension with the same function pointer non-NULL, then the function table of the closest such preceding extension points to the original overload function of the curr extension, and the curr extension contains the pointer to the base function behavior, so now the function table of that preceding extension must be changed to the function pointer value in the curr extension. ********************************************************************/ void _FixupFunctionTables(graphP theGraph, graphExtensionP curr) { void **currFunctionTable = (void **)(curr->functions); int numFunctions = sizeof(*(curr->functions)) / sizeof(void *); int K; for (K = 0; K < numFunctions; K++) { if (currFunctionTable[K] != NULL) { void **nearestOverloadFunctionTable = (void **)&theGraph->functions; graphExtensionP pred = _FindNearestOverload(theGraph, curr, K); if (pred != NULL) nearestOverloadFunctionTable = (void **)pred->functions; nearestOverloadFunctionTable[K] = currFunctionTable[K]; } } } /******************************************************************** _FindNearestOverload() ********************************************************************/ graphExtensionP _FindNearestOverload(graphP theGraph, graphExtensionP target, int functionIndex) { graphExtensionP curr = theGraph->extensions; graphExtensionP found = NULL; void **functionTable; while (curr != target) { functionTable = (void **)curr->functions; if (functionTable[functionIndex] != NULL) found = curr; curr = (graphExtensionP)curr->next; } return found; } /******************************************************************** gp_DupExtensions() ********************************************************************/ int gp_DupExtensions(graphP dstGraph, graphP srcGraph) { graphExtensionP next = NULL, newNext = NULL, newLast = NULL; if (srcGraph == NULL || dstGraph == NULL) return NOTOK; gp_FreeExtensions(dstGraph); next = srcGraph->extensions; while (next != NULL) { if ((newNext = (graphExtensionP)malloc(sizeof(graphExtensionStruct))) == NULL) { gp_FreeExtensions(dstGraph); return NOTOK; } newNext->moduleID = next->moduleID; newNext->context = next->dupContext(next->context, dstGraph); newNext->dupContext = next->dupContext; newNext->copyData = next->copyData; newNext->freeContext = next->freeContext; newNext->functions = next->functions; newNext->next = NULL; if (newLast != NULL) newLast->next = (struct graphExtensionStruct *)newNext; else dstGraph->extensions = newNext; newLast = newNext; next = (graphExtensionP)next->next; } return OK; } /******************************************************************** gp_CopyExtensions() ********************************************************************/ // This number can just be made bigger if ever needed #define MAXNUMSUPPORTEDEXTENSIONS 32 int gp_CopyExtensions(graphP dstGraph, graphP srcGraph) { graphExtensionP dstExtension = NULL, srcExtension = NULL; graphExtensionP srcExtensionIDMap[MAXNUMSUPPORTEDEXTENSIONS+1]; if (srcGraph == NULL || dstGraph == NULL) return NOTOK; if (gp_GetN(dstGraph) != gp_GetN(srcGraph) || gp_GetN(dstGraph) == 0) return NOTOK; // NULL out the pointers in the srcExtensionIDMap memset(srcExtensionIDMap, 0, (MAXNUMSUPPORTEDEXTENSIONS+1)*sizeof(graphExtensionP)); // Run through the srcGraph extensions linked list and put the pointer to // each extension in the srcExtensionIDMap at the index location indicated // by the extension module ID. This mapping is needed because the srcGraph // may have been extended with the extensions in a different order than the // dstGraph, but the moduleID for each extension is the same across graphs. srcExtension = srcGraph->extensions; while (srcExtension != NULL) { if (srcExtension->moduleID < 0 || srcExtension->moduleID > MAXNUMSUPPORTEDEXTENSIONS) return NOTOK; srcExtensionIDMap[srcExtension->moduleID] = srcExtension; srcExtension = (graphExtensionP)srcExtension->next; } // For each extension in the dstGraph, if the srcGraph has the same extension, // then we invoke the extension's copyData to copy from srcGraph to dstGraph. // If the dstGraph has an extension that the srcGraph does not have, then // NULL is passed for the srcGraph extension, which is expected to cause // the extension's copyData() to reset/reinitialize the dstGraph structures. dstExtension = dstGraph->extensions; while (dstExtension != NULL) { srcExtension = srcExtensionIDMap[dstExtension->moduleID]; if (dstExtension->copyData(dstExtension->context, srcExtension ? srcExtension->context : NULL) != OK) return NOTOK; dstExtension = (graphExtensionP)dstExtension->next; } return OK; } /******************************************************************** gp_FreeExtensions() @param pFirst - pointer to head pointer of graph extension list Each graph extension is freed, including invoking the freeContext function provided when the extension was added. ********************************************************************/ void gp_FreeExtensions(graphP theGraph) { if (theGraph != NULL) { graphExtensionP curr = theGraph->extensions; graphExtensionP next = NULL; while (curr != NULL) { next = (graphExtensionP)curr->next; _FreeExtension(curr); curr = next; } theGraph->extensions = NULL; _InitFunctionTable(theGraph); } } /******************************************************************** _FreeExtension() ********************************************************************/ void _FreeExtension(graphExtensionP extension) { if (extension->context != NULL && extension->freeContext != NULL) { extension->freeContext(extension->context); } free(extension); } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/graphExtensions.h000066400000000000000000000017321521450711600317360ustar00rootroot00000000000000#ifndef GRAPH_EXTENSIONS_H #define GRAPH_EXTENSIONS_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef __cplusplus extern "C" { #endif #include "../graph.h" int gp_AddExtension(graphP theGraph, int *pModuleID, void *context, void *(*dupContext)(void *, void *), int (*copyData)(void *, void *), void (*freeContext)(void *), graphFunctionTableP overloadTable); int gp_FindExtension(graphP theGraph, int moduleID, void **pContext); void *gp_GetExtension(graphP theGraph, int moduleID); int gp_DupExtensions(graphP dstGraph, graphP srcGraph); int gp_CopyExtensions(graphP dstGraph, graphP srcGraph); void gp_FreeExtensions(graphP theGraph); int gp_RemoveExtension(graphP theGraph, int moduleID); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/graphExtensions.private.h000066400000000000000000000013101521450711600333770ustar00rootroot00000000000000#ifndef GRAPH_EXTENSIONS_PRIVATE_H #define GRAPH_EXTENSIONS_PRIVATE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphFunctionTable.h" #ifdef __cplusplus extern "C" { #endif struct graphExtensionStruct { int moduleID; void *context; void *(*dupContext)(void *, void *); int (*copyData)(void *, void *); void (*freeContext)(void *); graphFunctionTableP functions; struct graphExtensionStruct *next; }; typedef struct graphExtensionStruct graphExtensionStruct; typedef graphExtensionStruct *graphExtensionP; #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/graphFunctionTable.h000066400000000000000000000043001521450711600323260ustar00rootroot00000000000000#ifndef GRAPHFUNCTIONTABLE_H #define GRAPHFUNCTIONTABLE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef __cplusplus extern "C" { #endif /* NOTE: If you add any FUNCTION POINTERS to this function table, then you must also initialize them in _InitFunctionTable() in graph.c. */ typedef struct graphStruct graphStruct; typedef graphStruct *graphP; struct graphFunctionTableStruct { // These function pointers allow extension modules to overload some of // the behaviors of protected functions. Only advanced applications // will overload these functions int (*fpEmbeddingInitialize)(graphP); void (*fpEmbedBackEdgeToDescendant)(graphP, int, int, int, int); void (*fpWalkUp)(graphP, int, int); int (*fpWalkDown)(graphP, int, int); int (*fpMergeBicomps)(graphP, int, int, int, int); void (*fpMergeVertex)(graphP, int, int, int); int (*fpHandleInactiveVertex)(graphP, int, int *, int *); int (*fpHandleBlockedBicomp)(graphP, int, int, int); int (*fpEmbedPostprocess)(graphP, int, int); int (*fpMarkDFSPath)(graphP, int, int); int (*fpCheckEmbeddingIntegrity)(graphP, graphP); int (*fpCheckObstructionIntegrity)(graphP, graphP); // These function pointers allow extension modules to overload some // of the behaviors of gp_* function in the public API int (*fpEnsureVertexCapacity)(graphP, int); void (*fpResetGraphStorage)(graphP); int (*fpEnsureEdgeCapacity)(graphP, int); int (*fpSortVertices)(graphP); int (*fpReadPostprocess)(graphP, char *); int (*fpWritePostprocess)(graphP, char **); void (*fpHideEdge)(graphP, int); void (*fpRestoreEdge)(graphP, int); int (*fpHideVertex)(graphP, int); int (*fpRestoreVertex)(graphP); int (*fpContractEdge)(graphP, int); int (*fpIdentifyVertices)(graphP, int, int, int); }; typedef struct graphFunctionTableStruct graphFunctionTableStruct; typedef graphFunctionTableStruct *graphFunctionTableP; #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graph.c000066400000000000000000003236031521450711600244340ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graph.h" #include "graph.private.h" // To enable performing of certain initialization calls for the // DFSUtils, Planarity, and Outerplanarity pseudo-extensions. #include "planarityRelated/graphPlanarity.h" #include "planarityRelated/graphPlanarity.private.h" #include "planarityRelated/graphOuterplanarity.h" #include /* Imported functions for FUNCTION POINTERS */ extern int _EmbeddingInitialize(graphP theGraph); extern int _SortVertices(graphP theGraph); extern void _EmbedBackEdgeToDescendant(graphP theGraph, int RootSide, int RootVertex, int W, int WPrevLink); extern void _WalkUp(graphP theGraph, int v, int e); extern int _WalkDown(graphP theGraph, int v, int RootVertex); extern int _MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink); extern void _MergeVertex(graphP theGraph, int W, int WPrevLink, int R); extern int _HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R); extern int _HandleInactiveVertex(graphP theGraph, int BicompRoot, int *pW, int *pWPrevLink); extern int _MarkDFSPath(graphP theGraph, int ancestor, int descendant); extern int _EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); extern int _CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); extern int _CheckObstructionIntegrity(graphP theGraph, graphP origGraph); extern int _ReadPostprocess(graphP theGraph, char *extraData); extern int _WritePostprocess(graphP theGraph, char **pExtraData); /* Internal util functions for FUNCTION POINTERS */ int _HideVertex(graphP theGraph, int vertex); void _HideEdge(graphP theGraph, int e); void _RestoreEdge(graphP theGraph, int e); int _ContractEdge(graphP theGraph, int e); int _IdentifyVertices(graphP theGraph, int u, int v, int eBefore); int _RestoreVertex(graphP theGraph); /******************************************************************** Private functions, except exported within library ********************************************************************/ void _InitIsolatorContext(graphP theGraph); void _ClearAllVisitedFlagsInGraph(graphP theGraph); void _ClearVertexVisitedFlags(graphP theGraph, int includeVirtualVertices); void _ClearEdgeVisitedFlags(graphP theGraph); int _ClearAllVisitedFlagsInBicomp(graphP theGraph, int BicompRoot); int _ClearAllVisitedFlagsInOtherBicomps(graphP theGraph, int BicompRoot); void _ClearEdgeVisitedFlagsInUnembeddedEdges(graphP theGraph); int _FillVertexVisitedInfoInBicomp(graphP theGraph, int BicompRoot, int FillValue); int _ClearObstructionMarksInBicomp(graphP theGraph, int BicompRoot); int _gp_FindEdge(graphP theGraph, int u, int v); int _ClearAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); int _SetAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); int _ComputeEdgeRecordType(graphP theGraph, int a, int b, int edgeType); int _RestoreEdgeType(graphP theGraph, int u, int v); int _HideInternalEdges(graphP theGraph, int vertex); int _RestoreInternalEdges(graphP theGraph, int stackBottom); int _RestoreHiddenEdges(graphP theGraph, int stackBottom); int _GetBicompSize(graphP theGraph, int BicompRoot); int _DeleteUnmarkedEdgesInBicomp(graphP theGraph, int BicompRoot); int _ClearInvertedFlagsInBicomp(graphP theGraph, int BicompRoot); void _InitFunctionTable(graphP theGraph); /******************************************************************** Private functions. ********************************************************************/ void _InitVertices(graphP theGraph); void _InitEdges(graphP theGraph); void _ClearGraph(graphP theGraph); int _GetRandomNumber(int NMin, int NMax); int _getUnprocessedChild(graphP theGraph, int parent); int _hasUnprocessedChild(graphP theGraph, int parent); void _AttachEdgeRecord(graphP theGraph, int v, int e, int link, int newEdge); void _DetachEdgeRecord(graphP theGraph, int e); void _RestoreEdgeRecord(graphP theGraph, int e); /* Private functions for which there are FUNCTION POINTERS */ void _InitVertexRec(graphP theGraph, int v); void _InitVertexInfo(graphP theGraph, int v); void _InitEdgeRec(graphP theGraph, int e); int _EnsureVertexCapacity(graphP theGraph, int N); void _ResetGraphStorage(graphP theGraph); int _EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity); /******************************************************************** gp_New() Constructor for graph object. Can create two graphs if restricted to no dynamic memory. ********************************************************************/ graphP gp_New(void) { graphP theGraph = (graphP)calloc(1, sizeof(graphStruct)); graphFunctionTableP functionTable = (graphFunctionTableP)calloc(1, sizeof(graphFunctionTableStruct)); graphPrivateDataP theGraphPrivateData = (graphPrivateDataP)calloc(1, sizeof(graphPrivateDataStruct)); if (theGraph != NULL && functionTable != NULL && theGraphPrivateData != NULL) { theGraph->privateData = (void *)theGraphPrivateData; theGraph->functions = functionTable; _InitFunctionTable(theGraph); _ClearGraph(theGraph); } else { if (theGraph != NULL) { free(theGraph); theGraph = NULL; } if (functionTable != NULL) { free(functionTable); functionTable = NULL; } if (theGraphPrivateData != NULL) { free(theGraphPrivateData); theGraphPrivateData = NULL; } } return theGraph; } /******************************************************************** _InitFunctionTable() If you add functions to the function table, then they must be initialized here, but you must also add the new function pointer to the definition of the graphFunctionTableStruct in graphFunctionTable.h Function headers for the functions used to initialize the table are classified at the top of this file as either imported from other compilation units (extern) or private to this compilation unit. Search for FUNCTION POINTERS in this file to see where to add the function header. ********************************************************************/ void _InitFunctionTable(graphP theGraph) { if (theGraph != NULL && theGraph->functions != NULL) { theGraph->functions->fpEmbeddingInitialize = _EmbeddingInitialize; theGraph->functions->fpEmbedBackEdgeToDescendant = _EmbedBackEdgeToDescendant; theGraph->functions->fpWalkUp = _WalkUp; theGraph->functions->fpWalkDown = _WalkDown; theGraph->functions->fpMergeBicomps = _MergeBicomps; theGraph->functions->fpMergeVertex = _MergeVertex; theGraph->functions->fpHandleBlockedBicomp = _HandleBlockedBicomp; theGraph->functions->fpHandleInactiveVertex = _HandleInactiveVertex; theGraph->functions->fpEmbedPostprocess = _EmbedPostprocess; theGraph->functions->fpMarkDFSPath = _MarkDFSPath; theGraph->functions->fpCheckEmbeddingIntegrity = _CheckEmbeddingIntegrity; theGraph->functions->fpCheckObstructionIntegrity = _CheckObstructionIntegrity; theGraph->functions->fpEnsureVertexCapacity = _EnsureVertexCapacity; theGraph->functions->fpResetGraphStorage = _ResetGraphStorage; theGraph->functions->fpEnsureEdgeCapacity = _EnsureEdgeCapacity; theGraph->functions->fpSortVertices = _SortVertices; theGraph->functions->fpReadPostprocess = _ReadPostprocess; theGraph->functions->fpWritePostprocess = _WritePostprocess; theGraph->functions->fpHideEdge = _HideEdge; theGraph->functions->fpRestoreEdge = _RestoreEdge; theGraph->functions->fpHideVertex = _HideVertex; theGraph->functions->fpRestoreVertex = _RestoreVertex; theGraph->functions->fpContractEdge = _ContractEdge; theGraph->functions->fpIdentifyVertices = _IdentifyVertices; } } /******************************************************************** gp_EnsureVertexCapacity() Allocates memory for N vertices and N virtual vertices. Once N > 0 vertices have been allocated, this method currently does not support being called a second time to add more vertices. This method will also ensure that the edge capacity is allocated or reallocated to be at least (DEFAULT_EDGE_CAPACITY_FACTOR * N), i.e., a capacity for twice that many edge records, two per edge (plus 2 for the default of using one-based arrays). The edge capacity can be set before this function using gp_EnsureEdgeCapacity(). The edgeHoles stack, initially empty, is set to the edgeCapacity, which is big enough to push every edge (to indicate an edge, only one of its two edge records need be pushed). The numEdgeHoles is set to 0; it tracks the edgeHoles stack size, so the number of edge records in use can be efficiently computed. The stack, initially empty, is made big enough for a pair of integers per edge (2 * edgeCapacity), or 6N integers if the edgeCapacity was set below the default. Space for 2 extra integers is added so depth-first search can push (NIL, NIL) to start at a DFS tree root. The BicompRootLists and sortedDFSChildLists are set to a size of N, and they start out empty. DVI and PVI are set to store N of their respective vertex info records. An instance of the isolator context is created. Returns OK on success, NOTOK on aany failure. On NOTOK, graph extensions are freed so that the graph is returned to the post-condition of gp_New(). ********************************************************************/ int gp_EnsureVertexCapacity(graphP theGraph, int N) { // valid params check if (theGraph == NULL || N <= 0) return NOTOK; // Should not call init a second time; use reinit if (gp_GetN(theGraph) > 0) return NOTOK; return theGraph->functions->fpEnsureVertexCapacity(theGraph, N); } int _EnsureVertexCapacity(graphP theGraph, int N) { int Vsize, VIsize, Esize, stackSize; // Compute the vertex and edge capacities of the graph theGraph->N = N; theGraph->NV = N; theGraph->edgeCapacity = theGraph->edgeCapacity > 0 ? theGraph->edgeCapacity : DEFAULT_EDGE_CAPACITY_FACTOR * N; theGraph->numEdgeHoles = 0; VIsize = gp_UpperBoundVertices(theGraph); Vsize = gp_UpperBoundVertexStorage(theGraph); Esize = gp_UpperBoundEdgeStorage(theGraph); // Stack size is 2 integers per edge record plus 2 to start depth-first search at a tree root stackSize = (theGraph->edgeCapacity << 2) + 2; // In case of small edgeCapacity, ensure minimum based on number of vertices stackSize = stackSize <= 2 * 2 * DEFAULT_EDGE_CAPACITY_FACTOR * N ? 2 * 2 * DEFAULT_EDGE_CAPACITY_FACTOR * N + 2 : stackSize; // Allocate memory as described above if ((theGraph->V = (vertexRecP)calloc(Vsize, sizeof(vertexRec))) == NULL || (theGraph->E = (edgeRecP)calloc(Esize, sizeof(edgeRec))) == NULL || (theGraph->edgeHoles = sp_New(theGraph->edgeCapacity)) == NULL || (theGraph->theStack = sp_New(stackSize)) == NULL || (theGraphBicompRootLists(theGraph) = LCNew(VIsize)) == NULL || (theGraphDVI(theGraph) = (DFSUtils_VertexInfoP)calloc(VIsize, sizeof(DFSUtils_VertexInfo))) == NULL || (theGraphPVI(theGraph) = (Planarity_VertexInfoP)calloc(VIsize, sizeof(Planarity_VertexInfo))) == NULL || (theGraphSortedDFSChildLists(theGraph) = LCNew(VIsize)) == NULL || (theGraphExtFace(theGraph) = (extFaceLinkRecP)calloc(Vsize, sizeof(extFaceLinkRec))) == NULL || (theGraphIC(theGraph) = (isolatorContextP)calloc(1, sizeof(isolatorContextStruct))) == NULL || 0) { _ClearGraph(theGraph); return NOTOK; } // Initialize memory _InitVertices(theGraph); _InitEdges(theGraph); _InitIsolatorContext(theGraph); return OK; } /******************************************************************** _InitVertices() ********************************************************************/ void _InitVertices(graphP theGraph) { memset(theGraph->V, NIL_CHAR, gp_UpperBoundVertexStorage(theGraph) * sizeof(vertexRec)); memset(theGraphDVI(theGraph), NIL_CHAR, gp_UpperBoundVertices(theGraph) * sizeof(DFSUtils_VertexInfo)); memset(theGraphPVI(theGraph), NIL_CHAR, gp_UpperBoundVertices(theGraph) * sizeof(Planarity_VertexInfo)); memset(theGraphExtFace(theGraph), NIL_CHAR, gp_UpperBoundVertexStorage(theGraph) * sizeof(extFaceLinkRec)); #ifdef USE_1BASEDARRAYS // For 1-based arrays, the memset() initializes the flags correctly #else for (int v = gp_LowerBoundVertexStorage(theGraph); v < gp_UpperBoundVertexStorage(theGraph); ++v) gp_InitFlags(theGraph, v); #endif } /******************************************************************** _InitEdges() ********************************************************************/ void _InitEdges(graphP theGraph) { memset(theGraph->E, NIL_CHAR, gp_UpperBoundEdgeStorage(theGraph) * sizeof(edgeRec)); #ifdef USE_1BASEDARRAYS #else for (int e = gp_LowerBoundEdgeStorage(theGraph); e < gp_UpperBoundEdgeStorage(theGraph); ++e) gp_InitEdgeFlags(theGraph, e); #endif } /******************************************************************** gp_ResetGraphStorage() Resets the graph to the state immediately after processing by gp_EnsureVertexCapacity(). ********************************************************************/ void gp_ResetGraphStorage(graphP theGraph) { if (theGraph == NULL || gp_GetN(theGraph) <= 0) return; theGraph->functions->fpResetGraphStorage(theGraph); } void _ResetGraphStorage(graphP theGraph) { theGraph->M = 0; theGraph->embedFlags = 0; theGraph->graphFlags &= ~GRAPHFLAGS_DFSNUMBERED; theGraph->graphFlags &= ~GRAPHFLAGS_SORTEDBYDFI; _InitVertices(theGraph); _InitEdges(theGraph); _InitIsolatorContext(theGraph); LCReset(theGraphBicompRootLists(theGraph)); LCReset(theGraphSortedDFSChildLists(theGraph)); sp_ClearStack(theGraph->theStack); sp_ClearStack(theGraph->edgeHoles); theGraph->numEdgeHoles = 0; } /******************************************************************** gp_EnsureEdgeCapacity() This method ensures that theGraph is or will be capable of storing at least requiredEdgeCapacity edge records. Two edge records are needed per edge. This method is most performant when invoked immediately after gp_New(), since it must only set the edgeCapacity and then let normal processing by gp_EnsureVertexCapacity() occur. This method is also a constant time operation if the graph already has at least the requiredEdgeCapacity, since it will return OK without making any structural changes. This method is generally more performant if it is invoked before attaching extensions to the graph. Some extensions associate parallel data with edge records, which is a faster operation if the associated data storage is created and initialized only after the proper edgeCapacity is specified. The array of edge records is allocated or reallocated to satisfy the requiredEdgeCapacity. The new array contains the old edges and edge holes, if any, at the same locations. All newly created edge records are initialized. Also, if the edge capacity was lower than required, then the edgeCapacity member of theGraph is increased and both theStack and the edgeHoles are expanded (since the sizes of both are based on the edge capacity). Extensions that add to data associated with edges must overload this method to ensure the reqquired capacity in the parallel extension data structures. Returns NOTOK on failure to reallocate the edge record array to satisfy the requiredEdgeCapacity OK if reallocation succeeds or is not required ********************************************************************/ int gp_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity) { if (theGraph == NULL || requiredEdgeCapacity <= 0) return NOTOK; if (theGraph->edgeCapacity >= requiredEdgeCapacity) return OK; // In the special case where gp_EnsureVertexCapacity() has not yet // been called, we can simply set the higher edgeCapacity since normal // behavior of gp_EnsureVertexCapacity() will then allocate the // correct number of edge records. if (gp_GetN(theGraph) == 0) { theGraph->edgeCapacity = requiredEdgeCapacity; return OK; } // Try to expand the edge capacity return theGraph->functions->fpEnsureEdgeCapacity(theGraph, requiredEdgeCapacity); } int _EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity) { stackP newStack = NULL; int newEsize = gp_LowerBoundEdgeStorage(theGraph) + (requiredEdgeCapacity << 1); // If the new size is less than or equal to the current edge storage size, // then the graph already has the required edge capacity if (newEsize <= gp_UpperBoundEdgeStorage(theGraph)) return OK; // Expand theStack. Depth-first search needs 2 integers per edge record // (2 edge records per edge), plus 2 to start the DFS on a tree root // if (sp_GetCapacity(theGraph->theStack) < 2 * (2 * requiredEdgeCapacity) + 2) { int newStackSize = 2 * (2 * requiredEdgeCapacity) + 2; if (newStackSize < 2 * DEFAULT_EDGE_CAPACITY_FACTOR * gp_GetN(theGraph) + 2) { // NOTE: We enforce a minimum stack based on number of vertices // if edgeCapacity is small. Currently, this will not // happen because we only 'ensure' edge capacity, i.e., // the capacity can only ever get bigger. However, this // rule is enforced in case future methods are added // that reduce edge capacity newStackSize = 2 * DEFAULT_EDGE_CAPACITY_FACTOR * gp_GetN(theGraph) + 2; } if ((newStack = sp_New(newStackSize)) == NULL) return NOTOK; sp_CopyContent(newStack, theGraph->theStack); sp_Free(&theGraph->theStack); theGraph->theStack = newStack; } // Expand edgeHoles (at most, every edge is a hole if all edges deleted) if ((newStack = sp_New(requiredEdgeCapacity)) == NULL) { return NOTOK; } sp_CopyContent(newStack, theGraph->edgeHoles); sp_Free(&theGraph->edgeHoles); theGraph->edgeHoles = newStack; theGraph->numEdgeHoles = sp_GetCurrentSize(theGraph->edgeHoles); // Reallocate the edgeRec array to the new size, theGraph->E = (edgeRecP)realloc(theGraph->E, newEsize * sizeof(edgeRec)); if (theGraph->E == NULL) return NOTOK; // Initialize the new edge records for (int e = gp_UpperBoundEdgeStorage(theGraph); e < newEsize; ++e) _InitEdgeRec(theGraph, e); // The new edgeCapacity has been successfully allocated theGraph->edgeCapacity = requiredEdgeCapacity; return OK; } /******************************************************************** _InitVertexRec() Sets the fields in a single vertex record to initial values ********************************************************************/ void _InitVertexRec(graphP theGraph, int v) { gp_SetFirstEdge(theGraph, v, NIL); gp_SetLastEdge(theGraph, v, NIL); gp_SetIndex(theGraph, v, NIL); gp_InitFlags(theGraph, v); } /******************************************************************** _InitVertexInfo() Sets the fields in a single vertex record to initial values ********************************************************************/ void _InitVertexInfo(graphP theGraph, int v) { gp_SetVertexParent(theGraph, v, NIL); gp_SetVertexLeastAncestor(theGraph, v, NIL); gp_SetVertexLowpoint(theGraph, v, NIL); gp_SetVertexVisitedInfo(theGraph, v, NIL); gp_SetVertexPertinentEdge(theGraph, v, NIL); gp_SetVertexPertinentRootsList(theGraph, v, NIL); gp_SetVertexFuturePertinentChild(theGraph, v, NIL); gp_SetVertexSortedDFSChildList(theGraph, v, NIL); gp_SetVertexFwdEdgeList(theGraph, v, NIL); } /******************************************************************** _InitEdgeRec() Sets the fields in a single edge record structure to initial values ********************************************************************/ void _InitEdgeRec(graphP theGraph, int e) { gp_SetNeighbor(theGraph, e, NIL); gp_SetPrevEdge(theGraph, e, NIL); gp_SetNextEdge(theGraph, e, NIL); gp_InitEdgeFlags(theGraph, e); } /******************************************************************** _InitIsolatorContext() ********************************************************************/ void _InitIsolatorContext(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (IC != NULL) { IC->minorType = MINORTYPE_NONE; IC->v = IC->r = IC->x = IC->y = IC->w = IC->px = IC->py = IC->z = IC->ux = IC->dx = IC->uy = IC->dy = IC->dw = IC->uz = IC->dz = NIL; } } /******************************************************************** _ClearAllVisitedFlagsInGraph() ********************************************************************/ void _ClearAllVisitedFlagsInGraph(graphP theGraph) { _ClearVertexVisitedFlags(theGraph, TRUE); _ClearEdgeVisitedFlags(theGraph); } /******************************************************************** _ClearVertexVisitedFlags() Clears the visited flags of vertices, and if the second parameter is truthy, also clears the visited flags of virtual vertices. ********************************************************************/ void _ClearVertexVisitedFlags(graphP theGraph, int includeVirtualVertices) { for (int v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) gp_ClearVisited(theGraph, v); if (includeVirtualVertices) for (int vv = gp_LowerBoundVirtualVertices(theGraph); vv < gp_UpperBoundVirtualVertices(theGraph); ++vv) gp_ClearVisited(theGraph, vv); } /******************************************************************** _ClearEdgeVisitedFlags() ********************************************************************/ void _ClearEdgeVisitedFlags(graphP theGraph) { for (int e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); ++e) gp_ClearEdgeVisited(theGraph, e); } /******************************************************************** _ClearAllVisitedFlagsInBicomp() Clears the visited flag of the vertices and edge records in the bicomp rooted by BicompRoot. This method uses the stack but preserves whatever may have been on it. In debug mode, it will return NOTOK if the stack overflows. This method pushes at most one integer per vertex in the bicomp. Returns OK on success, NOTOK on implementation failure. ********************************************************************/ int _ClearAllVisitedFlagsInBicomp(graphP theGraph, int BicompRoot) { int stackBottom = sp_GetCurrentSize(theGraph->theStack); int v, e; sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, v); gp_ClearVisited(theGraph, v); e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { gp_ClearEdgeVisited(theGraph, e); if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } } return OK; } /******************************************************************** _ClearAllVisitedFlagsInOtherBicomps() Typically, we want to clear all visited flags in the graph (see _ClearVisitedFlags). However, in some algorithms this would be too costly, so it is necessary to clear the visited flags only in one bicomp (see _ClearAllVisitedFlagsInBicomp), then do some processing that sets some of the flags then performs some tests. If the tests are positive, then we can clear all the visited flags in the other bicomps (the processing may have set the visited flags in the one bicomp in a particular way that we want to retain, so we skip the given bicomp). ********************************************************************/ int _ClearAllVisitedFlagsInOtherBicomps(graphP theGraph, int BicompRoot) { int R; for (R = gp_LowerBoundVirtualVertices(theGraph); R < gp_UpperBoundVirtualVertices(theGraph); ++R) { if (R != BicompRoot && gp_VirtualVertexInUse(theGraph, R)) { if (_ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; } } return OK; } /******************************************************************** _ClearEdgeVisitedFlagsInUnembeddedEdges() Unembedded edges aren't part of any bicomp yet, but it may be necessary to clear their visited flags. ********************************************************************/ void _ClearEdgeVisitedFlagsInUnembeddedEdges(graphP theGraph) { int v, e; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { e = gp_GetVertexFwdEdgeList(theGraph, v); while (gp_IsEdge(theGraph, e)) { gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, v)) e = NIL; } } } /**************************************************************************** _ClearAllVisitedFlagsOnPath() This method clears the visited flags on the vertices and edges on the path (u, v, ..., w, x) in which all vertices except the endpoints u and x are degree 2. This method avoids performing more than constant work at the path endpoints u and x, so the total work is on the order of the path length. Returns OK on success, NOTOK on internal failure ****************************************************************************/ int _ClearAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x) { int e, eTwin; // We want to exit u from e, but we get eTwin first here in order to avoid // work, in case the degree of u is greater than 2. eTwin = _gp_FindEdge(theGraph, v, u); if (gp_IsNotEdge(theGraph, eTwin)) return NOTOK; e = gp_GetTwin(theGraph, eTwin); v = u; do { // Mark the vertex and the exiting edge gp_ClearVisited(theGraph, v); gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, eTwin); // Get the next vertex v = gp_GetNeighbor(theGraph, e); e = gp_GetNextEdgeCircular(theGraph, eTwin); eTwin = gp_GetTwin(theGraph, e); } while (v != x); // Mark the last vertex with 'visited' gp_ClearVisited(theGraph, x); return OK; } /**************************************************************************** _SetAllVisitedFlagsOnPath() This method sets the visited flags on the vertices and edges on the path (u, v, ..., w, x) in which all vertices except the endpoints u and x are degree 2. This method avoids performing more than constant work at the path endpoints u and x, so the total work is on the order of the path length. Returns OK on success, NOTOK on internal failure ****************************************************************************/ int _SetAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x) { int e, eTwin; // We want to exit u from e, but we get eTwin first here in order to avoid // work, in case the degree of u is greater than 2. eTwin = _gp_FindEdge(theGraph, v, u); if (gp_IsNotEdge(theGraph, eTwin)) return NOTOK; e = gp_GetTwin(theGraph, eTwin); v = u; do { // Mark the vertex and the exiting edge gp_SetVisited(theGraph, v); gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, eTwin); // Get the next vertex v = gp_GetNeighbor(theGraph, e); e = gp_GetNextEdgeCircular(theGraph, eTwin); eTwin = gp_GetTwin(theGraph, e); } while (v != x); // Mark the last vertex with 'visited' gp_SetVisited(theGraph, x); return OK; } /******************************************************************** _FillVertexVisitedInfoInBicomp() Places the FillValue into the visitedInfo of the non-virtual vertices in the bicomp rooted by BicompRoot. This method uses the stack but preserves whatever may have been on it. In debug mode, it will return NOTOK if the stack overflows. This method pushes at most one integer per vertex in the bicomp. Returns OK on success, NOTOK on implementation failure. ********************************************************************/ int _FillVertexVisitedInfoInBicomp(graphP theGraph, int BicompRoot, int FillValue) { int v, e; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, v); if (gp_IsNotVirtualVertex(theGraph, v)) gp_SetVertexVisitedInfo(theGraph, v, FillValue); e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } } return OK; } /******************************************************************** _ClearObstructionMarksInBicomp() Clears the 'obstruction type' bits for each vertex in the bicomp rooted by BicompRoot. This method uses the stack but preserves whatever may have been on it. In debug mode, it will return NOTOK if the stack overflows. This method pushes at most one integer per vertex in the bicomp. Returns OK on success, NOTOK on implementation failure. ********************************************************************/ int _ClearObstructionMarksInBicomp(graphP theGraph, int BicompRoot) { int V, e; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); gp_ClearObstructionMark(theGraph, V); e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } } return OK; } /******************************************************************** _ClearGraph() Clears all memory used by the graph, restoring it to the state it was in immediately after gp_New() created it. ********************************************************************/ void _ClearGraph(graphP theGraph) { if (theGraph->V != NULL) { free(theGraph->V); theGraph->V = NULL; } if (theGraph->E != NULL) { free(theGraph->E); theGraph->E = NULL; } theGraph->N = 0; theGraph->NV = 0; theGraph->M = 0; theGraph->edgeCapacity = 0; theGraph->embedFlags = 0; sp_Free(&theGraph->edgeHoles); theGraph->numEdgeHoles = 0; sp_Free(&theGraph->theStack); LCFree(&theGraphBicompRootLists(theGraph)); if (theGraphDVI(theGraph) != NULL) { free(theGraphDVI(theGraph)); theGraphDVI(theGraph) = NULL; } if (theGraphPVI(theGraph) != NULL) { free(theGraphPVI(theGraph)); theGraphPVI(theGraph) = NULL; } LCFree(&theGraphSortedDFSChildLists(theGraph)); if (theGraphExtFace(theGraph) != NULL) { free(theGraphExtFace(theGraph)); theGraphExtFace(theGraph) = NULL; } if (theGraphIC(theGraph) != NULL) { free(theGraphIC(theGraph)); theGraphIC(theGraph) = NULL; } gp_FreeExtensions(theGraph); // Free the pseudo-extensions if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY) gp_Detach_Outerplanarity(theGraph); if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_PLANARITY) gp_Detach_Planarity(theGraph); if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_DFSUTILS) gp_Detach_DFSUtils(theGraph); theGraph->graphFlags = 0; } /******************************************************************** gp_Free() Frees G and V, then the graph record. Then sets the caller's graph pointer to NULL (caller must pass the address of a graphP variable). ********************************************************************/ void gp_Free(graphP *pGraph) { if (pGraph == NULL) return; if (*pGraph == NULL) return; _ClearGraph(*pGraph); if ((*pGraph)->functions != NULL) { free((*pGraph)->functions); (*pGraph)->functions = NULL; } if ((*pGraph)->privateData != NULL) { free((*pGraph)->privateData); (*pGraph)->privateData = NULL; } free(*pGraph); *pGraph = NULL; } /******************************************************************** gp_CopyAdjacencyLists() Copies the adjacency lists from the srcGraph to the dstGraph. This method intentionally copies only the adjacency lists of the first N vertices, so the adjacency lists of virtual vertices are excluded (unless the caller temporarily resets the value of N to include NV). Returns OK on success, NOTOK on failures, e.g. if the two graphs have different orders N or if the edgeCapacity of dstGraph cannot be made to be at least that of srcGraph. ********************************************************************/ int gp_CopyAdjacencyLists(graphP dstGraph, graphP srcGraph) { int v, e; if (dstGraph == NULL || srcGraph == NULL) return NOTOK; if (gp_GetN(dstGraph) != gp_GetN(srcGraph) || gp_GetN(dstGraph) == 0) return NOTOK; if (gp_EnsureEdgeCapacity(dstGraph, srcGraph->edgeCapacity) != OK) return NOTOK; // Copy the links that hook each owning vertex to its adjacency list for (v = gp_LowerBoundVertices(srcGraph); v < gp_UpperBoundVertices(srcGraph); ++v) { gp_SetFirstEdge(dstGraph, v, gp_GetFirstEdge(srcGraph, v)); gp_SetLastEdge(dstGraph, v, gp_GetLastEdge(srcGraph, v)); } // Copy the adjacency links and neighbor pointers for each edge record for (e = gp_LowerBoundEdges(srcGraph); e < gp_UpperBoundEdges(srcGraph); ++e) { gp_SetNeighbor(dstGraph, e, gp_GetNeighbor(srcGraph, e)); gp_SetNextEdge(dstGraph, e, gp_GetNextEdge(srcGraph, e)); gp_SetPrevEdge(dstGraph, e, gp_GetPrevEdge(srcGraph, e)); } // Tell the dstGraph how many edges it now has and where the edge holes are dstGraph->M = gp_GetM(srcGraph); sp_Copy(dstGraph->edgeHoles, srcGraph->edgeHoles); dstGraph->numEdgeHoles = sp_GetCurrentSize(dstGraph->edgeHoles); return OK; } /******************************************************************** gp_CopyGraph() Copies the content of the srcGraph into the dstGraph. The dstGraph must have been previously initialized with the same number of vertices as the srcGraph. NOTE: If the dstGraph has a higher edge capacity than the srcGraph, then this call will fail unless the caller first ensures that the edge capacity of the srcGraph is increased to match the dstGraph. Returns OK for success, NOTOK for failure. ********************************************************************/ // Give macro names to three copy operations #define _gp_CopyVertexRec(dstGraph, vdst, srcGraph, vsrc) (dstGraph->V[vdst] = srcGraph->V[vsrc]) #define _gp_CopyDFSUtilsVertexInfo(dstGraph, dstI, srcGraph, srcI) (theGraphDVI(dstGraph)[dstI] = theGraphDVI(srcGraph)[srcI]) #define _gp_CopyPlanarityVertexInfo(dstGraph, dstI, srcGraph, srcI) (theGraphPVI(dstGraph)[dstI] = theGraphPVI(srcGraph)[srcI]) #define _gp_CopyEdgeRec(dstGraph, edst, srcGraph, esrc) (dstGraph->E[edst] = srcGraph->E[esrc]) int gp_CopyGraph(graphP dstGraph, graphP srcGraph) { int v, e; // Parameter checks if (dstGraph == NULL || srcGraph == NULL) { return NOTOK; } // The graphs need to be the same order and initialized if (gp_GetN(dstGraph) != gp_GetN(srcGraph) || gp_GetN(dstGraph) == 0) { return NOTOK; } // Ensure dstGraph has the required edge capacity; this expands // dstGraph if needed, but does not contract. An error is only // returned if the expansion fails. if (gp_EnsureEdgeCapacity(dstGraph, srcGraph->edgeCapacity) != OK) { return NOTOK; } // Copy the vertices (non-virtual only). Augmentations to vertices created // by extensions are copied below by gp_CopyExtensions() for (v = gp_LowerBoundVertices(srcGraph); v < gp_UpperBoundVertices(srcGraph); ++v) { _gp_CopyVertexRec(dstGraph, v, srcGraph, v); if (theGraphDVI(dstGraph) != NULL && theGraphDVI(srcGraph) != NULL) _gp_CopyDFSUtilsVertexInfo(dstGraph, v, srcGraph, v); if (theGraphPVI(dstGraph) != NULL && theGraphPVI(srcGraph) != NULL) _gp_CopyPlanarityVertexInfo(dstGraph, v, srcGraph, v); gp_SetExtFaceVertex(dstGraph, v, 0, gp_GetExtFaceVertex(srcGraph, v, 0)); gp_SetExtFaceVertex(dstGraph, v, 1, gp_GetExtFaceVertex(srcGraph, v, 1)); } // Copy the virtual vertices. Augmentations to virtual vertices created // by extensions are copied below by gp_CopyExtensions() for (v = gp_LowerBoundVirtualVertices(srcGraph); v < gp_UpperBoundVirtualVertices(srcGraph); ++v) { _gp_CopyVertexRec(dstGraph, v, srcGraph, v); gp_SetExtFaceVertex(dstGraph, v, 0, gp_GetExtFaceVertex(srcGraph, v, 0)); gp_SetExtFaceVertex(dstGraph, v, 1, gp_GetExtFaceVertex(srcGraph, v, 1)); } // Copy the basic EdgeRec structures. Augmentations to the edgeRec structure // created by extensions are copied below by gp_CopyExtensions() for (e = gp_LowerBoundEdgeStorage(srcGraph); e < gp_UpperBoundEdgeStorage(srcGraph); e++) _gp_CopyEdgeRec(dstGraph, e, srcGraph, e); // If the dstGraph has more edge storage than the srcGraph, then we clear the extra // base edgeRec structures. In gp_CopyExtensions(), the various extensions' copyData() // functions are expected to clear out any extension-specific extra edgeRec structures if (gp_UpperBoundEdgeStorage(dstGraph) > gp_UpperBoundEdgeStorage(srcGraph)) { for (e = gp_UpperBoundEdgeStorage(srcGraph); e < gp_UpperBoundEdgeStorage(dstGraph); e++) _InitEdgeRec(dstGraph, e); } // Give the dstGraph the same size and intrinsic properties dstGraph->N = gp_GetN(srcGraph); dstGraph->NV = gp_GetNV(srcGraph); dstGraph->M = gp_GetM(srcGraph); dstGraph->embedFlags = gp_GetEmbedFlags(srcGraph); dstGraph->graphFlags = gp_GetGraphFlags(srcGraph); LCCopy(theGraphBicompRootLists(dstGraph), theGraphBicompRootLists(srcGraph)); LCCopy(theGraphSortedDFSChildLists(dstGraph), theGraphSortedDFSChildLists(srcGraph)); sp_Copy(dstGraph->theStack, srcGraph->theStack); sp_Copy(dstGraph->edgeHoles, srcGraph->edgeHoles); dstGraph->numEdgeHoles = sp_GetCurrentSize((dstGraph)->edgeHoles); // Copy the set of extensions, which includes copying the // extension data as well as the function overload tables if (gp_CopyExtensions(dstGraph, srcGraph) != OK) return NOTOK; // We do not copy the function table of srcGraph to the dstGraph // because copy extensions now copies all possible data from // srcGraph to dstGraph, but it does not extend dstGraph with // any extensions it doesn't already have, so the dstGraph // function table is already correct for its type. return OK; } /******************************************************************** gp_DupGraph() ********************************************************************/ graphP gp_DupGraph(graphP theGraph) { graphP result = NULL; if ((result = gp_New()) == NULL) { gp_ErrorMessage("Failed to create a new graph."); return NULL; } if (gp_EnsureVertexCapacity(result, gp_GetN(theGraph)) != OK) { gp_ErrorMessage("Failed to increase the vertex capacity of the new " "graph."); gp_Free(&result); return NULL; } if (gp_CopyGraph(result, theGraph) != OK) { gp_ErrorMessage("Failed to copy the source graph to the new graph."); gp_Free(&result); return NULL; } if (gp_DupExtensions(result, theGraph) != OK) { gp_ErrorMessage("Failed to duplicate the extensions of the source graph into the new graph."); gp_Free(&result); return NULL; } return result; } /******************************************************************** gp_CreateRandomGraph() Creates a randomly generated graph. First a tree is created by connecting each vertex to some successor. Then a random number of additional random edges are added. If an edge already exists, then we retry until a non-existent edge is picked. This function assumes the caller has already called srand(). Returns OK on success, NOTOK on failure ********************************************************************/ int gp_CreateRandomGraph(graphP theGraph) { int N, M, u, v, m; N = gp_GetN(theGraph); /* Generate a random tree; note that this method virtually guarantees that the graph will be renumbered, but it is linear time. Also, we are not generating the DFS tree but rather a tree that simply ensures the resulting random graph is connected. */ for (v = gp_LowerBoundVertices(theGraph) + 1; v < gp_UpperBoundVertices(theGraph); ++v) { u = _GetRandomNumber(gp_LowerBoundVertices(theGraph), v - 1); if (gp_AddEdge(theGraph, u, 0, v, 0) != OK) return NOTOK; } /* Generate a random number of additional edges (actually, leave open a small chance that no additional edges will be added). */ M = _GetRandomNumber(7 * N / 8, theGraph->edgeCapacity); if (M > N * (N - 1) / 2) M = N * (N - 1) / 2; for (m = N - 1; m < M; m++) { u = _GetRandomNumber(gp_LowerBoundVertices(theGraph), gp_UpperBoundVertices(theGraph) - 2); v = _GetRandomNumber(u + 1, gp_UpperBoundVertices(theGraph) - 1); // If the edge (u,v) exists, decrement eIndex to try again if (gp_IsNeighbor(theGraph, u, v)) m--; // If the edge (u,v) doesn't exist, add it else { if (gp_AddEdge(theGraph, u, 0, v, 0) != OK) return NOTOK; } } return OK; } /******************************************************************** _GetRandomNumber() This function generates a random number between NMin and NMax inclusive. It assumes that the caller has called srand(). It calls rand(), but before truncating to the proper range, it adds the high bits of the rand() result into the low bits. The result of this is that the randomness appearing in the truncated bits also has an affect on the non-truncated bits. ********************************************************************/ int _GetRandomNumber(int NMin, int NMax) { int N = rand(); if (NMax < NMin) return NMin; N += ((N & 0xFFFF0000) >> 16); N += ((N & 0x0000FF00) >> 8); N &= 0x7FFFFFF; N %= (NMax - NMin + 1); return N + NMin; } /******************************************************************** _getUnprocessedChild() Support routine for gp_Create RandomGraphEx(), this function obtains a child of the given vertex in the randomly generated tree that has not yet been processed. NIL is returned if the given vertex has no unprocessed children ********************************************************************/ int _getUnprocessedChild(graphP theGraph, int parent) { int e = gp_GetFirstEdge(theGraph, parent); int eTwin = gp_GetTwin(theGraph, e); int child = gp_GetNeighbor(theGraph, e); // The tree edges were added to the beginning of the adjacency list, // and we move processed tree edge records to the end of the list, so // if the immediate next edge record is not a tree edge, then we // return NIL because the vertex has no remaining unprocessed children if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_NOTDEFINED) return NIL; // If the child has already been processed, then all children // have been pushed to the end of the list, and we have just // encountered the first child we processed, so there are no // remaining unprocessed children */ if (gp_GetEdgeVisited(theGraph, e)) return NIL; // We have found an edge leading to an unprocessed child, so // we mark it as processed so that it doesn't get returned // again in future iterations. gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, eTwin); // Now we move the edge record in the parent vertex to the end // of the adjacency list of that vertex. gp_MoveEdgeToLast(theGraph, parent, e); // Now we move the edge record in the child vertex to the // end of the adjacency list of the child. gp_MoveEdgeToLast(theGraph, child, eTwin); // Now we set the child's parent and return the child. gp_SetVertexParent(theGraph, child, parent); return child; } /******************************************************************** _hasUnprocessedChild() Support routine for gp_Create RandomGraphEx(), this function obtains a child of the given vertex in the randomly generated tree that has not yet been processed. False (0) is returned unless the given vertex has an unprocessed child. ********************************************************************/ int _hasUnprocessedChild(graphP theGraph, int parent) { int e = gp_GetFirstEdge(theGraph, parent); if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_NOTDEFINED) return 0; if (gp_GetEdgeVisited(theGraph, e)) return 0; return 1; } /******************************************************************** gp_CreateRandomGraphEx() Given a graph structure with a pre-specified number of vertices N, this function creates a graph with the specified number of edges. The primary use case for this method is to generate either a maximal planar graph, or a maximal planar graph plus a number of random additional edges. These cases correspond to the numEdges being equal to 3N-6 or greater than 3N-6, respectively. If numEdges < 3N-6, then the graph generated is a random tree plus edges added systematically to the tree while maintaining planarity. The output graph will have at least numEdges edges, but it may have a few more since more than one edge is added per iteration of the loop that adds the extra edges to the random tree. This function assumes the caller has already called srand(). ********************************************************************/ int gp_CreateRandomGraphEx(graphP theGraph, int numEdges) { int N, M, root, v, c, p, last, u, e; // Parameter checks: Must have a graph of at least three vertices, and the // number of edges must be at least enough to support making a random tree. if (theGraph == NULL || gp_GetN(theGraph) < 3 || numEdges < (gp_GetN(theGraph) - 1)) return NOTOK; N = gp_GetN(theGraph); if (numEdges > theGraph->edgeCapacity) numEdges = theGraph->edgeCapacity; /* Generate a random tree. */ for (v = gp_LowerBoundVertices(theGraph) + 1; v < gp_UpperBoundVertices(theGraph); ++v) { u = _GetRandomNumber(gp_LowerBoundVertices(theGraph), v - 1); if (gp_AddEdge(theGraph, u, 0, v, 0) != OK) return NOTOK; else { e = _gp_FindEdge(theGraph, u, v); gp_SetEdgeType(theGraph, e, EDGE_TYPE_TREE); gp_SetEdgeType(theGraph, gp_GetTwin(theGraph, e), EDGE_TYPE_TREE); gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); } } // Start with generating a maxplanar graph on the random tree // (or adding edges up to numEdges in the fashion of generating a maxplanar graph) M = numEdges <= 3 * N - 6 ? numEdges : 3 * N - 6; // Start with the first vertex root = gp_LowerBoundVertices(theGraph); // Generally, we use v keep track of a traversal down and up all the random tree edges // The last variable marks the location of the last vertex that was an endpoint of the // most recently added edge. v = last = _getUnprocessedChild(theGraph, root); // Just a safety check (all children of root are initially unprocessed) if (gp_IsNotVertex(theGraph, v)) return NOTOK; // Vertex v starts at the first unprocessed child of root and traverses around both sides // of the edges of the random tree until it gets back to the root... except, // The original version of this method generated a maxplanar graph only, but it was // refactored to give greater control of the number of edges. After the refactor, this // loop now stops when the edge count reaches M. Even for a maxplanar graph, that will // happen when v reaches the last unprocessed child of the last unprocessed child of root. // Still, we test for v != root for greater understanding of the idea of this method. while (v != root && gp_GetM(theGraph) < M) { // Get the next unprocessed child of v, if any. This method has the side effect // that it marks the edge (v, c) and hence c as being processed. This method // returns NIL (which not a vertex) if v has no _unprocessed_ children left. c = _getUnprocessedChild(theGraph, v); // If v did have an unprocessed child... if (gp_IsVertex(theGraph, c)) { // FORWARD_LABEL_0 (see below) if (last != v) { if (gp_AddEdge(theGraph, last, 1, c, 1) != OK) return NOTOK; } // Add an edge to create a new triangular face with root, v, and the child c // FORWARD_LABEL_1 (see below) if (gp_AddEdge(theGraph, root, 1, c, 1) != OK) return NOTOK; // Advance the traversal of v to the child c, and also assign c to last because // (root, c) is the last non-tree edge added. v = last = c; } // If v did not have any more unprocessed children, then we have to back up to // the nearest of its tree ancestors that does have an unprocessed child else { // Get the parent of v and get its next unprocessed child, if any p = gp_GetVertexParent(theGraph, v); if (gp_IsVertex(theGraph, p)) c = _getUnprocessedChild(theGraph, p); // Loop until we find an ancestor (p) of v that does have an unprocessed child // This loop also creates more triangular faces as it traverses back up along // the child-to-parent sides of edges to the successive ancestors of v. // FORWARD_LABEL_2 (see below) while (gp_IsVertex(theGraph, p) && gp_IsNotVertex(theGraph, (c))) { // Since we are in this loop, the parent p did not have // an unprocessed child, so we advance both p and v to // enable checking the next higher ancestor v = p; p = gp_GetVertexParent(theGraph, v); // Now that we have advanced upward, there is now a triangular face // we can create between the original v (denoted last) and the new // parent, which is a grandparent or higher of last. // This ensures that we triangulate along the path leading back // up to the next vertex with an unprocessed child. if (gp_IsVertex(theGraph, p)) { // We exclude adding an edge between last and p in the special case // that p has ascended back up to the root because adding the edge // would create a duplicate of the edge added at FORWARD_LABEL_1 if (p != root) { if (gp_AddEdge(theGraph, last, 1, p, 1) != OK) return NOTOK; } } // Now that we have dealt with triangulation of that path up to the new p, // we obtain its next unprocessed child, if any to see if we have gone // to a high enough ancestor that we have an unprocessed child to deal with. // NOTE: At the very least, there will still be an unprocessed child by the // time p gets to the tree root because we haven't yet reached the // edge limit in the outer loop condition. if (gp_IsVertex(theGraph, p)) c = _getUnprocessedChild(theGraph, p); } // Back when v != root was the outer loop condition, it was possible for v to // go to the root, and for p to become NIL (not a vertex). Now, that the outer // loop ends as soon as enough edges are added, p is always a vertex. // Still, we do the test here. if (gp_IsVertex(theGraph, p)) { if (p == root) { // If p is the root, then we create a triangular face containing // v, p==root, and c, where v is the last vertex visited in one // of subtree of p==root, and c is the first vertex visited in the // next subtree of p== root. // NOTE: This is a special kind of edge called a "cross edge" that // joins two vertices that do not have the ancestor-descendant // relationship (i.e., it is not a "back edge" and so the tree // is not a DFS tree). if (gp_AddEdge(theGraph, v, 1, c, 1) != OK) return NOTOK; // If v advanced upward to a higher ancestor than the parent of last, // then we entered the loop at FORWARD_LABEL_2, which triangulated // on the way up, except now we must add an edge that creates a // triangular face with last, v, and c. if (v != last) { if (gp_AddEdge(theGraph, last, 1, c, 1) != OK) return NOTOK; } // NOTE: Because p is the root, we do not advance 'last' to c quite yet // because v will advance to c below and the next iteration of // the outer loop will get _its_ next unprocessed child, say c2. // Only once we know the identity of c2 can we add the extra edge // needed to create a triangular face with last, c, and c2. // This occurs at FORWARD_LABEL_0 above, with v=c and c=c2, // after which last is assigned the value c2. // Perhaps one day enough guilt will accrue to foster doing what // is needed here to allow c to be assigned to last. } // In case p is not the root, then we have already triangulated along the // path up from last to p, so... else { // We add an edge that creates a triangular face with last, p, and c. if (gp_AddEdge(theGraph, last, 1, c, 1) != OK) return NOTOK; // And then an edge that creates a triangular face with root, last, and c. if (gp_AddEdge(theGraph, root, 1, c, 1) != OK) return NOTOK; // At which point, last can advance to c last = c; } // The main traversal tracking variable v can now advance to c v = c; } } } /* Add additional edges if the limit has not yet been reached. */ while (gp_GetM(theGraph) < numEdges) { u = _GetRandomNumber(gp_LowerBoundVertices(theGraph), gp_UpperBoundVertices(theGraph) - 1); v = _GetRandomNumber(gp_LowerBoundVertices(theGraph), gp_UpperBoundVertices(theGraph) - 1); if (u != v && !gp_IsNeighbor(theGraph, u, v)) if (gp_AddEdge(theGraph, u, 0, v, 0) != OK) return NOTOK; } /* Clear the edge types back to 'unknown' */ for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); ++e) { gp_ClearEdgeType(theGraph, e); gp_ClearEdgeVisited(theGraph, e); } /* Put all DFSParent indicators back to NIL */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) gp_SetVertexParent(theGraph, v, NIL); return OK; } /******************************************************************** gp_IsNeighbor() Checks whether the adjacency list of an any-type vertex u contains an edge record with a neighbor field indicating v. Returns TRUE or FALSE. NOTE: The edge may be undirected, INONLY or OUTONLY. To test if v is an in-neighbor or out-neighbor of u, use the directed method gp_IsNeighborDirected() instead. ********************************************************************/ int gp_IsNeighbor(graphP theGraph, int u, int v) { int e = NIL; if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) { #ifdef DEBUG NOTOK; #endif return FALSE; } e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) == v) return TRUE; e = gp_GetNextEdge(theGraph, e); } return FALSE; } /******************************************************************** gp_IsNeighborDirected() Checks whether the adjacency list of an any-type vertex u contains an edge record with a neighbor field indicating v and a direction flag matching the direction parameter. Returns TRUE or FALSE. NOTE: The valid direction flag values are 0 to match any edge record, or EDGEFLAG_DIRECTION_INONLY to test if v is an in-neighbor of u, or EDGEFLAG_DIRECTION_OUTONLY to test if v is an out-neighbor of u. ********************************************************************/ int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned direction) { int e = NIL; if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph) || (direction != 0 && direction != EDGEFLAG_DIRECTION_INONLY && direction != EDGEFLAG_DIRECTION_OUTONLY)) { #ifdef DEBUG NOTOK; #endif return FALSE; } e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) == v) { if (direction == 0 || direction == gp_GetDirection(theGraph, e)) return TRUE; } e = gp_GetNextEdge(theGraph, e); } return FALSE; } /******************************************************************** gp_FindEdge() Searches the adjacency list of an any-type of vertex u to obtain an edge record with v in the neighbor field. Returns the edge record's location, or NIL if there is no such edge. NOTE: The returned edge may be undirected, INONLY or OUTONLY. This method is intended for when a graph is undirected or when an application must treat a directed graph as if it is undirected. To obtain a result for INONLY or OUTONLY edges, use gp_FindDirectedEdge() instead. ********************************************************************/ int gp_FindEdge(graphP theGraph, int u, int v) { if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) { #ifdef DEBUG NOTOK; #endif return NIL; } return _gp_FindEdge(theGraph, u, v); } /***************************************************************** * _gp_FindEdge() * * Private version of the public method that performs the search * without preceding parameter validation checks. This is called * from other private methods of the graph library, to avoid * duplication of the effort of the checks performed by invoking * public methods. */ int _gp_FindEdge(graphP theGraph, int u, int v) { int e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) == v) return e; e = gp_GetNextEdge(theGraph, e); } return NIL; } /******************************************************************** gp_FindDirectedEdge() Searches the adjacency list of an any-type of vertex u to obtain an edge record that matches the direction flag and that has v in the neighbor field. Returns the edge record's location, or NIL if there is no such edge. NOTE: The valid direction flag value are 0 for any direction, EDGEFLAG_DIRECTION_INONLY, or EDGEFLAG_DIRECTION_OUTONLY. ********************************************************************/ int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned direction) { int e = NIL; if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph) || (direction != 0 && direction != EDGEFLAG_DIRECTION_INONLY && direction != EDGEFLAG_DIRECTION_OUTONLY)) { #ifdef DEBUG NOTOK; #endif return NIL; } // If undirected, call the undirected version if (direction == 0) return _gp_FindEdge(theGraph, u, v); // If a direction was given, then use it e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) == v && gp_GetDirection(theGraph, e) == direction) return e; e = gp_GetNextEdge(theGraph, e); } return NIL; } /******************************************************************** gp_GetVertexDegree() Counts the number of edge records in the adjacency list of a given vertex V. NOTE: For digraphs, this method returns the total degree of the vertex, including undirected, OUTONLY and INONLY edge records. Other functions are defined to get the in-degree or out-degree of a vertex. NOTE: This function determines the degree by counting. An extension could cache the degree value of each vertex and update the cached value as edges are added and deleted. ********************************************************************/ int gp_GetVertexDegree(graphP theGraph, int v) { int e, degree; if (theGraph == NULL || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) { #ifdef DEBUG NOTOK; ; #endif return 0; } degree = 0; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { degree++; e = gp_GetNextEdge(theGraph, e); } return degree; } /******************************************************************** gp_GetVertexInDegree() Counts the number of edge records in the adjacency list of a given vertex v that represent edge records from another vertex into v. This includes undirected edges and INONLY edge records, so it only excludes edges records that are marked as OUTONLY. NOTE: This function determines the in-degree by counting. An extension could cache the in-degree value of each vertex and update the cached value as edges are added and deleted. ********************************************************************/ int gp_GetVertexInDegree(graphP theGraph, int v) { int e, degree; if (theGraph == NULL || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) { #ifdef DEBUG NOTOK; #endif return 0; } degree = 0; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetDirection(theGraph, e) != EDGEFLAG_DIRECTION_OUTONLY) degree++; e = gp_GetNextEdge(theGraph, e); } return degree; } /******************************************************************** gp_GetVertexOutDegree() Counts the number of edge records in the adjacency list of a given vertex V that represent edges from V to another vertex. This includes undirected edges and OUTONLY edges, so it only excludes edges records that are marked as INONLY edges. NOTE: This function determines the out-degree by counting. An extension could cache the out-degree value of each vertex and update the cached value as edges are added and deleted. ********************************************************************/ int gp_GetVertexOutDegree(graphP theGraph, int v) { int e, degree; if (theGraph == NULL || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) { #ifdef DEBUG NOTOK; #endif return 0; } degree = 0; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetDirection(theGraph, e) != EDGEFLAG_DIRECTION_INONLY) degree++; e = gp_GetNextEdge(theGraph, e); } return degree; } /******************************************************************** _AttachEdgeRecord() This routine adds newEdge into v's adjacency list at a position adjacent to the edge record for e, either before or after e, depending on link. If e is not an edge (e.g. if e is NIL), then link is assumed to indicate whether the newEdge is to be placed at the beginning or end of v's adjacency list. NOTE: The caller can pass NIL for v if e is not NIL, since the vertex is implied (gp_GetNeighbor(theGraph, eTwin)) The newEdge is assumed to already exist in the data structure (i.e. the storage of edges), as only a whole edge (both edge records) can be inserted into or deleted from the data structure. See also _RestoreEdgeRecord() ********************************************************************/ void _AttachEdgeRecord(graphP theGraph, int v, int e, int link, int newEdge) { if (gp_IsEdge(theGraph, e)) { int e2 = gp_GetAdjacentEdge(theGraph, e, link); // e's link is newEdge, and newEdge's 1^link is e gp_SetAdjacentEdge(theGraph, e, link, newEdge); gp_SetAdjacentEdge(theGraph, newEdge, 1 ^ link, e); // newEdge's link is e2 gp_SetAdjacentEdge(theGraph, newEdge, link, e2); // if e2 is an edge, then e2's 1^link is newEdge, // else v's 1^link is newEdge if (gp_IsEdge(theGraph, e2)) gp_SetAdjacentEdge(theGraph, e2, 1 ^ link, newEdge); else gp_SetEdgeByLink(theGraph, v, 1 ^ link, newEdge); } else { int e2 = gp_GetEdgeByLink(theGraph, v, link); // v's link is newEdge, and newEdge's 1^link is NIL gp_SetEdgeByLink(theGraph, v, link, newEdge); gp_SetAdjacentEdge(theGraph, newEdge, 1 ^ link, NIL); // newEdge's elink is e2 gp_SetAdjacentEdge(theGraph, newEdge, link, e2); // if e2 is an edge, then e2's 1^link is newEdge, // else v's 1^link is newEdge if (gp_IsEdge(theGraph, e2)) gp_SetAdjacentEdge(theGraph, e2, 1 ^ link, newEdge); else gp_SetEdgeByLink(theGraph, v, 1 ^ link, newEdge); } } /**************************************************************************** _DetachEdge() This routine detaches edge record e from its adjacency list, but it does not delete it from the data structure (only a whole edge can be deleted). Some algorithms must temporarily detach an edge, perform some calculation, and eventually put the edge back. This routine supports that operation. The neighboring adjacency list nodes are cross-linked, but the two link members of edge record e are retained, so edge record e can be reattached later by invoking _RestoreEdgeRecord(). A sequence of detached edge records can only be restored in the exact opposite order of their detachment. Thus, algorithms do not directly use this method to implement the temporary detach/restore method. Instead, gp_HideEdge() and gp_RestoreEdge() are used, and algorithms push and pop hidden edges onto and from a stack. A example of this is shown by detaching edges with gp_ContractEdge() or gp_IdentifyVertices(), and then reattaching them with gp_RestoreVertices(), which unwinds the stack with gp_RestoreVertex(). ****************************************************************************/ void _DetachEdgeRecord(graphP theGraph, int e) { int nextEdge = gp_GetNextEdge(theGraph, e), prevEdge = gp_GetPrevEdge(theGraph, e); if (gp_IsEdge(theGraph, nextEdge)) gp_SetPrevEdge(theGraph, nextEdge, prevEdge); else gp_SetLastEdge(theGraph, gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, e)), prevEdge); if (gp_IsEdge(theGraph, prevEdge)) gp_SetNextEdge(theGraph, prevEdge, nextEdge); else gp_SetFirstEdge(theGraph, gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, e)), nextEdge); } /******************************************************************** gp_AddEdge() Adds the undirected edge (u,v) to the graph by placing edge records representing u into v's circular edge record list and v into u's circular edge record list. upos receives the location in G where the u record in v's list will be placed, and vpos is the location in G of the v record we placed in u's list. These are used to initialize the short circuit links. ulink (0|1) indicates whether the edge record to v in u's list should become adjacent to u by its 0 or 1 link, i.e. u[ulink] == vpos. vlink (0|1) indicates whether the edge record to u in v's list should become adjacent to v by its 0 or 1 link, i.e. v[vlink] == upos. NOTE: Only the neighbor and link pointer data members are modified in the edge records. The edge records are otherwise assumed to be in initial state, either from graph initialization/reinitialization, or from edge record reinitialization during gp_DeleteEdge(), if the new edge is filling an edge hole in the edge array.This expectation of being in initial state includes data stored in parallel edge record extension arrays. NOTE: This method does not forbid the addition of duplicate and loop edges. Use with care because other API endpoints do not all support nor check for and eliminate duplicates and loops. The caller can guard against these conditions by pre-testing that u != v and that gp_FindEdge() returns NIL. Returns OK on success, NOTOK on failure, or AT_EDGE_CAPACITY_LIMIT if adding the edge would exceed the graph's edge capacity (the caller can use gp_DynamicAddEdge()). ********************************************************************/ int gp_AddEdge(graphP theGraph, int u, int ulink, int v, int vlink) { int upos, vpos; if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph)) return NOTOK; /* We enforce the edge limit */ if (gp_GetM(theGraph) >= theGraph->edgeCapacity) return AT_EDGE_CAPACITY_LIMIT; if (sp_NonEmpty(theGraph->edgeHoles)) { sp_Pop(theGraph->edgeHoles, vpos); theGraph->numEdgeHoles = sp_GetCurrentSize(theGraph->edgeHoles); } else vpos = gp_UpperBoundEdges(theGraph); upos = gp_GetTwin(theGraph, vpos); gp_SetNeighbor(theGraph, upos, v); _AttachEdgeRecord(theGraph, u, NIL, ulink, upos); gp_SetNeighbor(theGraph, vpos, u); _AttachEdgeRecord(theGraph, v, NIL, vlink, vpos); theGraph->M++; return OK; } /******************************************************************** gp_DynamicAddEdge() Refer to documentation for gp_AddEdge() for parameter description. Calls gp_AddEdge(); if AT_EDGE_CAPACITY_LIMIT, doubles the edge capacity using gp_EnsureEdgeCapacity(), then retries gp_AddEdge(). Returns OK on success, NOTOK on failure. ********************************************************************/ int gp_DynamicAddEdge(graphP theGraph, int u, int ulink, int v, int vlink) { int Result = OK; Result = gp_AddEdge(theGraph, u, ulink, v, vlink); if (Result == AT_EDGE_CAPACITY_LIMIT) { // The candidate edge capacity is double the current capacity int candidateEdgeCapacity = gp_GetEdgeCapacity(theGraph) << 1; int N = gp_GetN(theGraph); int newEdgeCapacity = candidateEdgeCapacity; // If the candidate edge capacity exceeds the number of edges // needed in an undirected clique on N vertices, then attempt // to use that as the new edge capacity. if (candidateEdgeCapacity > ((N * (N - 1)) >> 1)) newEdgeCapacity = ((N * (N - 1)) >> 1); // However, if the edge capacity is already greater than or // equal to that maximum capacity needed for an undirected // clique on N vertices, then we allow the capacity to double // beyond the simple undirected graph limit. if (newEdgeCapacity <= gp_GetEdgeCapacity(theGraph)) newEdgeCapacity = candidateEdgeCapacity; Result = gp_EnsureEdgeCapacity(theGraph, newEdgeCapacity); if (Result != OK) return NOTOK; Result = gp_AddEdge(theGraph, u, ulink, v, vlink); } return Result != OK ? NOTOK : Result; } /******************************************************************** gp_InsertEdge() This function adds the edge (u, v) such that the edge record added to the adjacency list of u is adjacent to e_u and the edge record added to the adjacency list of v is adjacent to e_v. The direction of adjacency is given by e_ulink for e_u and e_vlink for e_v. Specifically, the new edge will be comprised of two edge records, n_u and n_v. In u's (v's) adjacency list, n_u (n_v) will be added so that it is indicated by e_u's (e_v's) e_ulink (e_vlink). If e_u (or e_v) is not an edge, then e_ulink (e_vlink) indicates whether to prepend or append to the adjacency list for u (v). NOTE: See notes on gp_AddEdge(). Returns OK on success, NOTOK on failure, or AT_EDGE_CAPACITY_LIMIT if adding the edge would exceed the graph's edge capacity (the caller can invoke gp_EnsureEdgeCapacity() beforehand to avoid an AT_EDGE_CAPACITY_LIMIT result). ********************************************************************/ int gp_InsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink) { int upos, vpos; if (theGraph == NULL) return NOTOK; if (u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph) || (e_u < gp_LowerBoundEdges(theGraph) && gp_IsEdge(theGraph, e_u)) || e_u >= gp_UpperBoundEdges(theGraph) || (gp_IsEdge(theGraph, e_u) && gp_EdgeNotInUse(theGraph, e_u)) || (e_v < gp_LowerBoundEdges(theGraph) && gp_IsEdge(theGraph, e_v)) || e_v >= gp_UpperBoundEdges(theGraph) || (gp_IsEdge(theGraph, e_v) && gp_EdgeNotInUse(theGraph, e_v)) || e_ulink < 0 || e_ulink > 1 || e_vlink < 0 || e_vlink > 1) return NOTOK; if (gp_GetM(theGraph) >= theGraph->edgeCapacity) return AT_EDGE_CAPACITY_LIMIT; if (sp_NonEmpty(theGraph->edgeHoles)) { sp_Pop(theGraph->edgeHoles, vpos); theGraph->numEdgeHoles = sp_GetCurrentSize(theGraph->edgeHoles); } else vpos = gp_UpperBoundEdges(theGraph); // NOTE: We do not _InitEdgeRec() nor gp_InitEdgeFlags() here because // the vpos edge location is expected to be in initialized state, // either from graph initialization/reinitialization, or from // edge record reinitialization during gp_DeleteEdge, if vpos was // an edge hole. This expectation includes edge record extensions // in graph extensions. upos = gp_GetTwin(theGraph, vpos); gp_SetNeighbor(theGraph, upos, v); _AttachEdgeRecord(theGraph, u, e_u, e_ulink, upos); gp_SetNeighbor(theGraph, vpos, u); _AttachEdgeRecord(theGraph, v, e_v, e_vlink, vpos); theGraph->M++; return OK; } /**************************************************************************** gp_DeleteEdge() This function deletes the given edge record e and its twin, reducing the number of edges M in the graph. NOTE: This method reinitializes the edge records for e and its twin in the base graph data structure. Extensions having parallel edge record extension data elements must implement and use their own edge deletion methods, which must then call gp_DeleteEdge(). Calling gp_DeleteEdge() does not currently clear data in extension data structures. Returns OK on success, NOTOK on failure ****************************************************************************/ int gp_DeleteEdge(graphP theGraph, int e) { if (theGraph == NULL || e < gp_LowerBoundEdges(theGraph) || e >= gp_UpperBoundEdges(theGraph) || gp_EdgeNotInUse(theGraph, e)) return NOTOK; // Delete the edge records e and eTwin from their adjacency lists. _DetachEdgeRecord(theGraph, e); _DetachEdgeRecord(theGraph, gp_GetTwin(theGraph, e)); // Clear the two edge records // (the bit twiddle (e & ~1) chooses the lesser of e and its twin) #ifdef USE_1BASEDARRAYS memset(theGraph->E + (e & ~1), NIL_CHAR, sizeof(edgeRec) << 1); #else _InitEdgeRec(theGraph, e); _InitEdgeRec(theGraph, gp_GetTwin(theGraph, e)); #endif // Now we reduce the number of edges in the data structure theGraph->M--; // If records e and eTwin were not the last in the edge record array, // then record a new hole in the edge array. */ if (e < gp_UpperBoundEdges(theGraph)) { if (theGraph->edgeHoles->size + 1 >= theGraph->edgeHoles->capacity) return NOTOK; sp_Push(theGraph->edgeHoles, e); theGraph->numEdgeHoles = sp_GetCurrentSize(theGraph->edgeHoles); } // Return the previously calculated successor of e. return OK; } /******************************************************************** _RestoreEdgeRecord() This routine reinserts an edge record e into the adjacency list from which it was previously removed by _DetachEdgeRecord(). The assumed processing model is that edge records will be restored in reverse of the order in which they were hidden, i.e. it is assumed that the hidden edges will be pushed on a stack from which they will be popped during restoration. ********************************************************************/ void _RestoreEdgeRecord(graphP theGraph, int e) { int nextEdge = gp_GetNextEdge(theGraph, e), prevEdge = gp_GetPrevEdge(theGraph, e); if (gp_IsEdge(theGraph, nextEdge)) gp_SetPrevEdge(theGraph, nextEdge, e); else gp_SetLastEdge(theGraph, gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, e)), e); if (gp_IsEdge(theGraph, prevEdge)) gp_SetNextEdge(theGraph, prevEdge, e); else gp_SetFirstEdge(theGraph, gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, e)), e); } /******************************************************************** gp_HideEdge() This routine removes the two edge records of an edge from the adjacency lists of its endpoint vertices, but it does not delete them from the storage data structure. Many algorithms must temporarily remove an edge, perform some calculation, and eventually put the edge back. This routine supports that operation. For each edge record of e, the neighboring adjacency list nodes are cross-linked, but the links in the edge record are retained because they indicate the neighbor edge records to which the edge record can be reattached by gp_RestoreEdge(). ********************************************************************/ void gp_HideEdge(graphP theGraph, int e) { if (theGraph == NULL || e < gp_LowerBoundEdges(theGraph) || e >= gp_UpperBoundEdges(theGraph) || gp_EdgeNotInUse(theGraph, e)) { #ifdef DEBUG NOTOK; #endif return; } theGraph->functions->fpHideEdge(theGraph, e); } void _HideEdge(graphP theGraph, int e) { _DetachEdgeRecord(theGraph, e); _DetachEdgeRecord(theGraph, gp_GetTwin(theGraph, e)); } /******************************************************************** gp_RestoreEdge() This routine reinserts two edge records of an edge into the adjacency lists of the edge's endpoints, the edge records having been previously removed by gp_HideEdge(). The assumed processing model is that edges will be restored in reverse of the order in which they were hidden, i.e. it is assumed that the hidden edges will be pushed on a stack and the edges will be popped from the stack for restoration. NOTE: Since both edge records of an edge are restored, only one edge record needs to be pushed on the stack for restoration. This routine restores the two edge records in the opposite order from the order in which they were hidden by gp_HideEdge(). ********************************************************************/ void gp_RestoreEdge(graphP theGraph, int e) { if (theGraph == NULL || e < gp_LowerBoundEdges(theGraph) || e >= gp_UpperBoundEdges(theGraph) || gp_EdgeNotInUse(theGraph, e)) { #ifdef DEBUG NOTOK; #endif return; } theGraph->functions->fpRestoreEdge(theGraph, e); } void _RestoreEdge(graphP theGraph, int e) { _RestoreEdgeRecord(theGraph, gp_GetTwin(theGraph, e)); _RestoreEdgeRecord(theGraph, e); } /******************************************************************** _HideInternalEdges() Pushes onto the graph's stack and hides all edge records of the given vertex's adjacency list except the first and last. This method is typically called on a vertex that is on the external face of a bicomp, where the first and last edge records are the ones that attach the vertex to the external face cycle, and any other edge records in the adjacency list are inside of that cycle. This method uses the stack. The caller is expected to clear the stack or save the stack size before invocation, since the stack size is needed to _RestoreInternalEdges(). ********************************************************************/ int _HideInternalEdges(graphP theGraph, int vertex) { int e = gp_GetFirstEdge(theGraph, vertex); // If the vertex adjacency list is empty or if it contains // only one edge, then there are no *internal* edges to hide if (e == gp_GetLastEdge(theGraph, vertex)) return OK; // Start with the first internal edge e = gp_GetNextEdge(theGraph, e); // Cycle through all the edges, pushing each except stop // before pushing the last edge, which is not internal while (e != gp_GetLastEdge(theGraph, vertex)) { sp_Push(theGraph->theStack, e); gp_HideEdge(theGraph, e); e = gp_GetNextEdge(theGraph, e); } return OK; } /******************************************************************** _RestoreInternalEdges() Reverses the effects of _HideInternalEdges() ********************************************************************/ int _RestoreInternalEdges(graphP theGraph, int stackBottom) { return _RestoreHiddenEdges(theGraph, stackBottom); } /******************************************************************** _RestoreHiddenEdges() Each entry on the stack, down to stackBottom, is assumed to be an edge record pushed in concert with invoking gp_HideEdge(). Each edge is restored using gp_RestoreEdge() in exact reverse of the hiding order. The stack is reduced in content size to stackBottom. Returns OK on success, NOTOK on internal failure. ********************************************************************/ int _RestoreHiddenEdges(graphP theGraph, int stackBottom) { int e; while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, e); if (gp_IsNotEdge(theGraph, e)) return NOTOK; gp_RestoreEdge(theGraph, e); } return OK; } /******************************************************************** gp_HideVertex() Pushes onto the graph's stack and hides all edge records of the vertex's adjacency list. Additional integers are then pushed so that the result is reversible by gp_RestoreVertex(). See that method for d etails on the expected stack segment. Returns OK for success, NOTOK for internal failure. ********************************************************************/ int gp_HideVertex(graphP theGraph, int vertex) { if (theGraph == NULL || vertex < gp_LowerBoundVertexStorage(theGraph) || vertex >= gp_UpperBoundVertexStorage(theGraph)) { return NOTOK; } return theGraph->functions->fpHideVertex(theGraph, vertex); } int _HideVertex(graphP theGraph, int vertex) { int hiddenEdgeStackBottom = sp_GetCurrentSize(theGraph->theStack); int e = gp_GetFirstEdge(theGraph, vertex); // Cycle through all the edges, pushing and hiding each while (gp_IsEdge(theGraph, e)) { sp_Push(theGraph->theStack, e); gp_HideEdge(theGraph, e); e = gp_GetNextEdge(theGraph, e); } // Push the additional integers needed by gp_RestoreVertex() sp_Push(theGraph->theStack, hiddenEdgeStackBottom); sp_Push(theGraph->theStack, NIL); sp_Push(theGraph->theStack, NIL); sp_Push(theGraph->theStack, NIL); sp_Push(theGraph->theStack, NIL); sp_Push(theGraph->theStack, NIL); sp_Push(theGraph->theStack, vertex); return OK; } /******************************************************************** gp_ContractEdge() Contracts the edge e=(u,v). This hides the edge (both e and its twin edge record), and it also identifies vertex v with u. See gp_IdentifyVertices() for further details. Returns OK for success, NOTOK for internal failure. ********************************************************************/ int gp_ContractEdge(graphP theGraph, int e) { if (theGraph == NULL || e < gp_LowerBoundEdges(theGraph) || e >= gp_UpperBoundEdges(theGraph) || gp_EdgeNotInUse(theGraph, e)) { return NOTOK; } return theGraph->functions->fpContractEdge(theGraph, e); } int _ContractEdge(graphP theGraph, int e) { int eBefore, u, v; if (gp_IsNotEdge(theGraph, e)) return NOTOK; u = gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, e)); v = gp_GetNeighbor(theGraph, e); eBefore = gp_GetNextEdge(theGraph, e); sp_Push(theGraph->theStack, e); gp_HideEdge(theGraph, e); return gp_IdentifyVertices(theGraph, u, v, eBefore); } /******************************************************************** gp_IdentifyVertices() Identifies vertex v with vertex u by transferring all adjacencies of v to u. Any duplicate edges are removed as described below. The non-duplicate edges of v are added to the adjacency list of u without disturbing their relative order, and they are added before the edge record eBefore in u's list. If eBefore is NIL, then the edges are simply appended to u's list. If u and v are adjacent, then gp_HideEdge() is invoked to remove the edge e=(u,v). Then, the edges of v that indicate neighbors of u are also hidden. This is done by setting the visited flags of u's neighbors, then traversing the adjacency list of v. For each visited neighbor of v, the edge is hidden because it would duplicate an adjacency already expressed in u's list. Finally, the remaining edges of v are moved to u's list, and each twin edge record is adjusted to indicate u as a neighbor rather than v. This routine assumes that the visited flags are clear beforehand, and visited flag settings made herein are cleared before returning. The following are pushed, in order, onto the graph's built-in stack: 1) an integer for each hidden edge 2) the stack size before any hidden edges were pushed 3) six integers that indicate u, v and the edges moved from v to u An algorithm that identifies a series of vertices, either through directly calling this method or via gp_ContractEdge(), can unwind the identifications using gp_RestoreVertices(), which invokes gp_RestoreVertex() repeatedly. Returns OK on success, NOTOK on internal failure ********************************************************************/ int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore) { if (theGraph == NULL || u < gp_LowerBoundVertexStorage(theGraph) || u >= gp_UpperBoundVertexStorage(theGraph) || v < gp_LowerBoundVertexStorage(theGraph) || v >= gp_UpperBoundVertexStorage(theGraph) || (eBefore != NIL && eBefore < gp_LowerBoundEdges(theGraph)) || eBefore >= gp_UpperBoundEdges(theGraph) || (eBefore != NIL && gp_EdgeNotInUse(theGraph, eBefore))) { return NOTOK; } return theGraph->functions->fpIdentifyVertices(theGraph, u, v, eBefore); } int _IdentifyVertices(graphP theGraph, int u, int v, int eBefore) { int e = _gp_FindEdge(theGraph, u, v); int hiddenEdgeStackBottom, eBeforePred; // If the vertices are adjacent, then the identification is // essentially an edge contraction with a bit of fixup. if (gp_IsEdge(theGraph, e)) { int result = gp_ContractEdge(theGraph, e); // The edge contraction operation pushes one hidden edge then // recursively calls this method. This method then pushes K // hidden edges then an integer indicating where the top of // stack was before the edges were hidden. That integer // indicator must be decremented, thereby incrementing the // number of hidden edges to K+1. // After pushing the K hidden edges and the stackBottom of // the hidden edges, the recursive call to this method pushes // six more integers to indicate edges that were moved from // v to u, so the "hidden edges stackBottom" is in the next // position down. int hiddenEdgesStackBottomIndex = sp_GetCurrentSize(theGraph->theStack) - 7; int hiddenEdgesStackBottomValue = sp_Get(theGraph->theStack, hiddenEdgesStackBottomIndex); sp_Set(theGraph->theStack, hiddenEdgesStackBottomIndex, hiddenEdgesStackBottomValue - 1); return result; } // Now, u and v are not adjacent. Before we do any edge hiding or // moving, we record the current stack size, as this is the // stackBottom for the edges that will be hidden next. hiddenEdgeStackBottom = sp_GetCurrentSize(theGraph->theStack); // Mark as visited all neighbors of u e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetVisited(theGraph, gp_GetNeighbor(theGraph, e))) return NOTOK; gp_SetVisited(theGraph, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } // For each edge record of v, if the neighbor is visited, then // push and hide the edge. e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetVisited(theGraph, gp_GetNeighbor(theGraph, e))) { sp_Push(theGraph->theStack, e); gp_HideEdge(theGraph, e); } e = gp_GetNextEdge(theGraph, e); } // Mark as unvisited all neighbors of u e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { gp_ClearVisited(theGraph, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } // Push the hiddenEdgeStackBottom as a record of how many hidden // edges were pushed (also, see above for Contract Edge adjustment) sp_Push(theGraph->theStack, hiddenEdgeStackBottom); // Moving v's adjacency list to u is aided by knowing the predecessor // of u's eBefore (the edge record in u's list before which the // edge records of v will be added). eBeforePred = gp_IsEdge(theGraph, eBefore) ? gp_GetPrevEdge(theGraph, eBefore) : gp_GetLastEdge(theGraph, u); // Turns out we only need to record six integers related to the edges // being moved in order to easily restore them later. sp_Push(theGraph->theStack, eBefore); sp_Push(theGraph->theStack, gp_GetLastEdge(theGraph, v)); sp_Push(theGraph->theStack, gp_GetFirstEdge(theGraph, v)); sp_Push(theGraph->theStack, eBeforePred); sp_Push(theGraph->theStack, u); sp_Push(theGraph->theStack, v); // For the remaining edge records of v, reassign the 'v' member // of each twin edge record to indicate u rather than v. e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { gp_SetNeighbor(theGraph, gp_GetTwin(theGraph, e), u); e = gp_GetNextEdge(theGraph, e); } // If v has any edges left after hiding edges, indicating common neighbors with u, ... if (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, v))) { // Then perform the list union of v into u between eBeforePred and eBefore if (gp_IsEdge(theGraph, eBeforePred)) { if (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, v))) { gp_SetNextEdge(theGraph, eBeforePred, gp_GetFirstEdge(theGraph, v)); gp_SetPrevEdge(theGraph, gp_GetFirstEdge(theGraph, v), eBeforePred); } } else { gp_SetFirstEdge(theGraph, u, gp_GetFirstEdge(theGraph, v)); } if (gp_IsEdge(theGraph, eBefore)) { if (gp_IsEdge(theGraph, gp_GetLastEdge(theGraph, v))) { gp_SetNextEdge(theGraph, gp_GetLastEdge(theGraph, v), eBefore); gp_SetPrevEdge(theGraph, eBefore, gp_GetLastEdge(theGraph, v)); } } else { gp_SetLastEdge(theGraph, u, gp_GetLastEdge(theGraph, v)); } gp_SetFirstEdge(theGraph, v, NIL); gp_SetLastEdge(theGraph, v, NIL); } return OK; } /******************************************************************** gp_RestoreVertex() This method assumes the built-in graph stack contents are the result of vertex hide, vertex identify and edge contract operations. This content consists of segments of integers, each segment corresponding to the removal of a vertex during an edge contraction or vertex identification in which a vertex v was merged into a vertex u. The segment contains two blocks of integers. The first block contains information about u, v, the edge records in v's adjacency list that were added to u, and where in u's adjacency list they were added. The second block of integers contains a list of edges incident to v that were hidden from the graph because they were incident to neighbors of v that were also neighbors of u (so they would have produced duplicate edges had they been left in v's adjacency list when it was merged with u's adjacency list). This method pops the first block of the segment off the stack and uses the information to help remove v's adjacency list from u and restore it into v. Then, the second block is removed from the stack, and each indicated edge is restored from the hidden state. It is anticipated that this method will be overloaded by extension algorithms to perform some processing as each vertex is restored. Before restoration, the topmost segment has the following structure: ... FHE ... LHE HESB e_u_succ e_v_last e_v_first e_u_pred u v ^------------| FHE = First hidden edge LHE = Last hidden edge HESB = Hidden edge stack bottom e_u_succ, e_u_pred = The edges of u between which the edges of v were inserted. NIL can appear if the edges of v were added to the beginning or end of u's list e_v_first, e_v_last = The first and last edges of v's list, once the hidden edges were removed Returns OK for success, NOTOK for internal failure. ********************************************************************/ int gp_RestoreVertex(graphP theGraph) { if (theGraph == NULL) return NOTOK; return theGraph->functions->fpRestoreVertex(theGraph); } int _RestoreVertex(graphP theGraph) { int u, v, e_u_succ, e_u_pred, e_v_first, e_v_last, HESB, e; if (sp_GetCurrentSize(theGraph->theStack) < 7) return NOTOK; sp_Pop(theGraph->theStack, v); sp_Pop(theGraph->theStack, u); sp_Pop(theGraph->theStack, e_u_pred); sp_Pop(theGraph->theStack, e_v_first); sp_Pop(theGraph->theStack, e_v_last); sp_Pop(theGraph->theStack, e_u_succ); // If u is not NIL, then vertex v was identified with u. Otherwise, v was // simply hidden, so we skip to restoring the hidden edges. if (gp_IsVertex(theGraph, u)) { // Remove v's adjacency list from u, including accounting for degree 0 case if (gp_IsEdge(theGraph, e_u_pred)) { gp_SetNextEdge(theGraph, e_u_pred, e_u_succ); // If the successor edge exists, link it to the predecessor, // otherwise the predecessor is the new last edge if (gp_IsEdge(theGraph, e_u_succ)) gp_SetPrevEdge(theGraph, e_u_succ, e_u_pred); else gp_SetLastEdge(theGraph, u, e_u_pred); } else if (gp_IsEdge(theGraph, e_u_succ)) { // The successor edge exists, but not the predecessor, // so the successor is the new first edge gp_SetPrevEdge(theGraph, e_u_succ, NIL); gp_SetFirstEdge(theGraph, u, e_u_succ); } else { // Just in case u was degree zero gp_SetFirstEdge(theGraph, u, NIL); gp_SetLastEdge(theGraph, u, NIL); } // Place v's adjacency list into v, including accounting for degree 0 case gp_SetFirstEdge(theGraph, v, e_v_first); gp_SetLastEdge(theGraph, v, e_v_last); if (gp_IsEdge(theGraph, e_v_first)) gp_SetPrevEdge(theGraph, e_v_first, NIL); if (gp_IsEdge(theGraph, e_v_last)) gp_SetPrevEdge(theGraph, e_v_last, NIL); // For each edge record restored to v's adjacency list, reassign the 'v' member // of each twin edge record to indicate v rather than u. e = e_v_first; while (gp_IsEdge(theGraph, e)) { gp_SetNeighbor(theGraph, gp_GetTwin(theGraph, e), v); e = (e == e_v_last ? NIL : gp_GetNextEdge(theGraph, e)); } } // Restore the hidden edges of v, if any sp_Pop(theGraph->theStack, HESB); return _RestoreHiddenEdges(theGraph, HESB); } /******************************************************************** gp_RestoreVertices() This method assumes the built-in graph stack has content consistent with numerous vertex identification or edge contraction operations. This method unwinds the stack, moving edges back to their original vertex owners and restoring hidden edges. This method is a simple iterator that invokes gp_RestoreVertex() until the stack is empty, so extension algorithms are more likely to overload gp_RestoreVertex(). Returns OK for success, NOTOK for internal failure. ********************************************************************/ int gp_RestoreVertices(graphP theGraph) { if (theGraph == NULL) return NOTOK; while (sp_NonEmpty(theGraph->theStack)) { if (gp_RestoreVertex(theGraph) != OK) return NOTOK; } return OK; } /**************************************************************************** _ComputeEdgeRecordType() This is just a little helper function that automates a sequence of decisions that has to be made a number of times. An edge record is being added to the adjacency list of a; it indicates that b is a neighbor. The edgeType can be either 'tree' (EDGE_TYPE_PARENT or EDGE_TYPE_CHILD) or 'cycle' (EDGE_TYPE_BACK or EDGE_TYPE_FORWARD). If a or b is a root copy, we translate to the non-virtual counterpart, then wedetermine which has the lesser DFI. If a has the lower DFI then the edge record is a tree edge to a child (EDGE_TYPE_CHILD) if edgeType indicates a tree edge. If edgeType indicates a cycle edge, then it is a forward cycle edge (EDGE_TYPE_FORWARD) to a descendant. Symmetric conditions define the types for a > b. ****************************************************************************/ int _ComputeEdgeRecordType(graphP theGraph, int a, int b, int edgeType) { a = gp_IsVirtualVertex(theGraph, a) ? gp_GetVertexFromBicompRoot(theGraph, a) : a; b = gp_IsVirtualVertex(theGraph, b) ? gp_GetVertexFromBicompRoot(theGraph, b) : b; if (a < b) return edgeType == EDGE_TYPE_PARENT || edgeType == EDGE_TYPE_CHILD ? EDGE_TYPE_CHILD : EDGE_TYPE_FORWARD; return edgeType == EDGE_TYPE_PARENT || edgeType == EDGE_TYPE_CHILD ? EDGE_TYPE_PARENT : EDGE_TYPE_BACK; } /**************************************************************************** _RestoreEdgeType() When we are restoring an edge, we must restore its type (tree edge or cycle edge). We can deduce what the type was based on other information in the graph. Each edge record of the edge gets the appropriate type setting (parent/child or back/forward). This method runs in constant time plus the degree of vertex u, or constant time if u is known to have a degree bound by a constant. ****************************************************************************/ int _RestoreEdgeType(graphP theGraph, int u, int v) { int e, eTwin, u_orig, v_orig; // If u or v is a virtual vertex (a root copy), then get the non-virtual counterpart. u_orig = gp_IsVirtualVertex(theGraph, u) ? (gp_GetVertexFromBicompRoot(theGraph, u)) : u; v_orig = gp_IsVirtualVertex(theGraph, v) ? (gp_GetVertexFromBicompRoot(theGraph, v)) : v; // Get the edge for which we will set the type e = _gp_FindEdge(theGraph, u, v); eTwin = gp_GetTwin(theGraph, e); // If u_orig is the parent of v_orig, or vice versa, then the edge is a tree edge if (gp_GetVertexParent(theGraph, v_orig) == u_orig || gp_GetVertexParent(theGraph, u_orig) == v_orig) { if (u_orig > v_orig) { gp_ResetEdgeType(theGraph, e, EDGE_TYPE_PARENT); gp_ResetEdgeType(theGraph, eTwin, EDGE_TYPE_CHILD); } else { gp_ResetEdgeType(theGraph, eTwin, EDGE_TYPE_PARENT); gp_ResetEdgeType(theGraph, e, EDGE_TYPE_CHILD); } } // Otherwise it is a back edge else { if (u_orig > v_orig) { gp_ResetEdgeType(theGraph, e, EDGE_TYPE_BACK); gp_ResetEdgeType(theGraph, eTwin, EDGE_TYPE_FORWARD); } else { gp_ResetEdgeType(theGraph, eTwin, EDGE_TYPE_BACK); gp_ResetEdgeType(theGraph, e, EDGE_TYPE_FORWARD); } } return OK; } /******************************************************************** _DeleteUnmarkedEdgesInBicomp() This function deletes from a given biconnected component all edges whose visited member is zero. The stack is used but preserved. In debug mode, NOTOK can result if there is a stack overflow. This method pushes at most one integer per vertex in the bicomp. Returns OK on success, NOTOK on implementation failure ********************************************************************/ int _DeleteUnmarkedEdgesInBicomp(graphP theGraph, int BicompRoot) { int V, e, eNext; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); eNext = gp_GetNextEdge(theGraph, e); if (!gp_GetEdgeVisited(theGraph, e)) gp_DeleteEdge(theGraph, e); e = eNext; } } return OK; } /******************************************************************** _ClearInvertedFlagsInBicomp() This function clears the inverted flag markers on any edges in a given biconnected component. The stack is used but preserved. In debug mode, NOTOK can result if there is a stack overflow. This method pushes at most one integer per vertex in the bicomp. Returns OK on success, NOTOK on implementation failure ********************************************************************/ int _ClearInvertedFlagsInBicomp(graphP theGraph, int BicompRoot) { int V, e; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); gp_ClearEdgeFlagInverted(theGraph, e); } e = gp_GetNextEdge(theGraph, e); } } return OK; } /******************************************************************** _GetBicompSize() Determine the number of vertices in the bicomp. The stack is used but preserved. In debug mode, NOTOK can result if there is a stack overflow. This method pushes at most one integer per vertex in the bicomp. Returns a positive number on success, NOTOK on implementation failure ********************************************************************/ int _GetBicompSize(graphP theGraph, int BicompRoot) { int V, e; int theSize = 0; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); theSize++; e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } } return theSize; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graph.h000066400000000000000000000563641521450711600244500ustar00rootroot00000000000000#ifndef GRAPH_H #define GRAPH_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef __cplusplus extern "C" { #endif // Basic declarations, such as for OK, NOTOK, and NIL #include "lowLevelUtils/appconst.h" /////////////////////////////////////////////////////////////////////////////// // The top-level operations at the graph, vertex, and edge levels /////////////////////////////////////////////////////////////////////////////// // Forward declaration of graph structure and graph pointer type definitions // (see the end of this header file). typedef struct graphStruct graphStruct; typedef graphStruct *graphP; // Methods related to graph allocation and destruction graphP gp_New(void); int gp_EnsureVertexCapacity(graphP theGraph, int N); int gp_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity); void gp_ResetGraphStorage(graphP theGraph); void gp_Free(graphP *pGraph); // N=# of vertices; NV=# of virtual vertices; M=# of edges #define gp_GetN(theGraph) ((theGraph)->N) #define gp_GetNV(theGraph) ((theGraph)->NV) #define gp_GetM(theGraph) ((theGraph)->M) #define gp_GetEdgeCapacity(theGraph) ((theGraph)->edgeCapacity) // Basic graph utility methods int gp_CopyGraph(graphP dstGraph, graphP srcGraph); graphP gp_DupGraph(graphP theGraph); int gp_CopyAdjacencyLists(graphP dstGraph, graphP srcGraph); int gp_CreateRandomGraph(graphP theGraph); int gp_CreateRandomGraphEx(graphP theGraph, int numEdges); // Basic graph I/O methods: see graphIO.h // Intermediate graph I/O methods: see g6-read-iterator.h and g6-write-iterator.h // Basic vertex interrogators int gp_IsNeighbor(graphP theGraph, int u, int v); int gp_FindEdge(graphP theGraph, int u, int v); int gp_GetVertexDegree(graphP theGraph, int v); // Basic interrogators for directed graphs // The direction can be EDGEFLAG_DIRECTION_INONLY or EDGEFLAG_DIRECTION_OUTONLY int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned direction); int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned direction); int gp_GetVertexInDegree(graphP theGraph, int v); int gp_GetVertexOutDegree(graphP theGraph, int v); // Basic graph structure manipulators int gp_AddEdge(graphP theGraph, int u, int ulink, int v, int vlink); int gp_DynamicAddEdge(graphP theGraph, int u, int ulink, int v, int vlink); int gp_InsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink); int gp_DeleteEdge(graphP theGraph, int e); // Intermediate graph structure manipulators void gp_HideEdge(graphP theGraph, int e); void gp_RestoreEdge(graphP theGraph, int e); int gp_HideVertex(graphP theGraph, int vertex); int gp_RestoreVertex(graphP theGraph); // Advanced graph structure manipulators int gp_ContractEdge(graphP theGraph, int e); int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore); int gp_RestoreVertices(graphP theGraph); // For methods and declarations related to depth-first search (DFS), see graphDFSUtils.h /* Graph Flags (bit flags set by various public graphLib APIs): Bits 0-3 reserved for base graph class Bits 4-7 reserved for graph I/O Bits 8-15 reserved for DFS Utils Bits 16-23 reserved for Planarity-Related bits 24-31 reserved for future expansion */ #define gp_GetGraphFlags(theGraph) ((theGraph)->graphFlags) // For graph embedding methods and declarations, see graphPlanarity.h /******************************************************************** Vertex Record Definition (For non-virtual and virtual vertices) This record definition provides the data members needed for the core structural information for both vertices and virtual vertices. Non-virtual vertices are also equipped with additional information provided by private vertexInfo records. The vertices of a graph are stored in the first N locations of array V. Virtual vertices are secondary vertices used to help represent the main vertices in substructural components of a graph (such as in biconnected components). link[2]: the first and last edge records in the adjacency list of the vertex. index: In vertices, stores either the depth first index of a vertex or the original array index of the vertex if the vertices of the graph are sorted by DFI. In virtual vertices, the index may be used to indicate the vertex that the virtual vertex represents, unless an algorithm has some other way of making the association (for example, the planarity algorithms rely on biconnected components and therefore place virtual vertices of a vertex at positions corresponding to the DFS children of the vertex). flags: Bits 0-15 reserved for library; bits 16 and higher for apps Bit 0: visited, for vertices and virtual vertices Bit 1: marked, 2nd visited flag, for while visiting all Used in K4 homeomorph search algorithm Bit 2: Obstruction type VERTEX_TYPE_SET (versus not set, i.e. VERTEX_TYPE_UNKNOWN) Bit 3: Obstruction type qualifier RYW (set) versus RXW (clear) Bit 4: Obstruction type qualifier high (set) versus low (clear) Bits 2-4 used in planarity-related algorithms ********************************************************************/ struct vertexRec { int link[2]; int index; unsigned flags; }; typedef struct vertexRec vertexRec; typedef vertexRec *vertexRecP; //////////////////////////////////////////// // Accessors for vertex adjacency list links // such as for adjacency list iteration // (see also gp_GetNextEdge and gp_IsEdge) //////////////////////////////////////////// #define gp_GetFirstEdge(theGraph, v) (theGraph->V[v].link[0]) #define gp_GetLastEdge(theGraph, v) (theGraph->V[v].link[1]) #define gp_GetEdgeByLink(theGraph, v, theLink) (theGraph->V[v].link[theLink]) // Setters for adjacency list links #define gp_SetFirstEdge(theGraph, v, newFirstEdge) (theGraph->V[v].link[0] = newFirstEdge) #define gp_SetLastEdge(theGraph, v, newLastEdge) (theGraph->V[v].link[1] = newLastEdge) #define gp_SetEdgeByLink(theGraph, v, theLink, newEdge) (theGraph->V[v].link[theLink] = newEdge) /////////////////////////////////// // Vertex iteration-related methods /////////////////////////////////// // The original N non-virtual vertices of a graph start at the lowest allowed storage location. // The upper bound is one-past-the-end of the storage for the N non-virtual vertices. #ifdef USE_1BASEDARRAYS #define gp_LowerBoundVertices(theGraph) (1) #else #define gp_LowerBoundVertices(theGraph) (0) #endif #define gp_UpperBoundVertices(theGraph) (gp_LowerBoundVertexStorage(theGraph) + gp_GetN(theGraph)) // The virtual vertices start at the one-past-the-end upper bound of the non-virtual vertices. // The upper bound is one-past-the-end of the virtual vertex storage locations. #define gp_LowerBoundVirtualVertices(theGraph) gp_UpperBoundVertices(theGraph) #define gp_UpperBoundVirtualVertices(theGraph) (gp_LowerBoundVertexStorage(theGraph) + gp_GetN(theGraph) + gp_GetNV(theGraph)) // These lower and upper bounds for all vertex storage are used for tasks like initializing // that must iterate through all of non-virtual and virtual vertex records. #define gp_LowerBoundVertexStorage(theGraph) gp_LowerBoundVertices(theGraph) #define gp_UpperBoundVertexStorage(theGraph) gp_UpperBoundVirtualVertices(theGraph) #ifdef USE_1BASEDARRAYS // The use of *Vertex* alone consistently refers to the first N vertices. // The use of *VirtualVertex* refers to vertex array locations after the first N. #ifndef DEBUG #define gp_IsVertex(theGraph, v) (v) #define gp_IsVirtualVertex(theGraph, v) ((v) > gp_GetN(theGraph)) #else // See below for definitions common to 1-based and 0-based #endif #else // Using 0-based Arrays #ifndef DEBUG #define gp_IsVertex(theGraph, v) ((v) != NIL) #define gp_IsVirtualVertex(theGraph, v) ((v) >= gp_GetN(theGraph)) #else // See below for definitions common to 1-based and 0-based #endif #endif // End of 0-based Arrays // The same for 1-based and 0-based when debugging #ifdef DEBUG #define gp_IsVertex(theGraph, v) \ ((v) == NIL ? 0 : ((v) < gp_LowerBoundVertices(theGraph) ? (NOTOK, 0) : ((v) >= gp_UpperBoundVertices(theGraph) ? (NOTOK, 0) : 1))) // NOTE: gp_IsVirtualVertex() is sometimes called to distinguish between // an existing non-virtual and a virtual #define gp_IsVirtualVertex(theGraph, v) \ ((v) == NIL \ ? 0 \ : ((v) < gp_LowerBoundVirtualVertices(theGraph) \ ? ((v) < gp_LowerBoundVertices(theGraph) ? (NOTOK, 0) : 0) \ : ((v) >= gp_UpperBoundVirtualVertices(theGraph) ? (NOTOK, 0) : 1))) #endif #define gp_IsNotVertex(theGraph, v) (!(gp_IsVertex(theGraph, v))) #define gp_IsNotVirtualVertex(theGraph, v) (!(gp_IsVirtualVertex(theGraph, v))) #define gp_VirtualVertexInUse(theGraph, virtualVertex) (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, virtualVertex))) #define gp_VirtualVertexNotInUse(theGraph, virtualVertex) (gp_IsNotEdge(theGraph, gp_GetFirstEdge(theGraph, virtualVertex))) /////////////////////////////////////////// // End of Vertex iteration-related methods ////////////////////////////////////////// // Accessors for non-virtual and virtual vertex index value #define gp_GetIndex(theGraph, v) (theGraph->V[v].index) #define gp_SetIndex(theGraph, v, theIndex) (theGraph->V[v].index = theIndex) // Initializer for non-virtual and virtual vertex flags #define gp_InitFlags(theGraph, v) (theGraph->V[v].flags = 0) // Definition and accessors for the non-virtual and virtual vertex visited flag #define VERTEX_VISITED_MASK 1 #define gp_GetVisited(theGraph, v) (theGraph->V[v].flags & VERTEX_VISITED_MASK) #define gp_ClearVisited(theGraph, v) (theGraph->V[v].flags &= ~VERTEX_VISITED_MASK) #define gp_SetVisited(theGraph, v) (theGraph->V[v].flags |= VERTEX_VISITED_MASK) // Definition and accessors for the non-virtual and virtual vertex marked flag // Essentially, this is a second visitation flag that can help applications that // must visit all vertices to analyze and mark the ones important for some purpose. #define VERTEX_MARKED_MASK 2 #define gp_GetMarked(theGraph, v) (theGraph->V[v].flags & VERTEX_MARKED_MASK) #define gp_ClearMarked(theGraph, v) (theGraph->V[v].flags &= ~VERTEX_MARKED_MASK) #define gp_SetMarked(theGraph, v) (theGraph->V[v].flags |= VERTEX_MARKED_MASK) /******************************************************************** Edge Record Definition An edge is defined by a pair of edge records allocated in array E of a graph. A pair of edge records represents the edge in the adjacency lists of each vertex to which the edge is incident. link[2]: the next and previous edge records in the adjacency list that contains this edge record. v: The vertex neighbor of the vertex whose adjacency list contains this edge record (an index into array V). flags: Bits 0-15 reserved for library; bits 16 and higher for apps Bit 0: Visited Bit 1: Marked (2nd visited flag, for while visiting all) Bit 2: DFS type has been set, versus not set Bit 3: DFS tree edge, versus cycle edge (co-tree edge, etc.) Bit 4: DFS edge pointing to descendant, versus to ancestor Bit 5: Inverted (same as marking an edge with a "sign" of -1) Bit 6: Edge record is directed into the containing vertex only Bit 7: Edge record is directed from the containing vertex only ********************************************************************/ struct edgeRec { int link[2]; int neighbor; unsigned flags; }; typedef struct edgeRec edgeRec; typedef edgeRec *edgeRecP; // An edge is represented by two consecutive edge records in the edge array E. // If an even number, xor 1 will add one; if an odd number, xor 1 will subtract 1 #define gp_GetTwin(theGraph, e) ((e) ^ 1) //////////////////////////////////////////// // Access to adjacency list pointers // such as for adjacency list iteration //////////////////////////////////////////// #define gp_GetNextEdge(theGraph, e) (theGraph->E[e].link[0]) #define gp_GetPrevEdge(theGraph, e) (theGraph->E[e].link[1]) #define gp_GetAdjacentEdge(theGraph, e, theLink) (theGraph->E[e].link[theLink]) #define gp_SetNextEdge(theGraph, e, newNextEdge) (theGraph->E[e].link[0] = newNextEdge) #define gp_SetPrevEdge(theGraph, e, newPrevEdge) (theGraph->E[e].link[1] = newPrevEdge) #define gp_SetAdjacentEdge(theGraph, e, theLink, newEdge) (theGraph->E[e].link[theLink] = newEdge) //////////////////////////////////////////// // gp_IsEdge() helps detect the end of an // adjacency list iteration loop //////////////////////////////////////////// #ifdef USE_1BASEDARRAYS #define gp_IsEdge(theGraph, e) (e) #define gp_IsNotEdge(theGraph, e) (!(e)) #else #define gp_IsEdge(theGraph, e) ((e) != NIL) #define gp_IsNotEdge(theGraph, e) ((e) == NIL) #endif // Get/set 'neighbor' member indicated by edge record e #define gp_GetNeighbor(theGraph, e) (theGraph->E[e].neighbor) #define gp_SetNeighbor(theGraph, e, v) (theGraph->E[e].neighbor = v) // Initializer for edge flags #define gp_InitEdgeFlags(theGraph, e) (theGraph->E[e].flags = 0) // Definitions of and access to edge flags #define EDGE_VISITED_MASK 1 #define gp_GetEdgeVisited(theGraph, e) (theGraph->E[e].flags & EDGE_VISITED_MASK) #define gp_ClearEdgeVisited(theGraph, e) (theGraph->E[e].flags &= ~EDGE_VISITED_MASK) #define gp_SetEdgeVisited(theGraph, e) (theGraph->E[e].flags |= EDGE_VISITED_MASK) // Definition and accessors for the edge marked flag // Essentially, this is a second visitation flag that can help applications that // must visit all edges to analyze and mark the ones important for some purpose. #define EDGE_MARKED_MASK 2 #define gp_GetEdgeMarked(theGraph, e) (theGraph->E[e].flags & EDGE_MARKED_MASK) #define gp_ClearEdgeMarked(theGraph, e) (theGraph->E[e].flags &= ~EDGE_MARKED_MASK) #define gp_SetEdgeMarked(theGraph, e) (theGraph->E[e].flags |= EDGE_MARKED_MASK) // The edge type is defined by bits 2-4, 4+8+16=28 #define EDGE_TYPE_MASK 28 // Call gp_GetEdgeType(), then compare to one of these four possibilities // EDGE_TYPE_CHILD - edge record points to a neighboring DFS child // EDGE_TYPE_FORWARD - edge record points to a DFS descendant, not a DFS child // EDGE_TYPE_PARENT - edge record points to the DFS parent // EDGE_TYPE_BACK - edge record points to a DFS ancestor, not the DFS parent // NOTE: A parent/child tree edge has bit 3 (8) set, forward/back edges do not #define EDGE_TYPE_CHILD 28 #define EDGE_TYPE_FORWARD 20 #define EDGE_TYPE_PARENT 12 #define EDGE_TYPE_BACK 4 // EDGE_TYPE_NOTDEFINED - the edge record type has not been defined // EDGE_TYPE_TREE - edge record is part of a randomly generated tree // NOTE: EDGE_TYPE_TREE uses the same bit 3 as DFS parent and child edges above #define EDGE_TYPE_NOTDEFINED 0 #define EDGE_TYPE_TREE 8 #define gp_GetEdgeType(theGraph, e) (theGraph->E[e].flags & EDGE_TYPE_MASK) #define gp_ClearEdgeType(theGraph, e) (theGraph->E[e].flags &= ~EDGE_TYPE_MASK) #define gp_SetEdgeType(theGraph, e, type) (theGraph->E[e].flags |= type) #define gp_ResetEdgeType(theGraph, e, type) \ (theGraph->E[e].flags = (theGraph->E[e].flags & ~EDGE_TYPE_MASK) | type) #define EDGEFLAG_INVERTED_MASK 32 #define gp_GetEdgeFlagInverted(theGraph, e) (theGraph->E[e].flags & EDGEFLAG_INVERTED_MASK) #define gp_SetEdgeFlagInverted(theGraph, e) (theGraph->E[e].flags |= EDGEFLAG_INVERTED_MASK) #define gp_ClearEdgeFlagInverted(theGraph, e) (theGraph->E[e].flags &= (~EDGEFLAG_INVERTED_MASK)) #define gp_XorEdgeFlagInverted(theGraph, e) (theGraph->E[e].flags ^= EDGEFLAG_INVERTED_MASK) #define EDGEFLAG_DIRECTION_INONLY 64 #define EDGEFLAG_DIRECTION_OUTONLY 128 #define EDGEFLAG_DIRECTION_MASK 192 // Returns the direction, if any, of the edge record #define gp_GetDirection(theGraph, e) (theGraph->E[e].flags & EDGEFLAG_DIRECTION_MASK) // A direction of 0 clears directedness. Otherwise, edge record e is set // to direction and e's twin edge record is set to the opposing setting. #define gp_SetDirection(theGraph, e, direction) \ { \ if (direction == EDGEFLAG_DIRECTION_INONLY) \ { \ theGraph->E[e].flags |= EDGEFLAG_DIRECTION_INONLY; \ theGraph->E[gp_GetTwin(theGraph, e)].flags |= EDGEFLAG_DIRECTION_OUTONLY; \ } \ else if (direction == EDGEFLAG_DIRECTION_OUTONLY) \ { \ theGraph->E[e].flags |= EDGEFLAG_DIRECTION_OUTONLY; \ theGraph->E[gp_GetTwin(theGraph, e)].flags |= EDGEFLAG_DIRECTION_INONLY; \ } \ else \ { \ theGraph->E[e].flags &= ~EDGEFLAG_DIRECTION_MASK; \ theGraph->E[gp_GetTwin(theGraph, e)].flags &= ~EDGEFLAG_DIRECTION_MASK; \ } \ } // Iterate through all edges with gp_LowerBoundEdges, gp_UpperBoundEdges, and gp_EdgeInUse #ifdef USE_1BASEDARRAYS #define gp_LowerBoundEdges(theGraph) (2) #define gp_UpperBoundEdges(theGraph) (gp_LowerBoundEdges(theGraph) + ((gp_GetM(theGraph) + (theGraph)->numEdgeHoles) << 1)) #define gp_EdgeInUse(theGraph, e) (gp_GetNeighbor(theGraph, e)) #define gp_EdgeNotInUse(theGraph, e) (!gp_GetNeighbor(theGraph, e)) #else #define gp_LowerBoundEdges(theGraph) (0) #define gp_UpperBoundEdges(theGraph) (gp_LowerBoundEdges(theGraph) + ((gp_GetM(theGraph) + (theGraph)->numEdgeHoles) << 1)) #define gp_EdgeInUse(theGraph, e) (gp_GetNeighbor(theGraph, e) != NIL) #define gp_EdgeNotInUse(theGraph, e) (gp_GetNeighbor(theGraph, e) == NIL) #endif // These lower and upper bounds for edge storage are used for tasks like initializing that // must iterate through all of the edge storage, including space not yet containing edges. #define gp_LowerBoundEdgeStorage(theGraph) (gp_LowerBoundEdges(theGraph)) #define gp_UpperBoundEdgeStorage(theGraph) (gp_LowerBoundEdgeStorage(theGraph) + ((theGraph)->edgeCapacity << 1)) // The initial setting for the edge storage capacity expressed as a constant factor of N, // which is the number of vertices in the graph. By default, array E is allocated enough // space to contain 3N edges, which is 6N edge records, but this initial setting can be // overridden using gp_EnsureEdgeCapacity(). It is especially efficient to change to ensure // a higher edge capacity if done before calling gp_EnsureVertexCapacity() or gp_Read(). #define DEFAULT_EDGE_CAPACITY_FACTOR 3 // This value is returned by gp_AddEdge() and gp_InsertEdge() if adding or inserting // the edge would exceed the edge capacity limit. The limit can be increased by // calling gp_EnsureEdgeCapacity(), or by calling gp_DynamicAddEdge(). #define AT_EDGE_CAPACITY_LIMIT -1 // A bounds-checking version of gp_IsEdge() for DEBUG mode compilation #ifdef DEBUG #undef gp_IsEdge #define gp_IsEdge(theGraph, e) \ ((e) == NIL \ ? 0 \ : ((e) < gp_LowerBoundEdgeStorage(theGraph) || (e) >= gp_UpperBoundEdgeStorage(theGraph) \ ? (NOTOK, 0) \ : 1)) #endif // Declaration of package-private data type for managing a // stack of integers typedef struct stackStruct stackStruct; typedef stackStruct *stackP; // Declaration of package private data types for extending the base Graph class // with subclasses having data members and function overloads typedef struct graphExtensionStruct graphExtensionStruct; typedef graphExtensionStruct *graphExtensionP; typedef struct graphFunctionTableStruct graphFunctionTableStruct; typedef graphFunctionTableStruct *graphFunctionTableP; /******************************************************************** Graph structure definition V : Array of vertex records (allocated size N + NV) N : Number of non-virtual vertices (the "order" of the graph) NV: Number of virtual vertices (currently always equal to N) E : Array of edge records (edge records come in pairs and represent an edge in each of the two vertex endpoints of the edge) M: Number of edges (the "size" of the graph) edgeCapacity: the maximum number of edges allowed in E edgeHoles: free locations in E where edges have been deleted numEdgeHoles: package private member indicating the number of edge holes. graphFlags: Additional state information about the graph embedFlags: records the type of embedding requested (uses EMBEDFLAGS) theStack: Used by methods of the base Graph class and its subclasses extensions: an object-oriented hierarchy of graph classes is implemented manually as a list of extensions for data of any subclasses with which a graph has been extended. extensionLookupTable: if not NULL, keeps an array of pointers to the extensions indexed by ID for constant-time lookup. functions: object-oriented class hierarchies includes virtual function overloading, which is provided by this function pointer table. privateData: pointer to a package private data structure that can be backwards compatibly changed in releases of graphLib. */ struct graphStruct { vertexRecP V; int N, NV; edgeRecP E; int M, edgeCapacity; stackP edgeHoles; int numEdgeHoles; unsigned graphFlags, embedFlags; // Used by base Graph class and its subclasses stackP theStack; // Provides ability to subclass the base Graph, // including virtual function overloads by subclasses graphExtensionP extensions; graphExtensionP *extensionLookupTable; graphFunctionTableP functions; void *privateData; }; typedef struct graphStruct graphStruct; typedef graphStruct *graphP; #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graph.private.h000066400000000000000000000270731521450711600261140ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHUTILS_PRIVATE_H #define GRAPHUTILS_PRIVATE_H #include "extensionSystem/graphExtensions.h" #include "extensionSystem/graphExtensions.private.h" #include "lowLevelUtils/listcoll.h" #include "lowLevelUtils/stack.h" #ifdef __cplusplus extern "C" { #endif // DFS-RELATED and PLANARITY-RELATED ONLY // Declaration of package-private data type for managing a // collection of lists of integers typedef struct listCollectionStruct listCollectionStruct; typedef listCollectionStruct *listCollectionP; // Declaration of package-private data type for managing additional // DFS--related information associated with each non-virtual vertex typedef struct DFSUtils_VertexInfo DFSUtils_VertexInfo; typedef DFSUtils_VertexInfo *DFSUtils_VertexInfoP; // PLANARITY-RELATED ONLY // Declaration of package-private data type for managing additional // planarity-related information associated with each non-virtual vertex typedef struct Planarity_VertexInfo Planarity_VertexInfo; typedef Planarity_VertexInfo *Planarity_VertexInfoP; // Declaration of package private data type for optimizing management of // the external face of a planar embedding as it is being built typedef struct extFaceLinkRec extFaceLinkRec; typedef extFaceLinkRec *extFaceLinkRecP; // Declaration of package private data type for isolating // minimal subgraphs obstructing planarity-related embedding typedef struct isolatorContextStruct isolatorContextStruct; typedef isolatorContextStruct *isolatorContextP; /******************************************************************** A structure for package private data associated with a graph. BicompRootLists: storage for bicomp root lists (DFSUtils) or, for Planarity, pertinent child bicomp lists that develop during embedding DVI: package private pointer; if the graph is extended to DFSUtils, then N instances of DFSUtils vertex info records are allocated PVI: package private pointer; if the graph is extended to Planarity, then N instances of Planarity vertex info records are allocated IC: contains additional useful variables for Kuratowski subgraph isolation. sortedDFSChildLists: for Planarity graphs, storage for the sorted DFS child lists of each vertex extFace: For Planarity graphs, an array of (N + NV) external face short circuit records ********************************************************************/ struct graphPrivateDataStruct { // Private Data members specific to a DFSUtilsGraph subclass listCollectionP BicompRootLists; DFSUtils_VertexInfoP DVI; // Private data members specific to a PlanarityGraph subclass Planarity_VertexInfoP PVI; listCollectionP sortedDFSChildLists; extFaceLinkRecP extFace; isolatorContextP IC; }; typedef struct graphPrivateDataStruct graphPrivateDataStruct; typedef graphPrivateDataStruct *graphPrivateDataP; // These macros describe how to currently get access to additional data structures // used in DFSUtils Graphs and Planarity Graphs. Once full extensions are // developed for those, then these definitions can change to access the data // from the extensions, and the data can be removed from graphPrivateDataStruct. #define theGraphBicompRootLists(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->BicompRootLists) #define theGraphDVI(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->DVI) #define theGraphPVI(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->PVI) #define theGraphSortedDFSChildLists(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->sortedDFSChildLists) #define theGraphExtFace(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->extFace) #define theGraphIC(theGraph) (((graphPrivateDataP)((theGraph)->privateData))->IC) /******************************************************************** Additional edge link accessors and manipulators ********************************************************************/ // Methods that enable getting the next or previous edge as if // the adjacency list of the containing vertex were circular, // i.e. that the first and last edge records were linked #define gp_GetNextEdgeCircular(theGraph, e) \ (gp_IsEdge(theGraph, gp_GetNextEdge(theGraph, e)) \ ? gp_GetNextEdge(theGraph, e) \ : gp_GetFirstEdge(theGraph, theGraph->E[gp_GetTwin(theGraph, e)].neighbor)) #define gp_GetPrevEdgeCircular(theGraph, e) \ (gp_IsEdge(theGraph, gp_GetPrevEdge(theGraph, e)) \ ? gp_GetPrevEdge(theGraph, e) \ : gp_GetLastEdge(theGraph, theGraph->E[gp_GetTwin(theGraph, e)].neighbor)) // Methods that make the cross-link binding between a vertex and an // edge record. The old first or last edge record should be bound to // this new first or last edge record, e, by separate calls, // e.g. see gp_AttachFirstEdge() and gp_AttachLastEdge() #define gp_BindFirstEdge(theGraph, v, e) \ { \ gp_SetPrevEdge(theGraph, e, NIL); \ gp_SetFirstEdge(theGraph, v, e); \ } #define gp_BindLastEdge(theGraph, v, e) \ { \ gp_SetNextEdge(theGraph, e, NIL); \ gp_SetLastEdge(theGraph, v, e); \ } // Attaches edge e between the current binding between v and its first edge #define gp_AttachFirstEdge(theGraph, v, e) \ { \ if (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, v))) \ { \ gp_SetNextEdge(theGraph, e, gp_GetFirstEdge(theGraph, v)); \ gp_SetPrevEdge(theGraph, gp_GetFirstEdge(theGraph, v), e); \ } \ else \ gp_BindLastEdge(theGraph, v, e); \ gp_BindFirstEdge(theGraph, v, e); \ } // Attaches edge e between the current binding between v and its last edge #define gp_AttachLastEdge(theGraph, v, e) \ { \ if (gp_IsEdge(theGraph, gp_GetLastEdge(theGraph, v))) \ { \ gp_SetPrevEdge(theGraph, e, gp_GetLastEdge(theGraph, v)); \ gp_SetNextEdge(theGraph, gp_GetLastEdge(theGraph, v), e); \ } \ else \ gp_BindFirstEdge(theGraph, v, e); \ gp_BindLastEdge(theGraph, v, e); \ } // Moves an edge e that is in the adjacency list of v to the start of the adjacency list #define gp_MoveEdgeToFirst(theGraph, v, e) \ if (e != gp_GetFirstEdge(theGraph, v)) \ { \ /* If e is last in the adjacency list of v, then we \ detach it by adjacency list end management */ \ if (e == gp_GetLastEdge(theGraph, v)) \ { \ gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, e), NIL); \ gp_SetLastEdge(theGraph, v, gp_GetPrevEdge(theGraph, e)); \ } \ /* Otherwise, we detach e from the middle of the list */ \ else \ { \ gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, e), gp_GetNextEdge(theGraph, e)); \ gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, e), gp_GetPrevEdge(theGraph, e)); \ } \ \ /* Now add e as the new first edge of v. \ Note that the adjacency list is non-empty at this time */ \ gp_SetNextEdge(theGraph, e, gp_GetFirstEdge(theGraph, v)); \ gp_SetPrevEdge(theGraph, gp_GetFirstEdge(theGraph, v), e); \ gp_BindFirstEdge(theGraph, v, e); \ } // Moves an edge e that is in the adjacency list of v to the end of the adjacency list #define gp_MoveEdgeToLast(theGraph, v, e) \ if (e != gp_GetLastEdge(theGraph, v)) \ { \ /* If e is first in the adjacency list of vertex v, then we \ detach it by adjacency list beginning management */ \ if (e == gp_GetFirstEdge(theGraph, v)) \ { \ gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, e), NIL); \ gp_SetFirstEdge(theGraph, v, gp_GetNextEdge(theGraph, e)); \ } \ /* Otherwise, we detach e from the middle of the list */ \ else \ { \ gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, e), gp_GetNextEdge(theGraph, e)); \ gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, e), gp_GetPrevEdge(theGraph, e)); \ } \ \ /* Now we add e as the new last edge of v. \ Note that the adjacency list is non-empty at this time */ \ gp_SetPrevEdge(theGraph, e, gp_GetLastEdge(theGraph, v)); \ gp_SetNextEdge(theGraph, gp_GetLastEdge(theGraph, v), e); \ gp_BindLastEdge(theGraph, v, e); \ } #ifdef __cplusplus } #endif #endif /* GRAPHUTILS_PRIVATE_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graphDFSUtils.c000066400000000000000000000536441521450711600260170ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #define GRAPHDFSUTILS_C #include "graphDFSUtils.h" #include "graphDFSUtils.private.h" // For LOGGING-related declarations #include "lowLevelUtils/apiutils.private.h" // Allows the default _SortVertices() to swap planarity vertex info, if present #include "planarityRelated/graphPlanarity.private.h" // Private methods, except exported within library int _SortVertices(graphP theGraph); // Imported methods extern void _ClearVertexVisitedFlags(graphP theGraph, int); /******************************************************************** gp_ExtendWith_DFSUtils() Makes any necessary preparations for supporting DFS utility methods that create a DFS tree, sort vertices, and compute least ancestor. and lowpoint values. Those four utility methods automatically call this method to extend the graph, though this method can also be called beforehand. This method should be called after gp_EnsureVertexCapacity() or gp_Read() because the number of vertices must be known. On success, sets GRAPHFLAGS_EXTENDEDWITH_DFSUTILS. Returns OK on success, NOTOK on failure. ********************************************************************/ int gp_ExtendWith_DFSUtils(graphP theGraph) { if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // if the Graph has already been extended with DFS Utils, // then just return successfully if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_DFSUTILS) return OK; // Allocate supporting data structures as needed // Perform "on success" operations theGraph->graphFlags |= GRAPHFLAGS_EXTENDEDWITH_DFSUTILS; return OK; } /******************************************************************** gp_Detach_DFSUtils() This function is intended to disinherit the DFS Utils feature by removing the extension from the graph, which also frees any DFS-specific data structures. Clears GRAPHFLAGS_EXTENDEDWITH_DFSUTILS after detaching support for the DFS utility methods. Returns OK for success, NOTOK for failure ********************************************************************/ int gp_Detach_DFSUtils(graphP theGraph) { // Free any data structures allocated by the ExtendWith function // Indicate successful detachment of DFSUtils theGraph->graphFlags &= ~GRAPHFLAGS_EXTENDEDWITH_DFSUTILS; return OK; } /******************************************************************** gp_DepthFirstSearch() This depth-first search (DFS) assigns a Depth First Index (DFI) to each vertex and records the DFS parent of each vertex in each DFS tree that forms during the depth-first search. Also, the type of each edge record of each edge is set to indicate whether the edge record's neighbor value points to a DFS child or parent (a DFS tree edge) or a farther DFS ancestor or descendant (the backward and forward edge records of a "back" edge/"cycle" edge/"co-tree" edge). NOTE: This is a utility function provided for general use. The core planarity algorithm uses its own DFS so it can build related data structures at the same time. ********************************************************************/ int gp_DepthFirstSearch(graphP theGraph) { stackP theStack; int DFI, v, uparent, u, e; if (theGraph == NULL) return NOTOK; if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_DFSNUMBERED) return OK; if (gp_ExtendWith_DFSUtils(theGraph) != OK) return NOTOK; _gp_LogLine("\ngraphDFSUtils.c/gp_DepthFirstSearch() start"); theStack = theGraph->theStack; /* There are 2M edge records and for each we can push 2 integers, plus one extra (NIL, NIL) at the beginning to represent arriving at a DFS tree root. So, a stack of 2 * 2 * (1+M) integers suffices. This stack is already in theGraph structure, so we make sure it has the capacity and, if so, that it's empty. */ if (sp_GetCapacity(theStack) < 2 * 2 * gp_GetM(theGraph) + 2) return NOTOK; sp_ClearStack(theStack); /* Clear the visited flags because they are used to detect what has been visited as the DFS traverses the graph. */ _ClearVertexVisitedFlags(theGraph, FALSE); /* This outer loop causes the connected subgraphs of a disconnected graph to be numbered */ for (DFI = v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (gp_IsNotDFSTreeRoot(theGraph, v)) continue; sp_Push2(theStack, NIL, NIL); while (sp_NonEmpty(theStack)) { sp_Pop2(theStack, uparent, e); u = gp_IsNotVertex(theGraph, uparent) ? v : gp_GetNeighbor(theGraph, e); if (!gp_GetVisited(theGraph, u)) { _gp_LogLine(_gp_MakeLogStr3("V=%d, DFI=%d, Parent=%d", u, DFI, uparent)); gp_SetVisited(theGraph, u); gp_SetIndex(theGraph, u, DFI++); gp_SetVertexParent(theGraph, u, uparent); if (gp_IsEdge(theGraph, e)) { gp_SetEdgeType(theGraph, e, EDGE_TYPE_CHILD); gp_SetEdgeType(theGraph, gp_GetTwin(theGraph, e), EDGE_TYPE_PARENT); } /* Push edges to all unvisited neighbors. These will be either tree edges to children or forward edge records of back edges */ e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (!gp_GetVisited(theGraph, gp_GetNeighbor(theGraph, e))) sp_Push2(theStack, u, e); e = gp_GetNextEdge(theGraph, e); } } else { // If the edge leads to a visited vertex, then it is // the forward component of a back edge. gp_SetEdgeType(theGraph, e, EDGE_TYPE_FORWARD); gp_SetEdgeType(theGraph, gp_GetTwin(theGraph, e), EDGE_TYPE_BACK); } } } _gp_LogLine("graphDFSUtils.c/gp_DepthFirstSearch() end\n"); theGraph->graphFlags |= GRAPHFLAGS_DFSNUMBERED; return OK; } /******************************************************************** gp_SortVertices() Once depth first numbering has been applied to the graph, the index member of each vertex contains the DFI. This routine can reorder the vertices in linear time so that they appear in ascending order by DFI. Note that the index field is then used to store the original number of the vertex. Therefore, a second call to this method will put the vertices back to the original order and put the DFIs back into the index fields of the vertices. NOTE: This function is used by the core planarity algorithm, once its custom DFS has assigned DFIs to the vertices. Once gp_Embed() has finished creating an embedding or obstructing subgraph, this function can be called to restore the original vertex numbering, if needed. ********************************************************************/ int gp_SortVertices(graphP theGraph) { if (theGraph == NULL) return NOTOK; if (gp_ExtendWith_DFSUtils(theGraph) != OK) return NOTOK; return theGraph->functions->fpSortVertices(theGraph); } // Give macro names to swap operations used when sorting vertices // These are macros and hence not overloadable. If an extension // needs to reorder parallel vertex data, then this must be done // by a post-processing step in an overload of gp_SortVertices(). // The index values of the first N vertices are changed to hold // the prior locations of vertices when they are rearranged to // or from DFI order. #define _gp_SwapVertexRec(dstGraph, vdst, srcGraph, vsrc) \ { \ vertexRec tempV = dstGraph->V[vdst]; \ dstGraph->V[vdst] = srcGraph->V[vsrc]; \ srcGraph->V[vsrc] = tempV; \ } #define _gp_SwapDFSUtilsVertexInfo(dstGraph, dstPos, srcGraph, srcPos) \ { \ DFSUtils_VertexInfo tempDVI = theGraphDVI(dstGraph)[dstPos]; \ theGraphDVI(dstGraph)[dstPos] = theGraphDVI(srcGraph)[srcPos]; \ theGraphDVI(srcGraph)[srcPos] = tempDVI; \ } #define _gp_SwapPlanarityVertexInfo(dstGraph, dstPos, srcGraph, srcPos) \ if (theGraphPVI(dstGraph) != NULL && theGraphPVI(srcGraph) != NULL) \ { \ Planarity_VertexInfo tempPVI = theGraphPVI(dstGraph)[dstPos]; \ theGraphPVI(dstGraph)[dstPos] = theGraphPVI(srcGraph)[srcPos]; \ theGraphPVI(srcGraph)[srcPos] = tempPVI; \ } // This is the default method for sorting vertices into and back // out of DFI order. int _SortVertices(graphP theGraph) { int v, srcPos, dstPos; if (theGraph == NULL) return NOTOK; if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_DFSNUMBERED)) if (gp_DepthFirstSearch(theGraph) != OK) return NOTOK; _gp_LogLine("\ngraphDFSUtils.c/_SortVertices() start"); /* Change labels of edges from v to DFI(v)-- or vice versa Also, if any links go back to locations 0 to n-1, then they need to be changed because we are reordering the vertices */ if (theGraph->numEdgeHoles == 0) { // Slightly optimized loop body, for when edge deletion has not been used // (Optimization level O1 or higher hoists the upperBoundEdges calculation, // so this is mainly just a little less work in the loop body). int upperBoundEdges = gp_LowerBoundEdges(theGraph) + (gp_GetM(theGraph) << 1); for (int e = gp_LowerBoundEdges(theGraph); e < upperBoundEdges; ++e) gp_SetNeighbor(theGraph, e, gp_GetIndex(theGraph, gp_GetNeighbor(theGraph, e))); } else { for (int e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); e += 2) { if (gp_EdgeInUse(theGraph, e)) { gp_SetNeighbor(theGraph, e, gp_GetIndex(theGraph, gp_GetNeighbor(theGraph, e))); gp_SetNeighbor(theGraph, e + 1, gp_GetIndex(theGraph, gp_GetNeighbor(theGraph, e + 1))); } } } /* Convert DFSParent from v to DFI(v) or vice versa */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (gp_IsNotDFSTreeRoot(theGraph, v)) gp_SetVertexParent(theGraph, v, gp_GetIndex(theGraph, gp_GetVertexParent(theGraph, v))); /* Sort by 'v using constant time random access. Move each vertex to its destination 'v', and store its source location in 'v'. */ /* First we clear the visitation flags. We need these to help mark visited vertices because we change the 'v' field to be the source location, so we cannot use index==v as a test for whether the correct vertex is in location 'index'. */ _ClearVertexVisitedFlags(theGraph, FALSE); /* We visit each vertex location, skipping those marked as visited since we've already moved the correct vertex into that location. The inner loop swaps the vertex at location v into the correct position, given by the index of the vertex at location v. Then it marks that location as visited, then sets its index to be the location from whence we obtained the vertex record. */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { srcPos = v; while (!gp_GetVisited(theGraph, v)) { dstPos = gp_GetIndex(theGraph, v); _gp_SwapVertexRec(theGraph, dstPos, theGraph, v); _gp_SwapDFSUtilsVertexInfo(theGraph, dstPos, theGraph, v); _gp_SwapPlanarityVertexInfo(theGraph, dstPos, theGraph, v); gp_SetVisited(theGraph, dstPos); gp_SetIndex(theGraph, dstPos, srcPos); srcPos = dstPos; } } /* Invert the bit that records the sort order of the graph */ theGraph->graphFlags ^= GRAPHFLAGS_SORTEDBYDFI; _gp_LogLine("graphDFSUtils.c/_SortVertices() end\n"); return OK; } /******************************************************************** gp_ComputeLowpoints() leastAncestor(v): min(v, ancestor neighbors of v, excluding parent) Lowpoint(v): min(leastAncestor(v), Lowpoint of DFS children of v) The Lowpoint of each vertex is computed via a post-order traversal of the DFS tree. Lowpoint calculations require leastAncestor calculations, so both are computed by this method. We push the root of the DFS tree, then we loop while the stack is not empty. We pop a vertex; if it is not marked, then we are on our way down the DFS tree, so we mark it and push it back on, followed by pushing its DFS children. The next time we pop the node, all of its children will have been popped, marked+children pushed, and popped again. On the second pop of the vertex, we can therefore compute the lowpoint values based on the childrens' lowpoints and the least ancestor from among the edges in the vertex's adjacency list. If they have not already been performed, gp_DepthFirstSearch() and gp_SortVertices() are invoked on the graph, and it is left in the sorted state on completion of this method. NOTE: This is a utility function provided for general use of the graph library. The core planarity algorithm computes leastAncestor during its initial DFS, and it computes the lowpoint of each a vertex as it embeds the tree edges to its children. ********************************************************************/ int gp_ComputeLowpoints(graphP theGraph) { stackP theStack = NULL; int v, u, uneighbor, e, L, leastAncestor; if (theGraph == NULL) return NOTOK; if (gp_ExtendWith_DFSUtils(theGraph) != OK) return NOTOK; theStack = theGraph->theStack; if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_DFSNUMBERED)) if (gp_DepthFirstSearch(theGraph) != OK) return NOTOK; if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_SORTEDBYDFI)) if (gp_SortVertices(theGraph) != OK) return NOTOK; _gp_LogLine("\ngraphDFSUtils.c/gp_ComputeLowpoints() start"); // A stack of size N suffices because at maximum every vertex is pushed only once // However, since a larger stack is needed for the main DFS, this is really // just 'documentation' of the requirement if (sp_GetCapacity(theStack) < gp_GetN(theGraph)) return NOTOK; sp_ClearStack(theStack); _ClearVertexVisitedFlags(theGraph, FALSE); // This outer loop causes the connected subgraphs of a disconnected graph to be processed for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph);) { if (gp_GetVisited(theGraph, v)) { ++v; continue; } sp_Push(theStack, v); while (sp_NonEmpty(theStack)) { sp_Pop(theStack, u); // If not visited, then we're on the pre-order visitation, so push u and its DFS children if (!gp_GetVisited(theGraph, u)) { // Mark u as visited, then push it back on the stack gp_SetVisited(theGraph, u); ++v; sp_Push(theStack, u); // Push the DFS children of u e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { sp_Push(theStack, gp_GetNeighbor(theGraph, e)); } e = gp_GetNextEdge(theGraph, e); } } // If u has been visited before, then this is the post-order visitation else { // Start with high values because we are doing a min function leastAncestor = L = u; // Compute leastAncestor and L, the least lowpoint from the DFS children e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { uneighbor = gp_GetNeighbor(theGraph, e); if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { if (L > gp_GetVertexLowpoint(theGraph, uneighbor)) L = gp_GetVertexLowpoint(theGraph, uneighbor); } else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_BACK) { if (leastAncestor > uneighbor) leastAncestor = uneighbor; } e = gp_GetNextEdge(theGraph, e); } /* Assign leastAncestor and Lowpoint to the vertex */ gp_SetVertexLeastAncestor(theGraph, u, leastAncestor); gp_SetVertexLowpoint(theGraph, u, leastAncestor < L ? leastAncestor : L); } } } _gp_LogLine("graphDFSUtils.c/gp_ComputeLowpoints() end\n"); return OK; } /******************************************************************** gp_ComputeLeastAncestors() By simple pre-order visitation, compute the least ancestor of each vertex that is directly adjacent to the vertex by a back edge. If they have not already been performed, gp_DepthFirstSearch() and gp_SortVertices() are invoked on the graph, and it is left in the sorted state on completion of this method. NOTE: This method is not called by gp_ComputeLowpoints(), which computes both values at the same time. ********************************************************************/ int gp_ComputeLeastAncestors(graphP theGraph) { stackP theStack = NULL; int v, u, uneighbor, e, leastAncestor; if (theGraph == NULL) return NOTOK; if (gp_ExtendWith_DFSUtils(theGraph) != OK) return NOTOK; theStack = theGraph->theStack; if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_DFSNUMBERED)) if (gp_DepthFirstSearch(theGraph) != OK) return NOTOK; if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_SORTEDBYDFI)) if (gp_SortVertices(theGraph) != OK) return NOTOK; _gp_LogLine("\ngraphDFSUtils.c/gp_ComputeLeastAncestors() start"); // A stack of size N suffices because at maximum every vertex is pushed only once if (sp_GetCapacity(theStack) < gp_GetN(theGraph)) return NOTOK; sp_ClearStack(theStack); // This outer loop causes the connected subgraphs of a disconnected graph to be processed for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph);) { if (gp_GetVisited(theGraph, v)) { ++v; continue; } sp_Push(theStack, v); while (sp_NonEmpty(theStack)) { sp_Pop(theStack, u); if (!gp_GetVisited(theGraph, u)) { gp_SetVisited(theGraph, u); ++v; leastAncestor = u; e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { uneighbor = gp_GetNeighbor(theGraph, e); if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { sp_Push(theStack, uneighbor); } else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_BACK) { if (leastAncestor > uneighbor) leastAncestor = uneighbor; } e = gp_GetNextEdge(theGraph, e); } gp_SetVertexLeastAncestor(theGraph, u, leastAncestor); } } } _gp_LogLine("graphDFSUtils.c/gp_ComputeLeastAncestors() end\n"); return OK; } /******************************************************************** gp_GetParent() Once the DFS tree has been created in theGraph, this method returns the DFS parent of the given vertex v. This includes returning NIL for a DFS tree root, which has no DFS parent. Also returns NIL on error, such as invalid parameters or DFS tree not created yet. ********************************************************************/ int gp_GetParent(graphP theGraph, int v) { if (theGraph == NULL || v < gp_LowerBoundVertices(theGraph) || v >= gp_UpperBoundVertices(theGraph)) { #ifdef DEBUG NOTOK; ; #endif return NIL; } return gp_GetVertexParent(theGraph, v); } /******************************************************************** gp_GetLeastAncestor() Once the least ancestors have been computed in the graph, which includes when they are implicitly calculated as part of computing lowpoints, this method returns the least ancestor value for the given vertex v. Returns NIL on error, such as invalid parameters or least ancestor values not computed yet. ********************************************************************/ int gp_GetLeastAncestor(graphP theGraph, int v) { if (theGraph == NULL || v < gp_LowerBoundVertices(theGraph) || v >= gp_UpperBoundVertices(theGraph)) { #ifdef DEBUG NOTOK; ; #endif return NIL; } return gp_GetVertexLeastAncestor(theGraph, v); } /******************************************************************** gp_GetLowpoint() Once the lowpoints have been computed in the graph, this method returns the lowpoint value for the given vertex v. Returns NIL on error, such as invalid parameters or lowpoints not computed yet. ********************************************************************/ int gp_GetLowpoint(graphP theGraph, int v) { if (theGraph == NULL || v < gp_LowerBoundVertices(theGraph) || v >= gp_UpperBoundVertices(theGraph)) { #ifdef DEBUG NOTOK; ; #endif return NIL; } return gp_GetVertexLowpoint(theGraph, v); } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graphDFSUtils.h000066400000000000000000000106541521450711600260160ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHDFSUTILS_H #define GRAPHDFSUTILS_H #include "graph.h" #ifdef __cplusplus extern "C" { #endif // Create a DFSUtils Graph, i.e., subclass a Graph by extending it with the // ability to perform the depth-first search (DFS) utility methods below. #define DFSUTILS_NAME "DFSUtils" int gp_ExtendWith_DFSUtils(graphP theGraph); int gp_Detach_DFSUtils(graphP theGraph); /* Graph Flags: see gp_GetGraphFlags() GRAPHFLAGS_EXTENDEDWITH_DFSUTILS is set by calling gp_ExtendWith_DFSUtils() This is automatically called by the utility methods below that create a DFS tree, sort vertices, and compute least ancestors and lowpoints GRAPHFLAGS_DFSNUMBERED is set if DFS numbering has been performed on the graph, such as by calling the gp_DepthFirstSearch() utility method below GRAPHFLAGS_SORTEDBYDFI records whether the graph is in original vertex order or sorted by depth first index. Successive calls to the gp_SortVertices() utility method below toggle this bit. */ #define GRAPHFLAGS_EXTENDEDWITH_DFSUTILS 256 #define GRAPHFLAGS_DFSNUMBERED 512 #define GRAPHFLAGS_SORTEDBYDFI 1024 // DFS-related utility methods that create a DFS tree, sort vertices and // compute least ancestor and lowpoint values int gp_DepthFirstSearch(graphP theGraph); int gp_SortVertices(graphP theGraph); int gp_ComputeLowpoints(graphP theGraph); int gp_ComputeLeastAncestors(graphP theGraph); // Additional DFS-related uitility methods (functions and macros) that assume // one or more of the above methods have been called to create a DFS tree, // sort vertices and/or compute least ancestor and lowpoint values int gp_GetParent(graphP theGraph, int v); int gp_GetLeastAncestor(graphP theGraph, int v); int gp_GetLowpoint(graphP theGraph, int v); // A DFS tree root is one that has no DFS parent. There is one DFS tree root // per connected component of a graph (connected, not biconnected; component, not bicomp) #define gp_IsDFSTreeRoot(theGraph, v) gp_IsNotVertex(theGraph, gp_GetVertexParent(theGraph, v)) #define gp_IsNotDFSTreeRoot(theGraph, v) gp_IsVertex(theGraph, gp_GetVertexParent(theGraph, v)) // Mapping between bicomp roots and virtual vertex locations used to store them. // A cut vertex v separates one or more of its DFS children, say c1 and c2, from // the DFS parent and ancesstors of v. Because a DFS tree contains only tree edges // and back edges, there are no cross edges connecting vertices in the DFS subtree // rooted by c1, T(c1), with vertices in the DFS subtree rooted by c2, T(c2). // We say that v is a cut vertex because the only paths that go from vertices in // T(c1) to vertices in T(c2) are paths that contain v. // // Therefore, bicomp root copies of v, say R1 and R2, can be created at locations // c1 and c2 in virtual vertex space, in other words at locations N+c1 and N+c2. // The bicomps rooted by R1 and R2 are called child bicomps of v, and they contain, // respectively, c1 and c2 as well as possibly more vertices from, respectively, // T(c1) and T(c2), depending on what back edges may exist in the graph between // pairs of vertices in, respectively, T(c1) and T(c2). #define gp_GetBicompRootFromDFSChild(theGraph, c) ((c) + gp_GetN(theGraph)) #define gp_GetDFSChildFromBicompRoot(theGraph, R) ((R) - gp_GetN(theGraph)) #define gp_GetVertexFromBicompRoot(theGraph, R) gp_GetVertexParent(theGraph, gp_GetDFSChildFromBicompRoot(theGraph, R)) #define gp_IsBicompRoot(theGraph, v) ((v) >= gp_LowerBoundVirtualVertices(theGraph)) // If a vertex v is a cut vertex that separates one of its DFS children, say c, // from the DFS ancestors and other children of v, then when the graph has been // separated into bicomps, there will be a root copy of v in virtual vertex space // at location c+N that will have at least one edge connecting it to c. // These macros detect whether or not that is the case for a given DFS child. #define gp_IsSeparatedDFSChild(theGraph, theChild) (gp_VirtualVertexInUse(theGraph, gp_GetBicompRootFromDFSChild(theGraph, theChild))) #define gp_IsNotSeparatedDFSChild(theGraph, theChild) (gp_VirtualVertexNotInUse(theGraph, gp_GetBicompRootFromDFSChild(theGraph, theChild))) #ifdef __cplusplus } #endif #endif /* GRAPHDFSUTILS_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graphDFSUtils.private.h000066400000000000000000000035571521450711600274730ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHDFSUTILS_PRIVATE_H #define GRAPHDFSUTILS_PRIVATE_H #include "graph.private.h" #ifdef __cplusplus extern "C" { #endif /******************************************************************** Vertex Info Structure Definition. This structure equips the non-virtual vertices with additional information needed for DFS-related and planarity-related algorithms. parent: The DFI of the DFS tree parent of this vertex leastAncestor: min(DFI of neighbors connected by backedge) lowpoint: min(leastAncestor, min(lowpoint of DFS Children)) */ struct DFSUtils_VertexInfo { int parent, leastAncestor, lowpoint; }; typedef struct DFSUtils_VertexInfo DFSUtils_VertexInfo; typedef DFSUtils_VertexInfo *DFSUtils_VertexInfoP; struct vertexInfoRec { int parent, leastAncestor, lowpoint; int visitedInfo; int pertinentEdge, pertinentRoots, futurePertinentChild, sortedDFSChildList, fwdEdgeList; }; typedef struct vertexInfoRec vertexInfoRec; typedef vertexInfoRec *vertexInfoP; #define gp_GetVertexParent(theGraph, v) (theGraphDVI(theGraph)[v].parent) #define gp_SetVertexParent(theGraph, v, theParent) (theGraphDVI(theGraph)[v].parent = theParent) #define gp_GetVertexLeastAncestor(theGraph, v) (theGraphDVI(theGraph)[v].leastAncestor) #define gp_SetVertexLeastAncestor(theGraph, v, theLeastAncestor) (theGraphDVI(theGraph)[v].leastAncestor = theLeastAncestor) #define gp_GetVertexLowpoint(theGraph, v) (theGraphDVI(theGraph)[v].lowpoint) #define gp_SetVertexLowpoint(theGraph, v, theLowpoint) (theGraphDVI(theGraph)[v].lowpoint = theLowpoint) #ifdef __cplusplus } #endif #endif /* GRAPHPDFSUTILS_PRIVATE_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graphLib.c000066400000000000000000000024501521450711600250550ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphLib.h" #include /******************************************************************** gp_GetProjectVersionFull() Return full major.minor.maint.tweak version string for the graph planarity project ********************************************************************/ const char *gp_GetProjectVersionFull(void) { static char projectVersionStr[MAXLINE + 1]; sprintf(projectVersionStr, "%d.%d.%d.%d", GP_PROJECTVERSION_MAJOR, GP_PROJECTVERSION_MINOR, GP_PROJECTVERSION_MAINT, GP_PROJECTVERSION_TWEAK); return projectVersionStr; } /******************************************************************** gp_GetLibPlanarityVersionFull() Returns full current:revision:age version string for the graph planarity shared library ********************************************************************/ const char *gp_GetLibPlanarityVersionFull(void) { static char libPlanarityVersionStr[MAXLINE + 1]; sprintf(libPlanarityVersionStr, "%d:%d:%d", GP_LIBPLANARITYVERSION_CURRENT, GP_LIBPLANARITYVERSION_REVISION, GP_LIBPLANARITYVERSION_AGE); return libPlanarityVersionStr; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/graphLib.h000066400000000000000000000045151521450711600250660ustar00rootroot00000000000000#ifndef GRAPHLIB_H #define GRAPHLIB_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef __cplusplus extern "C" { #endif #include #include #include // Basic public API declarations, such as for OK, NOTOK, and NIL #include "lowLevelUtils/appconst.h" // And for get/set quiet mode, gp_ErrorMessage() and gp_Message() #include "lowLevelUtils/apiutils.h" // Graph structure and public API methods #include "graph.h" // Graph I/O public API methods and definitions #include "io/graphIO.h" #include "io/g6-read-iterator.h" #include "io/g6-write-iterator.h" // Depth-first search public API methods and definitions #include "graphDFSUtils.h" // Planarity-specific public API methods and definitions #include "planarityRelated/graphPlanarity.h" // Public APIs for extensions to the edge addition planarity algorithm #include "planarityRelated/graphOuterplanarity.h" #include "planarityRelated/graphDrawPlanar.h" #include "homeomorphSearch/graphK23Search.h" #include "homeomorphSearch/graphK33Search.h" #include "homeomorphSearch/graphK4Search.h" // This is the main location for the project and shared library version numbering. // Changes here must be mirrored in configure.ac // // The overall project version numbering format is major.minor.maintenance.tweak // Major is for an overhaul (e.g. many features, data structure change, change of backward compatibility) // Minor is for feature addition (e.g. a new algorithm implementation added, new interface) // Maintenance is for functional revision (e.g. bug fix to existing algorithm implementation) // Tweak is for a non-functional revision (e.g. change of build scripts or testing code, user-facing string changes) #define GP_PROJECTVERSION_MAJOR 5 #define GP_PROJECTVERSION_MINOR 0 #define GP_PROJECTVERSION_MAINT 0 #define GP_PROJECTVERSION_TWEAK 0 const char *gp_GetProjectVersionFull(void); // Any change to the project version numbers should also affect the // shared library version numbers below. // // See configure.ac for how to update these version numbers #define GP_LIBPLANARITYVERSION_CURRENT 4 #define GP_LIBPLANARITYVERSION_REVISION 0 #define GP_LIBPLANARITYVERSION_AGE 0 const char *gp_GetLibPlanarityVersionFull(void); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/000077500000000000000000000000001521450711600264635ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK23Search.c000066400000000000000000000226501521450711600313430ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphK23Search.h" #include "graphK23Search.private.h" /* Imported functions */ extern void _ClearAllVisitedFlagsInGraph(graphP); extern int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink); extern int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns); extern int _JoinBicomps(graphP theGraph); extern int _MarkHighestXYPath(graphP theGraph); extern int _FindUnembeddedEdgeToAncestor(graphP theGraph, int cutVertex, int *pAncestor, int *pDescendant); extern int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant); extern int _FindUnembeddedEdgeToSubtree(graphP theGraph, int ancestor, int SubtreeRoot, int *pDescendant); extern int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert); extern int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant); extern int _DeleteUnmarkedVerticesAndEdges(graphP theGraph); extern int _ChooseTypeOfNonOuterplanarityMinor(graphP theGraph, int v, int R); extern int _IsolateOuterplanarityObstructionA(graphP theGraph); extern int _IsolateOuterplanarityObstructionB(graphP theGraph); /* Private function declarations for K_{2,3} searching */ int _SearchForK23InBicomp(graphP theGraph, int v, int R); int _IsolateOuterplanarityObstructionE1orE2(graphP theGraph); int _IsolateOuterplanarityObstructionE3orE4(graphP theGraph); /**************************************************************************** _SearchForK23InBicomp() ****************************************************************************/ int _SearchForK23InBicomp(graphP theGraph, int v, int R) { isolatorContextP IC = theGraphIC(theGraph); int X, Y, XPrevLink, YPrevLink; /* Begin by determining whether minor A, B or E is detected */ if (_ChooseTypeOfNonOuterplanarityMinor(theGraph, v, R) != OK) return NOTOK; /* Minors A and B result in the desired K_{2,3} homeomorph, so we isolate it and return NONEMBEDDABLE. */ if (theGraphIC(theGraph)->minorType & (MINORTYPE_A | MINORTYPE_B)) { _ClearAllVisitedFlagsInGraph(theGraph); if (theGraphIC(theGraph)->minorType & MINORTYPE_A) { if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; if (_IsolateOuterplanarityObstructionA(theGraph) != OK) return NOTOK; } else if (theGraphIC(theGraph)->minorType & MINORTYPE_B) { int SubtreeRoot = gp_GetVertexLastPertinentRootChild(theGraph, IC->w); if (_FindUnembeddedEdgeToSubtree(theGraph, IC->v, SubtreeRoot, &IC->dw) != TRUE) return NOTOK; if (_IsolateOuterplanarityObstructionB(theGraph) != OK) return NOTOK; } if (_DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* For minor E (a K_4) , we run the additional tests to see if a K_{2,3} is entangled with the K_4. If not, then we return OK to indicate that the outerplanarity embedder should proceed as if the K_4 had not been found. */ /* If any vertices other than R, X, Y and W exist along the external face, then we can obtain a K_{2,3} by minor E1 or E2 */ X = IC->x; Y = IC->y; XPrevLink = 1; YPrevLink = 0; if (IC->w != _GetNeighborOnExtFace(theGraph, X, &XPrevLink) || IC->w != _GetNeighborOnExtFace(theGraph, Y, &YPrevLink)) { _ClearAllVisitedFlagsInGraph(theGraph); if (_IsolateOuterplanarityObstructionE1orE2(theGraph) != OK) return NOTOK; if (_DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* If X, Y or W make either a direct back edge connection or a connection through a separated child bicomp to an ancestor of the current vertex v, then we can obtain a K_{2,3} by minor E3 or E4. Note that this question is query on X, Y and W is equivalent to the planarity version of external activity. */ gp_UpdateVertexFuturePertinentChild(theGraph, X, v); gp_UpdateVertexFuturePertinentChild(theGraph, Y, v); gp_UpdateVertexFuturePertinentChild(theGraph, IC->w, v); if (FUTUREPERTINENT(theGraph, X, v) || FUTUREPERTINENT(theGraph, Y, v) || FUTUREPERTINENT(theGraph, IC->w, v)) { _ClearAllVisitedFlagsInGraph(theGraph); if (_IsolateOuterplanarityObstructionE3orE4(theGraph) != OK) return NOTOK; if (_DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* The extra cases for finding a K_{2,3} failed, so the bicomp rooted by R is a separable subgraph of the input that is isomorphic to K_4. So, we restore the original vertex orientation of the bicomp (because it's polite, not because we really have to). Then, we return OK to tell the outerplanarity embedder that it can ignore this K_4 and keep processing. */ if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateOuterplanarityObstructionE1orE2() ****************************************************************************/ int _IsolateOuterplanarityObstructionE1orE2(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); int XPrevLink = 1; if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; /* Isolate E1 */ if (theGraphIC(theGraph)->px != theGraphIC(theGraph)->x) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->w) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->py, IC->r) != OK) return NOTOK; } else if (theGraphIC(theGraph)->py != theGraphIC(theGraph)->y) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->x) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->w, IC->r) != OK) return NOTOK; } /* Isolate E2 */ else if (IC->w != _GetNeighborOnExtFace(theGraph, IC->x, &XPrevLink)) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->y) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->r) != OK) return NOTOK; } /* Final bits are in common */ if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE || theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateOuterplanarityObstructionE3orE4() ****************************************************************************/ int _IsolateOuterplanarityObstructionE3orE4(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); int u, d, XorY; // Minor E3 gp_UpdateVertexFuturePertinentChild(theGraph, theGraphIC(theGraph)->x, theGraphIC(theGraph)->v); gp_UpdateVertexFuturePertinentChild(theGraph, theGraphIC(theGraph)->y, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, theGraphIC(theGraph)->x, theGraphIC(theGraph)->v) || FUTUREPERTINENT(theGraph, theGraphIC(theGraph)->y, theGraphIC(theGraph)->v)) { if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; gp_UpdateVertexFuturePertinentChild(theGraph, theGraphIC(theGraph)->x, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, theGraphIC(theGraph)->x, theGraphIC(theGraph)->v)) XorY = theGraphIC(theGraph)->x; else XorY = theGraphIC(theGraph)->y; /* The cases of X future pertinent and Y future pertinent are the same except for the bicomp external face marking (because parameter order is important) */ if (XorY == theGraphIC(theGraph)->x) { if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->w) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->y, IC->r) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->x) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->w, IC->y) != OK) return NOTOK; } if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; if (_FindUnembeddedEdgeToAncestor(theGraph, XorY, &u, &d) != TRUE) return NOTOK; if (theGraph->functions->fpMarkDFSPath(theGraph, u, IC->v) != OK || theGraph->functions->fpMarkDFSPath(theGraph, XorY, d) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkEdge(theGraph, u, d) != OK || _AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; return OK; } /* Otherwise, isolate Minor E4 (reduce to minor A) */ if (_FindUnembeddedEdgeToAncestor(theGraph, IC->w, &u, &d) != TRUE) return NOTOK; IC->v = u; IC->dw = d; return _IsolateOuterplanarityObstructionA(theGraph); } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK23Search.h000066400000000000000000000011271521450711600313440ustar00rootroot00000000000000#ifndef GRAPH_K23SEARCH_H #define GRAPH_K23SEARCH_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphOuterplanarity.h" #ifdef __cplusplus extern "C" { #endif // Create a K23Search Graph, i.e., subclass an Outerplanarity Graph by extending it // with the ability to perform a search for a subgraph homeomorphic to K_{2,3}. #define K23SEARCH_NAME "K23Search" int gp_ExtendWith_K23Search(graphP theGraph); int gp_Detach_K23Search(graphP theGraph); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK23Search.private.h000066400000000000000000000007531521450711600330210ustar00rootroot00000000000000#ifndef GRAPH_K23SEARCH_PRIVATE_H #define GRAPH_K23SEARCH_PRIVATE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphOuterplanarity.private.h" #ifdef __cplusplus extern "C" { #endif typedef struct { // Overloaded function pointers graphFunctionTableStruct functions; } K23SearchContext; extern int K23SEARCH_ID; #ifdef __cplusplus } #endif #endif graphK23Search_Extensions.c000066400000000000000000000230451521450711600335020ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include "graphK23Search.h" #include "graphK23Search.private.h" // Need to save and restore a graph flag related to IO #include "../io/graphIO.h" extern int _SearchForK23InBicomp(graphP theGraph, int v, int R); extern int _TestForK23GraphObstruction(graphP theGraph, int *degrees, int *imageVerts); extern int _getImageVertices(graphP theGraph, int *degrees, int maxDegree, int *imageVerts, int maxNumImageVerts); extern int _TestSubgraph(graphP theSubgraph, graphP theGraph); /* Forward declarations of overloading functions */ int _K23Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R); int _K23Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); int _K23Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); int _K23Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph); /* Forward declarations of functions used by the extension system */ void *_K23Search_DupContext(void *pContext, void *theGraph); int _K23Search_CopyData(void *, void *); void _K23Search_FreeContext(void *); /**************************************************************************** * K23SEARCH_ID - the variable used to hold the integer identifier for this * extension, enabling this feature's extension context to be distinguished * from other features' extension contexts that may be attached to a graph. ****************************************************************************/ int K23SEARCH_ID = 0; /**************************************************************************** gp_ExtendWith_K23Search() This function adjusts the graph data structure to attach the K2,3 search feature. ****************************************************************************/ int gp_ExtendWith_K23Search(graphP theGraph) { K23SearchContext *context = NULL; if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the K2,3 search feature has already been attached to the graph // then there is no need to attach it again gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) { return OK; } // Ensure theGraph is an Outerplanarity Graph if (gp_ExtendWith_Outerplanarity(theGraph) != OK) return NOTOK; // Allocate a new extension context context = (K23SearchContext *)malloc(sizeof(K23SearchContext)); if (context == NULL) { return NOTOK; } // Put the overload functions into the context function table. // gp_AddExtension will overload the graph's functions with these, and // return the base function pointers in the context function table memset(&context->functions, 0, sizeof(graphFunctionTableStruct)); context->functions.fpHandleBlockedBicomp = _K23Search_HandleBlockedBicomp; context->functions.fpEmbedPostprocess = _K23Search_EmbedPostprocess; context->functions.fpCheckEmbeddingIntegrity = _K23Search_CheckEmbeddingIntegrity; context->functions.fpCheckObstructionIntegrity = _K23Search_CheckObstructionIntegrity; // Store the K23 search context, including the data structure and the // function pointers, as an extension of the graph if (gp_AddExtension(theGraph, &K23SEARCH_ID, (void *)context, _K23Search_DupContext, _K23Search_CopyData, _K23Search_FreeContext, &context->functions) != OK) { _K23Search_FreeContext(context); context = NULL; return NOTOK; } return OK; } /******************************************************************** gp_Detach_K23Search() ********************************************************************/ int gp_Detach_K23Search(graphP theGraph) { return gp_RemoveExtension(theGraph, K23SEARCH_ID); } /******************************************************************** _K23Search_DupContext() ********************************************************************/ void *_K23Search_DupContext(void *pContext, void *theGraph) { K23SearchContext *context = (K23SearchContext *)pContext; K23SearchContext *newContext = (K23SearchContext *)malloc(sizeof(K23SearchContext)); if (newContext != NULL) { *newContext = *context; } return newContext; } /******************************************************************** _K23Search_CopyData() ********************************************************************/ int _K23Search_CopyData(void *dstContext, void *srcContext) { return OK; } /******************************************************************** _K23Search_FreeContext() ********************************************************************/ void _K23Search_FreeContext(void *pContext) { free(pContext); } /******************************************************************** ********************************************************************/ int _K23Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK23) { // If R is the root of a descendant bicomp of v, we push it, but then we know the search for K2,3 // will be successful and return NONEMBEDDABLE because this condition corresponds to minor A, which // is a K2,3. Thus, an "OK to proceed with Walkdown searching elsewhere" result cannot happen, // so we don't have to test for it to detect if we have to pop these two back off the stack. if (R != RootVertex) sp_Push2(theGraph->theStack, R, 0); // The possible results here are NONEMBEDDABLE if a K2,3 homeomorph is found, or OK if only // a K4 was found and unblocked such that it is OK for the Walkdown to continue searching // elsewhere. Note that the OK result can only happen if RootVertex==R since minor E can only // happen on a child bicomp of vertex v, not a descendant bicomp. return _SearchForK23InBicomp(theGraph, v, R); } else { K23SearchContext *context = NULL; gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpHandleBlockedBicomp(theGraph, v, RootVertex, R); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K23Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult) { int savedEmbedFlags = 0, savedZEROBASEDIO = 0; // For K2,3 search, we just return the edge embedding result because the // search result has been obtained already. if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK23) { if (edgeEmbeddingResult == OK) { // When a graph does not contain a K2,3 homeomorph, the embedding // is meaningless, so we empty it out. We preserve the embedFlags // to ensure post-processing continues as expected. savedEmbedFlags = gp_GetEmbedFlags(theGraph); savedZEROBASEDIO = gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO; gp_ResetGraphStorage(theGraph); theGraph->embedFlags = savedEmbedFlags; theGraph->graphFlags &= savedZEROBASEDIO; } return edgeEmbeddingResult; } // When not searching for K2,3, we let the superclass do the work else { K23SearchContext *context = NULL; gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpEmbedPostprocess(theGraph, v, edgeEmbeddingResult); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K23Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK23) { return OK; } // When not searching for K2,3, we let the superclass do the work else { K23SearchContext *context = NULL; gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckEmbeddingIntegrity(theGraph, origGraph); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K23Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph) { // When searching for K2,3, we ensure that theGraph is a subgraph of // the original graph and that it contains a K2,3 homeomorph if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK23) { int degrees[4], imageVerts[5]; if (_TestSubgraph(theGraph, origGraph) != TRUE) return NOTOK; if (_getImageVertices(theGraph, degrees, 3, imageVerts, 5) != OK) return NOTOK; if (_TestForK23GraphObstruction(theGraph, degrees, imageVerts) == TRUE) { return OK; } return NOTOK; } // When not searching for K2,3, we let the superclass do the work else { K23SearchContext *context = NULL; gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckObstructionIntegrity(theGraph, origGraph); } } return NOTOK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK33Search.c000066400000000000000000002365361521450711600313560ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphK33Search.h" #include "graphK33Search.private.h" /* Imported functions */ // extern void _ClearAllVisitedFlagsInGraph(graphP); extern int _ClearAllVisitedFlagsInBicomp(graphP theGraph, int BicompRoot); extern int _ClearAllVisitedFlagsInOtherBicomps(graphP theGraph, int BicompRoot); extern void _ClearEdgeVisitedFlagsInUnembeddedEdges(graphP theGraph); extern int _FillVertexVisitedInfoInBicomp(graphP theGraph, int BicompRoot, int FillValue); // extern int _GetBicompSize(graphP theGraph, int BicompRoot); extern int _HideInternalEdges(graphP theGraph, int vertex); extern int _RestoreInternalEdges(graphP theGraph, int stackBottom); extern int _ClearInvertedFlagsInBicomp(graphP theGraph, int BicompRoot); extern int _ComputeEdgeRecordType(graphP theGraph, int a, int b, int edgeType); extern int _RestoreEdgeType(graphP theGraph, int u, int v); extern int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink); extern int _JoinBicomps(graphP theGraph); extern int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns); extern int _OrientVerticesInEmbedding(graphP theGraph); // extern void _InvertVertex(graphP theGraph, int V); extern int _ClearAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); extern int _SetAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); extern int _OrientExternalFacePath(graphP theGraph, int u, int v, int w, int x); extern int _ChooseTypeOfNonplanarityMinor(graphP theGraph, int v, int R); extern int _MarkLowestXYPath(graphP theGraph); extern int _IsolateKuratowskiSubgraph(graphP theGraph, int v, int R); extern int _GetLeastAncestorConnection(graphP theGraph, int cutVertex); extern int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant); extern int _FindUnembeddedEdgeToSubtree(graphP theGraph, int ancestor, int SubtreeRoot, int *pDescendant); extern int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert); extern int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant); extern int _DeleteUnmarkedVerticesAndEdges(graphP theGraph); extern int _IsolateMinorE1(graphP theGraph); #ifndef USE_MERGEBLOCKER extern int _IsolateMinorE2(graphP theGraph); #endif extern int _IsolateMinorE3(graphP theGraph); extern int _IsolateMinorE4(graphP theGraph); extern int _MarkDFSPathsToDescendants(graphP theGraph); extern int _AddAndMarkUnembeddedEdges(graphP theGraph); extern void _K33Search_InitEdgeRec(K33SearchContext *context, int e); /* Private functions for K_{3,3} searching. */ int _SearchForK33InBicomp(graphP theGraph, K33SearchContext *context, int v, int R); int _RunExtraK33Tests(graphP theGraph, K33SearchContext *context); int _SearchForMinorE1(graphP theGraph); int _FinishIsolatorContextInitialization(graphP theGraph, K33SearchContext *context); int _SearchForDescendantExternalConnection(graphP theGraph, K33SearchContext *context, int cutVertex, int u_max); int _Fast_GetLeastAncestorConnection(graphP theGraph, K33SearchContext *context, int cutVertex); int _GetAdjacentAncestorInRange(graphP theGraph, K33SearchContext *context, int vertex, int closerAncestor, int fartherAncestor); int _FindExternalConnectionDescendantEndpoint(graphP theGraph, int ancestor, int cutVertex, int *pDescendant); int _SearchForMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int *pMergeBlocker); int _FindK33WithMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int mergeBlocker); int _TestForZtoWPath(graphP theGraph); int _TestForStraddlingBridge(graphP theGraph, K33SearchContext *context, int u_max); int _K33Search_DeleteUnmarkedEdgesInBicomp(graphP theGraph, K33SearchContext *context, int BicompRoot); int _K33Search_DeleteEdge(graphP theGraph, K33SearchContext *context, int e); int _ReduceBicomp(graphP theGraph, K33SearchContext *context, int R); int _ReduceExternalFacePathToEdge(graphP theGraph, K33SearchContext *context, int u, int x, int edgeType); int _ReduceXYPathToEdge(graphP theGraph, K33SearchContext *context, int u, int x, int edgeType); int _RestoreReducedPath(graphP theGraph, K33SearchContext *context, int e); int _RestoreAndOrientReducedPaths(graphP theGraph, K33SearchContext *context); int _MarkStraddlingBridgePath(graphP theGraph, int u_min, int u_max, int u_d, int d); int _IsolateMinorE5(graphP theGraph); int _IsolateMinorE6(graphP theGraph, K33SearchContext *context); int _IsolateMinorE7(graphP theGraph, K33SearchContext *context); /**************************************************************************** _SearchForK33InBicomp() ****************************************************************************/ int _SearchForK33InBicomp(graphP theGraph, K33SearchContext *context, int v, int R) { isolatorContextP IC = theGraphIC(theGraph); int tempResult; /* Begin by determining which non-planarity minor is detected */ if (_ChooseTypeOfNonplanarityMinor(theGraph, v, R) != OK) return NOTOK; /* If minor A is selected, then the root of the oriented bicomp has been changed */ else R = IC->r; /* Minors A to D result in the desired K_{3,3} homeomorph, so we isolate it and return NONEMBEDDABLE. */ if (theGraphIC(theGraph)->minorType & (MINORTYPE_A | MINORTYPE_B | MINORTYPE_C | MINORTYPE_D)) { /* First we restore the orientations of the vertices in the one bicomp we have messed with so that there is no confusion. */ if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) return NOTOK; /* Next we restore the orientation of the embedding so we can restore the reduced paths (because we avoid modifying the Kuratowski subgraph isolator to restore reduced paths, which are a construct of the K_{3,3} search). */ if (_OrientVerticesInEmbedding(theGraph) != OK || _RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; /* Next we simply call the Kuratowski subgraph isolation since we know now that it will isolate a K_{3,3}. For minor A, we need to set up the stack that would be available immediately after a Walkdown failure. */ if (theGraphIC(theGraph)->minorType & MINORTYPE_A) { sp_ClearStack(theGraph->theStack); sp_Push2(theGraph->theStack, R, NIL); } if (_IsolateKuratowskiSubgraph(theGraph, v, R) != OK) return NOTOK; return NONEMBEDDABLE; } /* For minor E (a K5 minor), we run the additional tests to see if minors E1 to E4 apply since these minors isolate a K_{3,3} entangled with the K5. This is the key location where GetLeastAncestorConnection() must be constant time. */ IC->ux = _Fast_GetLeastAncestorConnection(theGraph, context, IC->x); IC->uy = _Fast_GetLeastAncestorConnection(theGraph, context, IC->y); IC->uz = _Fast_GetLeastAncestorConnection(theGraph, context, IC->z); if (IC->z != IC->w || IC->uz > MAX(IC->ux, IC->uy) || (IC->uz < MAX(IC->ux, IC->uy) && IC->ux != IC->uy) || (IC->x != IC->px || IC->y != IC->py)) { if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) return NOTOK; if (_OrientVerticesInEmbedding(theGraph) != OK || _RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; if (_IsolateKuratowskiSubgraph(theGraph, v, R) != OK) return NOTOK; return NONEMBEDDABLE; } /* If the Kuratowski subgraph isolator will not isolate a K_{3,3} based on minor E, then a K5 homeomorph could be isolated. However, a K_{3,3} may still be tangled with the K5, so we now run the additional tests of the K_{3,3} search algorithm. If the search finds a K_{3,3} (tempResult of NONEMBEDDABLE), then we remove unwanted edges from the graph and return NONEMBEDDABLE. If the search has a fault (NOTOK), then we return. If the result is OK, then a K_{3,3} was not found at this time and we proceed with some clean-up work below. */ if ((tempResult = _RunExtraK33Tests(theGraph, context)) != OK) { if (tempResult == NONEMBEDDABLE) if (_DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; return tempResult; } /* The extra cases for finding a K_{3,3} did not succeed, so the bicomp rooted by R is either a K5 homeomorph (with at most a superficially entangled K_{3,3}) or we have made the special setting that allows us to detect the one "merge blocker" case that would be too costly to try now. Either way, we can safely reduce the bicomp to the 4-cycle (R, X, W, Y, R) and proceed with the planarity algorithm. We also restore the mixed orientation of the bicomp (i.e. the proper orientation in the context of the edge signs) because this code can work when ReduceBicomp doesn't do any actual work. */ if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) return NOTOK; if (_ReduceBicomp(theGraph, context, R) != OK) return NOTOK; /* Set visitedInfo values in the bicomp to the initialized state so the planarity algorithm can properly do the Walkup procedure in future steps */ if (_FillVertexVisitedInfoInBicomp(theGraph, IC->r, gp_GetN(theGraph)) != OK) return NOTOK; /* We now intend to ignore the pertinence of W (conceptually eliminating the connection from W to the current vertex). Note that none of the bicomp roots in the pertinentRootsList (nor their respective subtrees) will be visited again by the planarity algorithm because they must've been only pertinent. If they were future pertinent and pertinent, then we would've found a K_{3,3} by non-planarity minor B. Thus, the original Walkup costs that identified the pertinent bicomps we intend to ignore are one-time costs, preserving linear time. */ gp_SetVertexPertinentEdge(theGraph, IC->w, NIL); gp_SetVertexPertinentRootsList(theGraph, IC->w, NIL); return OK; } /**************************************************************************** _RunExtraK33Tests() ****************************************************************************/ #define USE_MERGEBLOCKER int _RunExtraK33Tests(graphP theGraph, K33SearchContext *context) { isolatorContextP IC = theGraphIC(theGraph); int u_max = MAX3(IC->ux, IC->uy, IC->uz); #ifndef USE_MERGEBLOCKER int u; #endif /* Case 1: If there is a pertinent or future pertinent vertex other than W on the lower external face path between X and Y (the points of attachment of the x-y path), then we can isolate a K_{3,3} homeomorph by Minor E1. */ if (_SearchForMinorE1(theGraph) != OK) return NOTOK; if (IC->w != IC->z) { if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE1(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* Case 2: If W/Z can make an external connection to an ancestor of V that is descendant to u_{max}, then a K_{3,3} homeomorph can be isolated with Minor E2. OPTIMIZATION: We do not need to check for this case. We avoid doing so because in very specially crafted cases it could be too costly if the connection doesn't exist. However, if the highest numbered ancestor H of the current vertex that has an external connection from W is a descendant u_{max} then we will discover a K_{3,3} by Minor A or B in step H (unless some other test succeeds at finding a K_{3,3} first), so we just let the non-planarity detector do its work since Minors A and B both provide a K_{3,3} when found. This happens because w is pertinent to H and future pertinent to u_max or an ancestor of u_max. Minor A will happen if, in step H, Walkdown descends to the bicomp containing the current vertex, x, y and w. Since x and y would still be future pertinent (they connect to u_max or higher, i.e. with lesser DFI, than u_max). Minor B will happen if the bicomp containing the current vertex, x, y and w is a descendant of a bicomp that blocks planarity in step H. The bicomp would be both pertinent (due to w's connection to H) and future pertinent(due to connections to ancestors of H by w, x and y). */ #ifndef USE_MERGEBLOCKER u = _SearchForDescendantExternalConnection(theGraph, context, IC->w, u_max); if (u > u_max) { IC->uz = u; if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE2(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } #endif /* Case 3: If X or Y can make an external connection to an ancestor of V that is descendant to u_{max}, then a K_{3,3} homeomorph can be isolated with Minor E3. NOTE: Due to the prior use of the Kuratowski subgraph isolator, we know that at most one of X, Y or W/Z could have an external connection to an ancestor of u_{max} = MAX(ux, uy, uz). OPTIMIZATION: We do not check for the lower connection required to find Minor E3 because it might ultimately be too costly. Instead, we mark the vertex with a 'merge blocker' of u_{max}. If the planar embedder attempts to merge the vertex prior to step u_{max}, then the embedder has found the desired connection and a K_{3,3} homeomorph is isolated at that time. */ #ifdef USE_MERGEBLOCKER context->VI[IC->x].mergeBlocker = u_max; #endif #ifndef USE_MERGEBLOCKER u = _SearchForDescendantExternalConnection(theGraph, context, IC->x, u_max); if (u > u_max) { IC->ux = u; if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE3(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } #endif #ifdef USE_MERGEBLOCKER context->VI[IC->y].mergeBlocker = u_max; #endif #ifndef USE_MERGEBLOCKER u = _SearchForDescendantExternalConnection(theGraph, context, IC->y, u_max); if (u > u_max) { IC->uy = u; if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE3(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } #endif /* Case 4: If there exists any x-y path with points of attachment px and py such that px!=x or py!=y, then a K_{3,3} homeomorph can be isolated with Minor E4. */ // Prior tests to choose the type of non-planarity minor selected the highest // x-y path, so we need to clear the visited flags of that path before marking // instead the x-y path with the lowest attachment points (those closest to W // along the external face). if (_ClearAllVisitedFlagsInBicomp(theGraph, IC->r) != OK) return NOTOK; // Now mark the lowest x-y path so that we can test whether _any_ x-y path // has points of attachment, px or py, below x or y, respectively (where // below means closer to W than x or y, respectively, along the external face). if (_MarkLowestXYPath(theGraph) != OK) return NOTOK; // Now we test for E4 based on whether px!=x or py!=y. Note that the inequality // test is sufficient because not equal means attached lower by the time we are // testing for E4 because the prior test for non-planarity minor C already ruled // out the possibility that inequality could mean attached higher than x or y. if (IC->px != IC->x || IC->py != IC->y) { if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE4(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* Case 5: If the x-y path contains an internal vertex that starts a second internal path from the internal vertex to W/Z, then a K_{3,3} homeomorph can be isolated with Minor E5. */ // Since the E4 test above has already marked the lowest X-Y path, and only // the lowest one could possibly have a Z-to-W path attached to it, we // simply reuse the x-y path from E4 here in the E5 test. // (NOTE: Only the lowest X-Y path could have a Z-to-W path because all // bicomps are planar embeddings, and so a Z-to-W path emanating // from a higher X-Y path would cross the lowest one, violating the // planarity of the bicomp). if (_TestForZtoWPath(theGraph) != OK) return NOTOK; if (gp_GetVisited(theGraph, IC->w)) { if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE5(theGraph) != OK) return NOTOK; return NONEMBEDDABLE; } /* Case 6: If uz < u_{max} and there is an external connection (other than external connections involving X, Y and W/Z) between an ancestor of u_{max} and a vertex in the range [V...u_{max}), then a K_{3,3} homeomorph can be isolated with Minor E6. OPTIMIZATION: See _TestForStraddlingBridge() */ if (IC->uz < u_max) { if (gp_IsVertex(theGraph, _TestForStraddlingBridge(theGraph, context, u_max))) { if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE6(theGraph, context) != OK) return NOTOK; return NONEMBEDDABLE; } } /* Case 7: If ux < u_{max} or uy < u_{max} and there is an external connection between an ancestor of u_{max} and a vertex in the range [V...u_{max}) (except for external connections involving X, Y and W/Z), then a K_{3,3} homeomorph can be isolated with Minor E7. OPTIMIZATION: Same as Case 6.*/ if (IC->ux < u_max || IC->uy < u_max) { if (gp_IsVertex(theGraph, _TestForStraddlingBridge(theGraph, context, u_max))) { if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE7(theGraph, context) != OK) return NOTOK; return NONEMBEDDABLE; } } /* If none of the tests found a K_{3,3}, then we return OK to indicate that nothing went wrong, but a K_{3,3} was not found. */ return OK; } /**************************************************************************** _SearchForMinorE1() Search along the external face below the x-y path for a vertex Z other than W that is future pertinent or pertinent. ****************************************************************************/ int _SearchForMinorE1(graphP theGraph) { int Z = theGraphIC(theGraph)->px, ZPrevLink = 1; Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); while (Z != theGraphIC(theGraph)->py) { if (Z != theGraphIC(theGraph)->w) { gp_UpdateVertexFuturePertinentChild(theGraph, Z, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, Z, theGraphIC(theGraph)->v)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->uz = _GetLeastAncestorConnection(theGraph, Z); return OK; } else if (PERTINENT(theGraph, Z)) { /* Swap the roles of W and Z */ theGraphIC(theGraph)->z = theGraphIC(theGraph)->w; theGraphIC(theGraph)->w = Z; /* If the new W (indicated by Z) was on the path (R, X, old W) then the new Z (the old W, which has no type mark) is on the path (X, new W, new Z, Y) so we change the type new Z to being on the RYW path. Otherwise, the order is (X, new Z, new W, Y), so the new Z (old W with no type) is type changed to be on the RXW path.*/ if (gp_GetObstructionMark(theGraph, Z) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW) gp_ResetObstructionMark(theGraph, theGraphIC(theGraph)->z, ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW); else gp_ResetObstructionMark(theGraph, theGraphIC(theGraph)->z, ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW); /* For completeness, we change the new W to type unknown */ gp_ClearObstructionMark(theGraph, theGraphIC(theGraph)->w); /* The external activity ancestor connection of the new Z must be obtained */ theGraphIC(theGraph)->uz = _GetLeastAncestorConnection(theGraph, theGraphIC(theGraph)->z); return OK; } } Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } return OK; } /**************************************************************************** _FinishIsolatorContextInitialization() Once it has been decided that a desired subgraph can be isolated, it becomes safe to finish the isolator context initialization. ****************************************************************************/ int _FinishIsolatorContextInitialization(graphP theGraph, K33SearchContext *context) { isolatorContextP IC = theGraphIC(theGraph); /* Restore the orientation of the bicomp on which we're working, then perform orientation of all vertices in graph. (An unnecessary but polite step that simplifies the description of key states of the data structures). */ if (_OrientVerticesInBicomp(theGraph, IC->r, 1) != OK) return NOTOK; if (_OrientVerticesInEmbedding(theGraph) != OK) return NOTOK; /* Restore any paths that were reduced to single edges */ if (_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; /* We assume that the current bicomp has been marked appropriately, but we must now clear the visitation flags of all other bicomps. */ if (_ClearAllVisitedFlagsInOtherBicomps(theGraph, IC->r) != OK) return NOTOK; /* To complete the normal behavior of _ClearAllVisitedFlagsInGraph() in the normal isolator context initialization, we also have to clear the visited flags on all edges that have not yet been embedded */ _ClearEdgeVisitedFlagsInUnembeddedEdges(theGraph); /* Now we can find the descendant ends of unembedded back edges based on the ancestor settings ux, uy and uz. */ if (_FindExternalConnectionDescendantEndpoint(theGraph, IC->ux, IC->x, &IC->dx) != OK || _FindExternalConnectionDescendantEndpoint(theGraph, IC->uy, IC->y, &IC->dy) != OK || _FindExternalConnectionDescendantEndpoint(theGraph, IC->uz, IC->z, &IC->dz) != OK) return NOTOK; /* Finally, we obtain the descendant end of an unembedded back edge to the current vertex. */ if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; return OK; } /**************************************************************************** _Fast_GetLeastAncestorConnection() This function searches for an ancestor of the current vertex v adjacent by a cycle edge to the given cutVertex or one of its DFS descendants appearing in a separated bicomp. The given cutVertex is assumed to be future pertinent such that either the leastAncestor or the lowpoint of a separated DFS child is less than v. We obtain the minimum possible connection from the cutVertex to an ancestor of v. This function performs the same operation as _GetLeastAncestorConnection(), except in constant time. ****************************************************************************/ int _Fast_GetLeastAncestorConnection(graphP theGraph, K33SearchContext *context, int cutVertex) { int ancestor = gp_GetVertexLeastAncestor(theGraph, cutVertex); int child = context->VI[cutVertex].separatedDFSChildList; if (gp_IsVertex(theGraph, child) && ancestor > gp_GetVertexLowpoint(theGraph, child)) ancestor = gp_GetVertexLowpoint(theGraph, child); return ancestor; } /**************************************************************************** _GetAdjacentAncestorInRange() Returns the ancestor of theVertex that is adjacent to theVertex by an unembedded back edge and has a DFI strictly between closerAncestor and fartherAncestor. Returns NIL if theVertex has no such neighboring ancestor. ****************************************************************************/ int _GetAdjacentAncestorInRange(graphP theGraph, K33SearchContext *context, int theVertex, int closerAncestor, int fartherAncestor) { int e = context->VI[theVertex].backEdgeList; while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) < closerAncestor && gp_GetNeighbor(theGraph, e) > fartherAncestor) return gp_GetNeighbor(theGraph, e); e = gp_GetNextEdge(theGraph, e); if (e == context->VI[theVertex].backEdgeList) e = NIL; } return NIL; } /**************************************************************************** _SearchForDescendantExternalConnection() Search the cutVertex and each separated child subtree for an external connection to a vertex ancestor to the current vertex V and descendant to u_max. The function returns the descendant of u_max found to have an external connection to the given cut vertex. ****************************************************************************/ int _SearchForDescendantExternalConnection(graphP theGraph, K33SearchContext *context, int cutVertex, int u_max) { isolatorContextP IC = theGraphIC(theGraph); int u2 = _GetAdjacentAncestorInRange(theGraph, context, cutVertex, IC->v, u_max); int child, descendant; // Test cutVertex for an external connection to descendant of u_max via direct back edge if (gp_IsVertex(theGraph, u2)) return u2; // If there is no direct back edge connection from the cut vertex // to a vertex on the path between V and u_max, then we will // look for such a connection in the DFS subtrees rooted by // separated DFS children of the vertex (ignoring those whose // lowpoint indicates that they make no external connections) // Begin by pushing the separated DFS children of the cut vertex with // lowpoints indicating connections to ancestors of the current vertex. sp_ClearStack(theGraph->theStack); child = gp_GetVertexSortedDFSChildList(theGraph, cutVertex); while (gp_IsVertex(theGraph, child)) { if (gp_GetVertexLowpoint(theGraph, child) < IC->v && gp_IsSeparatedDFSChild(theGraph, child)) sp_Push(theGraph->theStack, child); child = gp_GetVertexNextDFSChild(theGraph, cutVertex, child); } // Now process the stack until it is empty or until we've found the desired connection. while (!sp_IsEmpty(theGraph->theStack)) { sp_Pop(theGraph->theStack, descendant); // If the vertex has a lowpoint indicating that it makes no external connections, // then skip the subtree rooted by the vertex if (gp_GetVertexLowpoint(theGraph, descendant) < IC->v) { // Check the subtree root for the desired connection. u2 = _GetAdjacentAncestorInRange(theGraph, context, descendant, IC->v, u_max); if (gp_IsVertex(theGraph, u2)) return u2; // Push each child as a new subtree root to be considered, except skip those whose lowpoint is too great. child = gp_GetVertexSortedDFSChildList(theGraph, descendant); while (gp_IsVertex(theGraph, child)) { if (gp_GetVertexLowpoint(theGraph, child) < IC->v) sp_Push(theGraph->theStack, child); child = gp_GetVertexNextDFSChild(theGraph, descendant, child); } } } // The only external connections from the cutVertex lead to u_max, so return it. return u_max; } /**************************************************************************** _FindExternalConnectionDescendantEndpoint() This operation is similar to _FindUnembeddedEdgeToAncestor() except that we need to be more precise in this case, finding an external connection from a given cut vertex to a *particular* given ancestor. NOTE: By external we don't mean externall active so much as not embedded in the bicomp containing the cut vertex. Returns OK if it finds that either the given cutVertex or one of its descendants in a separated bicomp has an unembedded back edge connection to the given ancestor vertex. Returns NOTOK otherwise (it is an error to not find the descendant because this function is only called if _SearchForDescendantExternalConnection() has already determined the existence of the descendant). ****************************************************************************/ int _FindExternalConnectionDescendantEndpoint(graphP theGraph, int ancestor, int cutVertex, int *pDescendant) { int child, e; // Check whether the cutVertex is directly adjacent to the ancestor // by an unembedded back edge. e = gp_GetVertexFwdEdgeList(theGraph, ancestor); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) == cutVertex) { *pDescendant = cutVertex; return OK; } e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, ancestor)) e = NIL; } // Now check the descendants of the cut vertex to see if any make // a connection to the ancestor. child = gp_GetVertexSortedDFSChildList(theGraph, cutVertex); while (gp_IsVertex(theGraph, child)) { if (gp_GetVertexLowpoint(theGraph, child) < theGraphIC(theGraph)->v && gp_IsSeparatedDFSChild(theGraph, child)) { if (_FindUnembeddedEdgeToSubtree(theGraph, ancestor, child, pDescendant) == TRUE) return OK; } child = gp_GetVertexNextDFSChild(theGraph, cutVertex, child); } return NOTOK; } /**************************************************************************** _SearchForMergeBlocker() This function helps to implement the merge blocking optimization of _SearchForDescendantExternalConnection(). The function RunExtraK33Tests() sets a mergeBlocker rather than run _SearchForDescendantExternalConnection() in certain cases. This procedure is called by MergeBicomps to test the embedding stack for a merge blocker before merging any biconnected components. If a merge blocker is found, then FindK33WithMergeBlocker() is called and ultimately the embedder's Walkdown function is terminated since a K_{3,3} is isolated. Returns OK on success (whether or not the search found a merge blocker) NOTOK on internal function failure pMergeBlocker is set to NIL unless a merge blocker is found. ****************************************************************************/ int _SearchForMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int *pMergeBlocker) { stackP tempStack; int Z; /* Set return result to 'not found' then return if there is no stack to inspect */ *pMergeBlocker = NIL; if (sp_IsEmpty(theGraph->theStack)) return OK; /* Create a copy of the embedding stack */ tempStack = sp_Duplicate(theGraph->theStack); if (tempStack == NULL) return NOTOK; /* Search the copy of the embedding stack for a merge blocked vertex */ while (!sp_IsEmpty(tempStack)) { sp_Pop2_Discard(tempStack); /* Move (R, Rout) out of the way */ sp_Pop2_Discard1(tempStack, Z); /* Get Z, discard ZPrevLink */ if (gp_IsVertex(theGraph, context->VI[Z].mergeBlocker) && context->VI[Z].mergeBlocker < v) { *pMergeBlocker = Z; break; } } sp_Free(&tempStack); return OK; } /**************************************************************************** _FindK33WithMergeBlocker() This function completes the merge blocking optimization by isolating a K_{3,3} based on minor E3 if a merge blocked vertex was previously found. Returns OK on success, NOTOK on internal function failure ****************************************************************************/ int _FindK33WithMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int mergeBlocker) { int R, RPrevLink, u_max, u, e; isolatorContextP IC = theGraphIC(theGraph); /* First, we orient the vertices so we can successfully restore all of the reduced paths. This needs to be done before reconstructing the context for CASE 3 of RunExtraK33Tests() because the reconstruction involves using the Walkup to v from a descendant of v, which will not work if the descendant is in one of the reduced paths. */ if (_OrientVerticesInEmbedding(theGraph) != OK || _RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; /* Reconstruct the context that was present for CASE 3 of RunExtraK33Tests() when we decided to set a mergeBlocker rather than calling _SearchForDescendantExternalConnection() */ /* Obtain the root of the bicomp containing the mergeBlocker. */ RPrevLink = 1; R = mergeBlocker; while (gp_IsNotVirtualVertex(theGraph, R)) R = _GetNeighborOnExtFace(theGraph, R, &RPrevLink); /* Switch the 'current step' variable v to be equal to the non-virtual counterpart of the bicomp root. */ IC->v = gp_GetVertexFromBicompRoot(theGraph, R); /* Reset the visitation, pertinence and future pertinence settings from step u_max for step v */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { gp_SetVertexVisitedInfo(theGraph, v, gp_GetN(theGraph)); gp_SetVertexPertinentEdge(theGraph, v, NIL); gp_SetVertexPertinentRootsList(theGraph, v, NIL); // Any calls to actually determine FUTUREPERTINENT status for a vertex w will actually invoke // gp_UpdateVertexFuturePertinentChild(theGraph, w, v) beforehand, so only need to reinitialize here gp_SetVertexFuturePertinentChild(theGraph, v, gp_GetVertexSortedDFSChildList(theGraph, v)); } /* Restore the pertinence settings of step v by doing the Walkup for each back edge that was not embedded when step v was originally performed. */ e = gp_GetVertexFwdEdgeList(theGraph, IC->v); while (gp_IsEdge(theGraph, e)) { theGraph->functions->fpWalkUp(theGraph, IC->v, e); e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, IC->v)) e = NIL; } /* Next, we make the standard initialization calls for when we have found a non-planarity condition. */ sp_ClearStack(theGraph->theStack); if (_ChooseTypeOfNonplanarityMinor(theGraph, IC->v, R) != OK) return NOTOK; IC->ux = _GetLeastAncestorConnection(theGraph, IC->x); IC->uy = _GetLeastAncestorConnection(theGraph, IC->y); IC->uz = _GetLeastAncestorConnection(theGraph, IC->z); u_max = MAX3(IC->ux, IC->uy, IC->uz); /* Perform the remainder of CASE 3 of RunExtraK33Tests() */ if (mergeBlocker == IC->x) { u = _SearchForDescendantExternalConnection(theGraph, context, IC->x, u_max); if (u > u_max) { IC->ux = u; if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE3(theGraph) != OK) return NOTOK; } else return NOTOK; } else if (mergeBlocker == IC->y) { u = _SearchForDescendantExternalConnection(theGraph, context, IC->y, u_max); if (u > u_max) { IC->uy = u; if (_FinishIsolatorContextInitialization(theGraph, context) != OK || _IsolateMinorE3(theGraph) != OK) return NOTOK; } else return NOTOK; } else return NOTOK; /* Do the final clean-up to obtain the K_{3,3} */ if (_DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _TestForZtoWPath() This function tests whether there is a path inside the bicomp leading from W to some internal node of the x-y path. If there is, the path is marked (the visited flags of its vertices and edges are set). Upon function return, the marking (visited flag setting) of W distinguishes whether the path was found. The function returns NOTOK on internal error, OK otherwise. Preconditions: The X-Y path is marked visited by a prior invocation of the method _MarkLowestXYPath() above. So, we start a depth first search from W to find a visited vertex, except we prune the search to ignore vertices whose obstruction type is other than unknown. This ensures the path found, if any, avoids external face vertices, including avoiding X and Y. Furthermore, the path search is completed without traversing to R due to the obstructing X-Y path. The depth first search has to "mark" the vertices it has seen as visited, but the visited flags are already in use to distinguish the X-Y path. So, we reuse the visitedInfo setting of each vertex. The core planarity algorithm makes settings between 0 and N, so we will regard all of those as indicating 'unvisited' by this method, and use -1 to indicate visited. These markings need not be cleaned up because, if the desired path is found the a K_{3,3} is isolated and if the desired path is not found then the bicomp is reduced and the visitedInfo in the remaining vertices are set appropriately for future Walkup processing of the core planarity algorithm. For each vertex we visit, if it is an internal vertex on the X-Y path (i.e. visited flag set and obstruction type unknown), then we want to stop and unroll the stack to obtain the desired path (described below). If the vertex is internal but not on the X-Y path (i.e. visited flag clear and obstruction type unknown), then we want to visit its neighbors, except those already marked visited by this method (i.e. those with visitedInfo of -1) and those with a known obstruction type. We want to manage the stack so that it when the desired vertex is found, the stack contains the desired path. So, we do not simply push all the neighbors of the vertex being visited. First, given that we have popped some vertex-edge pair (v, e), we push *only* the next edge after e in v's adjacency list (starting with the first if e is NIL) that leads to a new 'eligible' vertex. An eligible vertex is one whose obstruction type is unknown and whose visitedInfo is other than -1 (so, internal and not yet processed by this method). Second, when we decide a new vertex w adjacent to v is eligible, we push not only (v, e) but also (w, NIL). When we later pop the vertex-edge pair containing NIL, we know that the vertex obstruction type is unknown so we test whether its visited flag is set (indicating an internal vertex on the X-Y path). If so, then we can stop the depth first search, then use the vertices and edges remaining on the stack to mark the desired path from the external face vertex W to an internal vertex Z on the X-Y path. If we pop (v, NIL) and find that the visited flag of v is clear, then it is not the desired connection endpoint to the X-Y path. We need to process all paths extending from it, but we don't want any of those paths to cycle back to this vertex, so we mark it as ineligible by putting -1 in its visitedInfo member. This is also the case in which the _first_ edge record e leading from v to an eligible vertex w is obtained, whereupon we push both (v, e) and (w, NIL). Eventually all paths leading from w to eligible vertices will be explored, and if none find the desired vertex connection to the X-Y path, then (v, e) is popped. Now we search the adjacency list of v starting after e to find the _next_ edge record that indicates the an eligible vertex to visit. None of the vertices processed while visiting paths extending from w will be eligible anymore, so it can be seen that this method is a depth first search. If no remaining edges from v indicate eligible vertices, then nothing is pushed and we simply go to the next iteration, which pops a 2-tuple containing the vertex u and the edge record e that points to v. Finally, if the stack empties without finding the desired vertex, then the first loop ends, and the second main loop does not mark a path because the stack is empty. ****************************************************************************/ int _TestForZtoWPath(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); int v, e, w; sp_ClearStack(theGraph->theStack); sp_Push2(theGraph->theStack, IC->w, NIL); while (!sp_IsEmpty(theGraph->theStack)) { sp_Pop2(theGraph->theStack, v, e); if (gp_IsNotEdge(theGraph, e)) { // If the vertex is visited, then it is a member of the X-Y path // Because it is being popped, its obstruction type is unknown because // that is the only kind of vertex pushed. // Hence, we break because we've found the desired path. if (gp_GetVisited(theGraph, v)) break; // Mark this vertex as being visited by this method (i.e. ineligible // to have processing started on it again) gp_SetVertexVisitedInfo(theGraph, v, -1); e = gp_GetFirstEdge(theGraph, v); } else e = gp_GetNextEdge(theGraph, e); // This while loop breaks on the first edge it finds that is eligible to be // pushed. Once that happens, we break. The successive edges of a vertex are // only pushed (see the else clause above) once all paths extending from v // through e have been explored and found not to contain the desired path while (gp_IsEdge(theGraph, e)) { w = gp_GetNeighbor(theGraph, e); // The test for w being a virtual vertex is just safeguarding the two subsequent calls, // but it can never happen due to the obstructing X-Y path. if (gp_IsNotVirtualVertex(theGraph, w) && gp_GetVertexVisitedInfo(theGraph, w) != -1 && gp_GetObstructionMark(theGraph, w) == ANYVERTEX_OBSTRUCTIONMARK_UNMARKED) { sp_Push2(theGraph->theStack, v, e); sp_Push2(theGraph->theStack, w, NIL); break; } e = gp_GetNextEdge(theGraph, e); } } while (!sp_IsEmpty(theGraph->theStack)) { sp_Pop2(theGraph->theStack, v, e); gp_SetVisited(theGraph, v); gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); } return OK; } /**************************************************************************** _TestForStraddlingBridge() We proceed on the path [V...u_{max}) from the current vertex V up to and excluding u_{max}. For each vertex p, we test whether p has a least ancestor less than u_{max} and whether p has a DFS child c that is not an ancestor of X, Y and W and that has a connection to an ancestor of u_{max} (in other words, whether the child C has a lowpoint less than u_{max}). The sortedDFSChildLIst of the vertex p is scanned for the separated DFS child c of least lowpoint, excluding the ancestor of X, Y and W. If no bridge straddling u_{max} is found, the function returns NIL. If a straddling bridge is found, the function returns a descendant d of p in the subtree rooted by c such that d has a leastAncestor less than u_{max}. Given the vertex d, the path through the straddling bridge required in Minors E6 and E7 is easy to identify: Mark the DFS tree path from d to p, and add and mark the edge from d to its least ancestor. OPTIMIZATION: If a straddling bridge is not found, then in each tree edge of the path [V...u_{max}) we set the member noStraddle equal to u_{max}. Then, we modify the above stated routine so that if it is testing for a straddling bridge of u_{max} along this path, it will stop if it encounters an edge with noStraddle equal to u_{max}. Also, the optimization will only set noStraddle equal to u_{max} on the portion of the path that is traversed. Finally, if noStraddle is set to a value other than NIL, the setting will be ignored and it will not be changed. Due to this optimization, we do not traverse a path more than once to find out whether a vertex on the path has a bridge that straddles u_{max}. This leaves two questions: 1) What if a future step must determine whether there is a straddling bridge of an ancestor of u_{max}? 2) What if a future step must determine whether there is a straddling bridge of a descendant of u_{max}? The condition described in the first question cannot occur because it would imply the ability to detect a straddling bridge now. The condition described by the second question may occur, but in the future step, the bicomp now being tested for a K_{3,3} will be part of a straddling bridge in that future step. Thus, the straddling bridge query is asked at most twice along any DFS tree path. ****************************************************************************/ int _TestForStraddlingBridge(graphP theGraph, K33SearchContext *context, int u_max) { isolatorContextP IC = theGraphIC(theGraph); int p, c, d, excludedChild, e; p = IC->v; excludedChild = gp_GetDFSChildFromBicompRoot(theGraph, IC->r); d = NIL; // Starting at V, traverse the ancestor path to u_max looking for a straddling bridge while (p > u_max) { // If we find a direct edge from p to an ancestor of u_max, the break. if (gp_GetVertexLeastAncestor(theGraph, p) < u_max) { d = p; break; } // Check for a path from p to an ancestor of u_max using the child of p // with the least Lowpoint, except the child that is an ancestor of X, Y and W. // It is possible to do this just using the sortedDFSChildList, but no point // in not using the separatedDFSChildList /* { int c = gp_GetVertexSortedDFSChildList(theGraph, p); while (gp_IsVertex(c)) { if (c != excludedChild && gp_IsSeparatedDFSChild(theGraph, c)) { if (gp_GetVertexLowpoint(theGraph, c) < u_max) break; } c = gp_GetVertexNextDFSChild(theGraph, p, c); } } */ c = context->VI[p].separatedDFSChildList; if (c == excludedChild) c = LCGetNext(context->separatedDFSChildLists, c, c); if (gp_IsVertex(theGraph, c) && gp_GetVertexLowpoint(theGraph, c) < u_max) { _FindUnembeddedEdgeToSubtree(theGraph, gp_GetVertexLowpoint(theGraph, c), c, &d); break; } // Check for noStraddle of u_max, break if found e = gp_GetFirstEdge(theGraph, p); if (context->E[e].noStraddle == u_max) break; // Go to the next ancestor excludedChild = p; p = gp_GetVertexParent(theGraph, p); } // If d is NIL, then no straddling bridge was found, so we do the noStraddle optimization. if (gp_IsNotVertex(theGraph, d)) { c = IC->v; while (c != p) { e = gp_GetFirstEdge(theGraph, c); if (gp_IsVertex(theGraph, context->E[e].noStraddle)) break; context->E[e].noStraddle = u_max; c = gp_GetVertexParent(theGraph, c); } } // Return either NIL indicating no bridge straddling u_max or the descendant d // used to help mark a straddling bridge that was found by this test. return d; } /**************************************************************************** _ReduceBicomp() We want to reduce the given biconnected component to a 4-cycle plus an internal edge connecting X and Y. Each edge is to be associated with a path from the original graph, preserving the depth first search tree paths that help connect the vertices R, X, Y, and W. If a K_{3,3} is later found, the paths are restored, but it is necessary to preserve the DFS tree so that functions like MarkDFSPath() will be able to pass through the restored bicomp. Also, if a K_{3,3} is later found due to the merge blocker optimization, then the internal X-Y path may be needed and, once the bicomp reduction is reversed, a full DFS subtree connecting all vertices in the bicomp will need to be restored or else functions that traverse the bicomp will not work. For example, _FindK33WithMergeBlocker() invokes ChooseTypeOfNonplanarityMinor() to help reconstruct the context under which the mergeBlocker was set. ChooseTypeOfNonplanarityMinor() calls _ClearAllVisitedFlagsInBicomp(), which depends on the DFS tree. NOTE: The following are some general steps taken in this method: 1) All edges in the bicomp are marked unvisited 2) selected paths are marked visited 3) unvisited edges are deleted 4) the edges of the bicomp are marked unvisited again 5) the remaining paths of the bicomp are reduced Some of the edges that get deleted in step 3 above may represent paths that were reduced in prior embedder iterations. We delete the reduction edge but not the path it represents. If a K_{3,3} is ever found, then the edges of these reduced paths are still in the graph, though not connected to anything important. The desired K_{3,3} is marked visited, but step 4 above ensures that these reduction paths are not marked visited. Hence, they will be deleted when the K_{3,3} is isolated, and this routine does not need to restore any reduced paths on the edges it deletes. We also don't (and don't have the time to) restore any reduction edges along the paths we intend to keep. ****************************************************************************/ int _ReduceBicomp(graphP theGraph, K33SearchContext *context, int R) { isolatorContextP IC = theGraphIC(theGraph); int min, max, A, A_edge, B, B_edge; int rxType, xwType, wyType, yrType, xyType; /* The vertices in the bicomp need to be oriented so that functions like MarkPathAlongBicompExtFace() will work. */ if (_OrientVerticesInBicomp(theGraph, R, 0) != OK) return NOTOK; /* The reduced edges start with a default type of 'tree' edge. The tests below, which identify the additional non-tree paths needed to complete the reduced bicomp, also identify which reduced edges need to be cycle edges.*/ rxType = xwType = wyType = yrType = xyType = EDGE_TYPE_PARENT; /* Now we calculate some values that help figure out the shape of the DFS subtree whose structure will be retained in the bicomp. */ min = MIN3(IC->x, IC->y, IC->w); max = MAX3(IC->x, IC->y, IC->w); // int mid = MAX3(MIN(IC->x, IC->y), MIN(IC->x, IC->w), MIN(IC->y, IC->w)); /* If the order of descendendancy from V goes first to X, then it can proceed either to W then Y or to Y then W */ if (min == IC->x) { /* A is a descendant adjacent to the current vertex by a cycle edge whose DFS tree path to either mid or max is combined with the cycle edge to form the path that will be reduced to the external face cycle edge (V, max). */ A_edge = gp_GetLastEdge(theGraph, IC->r); A = gp_GetNeighbor(theGraph, A_edge); yrType = EDGE_TYPE_BACK; /* If Y is max, then a path parallel to the X-Y path will be a second path reduced to a cycle edge. We find the neighbor B of min=X on the X-Y path. The edge (B, min) is a cycle edge that, along with the DFS tree path (B, ..., max), will be retained and reduced to a cycle edge. */ if (max == IC->y) { B_edge = gp_GetLastEdge(theGraph, IC->x); while (B_edge != gp_GetFirstEdge(theGraph, IC->x)) { if (gp_GetEdgeVisited(theGraph, B_edge)) break; B_edge = gp_GetPrevEdge(theGraph, B_edge); } if (!gp_GetEdgeVisited(theGraph, B_edge)) return NOTOK; B = gp_GetNeighbor(theGraph, B_edge); xyType = EDGE_TYPE_BACK; } /* Otherwise, W is max so we find the neighbor B of min=X on the lower external face path (X, ..., W), which excludes V. The cycle edge (B, min) and the DFS tree path (B, max) will be retained and reduced to a cycle edge.*/ else if (max == IC->w) { B_edge = gp_GetFirstEdge(theGraph, IC->x); B = gp_GetNeighbor(theGraph, B_edge); xwType = EDGE_TYPE_BACK; } else return NOTOK; } /* Otherwise, the order of descendancy from V goes first to Y, then it proceeds to either W then X or to X then W. The */ else { A_edge = gp_GetFirstEdge(theGraph, IC->r); A = gp_GetNeighbor(theGraph, A_edge); rxType = EDGE_TYPE_BACK; if (max == IC->x) { B_edge = gp_GetFirstEdge(theGraph, IC->y); while (B_edge != gp_GetLastEdge(theGraph, IC->y)) { if (gp_GetEdgeVisited(theGraph, B_edge)) break; B_edge = gp_GetNextEdge(theGraph, B_edge); } if (!gp_GetEdgeVisited(theGraph, B_edge)) return NOTOK; B = gp_GetNeighbor(theGraph, B_edge); xyType = EDGE_TYPE_BACK; } else if (max == IC->w) { B_edge = gp_GetLastEdge(theGraph, IC->y); B = gp_GetNeighbor(theGraph, B_edge); wyType = EDGE_TYPE_BACK; } else return NOTOK; } /* Now that we have collected the information on which cycle edge and which tree paths will actually be retained, we clear the visited flags so the current X-Y path will not be retained (an X-Y path formed mostly or entirely from DFS tree edges is retained). */ if (_ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; /* Now we mark the tree path from the maximum numbered vertex up to the bicomp root. This marks one of the following four paths: Case 1. (V, ..., X=min, ..., W=mid, ..., Y=max) Case 2. (V, ..., X=min, ..., Y=mid, ..., W=max) Case 3. (V, ..., Y=min, ..., W=mid, ..., X=max) Case 4. (V, ..., Y=min, ..., X=mid, ..., W=max) */ if (theGraph->functions->fpMarkDFSPath(theGraph, R, max) != OK) return NOTOK; /* Now we use A to mark a path on the external face corresponding to: Case 1. (V, ..., Y=max) Case 2. (V, ..., Y=mid) Case 3. (V, ..., X=max) Case 4. (V, ..., X=mid) */ if (theGraph->functions->fpMarkDFSPath(theGraph, min == IC->x ? IC->y : IC->x, A) != OK) return NOTOK; gp_SetEdgeVisited(theGraph, A_edge); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, A_edge)); /* Now we use B to mark either an X-Y path or a path of the external face corresponding to: Case 1. (X=min, ..., B, ..., Y=max) Case 2. (X=min, ..., B, ..., W=max) Case 3. (Y=min, ..., B, ..., X=max) Case 4. (Y=min, ..., B, ..., W=max) */ if (theGraph->functions->fpMarkDFSPath(theGraph, max, B) != OK) return NOTOK; gp_SetEdgeVisited(theGraph, B_edge); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, B_edge)); /* Delete the unmarked edges in the bicomp. Note that if an unmarked edge * represents a reduced path, then only the reduction edge is deleted here. * The path it represents is only deleted later (see NOTE above) */ if (_K33Search_DeleteUnmarkedEdgesInBicomp(theGraph, context, R) != OK) return NOTOK; /* Clear all visited flags in the bicomp. This is the important "step 4" mentioned in the NOTE above */ if (_ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; /* Clear all orientation signs in the bicomp. Note that the whole bicomp may not be properly oriented at this point because we may have exchanged external face paths for internal DFS tree paths. However, the reduced bicomp will be properly oriented, and the paths of degree 2 vertices will have their orientations fixed if/when reduction edges are restored. */ if (_ClearInvertedFlagsInBicomp(theGraph, R) != OK) return NOTOK; /* Reduce the paths to single edges. Note that although the whole bicomp may not be properly oriented at this point (as noted above), the four principal vertices R, X, W and Y still are consistently oriented with one another, e.g. R's link[0] indicates the external face path toward X that excludes W and Y, and X's link[1] indicates that same path. */ if (_ReduceExternalFacePathToEdge(theGraph, context, R, IC->x, rxType) != OK || _ReduceExternalFacePathToEdge(theGraph, context, IC->x, IC->w, xwType) != OK || _ReduceExternalFacePathToEdge(theGraph, context, IC->w, IC->y, wyType) != OK || _ReduceExternalFacePathToEdge(theGraph, context, IC->y, R, yrType) != OK) return NOTOK; if (_ReduceXYPathToEdge(theGraph, context, IC->x, IC->y, xyType) != OK) return NOTOK; return OK; } /******************************************************************** Edge deletion that occurs during a reduction or restoration of a reduction is augmented by clearing the K_{3,3} search-specific data members. This is augmentation is not needed in the delete edge operations that happen once a K_{3,3} homeomorph has been found and marked for isolation. ********************************************************************/ int _K33Search_DeleteEdge(graphP theGraph, K33SearchContext *context, int e) { _K33Search_InitEdgeRec(context, e); _K33Search_InitEdgeRec(context, gp_GetTwin(theGraph, e)); return gp_DeleteEdge(theGraph, e); } /******************************************************************** _K33Search_DeleteUnmarkedEdgesInBicomp() This function deletes from a given biconnected component all edges whose visited member is zero. The stack is used but preserved. In debug mode, NOTOK can result if there is a stack overflow. This method pushes at most one integer per vertex in the bicomp. This is the same as _DeleteUnmarkedEdgesInBicomp(), except it calls the overloaded _K33_DeleteEdge() rather than gp_DeleteEdge() Returns OK on success, NOTOK on implementation failure ********************************************************************/ int _K33Search_DeleteUnmarkedEdgesInBicomp(graphP theGraph, K33SearchContext *context, int BicompRoot) { int V, e, eNext; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); eNext = gp_GetNextEdge(theGraph, e); if (!gp_GetEdgeVisited(theGraph, e)) _K33Search_DeleteEdge(theGraph, context, e); e = eNext; } } return OK; } /**************************************************************************** _ReduceExternalFacePathToEdge() ****************************************************************************/ int _ReduceExternalFacePathToEdge(graphP theGraph, K33SearchContext *context, int u, int x, int edgeType) { int prevLink, v, w, e; /* If the path is a single edge, then no need for a reduction */ prevLink = 1; v = _GetNeighborOnExtFace(theGraph, u, &prevLink); if (v == x) { gp_SetExtFaceVertex(theGraph, u, 0, x); gp_SetExtFaceVertex(theGraph, x, 1, u); return OK; } /* We have the endpoints u and x of the path, and we just computed the first vertex internal to the path and a neighbor of u. Now we compute the vertex internal to the path and a neighbor of x. */ prevLink = 0; w = _GetNeighborOnExtFace(theGraph, x, &prevLink); /* Delete the two edges that connect the path to the bicomp. If either edge is a reduction edge, then we have to restore the path it represents. We can only afford to visit the endpoints of the path. Note that in the restored path, the edge incident to each endpoint of the original path is a newly added edge, not a reduction edge. */ e = gp_GetFirstEdge(theGraph, u); // An edge e is a reduction edge if it has a pathConnector vertex set if (context->E[e].pathConnector != NIL) { if (_RestoreReducedPath(theGraph, context, e) != OK) return NOTOK; e = gp_GetFirstEdge(theGraph, u); v = gp_GetNeighbor(theGraph, e); } _K33Search_DeleteEdge(theGraph, context, e); e = gp_GetLastEdge(theGraph, x); // An edge e is a reduction edge if it has a pathConnector vertex set if (context->E[e].pathConnector != NIL) { if (_RestoreReducedPath(theGraph, context, e) != OK) return NOTOK; e = gp_GetLastEdge(theGraph, x); w = gp_GetNeighbor(theGraph, e); } _K33Search_DeleteEdge(theGraph, context, e); /* Add the reduction edge, then set its path connectors so the original path can be recovered and set the edge type so the essential structure of the DFS tree can be maintained (The 'Do X to Bicomp' functions and functions like MarkDFSPath(0 depend on this). */ gp_AddEdge(theGraph, u, 0, x, 1); e = gp_GetFirstEdge(theGraph, u); context->E[e].pathConnector = v; gp_SetEdgeType(theGraph, e, _ComputeEdgeRecordType(theGraph, u, x, edgeType)); e = gp_GetLastEdge(theGraph, x); context->E[e].pathConnector = w; gp_SetEdgeType(theGraph, e, _ComputeEdgeRecordType(theGraph, x, u, edgeType)); /* Set the external face info */ gp_SetExtFaceVertex(theGraph, u, 0, x); gp_SetExtFaceVertex(theGraph, x, 1, u); return OK; } /**************************************************************************** _ReduceXYPathToEdge() ****************************************************************************/ int _ReduceXYPathToEdge(graphP theGraph, K33SearchContext *context, int u, int x, int edgeType) { int e, v, w; e = gp_GetFirstEdge(theGraph, u); e = gp_GetNextEdge(theGraph, e); v = gp_GetNeighbor(theGraph, e); /* If the XY-path is a single edge, then no reduction is needed */ if (v == x) return OK; /* Otherwise, remove the two edges that join the XY-path to the bicomp */ // An edge e is a reduction edge if it has a pathConnector vertex set if (context->E[e].pathConnector != NIL) { if (_RestoreReducedPath(theGraph, context, e) != OK) return NOTOK; e = gp_GetFirstEdge(theGraph, u); e = gp_GetNextEdge(theGraph, e); v = gp_GetNeighbor(theGraph, e); } _K33Search_DeleteEdge(theGraph, context, e); e = gp_GetFirstEdge(theGraph, x); e = gp_GetNextEdge(theGraph, e); w = gp_GetNeighbor(theGraph, e); // An edge e is a reduction edge if it has a pathConnector vertex set if (context->E[e].pathConnector != NIL) { if (_RestoreReducedPath(theGraph, context, e) != OK) return NOTOK; e = gp_GetFirstEdge(theGraph, x); e = gp_GetNextEdge(theGraph, e); w = gp_GetNeighbor(theGraph, e); } _K33Search_DeleteEdge(theGraph, context, e); /* Now add a single edge to represent the XY-path */ gp_InsertEdge(theGraph, u, gp_GetFirstEdge(theGraph, u), 0, x, gp_GetFirstEdge(theGraph, x), 0); /* Now set up the path connectors so the original XY-path can be recovered if needed. Also, set the reduction edge's type to preserve the DFS tree structure */ e = gp_GetFirstEdge(theGraph, u); e = gp_GetNextEdge(theGraph, e); context->E[e].pathConnector = v; gp_SetEdgeType(theGraph, e, _ComputeEdgeRecordType(theGraph, u, x, edgeType)); e = gp_GetFirstEdge(theGraph, x); e = gp_GetNextEdge(theGraph, e); context->E[e].pathConnector = w; gp_SetEdgeType(theGraph, e, _ComputeEdgeRecordType(theGraph, x, u, edgeType)); return OK; } /**************************************************************************** _RestoreReducedPath() Given an edge record of an edge used to reduce a path, we want to restore the path in constant time. The path may contain more reduction edges internally, but we do not search for and process those since it would violate the constant time bound required of this function. return OK on success, NOTOK on failure ****************************************************************************/ int _RestoreReducedPath(graphP theGraph, K33SearchContext *context, int e) { int eTwin, u, v, w, x; int e0, e1, eTwin0, eTwin1; // Edge e does not represent a reducible path unless it has a // pathConnect vertex (i.e., a non-NIL value indicating the vertex) if (context->E[e].pathConnector == NIL) return OK; eTwin = gp_GetTwin(theGraph, e); u = gp_GetNeighbor(theGraph, eTwin); v = context->E[e].pathConnector; w = context->E[eTwin].pathConnector; x = gp_GetNeighbor(theGraph, e); /* Get the locations of the edge records between which the new edge records must be added in order to reconnect the path parallel to the edge. */ e0 = gp_GetNextEdge(theGraph, e); e1 = gp_GetPrevEdge(theGraph, e); eTwin0 = gp_GetNextEdge(theGraph, eTwin); eTwin1 = gp_GetPrevEdge(theGraph, eTwin); /* We first delete the edge represented by e and eTwin. We do so before restoring the path to ensure we do not exceed the maximum edge capacity. */ _K33Search_DeleteEdge(theGraph, context, e); /* Now we add the two edges to reconnect the reduced path represented by the edge [e, eTwin]. The edge record in u is added between e0 and e1. Likewise, the new edge record in x is added between eTwin0 and eTwin1. */ if (gp_IsEdge(theGraph, e0)) { if (gp_InsertEdge(theGraph, u, e0, 1, v, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, u, e1, 0, v, NIL, 0) != OK) return NOTOK; } if (gp_IsEdge(theGraph, eTwin0)) { if (gp_InsertEdge(theGraph, x, eTwin0, 1, w, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, x, eTwin1, 0, w, NIL, 0) != OK) return NOTOK; } // Set the types of the newly added edges. In both cases, the first of the two // vertex parameters is known to be degree 2 because they are internal to the // path being restored, so this operation is constant time. if (_RestoreEdgeType(theGraph, v, u) != OK || _RestoreEdgeType(theGraph, w, x) != OK) return NOTOK; return OK; } /**************************************************************************** _RestoreAndOrientReducedPaths() This function searches the embedding for any edges that are specially marked as being representative of a path that was previously reduced to a single edge by _ReduceBicomp(). The edge is replaced by the path. Note that the new path may contain more reduction edges, and these will be iteratively expanded by the outer for loop. If the edge records of an edge being expanded are the first or last edges of the edge's vertex endpoints, then the edge may be along the external face. If so, then the vertices along the path being restored must be given a consistent orientation with the endpoints. It is expected that the embedding will have been oriented prior to this operation. ****************************************************************************/ int _RestoreAndOrientReducedPaths(graphP theGraph, K33SearchContext *context) { int e, eTwin, u, v, w, x, visited; int e0, eTwin0, e1, eTwin1; for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph);) { // An edge e is a reduction edge if it has a pathConnector vertex set if (context->E[e].pathConnector != NIL) { visited = gp_GetEdgeVisited(theGraph, e); eTwin = gp_GetTwin(theGraph, e); u = gp_GetNeighbor(theGraph, eTwin); v = context->E[e].pathConnector; w = context->E[eTwin].pathConnector; x = gp_GetNeighbor(theGraph, e); /* Now we need the predecessor and successor edge records of e and eTwin. The edge (u, v) will be inserted so that the record in u's adjacency list that indicates v will be between e0 and e1. Likewise, the edge record (x -> w) will be placed between eTwin0 and eTwin1. */ e0 = gp_GetNextEdge(theGraph, e); e1 = gp_GetPrevEdge(theGraph, e); eTwin0 = gp_GetNextEdge(theGraph, eTwin); eTwin1 = gp_GetPrevEdge(theGraph, eTwin); /* We first delete the edge represented by e and eTwin. We do so before restoring the path to ensure we do not exceed the maximum edge capacity. */ _K33Search_DeleteEdge(theGraph, context, e); /* Now we add the two edges to reconnect the reduced path represented by the edge [e, eTwin]. The edge record in u is added between e0 and e1. Likewise, the new edge record in x is added between eTwin0 and eTwin1. */ if (gp_IsEdge(theGraph, e0)) { if (gp_InsertEdge(theGraph, u, e0, 1, v, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, u, e1, 0, v, NIL, 0) != OK) return NOTOK; } if (gp_IsEdge(theGraph, eTwin0)) { if (gp_InsertEdge(theGraph, x, eTwin0, 1, w, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, x, eTwin1, 0, w, NIL, 0) != OK) return NOTOK; } /* Set the types of the newly added edges */ if (_RestoreEdgeType(theGraph, u, v) != OK || _RestoreEdgeType(theGraph, w, x) != OK) return NOTOK; /* We determine whether the reduction edge may be on the external face, in which case we will need to ensure that the vertices on the path being restored are consistently oriented. This will accommodate future invocations of MarkPathAlongBicompExtFace(). Note: If e0, e1, eTwin0 or eTwin1 is not an edge, then it is because we've walked off the end of the edge record list, which happens when e and eTwin are either the first or last edge of the containing vertex. In turn, the first and last edges of a vertex are the ones that hold it onto the external face, if it is on the external face. */ if ((gp_IsNotEdge(theGraph, e0) && gp_IsNotEdge(theGraph, eTwin1)) || (gp_IsNotEdge(theGraph, e1) && gp_IsNotEdge(theGraph, eTwin0))) { if (_OrientExternalFacePath(theGraph, u, v, w, x) != OK) return NOTOK; } /* The internal XY path was already marked as part of the decision logic that made us decide we could find a K_{3,3} and hence that we should reverse all of the reductions. Subsequent code counts on the fact that the X-Y path is already marked, so if we replace a marked edge with a path, then we need to mark the path. Similarly, for an unmarked edge, the replacement path should be unmarked. */ if (visited) { if (_SetAllVisitedFlagsOnPath(theGraph, u, v, w, x) != OK) return NOTOK; } else { if (_ClearAllVisitedFlagsOnPath(theGraph, u, v, w, x) != OK) return NOTOK; } } else e += 2; } return OK; } /**************************************************************************** _MarkStraddlingBridgePath() ****************************************************************************/ int _MarkStraddlingBridgePath(graphP theGraph, int u_min, int u_max, int u_d, int d) { isolatorContextP IC = theGraphIC(theGraph); int p, e; /* Find the point of intersection p between the path (v ... u_max) and the path (d ... u_max). */ if (theGraph->functions->fpMarkDFSPath(theGraph, u_max, IC->r) != OK) return NOTOK; p = d; while (!gp_GetVisited(theGraph, p)) { gp_SetVisited(theGraph, p); e = gp_GetFirstEdge(theGraph, p); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_PARENT) break; e = gp_GetNextEdge(theGraph, e); } gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); p = gp_GetNeighbor(theGraph, e); /* If p is a root copy, mark it visited and skip to the parent copy */ if (gp_IsVirtualVertex(theGraph, p)) { gp_SetVisited(theGraph, p); p = gp_GetVertexFromBicompRoot(theGraph, p); } } /* Unmark the path (p ... u_max), which was marked to help find p. The path from v to u_{max} is not needed to form a K_{3,3} except for the portion of the path up to p that, with the straddling bridge path, comprises part of the connection to u_d. In the minor, the path between v and p is edge contracted. */ while (p != u_max) { e = gp_GetFirstEdge(theGraph, p); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_PARENT) break; e = gp_GetNextEdge(theGraph, e); } gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); p = gp_GetNeighbor(theGraph, e); gp_ClearVisited(theGraph, p); /* If p is a root copy, clear its visited flag and skip to the parent copy */ if (gp_IsVirtualVertex(theGraph, p)) { p = gp_GetVertexFromBicompRoot(theGraph, p); gp_ClearVisited(theGraph, p); } } /* The straddling bridge must join the path (u_max ... u_min). If u_d is an ancestor of u_min, then mark the path that joins u_d to u_min. */ if (u_d < u_min) if (theGraph->functions->fpMarkDFSPath(theGraph, u_d, u_min) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorE5() The paths (x, w), (y, w) and (v, u_{max}) are not needed. The x-y path and the internal w-z path are already marked. ****************************************************************************/ int _IsolateMinorE5(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->x) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->y, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN3(IC->ux, IC->uy, IC->uz), MAX3(IC->ux, IC->uy, IC->uz)) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorE6() The paths (x, y), (v, w) and (v, u_{max}) are not needed. The path through the straddling bridge that connects from an ancestor of u_{max} to v is required, but it may connect to an ancestor p of v. In such a case, the path (v, p) is required, while (p, u_{max}) is not. ****************************************************************************/ int _IsolateMinorE6(graphP theGraph, K33SearchContext *context) { isolatorContextP IC = theGraphIC(theGraph); int u_min, u_max, d, u_d; /* Clear the previously marked x-y path */ if (_ClearAllVisitedFlagsInBicomp(theGraph, IC->r) != OK) return NOTOK; /* Clear dw to stop the marking of path (v, w) */ IC->dw = NIL; /* Mark (v, ..., x, ..., w, ..., y, ... v) */ if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK) return NOTOK; /* Mark the path through the straddling bridge (except for the final edge (u_d, d) which is added last by convention). */ u_min = MIN3(IC->ux, IC->uy, IC->uz); u_max = MAX3(IC->ux, IC->uy, IC->uz); d = _TestForStraddlingBridge(theGraph, context, u_max); u_d = gp_GetVertexLeastAncestor(theGraph, d); if (_MarkStraddlingBridgePath(theGraph, u_min, u_max, u_d, d) != OK) return NOTOK; /* Make the final markings and edge additions */ if (theGraph->functions->fpMarkDFSPath(theGraph, u_min, u_max) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK || _AddAndMarkEdge(theGraph, u_d, d) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorE7() ****************************************************************************/ int _IsolateMinorE7(graphP theGraph, K33SearchContext *context) { isolatorContextP IC = theGraphIC(theGraph); int u_min, u_max, d, u_d; /* Mark the appropriate two portions of the external face depending on symmetry condition */ if (IC->uy < IC->ux) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->x) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->w, IC->y) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->w) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->y, IC->r) != OK) return NOTOK; } /* Mark the path through the straddling bridge (except for the final edge (u_d, d) which is added last by convention). */ u_min = MIN3(IC->ux, IC->uy, IC->uz); u_max = MAX3(IC->ux, IC->uy, IC->uz); d = _TestForStraddlingBridge(theGraph, context, u_max); u_d = gp_GetVertexLeastAncestor(theGraph, d); if (_MarkStraddlingBridgePath(theGraph, u_min, u_max, u_d, d) != OK) return NOTOK; /* Make the final markings and edge additions */ if (theGraph->functions->fpMarkDFSPath(theGraph, u_min, u_max) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK || _AddAndMarkEdge(theGraph, u_d, d) != OK) return NOTOK; return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK33Search.h000066400000000000000000000011141521450711600313410ustar00rootroot00000000000000#ifndef GRAPH_K33SEARCH_H #define GRAPH_K33SEARCH_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphPlanarity.h" #ifdef __cplusplus extern "C" { #endif // Create a K33Search Graph, i.e., subclass a Planarity Graph by extending it with // the ability to perform a search for a subgraph homeomorphic to K_{3,3}. #define K33SEARCH_NAME "K33Search" int gp_ExtendWith_K33Search(graphP theGraph); int gp_Detach_K33Search(graphP theGraph); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK33Search.private.h000066400000000000000000000027171521450711600330240ustar00rootroot00000000000000#ifndef GRAPH_K33SEARCH_PRIVATE_H #define GRAPH_K33SEARCH_PRIVATE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphPlanarity.private.h" #ifdef __cplusplus extern "C" { #endif // Additional equipment for each EdgeRec typedef struct { int noStraddle, pathConnector; } K33Search_EdgeRec; typedef K33Search_EdgeRec *K33Search_EdgeRecP; // Additional equipment for each vertex (non-virtual only) typedef struct { int separatedDFSChildList, backEdgeList, mergeBlocker; } K33Search_VertexInfo; typedef K33Search_VertexInfo *K33Search_VertexInfoP; typedef struct { // Helps distinguish initialize from re-initialize int initialized; // The graph that this context augments graphP theGraph; // Parallel array for additional edge level equipment K33Search_EdgeRecP E; // Parallel array for additional vertex info level equipment K33Search_VertexInfoP VI; // Storage for the separatedDFSChildLists, and // to help with linear time sorting of same by lowpoints listCollectionP separatedDFSChildLists; int *buckets; listCollectionP bin; // Overloaded function pointers graphFunctionTableStruct functions; } K33SearchContext; extern int K33SEARCH_ID; #ifdef __cplusplus } #endif #endif graphK33Search_Extensions.c000066400000000000000000000756331521450711600335150ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include "graphK33Search.h" #include "graphK33Search.private.h" // Need to save and restore a graph flag related to IO #include "../io/graphIO.h" extern int _SearchForMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int *pMergeBlocker); extern int _FindK33WithMergeBlocker(graphP theGraph, K33SearchContext *context, int v, int mergeBlocker); extern int _SearchForK33InBicomp(graphP theGraph, K33SearchContext *context, int v, int R); extern int _TestForK33GraphObstruction(graphP theGraph, int *degrees, int *imageVerts); extern int _getImageVertices(graphP theGraph, int *degrees, int maxDegree, int *imageVerts, int maxNumImageVerts); extern int _TestSubgraph(graphP theSubgraph, graphP theGraph); /* Forward declarations of local functions */ void _K33Search_ClearStructures(K33SearchContext *context); int _K33Search_CreateStructures(K33SearchContext *context); int _K33Search_InitStructures(K33SearchContext *context); void _K33Search_InitEdgeRec(K33SearchContext *context, int e); void _K33Search_InitVertexInfo(K33SearchContext *context, int v); /* Forward declarations of overloading functions */ int _K33Search_EmbeddingInitialize(graphP theGraph); void _CreateBackEdgeLists(graphP theGraph, K33SearchContext *context); void _CreateSeparatedDFSChildLists(graphP theGraph, K33SearchContext *context); void _K33Search_EmbedBackEdgeToDescendant(graphP theGraph, int RootSide, int RootVertex, int W, int WPrevLink); int _K33Search_MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink); void _K33Search_MergeVertex(graphP theGraph, int W, int WPrevLink, int R); int _K33Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R); int _K33Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); int _K33Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); int _K33Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph); int _K33Search_EnsureVertexCapacity(graphP theGraph, int N); void _K33Search_ResetGraphStorage(graphP theGraph); int _K33Search_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity); /* Forward declarations of functions used by the extension system */ void *_K33Search_DupContext(void *pContext, void *theGraph); int _K33Search_CopyData(void *dstContext, void *srcContext); void _K33Search_FreeContext(void *); /**************************************************************************** * K33SEARCH_ID - the variable used to hold the integer identifier for this * extension, enabling this feature's extension context to be distinguished * from other features' extension contexts that may be attached to a graph. ****************************************************************************/ int K33SEARCH_ID = 0; /**************************************************************************** gp_ExtendWith_K33Search() This function adjusts the graph data structure to attach the K3,3 search feature. ****************************************************************************/ int gp_ExtendWith_K33Search(graphP theGraph) { K33SearchContext *context = NULL; if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the K3,3 search feature has already been attached to the graph, // then there is no need to attach it again gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { return OK; } // Ensure theGraph is a Planarity Graph if (gp_ExtendWith_Planarity(theGraph) != OK) return NOTOK; // Allocate a new extension context context = (K33SearchContext *)malloc(sizeof(K33SearchContext)); if (context == NULL) { return NOTOK; } // First, tell the context that it is not initialized context->initialized = 0; // Save a pointer to theGraph in the context context->theGraph = theGraph; // Put the overload functions into the context function table. // gp_AddExtension will overload the graph's functions with these, and // return the base function pointers in the context function table memset(&context->functions, 0, sizeof(graphFunctionTableStruct)); context->functions.fpEmbeddingInitialize = _K33Search_EmbeddingInitialize; context->functions.fpEmbedBackEdgeToDescendant = _K33Search_EmbedBackEdgeToDescendant; context->functions.fpMergeBicomps = _K33Search_MergeBicomps; context->functions.fpMergeVertex = _K33Search_MergeVertex; context->functions.fpHandleBlockedBicomp = _K33Search_HandleBlockedBicomp; context->functions.fpEmbedPostprocess = _K33Search_EmbedPostprocess; context->functions.fpCheckEmbeddingIntegrity = _K33Search_CheckEmbeddingIntegrity; context->functions.fpCheckObstructionIntegrity = _K33Search_CheckObstructionIntegrity; context->functions.fpEnsureVertexCapacity = _K33Search_EnsureVertexCapacity; context->functions.fpResetGraphStorage = _K33Search_ResetGraphStorage; context->functions.fpEnsureEdgeCapacity = _K33Search_EnsureEdgeCapacity; _K33Search_ClearStructures(context); // Store the K33 search context, including the data structure and the // function pointers, as an extension of the graph if (gp_AddExtension(theGraph, &K33SEARCH_ID, (void *)context, _K33Search_DupContext, _K33Search_CopyData, _K33Search_FreeContext, &context->functions) != OK) { _K33Search_FreeContext(context); context = NULL; return NOTOK; } // Create the K33-specific structures if the size of the graph is known // Attach functions are always invoked after gp_New(), but if a graph // extension must be attached before gp_Read(), then the attachment // also happens before gp_EnsureVertexCapacity(), which means N==0. // However, a feature can be attached after gp_EnsureVertexCapacity(), // in which case there is extra work to do when N > 0. if (gp_GetN(theGraph) > 0) { if (_K33Search_CreateStructures(context) != OK || _K33Search_InitStructures(context) != OK) { _K33Search_FreeContext(context); context = NULL; return NOTOK; } } return OK; } /******************************************************************** gp_Detach_K33Search() ********************************************************************/ int gp_Detach_K33Search(graphP theGraph) { return gp_RemoveExtension(theGraph, K33SEARCH_ID); } /******************************************************************** _K33Search_ClearStructures() ********************************************************************/ void _K33Search_ClearStructures(K33SearchContext *context) { if (!context->initialized) { // Before initialization, the pointers are stray, not NULL // Once NULL or allocated, free() or LCFree() can do the job context->E = NULL; context->VI = NULL; context->separatedDFSChildLists = NULL; context->buckets = NULL; context->bin = NULL; context->initialized = 1; } else { if (context->E != NULL) { free(context->E); context->E = NULL; } if (context->VI != NULL) { free(context->VI); context->VI = NULL; } LCFree(&context->separatedDFSChildLists); if (context->buckets != NULL) { free(context->buckets); context->buckets = NULL; } LCFree(&context->bin); } } /******************************************************************** _K33Search_CreateStructures() Create uninitialized structures for the vertex and edge levels, and initialized structures for the graph level ********************************************************************/ int _K33Search_CreateStructures(K33SearchContext *context) { int VIsize = gp_UpperBoundVertices(context->theGraph); int Esize = gp_UpperBoundEdgeStorage(context->theGraph); if (gp_GetN(context->theGraph) <= 0) return NOTOK; if ((context->E = (K33Search_EdgeRecP)malloc(Esize * sizeof(K33Search_EdgeRec))) == NULL || (context->VI = (K33Search_VertexInfoP)malloc(VIsize * sizeof(K33Search_VertexInfo))) == NULL || (context->separatedDFSChildLists = LCNew(VIsize)) == NULL || (context->buckets = (int *)malloc(VIsize * sizeof(int))) == NULL || (context->bin = LCNew(VIsize)) == NULL) { return NOTOK; } return OK; } /******************************************************************** _K33Search_InitStructures() ********************************************************************/ int _K33Search_InitStructures(K33SearchContext *context) { memset(context->VI, NIL_CHAR, gp_UpperBoundVertices(context->theGraph) * sizeof(K33Search_VertexInfo)); memset(context->E, NIL_CHAR, gp_UpperBoundEdgeStorage(context->theGraph) * sizeof(K33Search_EdgeRec)); return OK; } /******************************************************************** ********************************************************************/ int _K33Search_EnsureVertexCapacity(graphP theGraph, int N) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context == NULL) { return NOTOK; } theGraph->N = N; theGraph->NV = N; if (theGraph->edgeCapacity == 0) theGraph->edgeCapacity = DEFAULT_EDGE_CAPACITY_FACTOR * N; if (_K33Search_CreateStructures(context) != OK || _K33Search_InitStructures(context) != OK) return NOTOK; context->functions.fpEnsureVertexCapacity(theGraph, N); return OK; } /******************************************************************** ********************************************************************/ void _K33Search_ResetGraphStorage(graphP theGraph) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { // Reset the graph storage in base class(es) context->functions.fpResetGraphStorage(theGraph); // Do the reset that is specific to this module _K33Search_InitStructures(context); LCReset(context->separatedDFSChildLists); LCReset(context->bin); } } /******************************************************************** _K33Search_EnsureEdgeCapacity() ********************************************************************/ int _K33Search_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity) { K33SearchContext *context = NULL; K33Search_EdgeRecP oldE = NULL, newE = NULL; int oldEsize = gp_UpperBoundEdgeStorage(theGraph), newEsize = 0; // If the requirement is already satisfied, then no work to do if (gp_GetEdgeCapacity(theGraph) >= requiredEdgeCapacity) return OK; // Get the graph's extension context so we can work on it gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; // Call the superclass function to make sure lower levels of parallel // edge arrays can successfully meet the new capacity requirement if (context->functions.fpEnsureEdgeCapacity(theGraph, requiredEdgeCapacity) != OK) return NOTOK; // Save the current E so it can be freed once we replace it oldE = context->E; // The superclass EnsureEdgeCapacity method succeeded, so the graph's // new edge capacity is already set, which means we the upper bound of // the graph's edge storage gives the new parallel array size we need. newEsize = gp_UpperBoundEdgeStorage(theGraph); // We must successfully allocate the new parallel edge array newE = (K33Search_EdgeRecP)malloc(newEsize * sizeof(K33Search_EdgeRec)); if (newE == NULL) return NOTOK; // Clear all new edge records memset(newE, NIL_CHAR, newEsize * sizeof(K33Search_EdgeRec)); // Copy the old edge records to the new edge records memcpy(newE, oldE, oldEsize * sizeof(K33Search_EdgeRec)); // Set the new edge array into the context and free the old one context->E = newE; free(oldE); return OK; } /******************************************************************** _K33Search_DupContext() ********************************************************************/ void *_K33Search_DupContext(void *pContext, void *theGraph) { K33SearchContext *context = (K33SearchContext *)pContext; K33SearchContext *newContext = (K33SearchContext *)malloc(sizeof(K33SearchContext)); if (newContext != NULL) { int VIsize = gp_UpperBoundVertices((graphP)theGraph); int Esize = gp_UpperBoundEdgeStorage((graphP)theGraph); *newContext = *context; newContext->theGraph = (graphP)theGraph; newContext->initialized = 0; _K33Search_ClearStructures(newContext); if (((graphP)theGraph)->N > 0) { #ifdef INCLUDE_K33SEARCH_EMBEDDER if (_K33Search_TestForEOTreeChildren(context->associatedEONode) == TRUE) { ErrorMessage("_K33Search_DupContext(): Duplicating an embedding obstruction tree is unsupported.\n"); _K33Search_FreeContext(newContext); return NULL; } #endif if (_K33Search_CreateStructures(newContext) != OK) { _K33Search_FreeContext(newContext); newContext = NULL; return NULL; } memcpy(newContext->E, context->E, Esize * sizeof(K33Search_EdgeRec)); memcpy(newContext->VI, context->VI, VIsize * sizeof(K33Search_VertexInfo)); LCCopy(newContext->separatedDFSChildLists, context->separatedDFSChildLists); } } return newContext; } /******************************************************************** _K33Search_CopyData() ********************************************************************/ int _K33Search_CopyData(void *dstContext, void *srcContext) { K33SearchContext *dstK33Context = (K33SearchContext *)dstContext; K33SearchContext *srcK33Context = (K33SearchContext *)srcContext; int dstEdgeStorage, srcEdgeStorage; if (dstContext == NULL) return NOTOK; // If the srcContext is NULL, then the caller wants the data // structures in the dstContext to be reset/reinitialized if (srcContext == NULL) return _K33Search_InitStructures(dstK33Context); // ELSE: If there is also a srcContext, then we copy data from it dstEdgeStorage = gp_UpperBoundEdgeStorage(dstK33Context->theGraph); srcEdgeStorage = gp_UpperBoundEdgeStorage(srcK33Context->theGraph); // The caller (ultimately gp_CopyGraph()) is responsible for making sure that the // destination graph has enough edge capacity to receive the source graph content if (dstEdgeStorage < srcEdgeStorage) return NOTOK; // If the destination graph has more edge capacity, then we make sure that the // extra edge capacity is reinitialized if (dstEdgeStorage > srcEdgeStorage) { memset(dstK33Context->E, NIL_CHAR, gp_UpperBoundEdgeStorage(dstK33Context->theGraph) * sizeof(K33Search_EdgeRec)); } memcpy(dstK33Context->E, srcK33Context->E, gp_UpperBoundEdgeStorage(dstK33Context->theGraph) * sizeof(K33Search_EdgeRec)); memcpy(dstK33Context->VI, srcK33Context->VI, gp_UpperBoundVertices(dstK33Context->theGraph) * sizeof(K33Search_VertexInfo)); return OK; } /******************************************************************** _K33Search_FreeContext() ********************************************************************/ void _K33Search_FreeContext(void *pContext) { K33SearchContext *context = (K33SearchContext *)pContext; _K33Search_ClearStructures(context); free(pContext); } /******************************************************************** _K33Search_EmbeddingInitialize() This method overloads the embedding initialization phase of the core planarity algorithm to provide post-processing that creates the back edges list and separated DFS child list (sorted by lowpoint) for each vertex. ********************************************************************/ int _K33Search_EmbeddingInitialize(graphP theGraph) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { if (context->functions.fpEmbeddingInitialize(theGraph) != OK) return NOTOK; if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { _CreateBackEdgeLists(theGraph, context); _CreateSeparatedDFSChildLists(theGraph, context); } return OK; } return NOTOK; } /******************************************************************** _CreateBackEdgeLists() ********************************************************************/ void _CreateBackEdgeLists(graphP theGraph, K33SearchContext *context) { int v, e, eTwin, ancestor; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { e = gp_GetVertexFwdEdgeList(theGraph, v); while (gp_IsEdge(theGraph, e)) { // Get the ancestor endpoint and the associated back edge record ancestor = gp_GetNeighbor(theGraph, e); eTwin = gp_GetTwin(theGraph, e); // Put it into the back edge list of the ancestor if (gp_IsNotEdge(theGraph, context->VI[ancestor].backEdgeList)) { context->VI[ancestor].backEdgeList = eTwin; gp_SetPrevEdge(theGraph, eTwin, eTwin); gp_SetNextEdge(theGraph, eTwin, eTwin); } else { int eHead = context->VI[ancestor].backEdgeList; int eTail = gp_GetPrevEdge(theGraph, eHead); gp_SetPrevEdge(theGraph, eTwin, eTail); gp_SetNextEdge(theGraph, eTwin, eHead); gp_SetPrevEdge(theGraph, eHead, eTwin); gp_SetNextEdge(theGraph, eTail, eTwin); } // Advance to the next forward edge record of v (or NIL if done) e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, v)) e = NIL; } } } /******************************************************************** _CreateSeparatedDFSChildLists() Each vertex gets a list of its DFS children, sorted by lowpoint. ********************************************************************/ void _CreateSeparatedDFSChildLists(graphP theGraph, K33SearchContext *context) { int *buckets; listCollectionP bin; int v, L, DFSParent, theList; buckets = context->buckets; bin = context->bin; // Initialize the bin and all the buckets to be empty LCReset(bin); for (L = gp_LowerBoundVertices(theGraph); L < gp_UpperBoundVertices(theGraph); L++) buckets[L] = NIL; // For each vertex, add it to the bucket whose index is equal to the lowpoint of the vertex. for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { L = gp_GetVertexLowpoint(theGraph, v); buckets[L] = LCAppend(bin, buckets[L], v); } // For each bucket, add each vertex in the bucket to the separatedDFSChildList of its DFSParent. // Since lower numbered buckets are processed before higher numbered buckets, vertices with lower // lowpoint values are added before those with higher lowpoint values, so the separatedDFSChildList // of each vertex is sorted by lowpoint for (L = gp_LowerBoundVertices(theGraph); L < gp_UpperBoundVertices(theGraph); L++) { v = buckets[L]; // Loop through all the vertices with lowpoint L, putting each in the list of its parent while (gp_IsVertex(theGraph, v)) { DFSParent = gp_GetVertexParent(theGraph, v); if (gp_IsVertex(theGraph, DFSParent) && DFSParent != v) { theList = context->VI[DFSParent].separatedDFSChildList; theList = LCAppend(context->separatedDFSChildLists, theList, v); context->VI[DFSParent].separatedDFSChildList = theList; } v = LCGetNext(bin, buckets[L], v); } } } /******************************************************************** _K33Search_EmbedBackEdgeToDescendant() The forward and back edge records of a back edge (cycle edge, co-tree edge, etc.) are embedded by the planarity version of this function. For K_{3,3} subgraph homeomorphism, we also maintain the list of unembedded back edges, so we need to remove the back edge from that list since it is now being put back into the adjacency list. ********************************************************************/ void _K33Search_EmbedBackEdgeToDescendant(graphP theGraph, int RootSide, int RootVertex, int W, int WPrevLink) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { // K33 search may have been attached, but not enabled if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { // Get the forward edge record from the adjacentTo field, and // use it to get the back edge record int backEdgeRec = gp_GetTwin(theGraph, gp_GetVertexPertinentEdge(theGraph, W)); // Remove the backEdgeRecfrom the backEdgeList if (context->VI[W].backEdgeList == backEdgeRec) { if (gp_GetNextEdge(theGraph, backEdgeRec) == backEdgeRec) context->VI[W].backEdgeList = NIL; else context->VI[W].backEdgeList = gp_GetNextEdge(theGraph, backEdgeRec); } gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, backEdgeRec), gp_GetNextEdge(theGraph, backEdgeRec)); gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, backEdgeRec), gp_GetPrevEdge(theGraph, backEdgeRec)); } // Invoke the superclass version of the function context->functions.fpEmbedBackEdgeToDescendant(theGraph, RootSide, RootVertex, W, WPrevLink); } } /******************************************************************** This override of _MergeBicomps() detects a special merge block that indicates a K3,3 can be found. The merge blocker is an optimization needed for one case for which detecting a K3,3 could not be done in linear time by direct searching of a path of ancestors that is naturally explored eventually by the core planarity algorithm. Returns OK for a successful merge, NOTOK on an internal failure, or NONEMBEDDABLE if the merge was blocked, in which case a K_{3,3} homeomorph was isolated. ********************************************************************/ int _K33Search_MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { /* If the merge is blocked, then a K_{3,3} homeomorph is isolated, and NONEMBEDDABLE is returned so that the Walkdown terminates */ if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { int mergeBlocker; // We want to test all merge points on the stack // as well as W, since the connection will go // from W. So we push W as a 'degenerate' merge point. sp_Push2(theGraph->theStack, W, WPrevLink); sp_Push2(theGraph->theStack, NIL, NIL); if (_SearchForMergeBlocker(theGraph, context, v, &mergeBlocker) != OK) return NOTOK; if (gp_IsVertex(theGraph, mergeBlocker)) { if (_FindK33WithMergeBlocker(theGraph, context, v, mergeBlocker) != OK) return NOTOK; return NONEMBEDDABLE; } // If no merge blocker was found, then remove W from the stack. sp_Pop2(theGraph->theStack, W, WPrevLink); sp_Pop2(theGraph->theStack, W, WPrevLink); } // If the merge was not blocked, then we perform the merge // When not doing a K3,3 search, then the merge is not // blocked as far as the K3,3 search method is concerned // Another algorithms could overload MergeBicomps and block // merges under certain conditions, but those would be based // on data maintained by the extension that implements the // other algorithm-- if *that* algorithm is the one being run return context->functions.fpMergeBicomps(theGraph, v, RootVertex, W, WPrevLink); } return NOTOK; } /******************************************************************** _K33Search_MergeVertex() Overload of merge vertex that does basic behavior but also removes the DFS child associated with R from the separatedDFSChildList of W. ********************************************************************/ void _K33Search_MergeVertex(graphP theGraph, int W, int WPrevLink, int R) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { int theList = context->VI[W].separatedDFSChildList; theList = LCDelete(context->separatedDFSChildLists, theList, gp_GetDFSChildFromBicompRoot(theGraph, R)); context->VI[W].separatedDFSChildList = theList; } context->functions.fpMergeVertex(theGraph, W, WPrevLink, R); } } /******************************************************************** ********************************************************************/ void _K33Search_InitEdgeRec(K33SearchContext *context, int e) { context->E[e].noStraddle = NIL; context->E[e].pathConnector = NIL; } /******************************************************************** ********************************************************************/ void _K33Search_InitVertexInfo(K33SearchContext *context, int v) { context->VI[v].separatedDFSChildList = NIL; context->VI[v].backEdgeList = NIL; context->VI[v].mergeBlocker = NIL; } /******************************************************************** ********************************************************************/ int _K33Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { // If R is the root of a descendant bicomp of v, we push it, but then we know the search for K3,3 // will be successful and return NONEMBEDDABLE because this condition corresponds to minor A, which // is a K3,3. Thus, an "OK to proceed with Walkdown searching elsewhere" result cannot happen, // so we don't have to test for it to detect if we have to pop these two back off the stack. if (R != RootVertex) sp_Push2(theGraph->theStack, R, 0); // The possible results here are NONEMBEDDABLE if a K3,3 homeomorph is found, or OK if only // a K5 was found and unblocked such that it is OK for the Walkdown to continue searching // elsewhere. Note that the OK result can only happen if RootVertex==R since minor E can only // happen on a child bicomp of vertex v, not a descendant bicomp. return _SearchForK33InBicomp(theGraph, context, v, RootVertex); } // else if we are not doing a K3,3 homeomorph search, then call the superclass to handle return context->functions.fpHandleBlockedBicomp(theGraph, v, RootVertex, R); } /******************************************************************** ********************************************************************/ int _K33Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult) { int savedEmbedFlags = 0, savedZEROBASEDIO = 0; // For K3,3 search, we just return the edge embedding result because the // search result has been obtained already. if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { if (edgeEmbeddingResult == OK) { // When a graph does not contain a K3,3 homeomorph, the embedding // is meaningless, so we empty it out. We preserve the embedFlags // to ensure post-processing continues as expected. savedEmbedFlags = gp_GetEmbedFlags(theGraph); savedZEROBASEDIO = gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO; gp_ResetGraphStorage(theGraph); theGraph->embedFlags = savedEmbedFlags; theGraph->graphFlags &= savedZEROBASEDIO; } return edgeEmbeddingResult; } // When not searching for K3,3, we let the superclass do the work else { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpEmbedPostprocess(theGraph, v, edgeEmbeddingResult); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K33Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { return OK; } // When not searching for K3,3, we let the superclass do the work else { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckEmbeddingIntegrity(theGraph, origGraph); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K33Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph) { // When searching for K3,3, we ensure that theGraph is a subgraph of // the original graph and that it contains a K3,3 homeomorph if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK33) { int degrees[5], imageVerts[6]; if (_TestSubgraph(theGraph, origGraph) != TRUE) { return NOTOK; } if (_getImageVertices(theGraph, degrees, 4, imageVerts, 6) != OK) { return NOTOK; } if (_TestForK33GraphObstruction(theGraph, degrees, imageVerts) == TRUE) { return OK; } return NOTOK; } // When not searching for K3,3, we let the superclass do the work else { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckObstructionIntegrity(theGraph, origGraph); } } return NOTOK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK4Search.c000066400000000000000000001660571521450711600312740ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphK4Search.h" #include "graphK4Search.private.h" /* Imported functions */ extern void _InitIsolatorContext(graphP theGraph); extern void _ClearAllVisitedFlagsInGraph(graphP); extern int _ClearAllVisitedFlagsInBicomp(graphP theGraph, int BicompRoot); // extern int _ClearAllVisitedFlagsInOtherBicomps(graphP theGraph, int BicompRoot); // extern void _ClearEdgeVisitedFlagsInUnembeddedEdges(graphP theGraph); extern int _ClearObstructionMarksInBicomp(graphP theGraph, int BicompRoot); // extern int _DeleteUnmarkedEdgesInBicomp(graphP theGraph, int BicompRoot); extern int _ComputeEdgeRecordType(graphP theGraph, int a, int b, int edgeType); extern int _RestoreEdgeType(graphP theGraph, int u, int v); extern int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink); extern int _JoinBicomps(graphP theGraph); // extern void _FindActiveVertices(graphP theGraph, int R, int *pX, int *pY); extern int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns); extern int _OrientVerticesInEmbedding(graphP theGraph); // extern void _InvertVertex(graphP theGraph, int V); extern int _ClearAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); extern int _SetAllVisitedFlagsOnPath(graphP theGraph, int u, int v, int w, int x); extern int _OrientExternalFacePath(graphP theGraph, int u, int v, int w, int x); extern int _FindUnembeddedEdgeToAncestor(graphP theGraph, int cutVertex, int *pAncestor, int *pDescendant); extern int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant); extern int _GetLeastAncestorConnection(graphP theGraph, int cutVertex); extern int _SetVertexTypesForMarkingXYPath(graphP theGraph); extern int _MarkHighestXYPath(graphP theGraph); extern int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert); extern int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant); extern int _DeleteUnmarkedVerticesAndEdges(graphP theGraph); extern int _IsolateOuterplanarityObstructionA(graphP theGraph); // extern int _IsolateOuterplanarityObstructionB(graphP theGraph); extern int _IsolateOuterplanarityObstructionE(graphP theGraph); extern void _K4Search_InitEdgeRec(K4SearchContext *context, int e); /* Private functions for K4 searching (exposed to the extension). */ int _SearchForK4InBicomp(graphP theGraph, K4SearchContext *context, int v, int R); /* Private functions for K4 searching. */ int _K4_ChooseTypeOfNonOuterplanarityMinor(graphP theGraph, int v, int R); int _K4_FindSecondActiveVertexOnLowExtFacePath(graphP theGraph); int _K4_FindPlanarityActiveVertex(graphP theGraph, int v, int R, int prevLink, int *pW); int _K4_FindSeparatingInternalEdge(graphP theGraph, int R, int prevLink, int A, int *pW, int *pX, int *pY); void _K4_SetMarksOnExternalFacePath(graphP theGraph, int R, int prevLink, int A); void _K4_ClearMarksOnExternalFacePath(graphP theGraph, int R, int prevLink, int A); int _K4_IsolateMinorA1(graphP theGraph); int _K4_IsolateMinorA2(graphP theGraph); int _K4_IsolateMinorB1(graphP theGraph); int _K4_IsolateMinorB2(graphP theGraph); int _K4_ReduceBicompToEdge(graphP theGraph, K4SearchContext *context, int R, int W); int _K4_ReducePathComponent(graphP theGraph, K4SearchContext *context, int R, int prevLink, int A); int _K4_ReducePathToEdge(graphP theGraph, K4SearchContext *context, int edgeType, int R, int e_R, int A, int e_A); int _K4_GetCumulativeOrientationOnDFSPath(graphP theGraph, int ancestor, int descendant); int _K4_TestPathComponentForAncestor(graphP theGraph, int R, int prevLink, int A); void _K4_ClearVisitedInPathComponent(graphP theGraph, int R, int prevLink, int A); int _K4_DeleteUnmarkedEdgesInPathComponent(graphP theGraph, int R, int prevLink, int A); int _K4_DeleteUnmarkedEdgesInBicomp(graphP theGraph, K4SearchContext *context, int BicompRoot); int _K4_DeleteEdge(graphP theGraph, K4SearchContext *context, int e); int _K4_RestoreReducedPath(graphP theGraph, K4SearchContext *context, int e); int _K4_RestoreAndOrientReducedPaths(graphP theGraph, K4SearchContext *context); // int _MarkEdge(graphP theGraph, int x, int y); /**************************************************************************** _SearchForK4InBicomp() ****************************************************************************/ int _SearchForK4InBicomp(graphP theGraph, K4SearchContext *context, int v, int R) { isolatorContextP IC = theGraphIC(theGraph); if (context == NULL) { gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; } // Begin by determining whether minor A, B or E is detected if (_K4_ChooseTypeOfNonOuterplanarityMinor(theGraph, v, R) != OK) return NOTOK; // Minor A indicates the existence of K_{2,3} homeomorphs, but // we run additional tests to see whether we can either find an // entwined K4 homeomorph or reduce the bicomp so that the WalkDown // is enabled to continue to resolve pertinence if (theGraphIC(theGraph)->minorType & MINORTYPE_A) { // Now that we know we have minor A, we can afford to orient the // bicomp because we will either find the desired K4 or we will // reduce the bicomp to an edge. The tests for A1 and A2 are easier // to implement on an oriented bicomp. // NOTE: We're in the midst of the WalkDown, so the stack may be // non-empty, and it has to be preserved with constant cost. // The stack will have at most 4 integers per cut vertex // merge point, and this operation will push at most two // integers per tree edge in the bicomp, so the stack // will not overflow. if (sp_GetCapacity(theGraph->theStack) < 6 * gp_GetN(theGraph)) return NOTOK; if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) return NOTOK; // Case A1: Test whether there is an active vertex Z other than W // along the external face path [X, ..., W, ..., Y] if (_K4_FindSecondActiveVertexOnLowExtFacePath(theGraph) == TRUE) { // Now that we know we can find a K4, the Walkdown will not continue // and we can do away with the stack content. sp_ClearStack(theGraph->theStack); // Restore the orientations of the vertices in the bicomp, then orient // the whole embedding, so we can restore and orient the reduced paths if (_OrientVerticesInBicomp(theGraph, R, 1) != OK || _OrientVerticesInEmbedding(theGraph) != OK || _K4_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; // Set up to isolate K4 homeomorph _ClearAllVisitedFlagsInGraph(theGraph); if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; if (IC->uz < IC->v) { if (_FindUnembeddedEdgeToAncestor(theGraph, IC->z, &IC->uz, &IC->dz) != TRUE) return NOTOK; } else { if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->z, &IC->dz) != TRUE) return NOTOK; } // Isolate the K4 homeomorph if (_K4_IsolateMinorA1(theGraph) != OK || _DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; // Indicate success by returning NONEMBEDDABLE return NONEMBEDDABLE; } // Case A2: Test whether the bicomp has an XY path // NOTE: As mentioned above, the stack is also preserved here. // It will have at most 4 integers per cut vertex merge point, // and this operation will push at most one integer per tree // edge in the bicomp, so the stack will not overflow. if (_SetVertexTypesForMarkingXYPath(theGraph) != OK) return NOTOK; // Marking the X-Y path relies on the bicomp visited flags being cleared if (_ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; // Now Mark the X-Y path // NOTE: This call preserves the stack and does not overflow. There // are at most 4 integers per cut vertex merge point, all of which // are not in the bicomp, and this call pushes at most 3 integers // per bicomp vertex, so the maximum stack requirement is 4N if (_MarkHighestXYPath(theGraph) != OK) return NOTOK; // If there was an X-Y path to mark... if (theGraphIC(theGraph)->py != NIL) { // Now that we know we can find a K4, the Walkdown will not continue // and we can do away with the stack content. sp_ClearStack(theGraph->theStack); // Restore the orientations of the vertices in the bicomp, then orient // the whole embedding, so we can restore and orient the reduced paths if (_OrientVerticesInBicomp(theGraph, R, 1) != OK || _OrientVerticesInEmbedding(theGraph) != OK || _K4_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; // Set up to isolate K4 homeomorph _ClearAllVisitedFlagsInGraph(theGraph); if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) { return NOTOK; } // Fail if there is an internal error or if there isn't an X-Y path if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; // Isolate the K4 homeomorph if (_K4_IsolateMinorA2(theGraph) != OK || _DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; // Indicate success by returning NONEMBEDDABLE return NONEMBEDDABLE; } // else if there was no X-Y path, then we restore the vertex types to // unknown (though it would suffice to do it just to R and W) if (_ClearObstructionMarksInBicomp(theGraph, R) != OK) return NOTOK; // Since neither A1 nor A2 is found, then we reduce the bicomp to the // tree edge (R, W). // NOTE: The visited flags for R and W are restored to values appropriate // for continuing with future embedding steps // NOTE: This method invokes several routines that use the stack, but // all of them preserve the stack and each pushes at most one // integer per bicomp vertex and pops all of them before returning. // Again, this means the stack will not overflow. if (_K4_ReduceBicompToEdge(theGraph, context, R, IC->w) != OK) return NOTOK; // Return OK so that the WalkDown can continue resolving the pertinence of v. return OK; } // Minor B also indicates the existence of K_{2,3} homeomorphs, but // we run additional tests to see whether we can either find an // entwined K4 homeomorph or reduce a portion of the bicomp so that // the WalkDown can be reinvoked on the bicomp else if (theGraphIC(theGraph)->minorType & MINORTYPE_B) { int a_x, a_y; // Reality check on stack state if (sp_NonEmpty(theGraph->theStack)) return NOTOK; // Find the vertices a_x and a_y that are active (pertinent or future pertinent) // and also first along the external face paths emanating from the bicomp root if (_K4_FindPlanarityActiveVertex(theGraph, v, R, 1, &a_x) != OK || _K4_FindPlanarityActiveVertex(theGraph, v, R, 0, &a_y) != OK) return NOTOK; // Case B1: If both a_x and a_y are future pertinent, then we can stop and // isolate a subgraph homeomorphic to K4. gp_UpdateVertexFuturePertinentChild(theGraph, a_x, v); gp_UpdateVertexFuturePertinentChild(theGraph, a_y, v); if (a_x != a_y && FUTUREPERTINENT(theGraph, a_x, v) && FUTUREPERTINENT(theGraph, a_y, v)) { if (_OrientVerticesInEmbedding(theGraph) != OK || _K4_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; // Set up to isolate K4 homeomorph _ClearAllVisitedFlagsInGraph(theGraph); IC->x = a_x; IC->y = a_y; if (_FindUnembeddedEdgeToAncestor(theGraph, IC->x, &IC->ux, &IC->dx) != TRUE || _FindUnembeddedEdgeToAncestor(theGraph, IC->y, &IC->uy, &IC->dy) != TRUE) return NOTOK; // Isolate the K4 homeomorph if (_K4_IsolateMinorB1(theGraph) != OK || _DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; // Indicate success by returning NONEMBEDDABLE return NONEMBEDDABLE; } // Reality check: The bicomp with root R is pertinent, and the only // pertinent or future pertinent vertex on the external face is a_x, // so it must also be pertinent. if (a_x == a_y && !PERTINENT(theGraph, a_x)) return NOTOK; // Case B2: Determine whether there is an internal separating X-Y path for a_x or for a_y // The method makes appropriate isolator context settings if the separator edge is found if (_K4_FindSeparatingInternalEdge(theGraph, R, 1, a_x, &IC->w, &IC->px, &IC->py) == TRUE || _K4_FindSeparatingInternalEdge(theGraph, R, 0, a_y, &IC->w, &IC->py, &IC->px) == TRUE) { if (_OrientVerticesInEmbedding(theGraph) != OK || _K4_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; // Set up to isolate K4 homeomorph _ClearAllVisitedFlagsInGraph(theGraph); if (PERTINENT(theGraph, IC->w)) { if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; } else { IC->z = IC->w; if (_FindUnembeddedEdgeToAncestor(theGraph, IC->z, &IC->uz, &IC->dz) != TRUE) return NOTOK; } // The X-Y path doesn't have to be the same one that was associated with // the separating internal edge (but it has to be there, else error). if (_SetVertexTypesForMarkingXYPath(theGraph) != OK || _MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; // Isolate the K4 homeomorph if (_K4_IsolateMinorB2(theGraph) != OK || _DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; // Indicate success by returning NONEMBEDDABLE return NONEMBEDDABLE; } // If K_4 homeomorph not found, make reductions along a_x and a_y paths. if (a_x == a_y) { // In the special case where both paths lead to the same vertex, we can // reduce the bicomp to a single edge, which avoids issues of reversed // orientation between the bicomp root and the vertex. if (_K4_ReduceBicompToEdge(theGraph, context, R, a_x) != OK) return NOTOK; } else { // When a_x and a_y are distinct, we reduce each path from root to the vertex if (_K4_ReducePathComponent(theGraph, context, R, 1, a_x) != OK || _K4_ReducePathComponent(theGraph, context, R, 0, a_y) != OK) return NOTOK; } // Return OK to indicate that WalkDown processing may proceed to resolve // more of the pertinence of this bicomp. return OK; } // Minor E indicates the desired K4 homeomorph, so we isolate it and return NONEMBEDDABLE else if (theGraphIC(theGraph)->minorType & MINORTYPE_E) { // Reality check on stack state if (sp_NonEmpty(theGraph->theStack)) return NOTOK; // Impose consistent orientation on the embedding so we can then // restore the reduced paths. if (_OrientVerticesInEmbedding(theGraph) != OK || _K4_RestoreAndOrientReducedPaths(theGraph, context) != OK) return NOTOK; // Set up to isolate minor E _ClearAllVisitedFlagsInGraph(theGraph); if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; if (_SetVertexTypesForMarkingXYPath(theGraph) != OK) return NOTOK; if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; // Isolate the K4 homeomorph if (_IsolateOuterplanarityObstructionE(theGraph) != OK || _DeleteUnmarkedVerticesAndEdges(theGraph) != OK) return NOTOK; // Return indication that K4 homeomorph has been found return NONEMBEDDABLE; } // You never get here in an error-free implementation like this one return NOTOK; } /**************************************************************************** _K4_ChooseTypeOfNonOuterplanarityMinor() This is an overload of the function _ChooseTypeOfNonOuterplanarityMinor() that avoids processing the whole bicomp rooted by R, e.g. to orient its vertices or label the vertices of its external face. This is necessary in particular because of the reduction processing on MINORTYPE_B. When a K2,3 is found by minor B, we may not be able to find an entangled K4, so a reduction is performed, but it only eliminates part of the bicomp and the operations here need to avoid touching parts of the bicomp that won't be reduced, except by a constant amount of course. ****************************************************************************/ int _K4_ChooseTypeOfNonOuterplanarityMinor(graphP theGraph, int v, int R) { int XPrevLink = 1, YPrevLink = 0; int Wx, WxPrevLink, Wy, WyPrevLink; _InitIsolatorContext(theGraph); theGraphIC(theGraph)->v = v; theGraphIC(theGraph)->r = R; // Reality check on data structure integrity if (!gp_VirtualVertexInUse(theGraph, R)) return NOTOK; // We are essentially doing a _FindActiveVertices() here, except two things: // 1) for outerplanarity we know the first vertices along the paths from R // are the desired vertices because no vertices are "inactive" // 2) We have purposely not oriented the bicomp, so the XPrevLink result is // needed to help find the pertinent vertex W theGraphIC(theGraph)->x = _GetNeighborOnExtFace(theGraph, R, &XPrevLink); theGraphIC(theGraph)->y = _GetNeighborOnExtFace(theGraph, R, &YPrevLink); // We are essentially doing a _FindPertinentVertex() here, except two things: // 1) It is not known whether the reduction of the path through X or the path // through Y will enable the pertinence of W to be resolved, so it is // necessary to perform parallel face traversal to find W with a cost no // more than twice what it will take to resolve the W's pertinence // (assuming we have to do a reduction rather than finding an entangled K4) // 2) In the normal _FindPertinentVertex(), the bicomp is already oriented, so // the "prev link" is hard coded to traverse down the X side. In this // implementation, the bicomp is purposely not oriented, so we need to know // XPrevLink and YPrevLink in order to set off in the correct directions. Wx = theGraphIC(theGraph)->x; WxPrevLink = XPrevLink; Wy = theGraphIC(theGraph)->y; WyPrevLink = YPrevLink; theGraphIC(theGraph)->w = NIL; while (Wx != theGraphIC(theGraph)->y) { Wx = _GetNeighborOnExtFace(theGraph, Wx, &WxPrevLink); if (PERTINENT(theGraph, Wx)) { theGraphIC(theGraph)->w = Wx; break; } Wy = _GetNeighborOnExtFace(theGraph, Wy, &WyPrevLink); if (PERTINENT(theGraph, Wy)) { theGraphIC(theGraph)->w = Wy; break; } } if (gp_IsNotVertex(theGraph, theGraphIC(theGraph)->w)) return NOTOK; // If the root copy is not a root copy of the current vertex v, // then the Walkdown terminated on a descendant bicomp, which is Minor A. if (gp_GetVertexFromBicompRoot(theGraph, R) != v) theGraphIC(theGraph)->minorType |= MINORTYPE_A; // If W has a pertinent child bicomp, then we've found Minor B. // Notice this is different from planarity, in which minor B is indicated // only if the pertinent child bicomp is also future pertinent. else if (gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, theGraphIC(theGraph)->w))) theGraphIC(theGraph)->minorType |= MINORTYPE_B; // The only other result is minor E (we will search for the X-Y path later) else theGraphIC(theGraph)->minorType |= MINORTYPE_E; return OK; } /**************************************************************************** _K4_FindSecondActiveVertexOnLowExtFacePath() This method is used in the processing of obstruction A, so it can take advantage of the bicomp being oriented beforehand. This method determines whether there is an active vertex Z other than W on the path [X, ..., W, ..., Y]. By active, we mean a vertex that connects by an unembedded edge to either v or an ancestor of v. That is, a vertex that is pertinent or future pertinent (would be pertinent in a future step of the embedder). ****************************************************************************/ int _K4_FindSecondActiveVertexOnLowExtFacePath(graphP theGraph) { int Z = theGraphIC(theGraph)->r, ZPrevLink = 1; // First we test X for future pertinence only (if it were pertinent, then // we wouldn't have been blocked up on this bicomp) Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); gp_UpdateVertexFuturePertinentChild(theGraph, Z, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, Z, theGraphIC(theGraph)->v)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->uz = _GetLeastAncestorConnection(theGraph, Z); return TRUE; } // Now we move on to test all the vertices strictly between X and Y on // the lower external face path, except W, for either pertinence or // future pertinence. Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); while (Z != theGraphIC(theGraph)->y) { if (Z != theGraphIC(theGraph)->w) { gp_UpdateVertexFuturePertinentChild(theGraph, Z, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, Z, theGraphIC(theGraph)->v)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->uz = _GetLeastAncestorConnection(theGraph, Z); return TRUE; } else if (PERTINENT(theGraph, Z)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->uz = theGraphIC(theGraph)->v; return TRUE; } } Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } // Now we test Y for future pertinence (same explanation as for X above) gp_UpdateVertexFuturePertinentChild(theGraph, Z, theGraphIC(theGraph)->v); if (FUTUREPERTINENT(theGraph, Z, theGraphIC(theGraph)->v)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->uz = _GetLeastAncestorConnection(theGraph, Z); return TRUE; } // We didn't find the desired second vertex, so report FALSE return FALSE; } /**************************************************************************** _K4_FindPlanarityActiveVertex() This service routine starts out at R and heads off in the direction opposite the prevLink to find the first "planarity active" vertex, i.e. the first one that is pertinent or future pertinent. ****************************************************************************/ int _K4_FindPlanarityActiveVertex(graphP theGraph, int v, int R, int prevLink, int *pW) { int W = R, WPrevLink = prevLink; W = _GetNeighborOnExtFace(theGraph, R, &WPrevLink); while (W != R) { if (PERTINENT(theGraph, W)) { *pW = W; return OK; } else { gp_UpdateVertexFuturePertinentChild(theGraph, W, v); if (FUTUREPERTINENT(theGraph, W, v)) { *pW = W; return OK; } } W = _GetNeighborOnExtFace(theGraph, W, &WPrevLink); } return NOTOK; } /**************************************************************************** _K4_FindSeparatingInternalEdge() Logically, this method is similar to calling MarkHighestXYPath() to see if there is an internal separator between R and A. However, that method cannot be called because the bicomp is not oriented. Because this is an outerplanarity related algorithm, there are no internal vertices to contend with, so it is easier to inspect the internal edges incident to each vertex internal to the path (R ... A), i.e. excluding endpoints, to see whether any of the edges connects outside of the path [R ... A], including endpoints. In order to avoid adding cost to the core planarity algorithm, we'll just use the fact that the vertex obstruction types are pre-initialized to the unmarked state. Until we know if there is a separating internal edge, we cannot be sure that we can isolate a K4 homeomorph, so we can't afford to initialize the whole bicomp. The obstruction type of each vertex along the path [R ... A] is marked. Then, for each vertex in the range (R ... A), if there is any edge that is also incident to a vertex whose obstruction type is still unmarked, then that edge is the desired separator edge between R and W (i.e. A), and so we save information about it. If the separator edge is found, then this method sets the *pW to A, and it sets *pX and *pY values with the endpoints of the separator edge. The visited flags of the separator edge and its endpoint vertices are not set at this time because it is easier to set them later as part of the overall K4 homeomorph isolation. Lastly, we restore the unmarked obstruction type settings on the path [R ... A]. Returns TRUE if separator edge found or FALSE otherwise ****************************************************************************/ int _K4_FindSeparatingInternalEdge(graphP theGraph, int R, int prevLink, int A, int *pW, int *pX, int *pY) { int Z, ZPrevLink, e, neighbor; // Mark the vertex obstruction type settings along the path [R ... A] _K4_SetMarksOnExternalFacePath(theGraph, R, prevLink, A); // Search each of the vertices in the range (R ... A) *pX = *pY = NIL; ZPrevLink = prevLink; Z = _GetNeighborOnExtFace(theGraph, R, &ZPrevLink); while (Z != A) { // Search for a separator among the edges of Z // It is OK to not bother skipping the external face edges, since we // know they are marked visited and so are ignored e = gp_GetFirstEdge(theGraph, Z); while (gp_IsEdge(theGraph, e)) { neighbor = gp_GetNeighbor(theGraph, e); if (!gp_GetMarked(theGraph, neighbor)) { *pW = A; *pX = Z; *pY = neighbor; break; } e = gp_GetNextEdge(theGraph, e); } // If we found the separator edge, then we don't need to go on if (gp_IsVertex(theGraph, *pX)) { break; } // Go to the next vertex Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } // Restore the unmarked obstruction type settings on the path [R ... A] _K4_ClearMarksOnExternalFacePath(theGraph, R, prevLink, A); return gp_IsVertex(theGraph, *pX) ? TRUE : FALSE; } /**************************************************************************** _K4_SetMarksOnExternalFacePath() Assumes A is a vertex along the external face of the bicomp rooted by R. Marks the obstruction type of vertices along the path (R ... A) that begins with R's link[1^prevLink] edge record. ****************************************************************************/ void _K4_SetMarksOnExternalFacePath(graphP theGraph, int R, int prevLink, int A) { int Z, ZPrevLink; gp_SetMarked(theGraph, R); ZPrevLink = prevLink; Z = R; while (Z != A) { Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); gp_SetMarked(theGraph, Z); } } /**************************************************************************** _K4_ClearMarksOnExternalFacePath() Assumes A is a vertex along the external face of the bicomp rooted by R. Unmarks the obstruction type of vertices along the path (R ... A) that begins with R's link[1^prevLink] edge record. ****************************************************************************/ void _K4_ClearMarksOnExternalFacePath(graphP theGraph, int R, int prevLink, int A) { int Z, ZPrevLink; gp_ClearMarked(theGraph, R); ZPrevLink = prevLink; Z = R; while (Z != A) { Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); gp_ClearMarked(theGraph, Z); } } /**************************************************************************** _K4_IsolateMinorA1() This pattern is essentially outerplanarity minor A, a K_{2,3}, except we get a K_4 via the additional path from some vertex Z to the current vertex. This path may be via some descendant of Z, and it may be a future pertinent connection to an ancestor of the current vertex. ****************************************************************************/ int _K4_IsolateMinorA1(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (IC->uz < IC->v) { if (theGraph->functions->fpMarkDFSPath(theGraph, IC->uz, IC->v) != OK) return NOTOK; } if (theGraph->functions->fpMarkDFSPath(theGraph, IC->z, IC->dz) != OK) return NOTOK; if (_IsolateOuterplanarityObstructionA(theGraph) != OK) return NOTOK; if (_AddAndMarkEdge(theGraph, IC->uz, IC->dz) != OK) { return NOTOK; } return OK; } /**************************************************************************** _K4_IsolateMinorA2() This pattern is essentially outerplanarity minor A, a K_{2,3}, except we get a K_4 via an additional X-Y path within the main bicomp, which is guaranteed to exist by the time this method is invoked. One might think to simply invoke _MarkHighestXYPath() to obtain the path, but the IC->px and IC->py values are already set before invoking this method, and the bicomp is outerplanar, so the XY path is just an edge. Also, one subcase of pattern B2 reduces to this pattern, except that the XY path is determined by the B2 isolator. ****************************************************************************/ int _K4_IsolateMinorA2(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); // We assume the X-Y path was already marked if (!gp_GetVisited(theGraph, IC->px) || !gp_GetVisited(theGraph, IC->py)) return NOTOK; return _IsolateOuterplanarityObstructionA(theGraph); } /**************************************************************************** _K4_IsolateMinorB1() It is possible to get a K_4 based on the pertinence of w, but we don't do it that way. If we use the pertinence of w, then we have to eliminate part of the bicomp external face, which has special cases if a_x==w or a_y==w. Typically we would mark (r ... a_x ... w ... a_y), which works even when a_y==w, but if instead a_x==w, then we'd have to mark (w ... a_y ... r). Since a_x and a_y are guaranteed to be distinct, it is easier to just ignore the pertinence of w, and instead use the lower bicomp external face path as the connection between a_x and a_y. This includes w, but then the isolation process is the same even if a_x==w or a_y==w. The other two connections for a_x and a_y are to v and MAX(ux, uy). ****************************************************************************/ int _K4_IsolateMinorB1(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (theGraph->functions->fpMarkDFSPath(theGraph, IC->x, IC->dx) != OK) return NOTOK; if (theGraph->functions->fpMarkDFSPath(theGraph, IC->y, IC->dy) != OK) return NOTOK; // The path from the bicomp root to MIN(ux,uy) is marked to ensure the // connection from the image vertices v and MAX(ux,uy) as well as the // connection from MAX(ux,uy) through MIN(ux,uy) to (ux==MIN(ux,uy)?x:y) if (theGraph->functions->fpMarkDFSPath(theGraph, MIN(IC->ux, IC->uy), IC->r) != OK) return NOTOK; // This makes the following connections (a_x ... v), (a_y ... v), and // (a_x ... w ... a_y), the last being tolerant of a_x==w or a_y==w if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK) return NOTOK; if (_JoinBicomps(theGraph) != OK) return NOTOK; if (_AddAndMarkEdge(theGraph, IC->ux, IC->dx) != OK) return NOTOK; if (_AddAndMarkEdge(theGraph, IC->uy, IC->dy) != OK) { return NOTOK; } return OK; } /**************************************************************************** _K4_IsolateMinorB2() The first subcase of B2 can be reduced to outerplanarity obstruction E The second subcase of B2 can be reduced to A2 by changing v to u ****************************************************************************/ int _K4_IsolateMinorB2(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); // First subcase, the active vertex is pertinent if (PERTINENT(theGraph, IC->w)) { // We assume the X-Y path was already marked if (!gp_GetVisited(theGraph, IC->px) || !gp_GetVisited(theGraph, IC->py)) return NOTOK; return _IsolateOuterplanarityObstructionE(theGraph); } // Second subcase, the active vertex is future pertinent else if (FUTUREPERTINENT(theGraph, IC->w, IC->v)) { IC->v = IC->uz; IC->dw = IC->dz; return _K4_IsolateMinorA2(theGraph); } return OK; } /**************************************************************************** _K4_ReduceBicompToEdge() This method is used when reducing the main bicomp of obstruction A to a single edge (R, W). We first delete all edges from the bicomp except those on the DFS tree path W to R, then we reduce that DFS tree path to a DFS tree edge. After the reduction, the outerplanarity Walkdown traversal can continue R to W without being blocked as was the case when R was adjacent to X and Y. Returns OK for success, NOTOK for internal (implementation) error. ****************************************************************************/ int _K4_ReduceBicompToEdge(graphP theGraph, K4SearchContext *context, int R, int W) { int newEdge; if (_OrientVerticesInBicomp(theGraph, R, 0) != OK || _ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; if (theGraph->functions->fpMarkDFSPath(theGraph, R, W) != OK) return NOTOK; if (_K4_DeleteUnmarkedEdgesInBicomp(theGraph, context, R) != OK) return NOTOK; // Now we have to reduce the path W -> R to the DFS tree edge (R, W) newEdge = _K4_ReducePathToEdge(theGraph, context, EDGE_TYPE_PARENT, R, gp_GetFirstEdge(theGraph, R), W, gp_GetFirstEdge(theGraph, W)); if (gp_IsNotEdge(theGraph, newEdge)) return NOTOK; // Finally, set the visited info state of W to unvisited so that // the core embedder (esp. Walkup) will not have any problems. gp_SetVertexVisitedInfo(theGraph, W, gp_GetN(theGraph)); return OK; } /**************************************************************************** _K4_ReducePathComponent() This method is invoked when the bicomp rooted by R contains a component subgraph that is separable from the bicomp by the 2-cut (R, A). The K_4 homeomorph isolator will have processed a significant fraction of the component, and so it must be reduced to an edge to ensure that said processing happens at most once on the component (except for future operations that are bound to linear time in total by other arguments). Because the bicomp is an outerplanar embedding, the component is known to consists of an external face path plus some internal edges that are parallel to that path. Otherwise, it wouldn't be separable by the 2-cut (R, A). The goal of this method is to reduce the component to the edge (R, A). This is done in such a way that, if the reduction must be restored, the DFS tree structure connecting the restored vertices is retained. The first step is to ensure that (R, A) is not already just an edge, in which case no reduction is needed. This can occur if A is future pertinent. Assuming a non-trivial reduction component, the next step is to determine the DFS tree structure within the component. Because it is separable by the 2-cut (R, A), there are only two cases: Case 1: The DFS tree path from A to R is within the reduction component. In this case, the DFS tree path is marked, the remaining edges of the reduction component are eliminated, and then the DFS tree path is reduced to the the tree edge (R, A). Note that the reduction component may also contain descendants of A as well as vertices that are descendant to R but are neither ancestors nor descendants of A. This depends on where the tree edge from R meets the external face path (R ... A). However, the reduction component can only contribute one path to any future K_4, so it suffices to preserve only the DFS tree path (A --> R). Case 2: The DFS tree path from A to R is not within the reduction component. In this case, the external face edge from R leads to a descendant D of A. We mark that back edge (R, D) plus the DFS tree path (D --> A). The remaining edges of the reduction component can be removed, and then the path (R, D, ..., A) is reduced to the edge (R, A). For the sake of contradiction, suppose that only part of the DFS tree path from A to R were contained by the reduction component. Then, a DFS tree edge would have to exit the reduction component and connect to some vertex not on the external face path (R, ..., A). This contradicts the assumption that the reduction subgraph is separable from the bicomp by the 2-cut (R, A). Returns OK for success, NOTOK for internal (implementation) error. ****************************************************************************/ int _K4_ReducePathComponent(graphP theGraph, K4SearchContext *context, int R, int prevLink, int A) { int e_R, e_A, Z, ZPrevLink, edgeType, invertedFlag = 0; // Check whether the external face path (R, ..., A) is just an edge e_R = gp_GetEdgeByLink(theGraph, R, 1 ^ prevLink); if (gp_GetNeighbor(theGraph, e_R) == A) return OK; // Check for Case 1: The DFS tree path from A to R is within the reduction component if (_K4_TestPathComponentForAncestor(theGraph, R, prevLink, A)) { _K4_ClearVisitedInPathComponent(theGraph, R, prevLink, A); if (theGraph->functions->fpMarkDFSPath(theGraph, R, A) != OK) return NOTOK; edgeType = EDGE_TYPE_PARENT; invertedFlag = _K4_GetCumulativeOrientationOnDFSPath(theGraph, R, A); } // Otherwise Case 2: The DFS tree path from A to R is not within the reduction component else { _K4_ClearVisitedInPathComponent(theGraph, R, prevLink, A); Z = gp_GetNeighbor(theGraph, e_R); gp_SetEdgeVisited(theGraph, e_R); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, e_R)); if (theGraph->functions->fpMarkDFSPath(theGraph, A, Z) != OK) { return NOTOK; } edgeType = EDGE_TYPE_BACK; } // The path to be kept/reduced is marked, so the other edges can go if (_K4_DeleteUnmarkedEdgesInPathComponent(theGraph, R, prevLink, A) != OK) return NOTOK; // Clear all the visited flags for safety, except the vertices R and A // will remain in the embedding, and the core embedder (Walkup) uses a // value greater than the current vertex to indicate an unvisited vertex _K4_ClearVisitedInPathComponent(theGraph, R, prevLink, A); gp_SetVertexVisitedInfo(theGraph, A, gp_GetN(theGraph)); // Find the component's remaining edges e_A and e_R incident to A and R ZPrevLink = prevLink; Z = R; while (Z != A) { Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } e_A = gp_GetEdgeByLink(theGraph, A, ZPrevLink); e_R = gp_GetEdgeByLink(theGraph, R, 1 ^ prevLink); // Reduce the path (R ... A) to an edge e_R = _K4_ReducePathToEdge(theGraph, context, edgeType, R, e_R, A, e_A); if (gp_IsNotEdge(theGraph, e_R)) return NOTOK; // Preserve the net orientation along the DFS path in the case of a tree edge if (gp_GetEdgeType(theGraph, e_R) == EDGE_TYPE_CHILD) { if (invertedFlag) gp_SetEdgeFlagInverted(theGraph, e_R); } return OK; } /******************************************************************** _K4_DeleteUnmarkedEdgesInBicomp() This function deletes from a given biconnected component all edges whose visited member is zero. The stack is used but preserved. In debug mode, NOTOK can result if there is a stack overflow. This method pushes at most one integer per vertex in the bicomp. This is the same as _DeleteUnmarkedEdgesInBicomp(), except it calls the overloaded _K4_DeleteEdge() rather than gp_DeleteEdge() Returns OK on success, NOTOK on implementation failure ********************************************************************/ int _K4_DeleteUnmarkedEdgesInBicomp(graphP theGraph, K4SearchContext *context, int BicompRoot) { int V, e, eNext; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push(theGraph->theStack, BicompRoot); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); e = gp_GetFirstEdge(theGraph, V); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) sp_Push(theGraph->theStack, gp_GetNeighbor(theGraph, e)); eNext = gp_GetNextEdge(theGraph, e); if (!gp_GetEdgeVisited(theGraph, e)) _K4_DeleteEdge(theGraph, context, e); e = eNext; } } return OK; } /******************************************************************** Edge deletion that occurs during a reduction or restoration of a reduction is augmented by clearing the K_4 search-specific data members. This is augmentation is not needed in the delete edge operations that happen once a K_4 homeomorph has been found and marked for isolation. ********************************************************************/ int _K4_DeleteEdge(graphP theGraph, K4SearchContext *context, int e) { _K4Search_InitEdgeRec(context, e); _K4Search_InitEdgeRec(context, gp_GetTwin(theGraph, e)); return gp_DeleteEdge(theGraph, e); } /**************************************************************************** _K4_GetCumulativeOrientationOnDFSPath() ****************************************************************************/ int _K4_GetCumulativeOrientationOnDFSPath(graphP theGraph, int ancestor, int descendant) { int e, parent; int invertedFlag = 0; /* If we are marking from a root vertex upward, then go up to the parent copy before starting the loop */ if (gp_IsVirtualVertex(theGraph, descendant)) descendant = gp_GetVertexFromBicompRoot(theGraph, descendant); while (descendant != ancestor) { if (descendant == NIL) return NOTOK; // If we are at a bicomp root, then ascend to its parent copy if (gp_IsVirtualVertex(theGraph, descendant)) { parent = gp_GetVertexFromBicompRoot(theGraph, descendant); } // If we are on a regular, non-virtual vertex then get the edge to the parent else { // Scan the edges for the one marked as the DFS parent parent = NIL; e = gp_GetFirstEdge(theGraph, descendant); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_PARENT) { parent = gp_GetNeighbor(theGraph, e); break; } e = gp_GetNextEdge(theGraph, e); } // If the edge to the parent vertex was not found, then the data structure is corrupt if (parent == NIL) return NOTOK; // Add the inversion flag on the child edge record to the cumulative result e = gp_GetTwin(theGraph, e); if (gp_GetEdgeType(theGraph, e) != EDGE_TYPE_CHILD || gp_GetNeighbor(theGraph, e) != descendant) return NOTOK; invertedFlag ^= gp_GetEdgeFlagInverted(theGraph, e); } // Hop to the parent and reiterate descendant = parent; } return invertedFlag; } /**************************************************************************** _K4_TestPathComponentForAncestor() Tests the external face path between R and A for a DFS ancestor of A. Returns TRUE if found, FALSE otherwise. ****************************************************************************/ int _K4_TestPathComponentForAncestor(graphP theGraph, int R, int prevLink, int A) { int Z, ZPrevLink; ZPrevLink = prevLink; Z = R; while (Z != A) { Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); if (Z < A) return TRUE; } return FALSE; } /**************************************************************************** _K4_ClearVisitedInPathComponent() There is a subcomponent of the bicomp rooted by R that is separable by the 2-cut (R, A). The component contains the external face path from R to A. The 1^prevLink edge record of R is contained in that path (i.e. the first edge record if prevLink indicates the last, or the last edge record if prevLink indicates the first). The prevLink is passed because _GetNeighborOnExtFace() uses the opposing link to traverse to the "next" vertex. All vertices in this desired component are along the external face, so we traverse along the external face vertices strictly between R and A and clear all the visited flags of the edges and their incident vertices. Note that the vertices along the path (R ... A) only have edges incident to each other and to R and A because the component is separable by the (R, A)-cut. ****************************************************************************/ void _K4_ClearVisitedInPathComponent(graphP theGraph, int R, int prevLink, int A) { int Z, ZPrevLink, e; ZPrevLink = prevLink; Z = _GetNeighborOnExtFace(theGraph, R, &ZPrevLink); while (Z != A) { gp_ClearVisited(theGraph, Z); e = gp_GetFirstEdge(theGraph, Z); while (gp_IsEdge(theGraph, e)) { gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); gp_ClearVisited(theGraph, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } } /**************************************************************************** _K4_DeleteUnmarkedEdgesInPathComponent() There is a subcomponent of the bicomp rooted by R that is separable by the 2-cut (R, A) and contains the external face path from R to A that includes the gp_GetEdgeByLink(theGraph, R, 1^prevLink) edge record, which is the first edge record traversed by _GetNeighborOnExtFace(..., &prevLink). The edges in the component have been marked unvisited except for a path we intend to preserve. This routine deletes the unvisited edges. NOTE: This reduction invalidates the short-circuit extFace data structure, but it will be repaired for use by WalkUp and WalkDown when the path component reduction is completed. Returns OK on success, NOTOK on internal error ****************************************************************************/ int _K4_DeleteUnmarkedEdgesInPathComponent(graphP theGraph, int R, int prevLink, int A) { int Z, ZPrevLink, e; K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context == NULL) { return NOTOK; } // We need to use the stack to store up the edges we're going to delete. // We want to make sure there is enough stack capacity to handle it, // which is of course true because the stack is supposed to be empty. // We're doing a reduction on a bicomp on which the WalkDown has completed, // so the stack contains no bicomp roots to merge. if (sp_NonEmpty(theGraph->theStack)) return NOTOK; // Traverse all vertices internal to the path (R ... A) and push // all non-visited edges ZPrevLink = prevLink; Z = _GetNeighborOnExtFace(theGraph, R, &ZPrevLink); while (Z != A) { e = gp_GetFirstEdge(theGraph, Z); while (gp_IsEdge(theGraph, e)) { // The comparison of e to its twin is a useful way of ensuring we // don't push the edge twice, which is of course only applicable // when processing an edge whose endpoints are both internal to // the path (R ... A) if (!gp_GetEdgeVisited(theGraph, e) && (e < gp_GetTwin(theGraph, e) || gp_GetNeighbor(theGraph, e) == R || gp_GetNeighbor(theGraph, e) == A)) { sp_Push(theGraph->theStack, e); } e = gp_GetNextEdge(theGraph, e); } Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } // Delete all the non-visited edges while (sp_NonEmpty(theGraph->theStack)) { sp_Pop(theGraph->theStack, e); _K4_DeleteEdge(theGraph, context, e); } return OK; } /**************************************************************************** _K4_ReducePathToEdge() Returns an edge record of the edge created on success, or NIL on failure On success, the edge record returned is in the adjacency list of R. The result can be tested for success or failure using gp_IsEdge() or gp_IsNotEdge() ****************************************************************************/ int _K4_ReducePathToEdge(graphP theGraph, K4SearchContext *context, int edgeType, int R, int e_R, int A, int e_A) { // Find out the links used in vertex R for edge e_R and in vertex A for edge e_A int Rlink = gp_GetFirstEdge(theGraph, R) == e_R ? 0 : 1; int Alink = gp_GetFirstEdge(theGraph, A) == e_A ? 0 : 1; // If the path is more than a single edge, then it must be reduced to an edge. // Note that even if the path is a single edge, the external face data structure // must still be modified since many edges connecting the external face have // been deleted if (gp_GetNeighbor(theGraph, e_R) != A) { int v_R, v_A; // Prepare for removing each of the two edges that join the path to the bicomp by // restoring it if it is a reduction edge (a constant time operation) // If e_R has a pathConnector vertex, then we prep by restoring a previously reduced // path for e_R if (context->E[e_R].pathConnector != NIL) { if (_K4_RestoreReducedPath(theGraph, context, e_R) != OK) { #ifdef DEBUG NOTOK; #endif return NIL; } e_R = gp_GetEdgeByLink(theGraph, R, Rlink); } // If e_A has a pathConnector vertex, then we prep by restoring a previously reduced // path for e_A if (context->E[e_A].pathConnector != NIL) { if (_K4_RestoreReducedPath(theGraph, context, e_A) != OK) { #ifdef DEBUG NOTOK; #endif return NIL; } e_A = gp_GetEdgeByLink(theGraph, A, Alink); } // Save the vertex neighbors of R and A indicated by e_R and e_A for // later use in setting up the path connectors. v_R = gp_GetNeighbor(theGraph, e_R); v_A = gp_GetNeighbor(theGraph, e_A); // Now delete the two edges that join the path to the bicomp. _K4_DeleteEdge(theGraph, context, e_R); _K4_DeleteEdge(theGraph, context, e_A); // Now add a single edge to represent the path // We use 1^Rlink, for example, because Rlink was the link from R that indicated e_R, // so 1^Rlink is the link that indicated e_R in the other edge record that was // adjacent to e_R. We want gp_InsertEdge to place the new edge record where e_R was // in R's adjacency list gp_InsertEdge(theGraph, R, gp_GetEdgeByLink(theGraph, R, Rlink), 1 ^ Rlink, A, gp_GetEdgeByLink(theGraph, A, Alink), 1 ^ Alink); // Now set up the path connectors so the original path can be recovered if needed. e_R = gp_GetEdgeByLink(theGraph, R, Rlink); context->E[e_R].pathConnector = v_R; e_A = gp_GetEdgeByLink(theGraph, A, Alink); context->E[e_A].pathConnector = v_A; // Also, set the reduction edge's type to preserve the DFS tree structure gp_SetEdgeType(theGraph, e_R, _ComputeEdgeRecordType(theGraph, R, A, edgeType)); gp_SetEdgeType(theGraph, e_A, _ComputeEdgeRecordType(theGraph, A, R, edgeType)); } // Set the external face data structure gp_SetExtFaceVertex(theGraph, R, Rlink, A); gp_SetExtFaceVertex(theGraph, A, Alink, R); // If the edge represents an entire bicomp, then more external face // settings are needed. if (gp_GetFirstEdge(theGraph, R) == gp_GetLastEdge(theGraph, R)) { gp_SetExtFaceVertex(theGraph, R, 1 ^ Rlink, A); gp_SetExtFaceVertex(theGraph, A, 1 ^ Alink, R); } return e_R; } /**************************************************************************** _K4_RestoreReducedPath() Given an edge record of an edge used to reduce a path, we want to restore the path in constant time. The path may contain more reduction edges internally, but we do not search for and process those since it would violate the constant time bound required of this function. Note that we don't bother amending the external face data structure because a reduced path is only restored when it will shortly be reduced again or when we don't really need the external face data structure anymore. Return OK on success, NOTOK on failure ****************************************************************************/ int _K4_RestoreReducedPath(graphP theGraph, K4SearchContext *context, int e) { int eTwin, u, v, w, x; int e0, e1, eTwin0, eTwin1; // An edge e does not represent a path reduction that needs to be restored // unless it was assigned a pathConnector vertex (and so is non-NIL) if (context->E[e].pathConnector == NIL) return OK; eTwin = gp_GetTwin(theGraph, e); u = gp_GetNeighbor(theGraph, eTwin); v = context->E[e].pathConnector; w = context->E[eTwin].pathConnector; x = gp_GetNeighbor(theGraph, e); // Get the locations of the EdgeRecs between which the new EdgeRecs // must be added in order to reconnect the path parallel to the edge. e0 = gp_GetNextEdge(theGraph, e); e1 = gp_GetPrevEdge(theGraph, e); eTwin0 = gp_GetNextEdge(theGraph, eTwin); eTwin1 = gp_GetPrevEdge(theGraph, eTwin); // We first delete the edge represented by e and eTwin. We do so before // restoring the path to ensure we do not exceed the maximum edge capacity. _K4_DeleteEdge(theGraph, context, e); // Now we add the two edges to reconnect the reduced path represented // by the edge [e, eTwin]. The edge record in u is added between e0 and e1. // Likewise, the new edge record in x is added between eTwin0 and eTwin1. if (gp_IsEdge(theGraph, e0)) { if (gp_InsertEdge(theGraph, u, e0, 1, v, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, u, e1, 0, v, NIL, 0) != OK) return NOTOK; } if (gp_IsEdge(theGraph, eTwin0)) { if (gp_InsertEdge(theGraph, x, eTwin0, 1, w, NIL, 0) != OK) return NOTOK; } else { if (gp_InsertEdge(theGraph, x, eTwin1, 0, w, NIL, 0) != OK) return NOTOK; } // Set the types of the newly added edges. In both cases, the first of the two // vertex parameters is known to be degree 2 because they are internal to the // path being restored, so this operation is constant time. if (_RestoreEdgeType(theGraph, v, u) != OK || _RestoreEdgeType(theGraph, w, x) != OK) return NOTOK; return OK; } /**************************************************************************** _K4_RestoreAndOrientReducedPaths() This function searches the embedding for any edges that are specially marked as being representative of a path that was previously reduced to a single edge by _ReducePathToEdge(). This method restores the path by replacing the edge with the path. Note that the new path may contain more reduction edges, and these will be iteratively expanded by the outer for loop. Equally, a reduced path may be restored into a path that itself is a reduction path that will only be attached to the embedding by some future step of the outer loop. The vertices along the path being restored must be given a consistent orientation with the endpoints. It is expected that the embedding will have been oriented prior to this operation. ****************************************************************************/ int _K4_RestoreAndOrientReducedPaths(graphP theGraph, K4SearchContext *context) { int e, eTwin, u, v, w, x, visited; for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph);) { // If e has a pathConnector vertex, then there is a reduced path // to restore and orient. if (context->E[e].pathConnector != NIL) { visited = gp_GetEdgeVisited(theGraph, e); eTwin = gp_GetTwin(theGraph, e); u = gp_GetNeighbor(theGraph, eTwin); v = context->E[e].pathConnector; w = context->E[eTwin].pathConnector; x = gp_GetNeighbor(theGraph, e); if (_K4_RestoreReducedPath(theGraph, context, e) != OK) return NOTOK; // If the path is on the external face, orient it if (gp_GetNeighbor(theGraph, gp_GetFirstEdge(theGraph, u)) == v || gp_GetNeighbor(theGraph, gp_GetLastEdge(theGraph, u)) == v) { // Reality check: ensure the path is connected to the // external face at both vertices. if (gp_GetNeighbor(theGraph, gp_GetFirstEdge(theGraph, x)) != w && gp_GetNeighbor(theGraph, gp_GetLastEdge(theGraph, x)) != w) return NOTOK; if (_OrientExternalFacePath(theGraph, u, v, w, x) != OK) return NOTOK; } if (visited) { if (_SetAllVisitedFlagsOnPath(theGraph, u, v, w, x) != OK) return NOTOK; } else { if (_ClearAllVisitedFlagsOnPath(theGraph, u, v, w, x) != OK) return NOTOK; } } else e += 2; } return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK4Search.h000066400000000000000000000011141521450711600312570ustar00rootroot00000000000000#ifndef GRAPH_K4SEARCH_H #define GRAPH_K4SEARCH_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphOuterplanarity.h" #ifdef __cplusplus extern "C" { #endif // Create a K4Search Graph, i.e., subclass an Outerplanarity Graph by extending it // with the ability to perform a search for a subgraph homeomorphic to K_4. #define K4SEARCH_NAME "K4Search" int gp_ExtendWith_K4Search(graphP theGraph); int gp_Detach_K4Search(graphP theGraph); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK4Search.private.h000066400000000000000000000032011521450711600327270ustar00rootroot00000000000000#ifndef GRAPH_K4SEARCH_PRIVATE_H #define GRAPH_K4SEARCH_PRIVATE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphOuterplanarity.private.h" #ifdef __cplusplus extern "C" { #endif /* Additional equipment for each EdgeRec pathConnector: Used in the edge records of a reduction edge to indicate the endpoints of a path that has been reduced from (removed from) the embedding so that the search for a K4 can continue. We only need a pathConnector because we reduce subgraphs that are separable by a 2-cut, so they can contribute at most one path to a subgraph homeomorphic to K4, if one is indeed found. Thus, we first delete all edges except for the desired path(s), then we reduce any retained path to an edge. */ typedef struct { int pathConnector; } K4Search_EdgeRec; typedef K4Search_EdgeRec *K4Search_EdgeRecP; /* Additional equipment for each vertex: None */ typedef struct { // Helps distinguish initialize from re-initialize int initialized; // The graph that this context augments graphP theGraph; // Parallel array for additional edge level equipment K4Search_EdgeRecP E; // Overloaded function pointers graphFunctionTableStruct functions; // Internal variable for converting a tail recursion into a simple loop int handlingBlockedBicomp; } K4SearchContext; extern int K4SEARCH_ID; #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK4Search_Extensions.c000066400000000000000000000517531521450711600335070ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include "graphK4Search.h" #include "graphK4Search.private.h" // Need to save and restore a graph flag related to IO #include "../io/graphIO.h" extern int _SearchForK4InBicomp(graphP theGraph, K4SearchContext *context, int v, int R); extern int _TestForCompleteGraphObstruction(graphP theGraph, int numVerts, int *degrees, int *imageVerts); extern int _getImageVertices(graphP theGraph, int *degrees, int maxDegree, int *imageVerts, int maxNumImageVerts); extern int _TestSubgraph(graphP theSubgraph, graphP theGraph); /* Forward declarations of local functions */ void _K4Search_ClearStructures(K4SearchContext *context); int _K4Search_CreateStructures(K4SearchContext *context); int _K4Search_InitStructures(K4SearchContext *context); void _K4Search_InitEdgeRec(K4SearchContext *context, int e); /* Forward declarations of overloading functions */ int _K4Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R); int _K4Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); int _K4Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); int _K4Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph); int _K4Search_EnsureVertexCapacity(graphP theGraph, int N); void _K4Search_ResetGraphStorage(graphP theGraph); int _K4Search_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity); /* Forward declarations of functions used by the extension system */ void *_K4Search_DupContext(void *pContext, void *theGraph); int _K4Search_CopyData(void *dstContext, void *srcContext); void _K4Search_FreeContext(void *); /**************************************************************************** * K4SEARCH_ID - the variable used to hold the integer identifier for this * extension, enabling this feature's extension context to be distinguished * from other features' extension contexts that may be attached to a graph. ****************************************************************************/ int K4SEARCH_ID = 0; /**************************************************************************** gp_ExtendWith_K4Search() This function adjusts the graph data structure to attach the K4 search feature. ****************************************************************************/ int gp_ExtendWith_K4Search(graphP theGraph) { K4SearchContext *context = NULL; if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the K4 search feature has already been attached to the graph, // then there is no need to attach it again gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) { return OK; } // Ensure theGraph is an Outerplanarity Graph if (gp_ExtendWith_Outerplanarity(theGraph) != OK) return NOTOK; // Allocate a new extension context context = (K4SearchContext *)malloc(sizeof(K4SearchContext)); if (context == NULL) { return NOTOK; } // First, tell the context that it is not initialized context->initialized = 0; // Save a pointer to theGraph in the context context->theGraph = theGraph; // Put the overload functions into the context function table. // gp_AddExtension will overload the graph's functions with these, and // return the base function pointers in the context function table memset(&context->functions, 0, sizeof(graphFunctionTableStruct)); context->functions.fpHandleBlockedBicomp = _K4Search_HandleBlockedBicomp; context->functions.fpEmbedPostprocess = _K4Search_EmbedPostprocess; context->functions.fpCheckEmbeddingIntegrity = _K4Search_CheckEmbeddingIntegrity; context->functions.fpCheckObstructionIntegrity = _K4Search_CheckObstructionIntegrity; context->functions.fpEnsureVertexCapacity = _K4Search_EnsureVertexCapacity; context->functions.fpResetGraphStorage = _K4Search_ResetGraphStorage; context->functions.fpEnsureEdgeCapacity = _K4Search_EnsureEdgeCapacity; _K4Search_ClearStructures(context); // Store the K4 search context, including the data structure and the // function pointers, as an extension of the graph if (gp_AddExtension(theGraph, &K4SEARCH_ID, (void *)context, _K4Search_DupContext, _K4Search_CopyData, _K4Search_FreeContext, &context->functions) != OK) { _K4Search_FreeContext(context); context = NULL; return NOTOK; } // Create the K4-specific structures if the size of the graph is known // Attach functions are always invoked after gp_New(), but if a graph // extension must be attached before gp_Read(), then the attachment // also happens before gp_EnsureVertexCapacity(), which means N==0. // However, a feature can be attached after gp_EnsureVertexCapacity(), // in which case there is extra work to do when N > 0. if (gp_GetN(theGraph) > 0) { if (_K4Search_CreateStructures(context) != OK || _K4Search_InitStructures(context) != OK) { _K4Search_FreeContext(context); context = NULL; return NOTOK; } } return OK; } /******************************************************************** gp_Detach_K4Search() ********************************************************************/ int gp_Detach_K4Search(graphP theGraph) { return gp_RemoveExtension(theGraph, K4SEARCH_ID); } /******************************************************************** _K4Search_ClearStructures() ********************************************************************/ void _K4Search_ClearStructures(K4SearchContext *context) { if (!context->initialized) { // Before initialization, the pointers are stray, not NULL // Once NULL or allocated, free() or LCFree() can do the job context->E = NULL; context->handlingBlockedBicomp = FALSE; context->initialized = 1; } else { if (context->E != NULL) { free(context->E); context->E = NULL; } context->handlingBlockedBicomp = FALSE; } } /******************************************************************** _K4Search_CreateStructures() Create uninitialized structures for the vertex and edge levels, and initialized structures for the graph level ********************************************************************/ int _K4Search_CreateStructures(K4SearchContext *context) { if (gp_GetN(context->theGraph) <= 0) return NOTOK; if ((context->E = (K4Search_EdgeRecP)malloc(gp_UpperBoundEdgeStorage(context->theGraph) * sizeof(K4Search_EdgeRec))) == NULL || 0) { return NOTOK; } return OK; } /******************************************************************** _K4Search_InitStructures() ********************************************************************/ int _K4Search_InitStructures(K4SearchContext *context) { memset(context->E, NIL_CHAR, gp_UpperBoundEdgeStorage(context->theGraph) * sizeof(K4Search_EdgeRec)); return OK; } /******************************************************************** ********************************************************************/ int _K4Search_EnsureVertexCapacity(graphP theGraph, int N) { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; theGraph->N = N; theGraph->NV = N; if (theGraph->edgeCapacity == 0) theGraph->edgeCapacity = DEFAULT_EDGE_CAPACITY_FACTOR * N; if (_K4Search_CreateStructures(context) != OK || _K4Search_InitStructures(context) != OK) return NOTOK; context->functions.fpEnsureVertexCapacity(theGraph, N); return OK; } /******************************************************************** ********************************************************************/ void _K4Search_ResetGraphStorage(graphP theGraph) { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) { // Reset the graph storage in base class(es) context->functions.fpResetGraphStorage(theGraph); // Do the reset that is specific to this module _K4Search_InitStructures(context); } } /******************************************************************** _K4Search_EnsureEdgeCapacity() ********************************************************************/ int _K4Search_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity) { K4SearchContext *context = NULL; K4Search_EdgeRecP oldE = NULL, newE = NULL; int oldEsize = gp_UpperBoundEdgeStorage(theGraph), newEsize = 0; // If the requirement is already satisfied, then no work to do if (gp_GetEdgeCapacity(theGraph) >= requiredEdgeCapacity) return OK; // Get the graph's extension context so we can work on it gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; // Call the superclass function to make sure lower levels of parallel // edge arrays can successfully meet the new capacity requirement if (context->functions.fpEnsureEdgeCapacity(theGraph, requiredEdgeCapacity) != OK) return NOTOK; // Save the current E so it can be freed once we replace it oldE = context->E; // The superclass EnsureEdgeCapacity method succeeded, so the graph's // new edge capacity is already set, which means we the upper bound of // the graph's edge storage gives the new parallel array size we need. newEsize = gp_UpperBoundEdgeStorage(theGraph); // We must successfully allocate the new parallel edge array newE = (K4Search_EdgeRecP)malloc(newEsize * sizeof(K4Search_EdgeRec)); if (newE == NULL) return NOTOK; // Clear all new edge records memset(newE, NIL_CHAR, newEsize * sizeof(K4Search_EdgeRec)); // Copy the old edge records to the new edge records memcpy(newE, oldE, oldEsize * sizeof(K4Search_EdgeRec)); // Set the new edge array into the context and free the old one context->E = newE; free(oldE); return OK; } /******************************************************************** _K4Search_DupContext() ********************************************************************/ void *_K4Search_DupContext(void *pContext, void *theGraph) { K4SearchContext *context = (K4SearchContext *)pContext; K4SearchContext *newContext = (K4SearchContext *)malloc(sizeof(K4SearchContext)); if (newContext != NULL) { int Esize = gp_UpperBoundEdgeStorage((graphP)theGraph); *newContext = *context; newContext->theGraph = (graphP)theGraph; newContext->initialized = 0; _K4Search_ClearStructures(newContext); if (((graphP)theGraph)->N > 0) { if (_K4Search_CreateStructures(newContext) != OK) { _K4Search_FreeContext(newContext); newContext = NULL; return NULL; } memcpy(newContext->E, context->E, Esize * sizeof(K4Search_EdgeRec)); } } return newContext; } /******************************************************************** _K4Search_CopyData() ********************************************************************/ int _K4Search_CopyData(void *dstContext, void *srcContext) { K4SearchContext *dstK4Context = (K4SearchContext *)dstContext; K4SearchContext *srcK4Context = (K4SearchContext *)srcContext; int dstEdgeStorage, srcEdgeStorage; if (dstContext == NULL) return NOTOK; // If the srcContext is NULL, then the caller wants the data // structures in the dstContext to be reset/reinitialized if (srcContext == NULL) return _K4Search_InitStructures(dstK4Context); // ELSE: If there is also a srcContext, then we copy data from it dstEdgeStorage = gp_UpperBoundEdgeStorage(dstK4Context->theGraph); srcEdgeStorage = gp_UpperBoundEdgeStorage(srcK4Context->theGraph); // The caller (ultimately gp_CopyGraph()) is responsible for making sure that the // destination graph has enough edge capacity to receive the source graph content if (dstEdgeStorage < srcEdgeStorage) return NOTOK; // If the destination graph has more edge capacity, then we make sure that the // extra edge capacity is reinitialized if (dstEdgeStorage > srcEdgeStorage) { memset(dstK4Context->E, NIL_CHAR, gp_UpperBoundEdgeStorage(dstK4Context->theGraph) * sizeof(K4Search_EdgeRec)); } memcpy(dstK4Context->E, srcK4Context->E, gp_UpperBoundEdgeStorage(dstK4Context->theGraph) * sizeof(K4Search_EdgeRec)); return OK; } /******************************************************************** _K4Search_FreeContext() ********************************************************************/ void _K4Search_FreeContext(void *pContext) { K4SearchContext *context = (K4SearchContext *)pContext; _K4Search_ClearStructures(context); free(pContext); } /******************************************************************** ********************************************************************/ void _K4Search_InitEdgeRec(K4SearchContext *context, int e) { context->E[e].pathConnector = NIL; } /******************************************************************** _K4Search_HandleBlockedBicomp() Returns OK if no K4 homeomorph found and blockage cleared (OK to proceed with Walkdown embedding) NONEMBEDDABLE if K4 homeomorph found, and Walkdown embedding should be terminated. NOTOK on internal error ********************************************************************/ int _K4Search_HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R) { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context == NULL) return NOTOK; if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK4) { int RetVal = OK; // If invoked on a descendant bicomp, then we push its root then search once // since not finding a K4 homeomorph will also clear the blockage and allow // the Walkdown to continue walking down if (R != RootVertex) { sp_Push2(theGraph->theStack, R, 0); if ((RetVal = _SearchForK4InBicomp(theGraph, context, v, R)) == OK) { // If the Walkdown will be told it is OK to continue, then we have to take the descendant // bicomp root back off the stack so the Walkdown can try to descend to it again. // The top of stack has the pair R and 0/1 direction Walkdown traversal proceeds from R // Need only R, so pop and discard the direction, then pop R sp_Pop2_Discard1(theGraph->theStack, R); // And we have to clear the indicator of the minor A that was reduced, since it was eliminated. theGraphIC(theGraph)->minorType = MINORTYPE_NONE; } } // Otherwise, if invoked on a child bicomp rooted by a virtual copy of v, // then we search for a K4 homeomorph, and if OK is returned, then that indicates // the blockage has been cleared and it is OK to Walkdown the bicomp. // But the Walkdown finished, already, so we launch it again. // If the Walkdown returns OK then all forward edge records of "back" edges were embedded. // If NONEMBEDDABLE is returned, then the bicomp got blocked again, so we have to reiterate the K4 search else { // If Walkdown has recursively called this handler on the bicomp rooted by RootVertex, // then it is still blocked, so we just return NONEMBEDDABLE, which causes Walkdown to // return to the loop below and signal that the loop should invoke the Walkdown again. if (context->handlingBlockedBicomp) return NONEMBEDDABLE; context->handlingBlockedBicomp = TRUE; do { // Detect whether bicomp can be used to find a K4 homeomorph. It it does, then // it returns NONEMBEDDABLE so we break the search because we found the desired K4 // If OK is returned, then the blockage was cleared and it is OK to Walkdown again. if ((RetVal = _SearchForK4InBicomp(theGraph, context, v, RootVertex)) != OK) break; // Walkdown again to embed more edges. If Walkdown returns OK, then all remaining // edges to its descendants are embedded, so we'll get out of this loop. If Walkdown // detects that it still has not embedded all the edges to descendants of the bicomp's // root edge child, then Walkdown calls this routine again, and the above non-reentrancy // code returns NONEMBEDDABLE, causing this loop to search again for a K4. theGraphIC(theGraph)->minorType = MINORTYPE_NONE; RetVal = theGraph->functions->fpWalkDown(theGraph, v, RootVertex); // Except if the Walkdown returns NONEMBEDDABLE due to finding a K4 homeomorph entangled // with a descendant bicomp (the R != RootVertex case above), then it was found // entangled with Minor A, so we can stop the search if minor A is detected if (theGraphIC(theGraph)->minorType & MINORTYPE_A) break; } while (RetVal == NONEMBEDDABLE); context->handlingBlockedBicomp = FALSE; } return RetVal; } // else if we are not doing a K3,3 homeomorph search, then call the superclass to handle return context->functions.fpHandleBlockedBicomp(theGraph, v, RootVertex, R); } /******************************************************************** ********************************************************************/ int _K4Search_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult) { int savedEmbedFlags = 0, savedZEROBASEDIO = 0; // For K4 search, we just return the edge embedding result because the // search result has been obtained already. if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK4) { if (edgeEmbeddingResult == OK) { // When a graph does not contain a K4 homeomorph, the embedding // is meaningless, so we empty it out. We preserve the embedFlags // to ensure post-processing continues as expected. savedEmbedFlags = gp_GetEmbedFlags(theGraph); savedZEROBASEDIO = gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO; gp_ResetGraphStorage(theGraph); theGraph->embedFlags = savedEmbedFlags; theGraph->graphFlags &= savedZEROBASEDIO; } return edgeEmbeddingResult; } // When not searching for K4, we let the superclass do the work else { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpEmbedPostprocess(theGraph, v, edgeEmbeddingResult); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K4Search_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK4) { return OK; } // When not searching for K4, we let the superclass do the work else { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckEmbeddingIntegrity(theGraph, origGraph); } } return NOTOK; } /******************************************************************** ********************************************************************/ int _K4Search_CheckObstructionIntegrity(graphP theGraph, graphP origGraph) { // When searching for K4, we ensure that theGraph is a subgraph of // the original graph and that it contains a K4 homeomorph if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_SEARCHFORK4) { int degrees[4], imageVerts[4]; if (_TestSubgraph(theGraph, origGraph) != TRUE) return NOTOK; if (_getImageVertices(theGraph, degrees, 3, imageVerts, 4) != OK) return NOTOK; if (_TestForCompleteGraphObstruction(theGraph, 4, degrees, imageVerts) == TRUE) { return OK; } return NOTOK; } // When not searching for K4, we let the superclass do the work else { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) { return context->functions.fpCheckObstructionIntegrity(theGraph, origGraph); } } return NOTOK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/000077500000000000000000000000001521450711600235675ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-api-utilities.c000066400000000000000000000122111521450711600270240ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include "../lowLevelUtils/appconst.h" /* Private function declarations (exported within system) */ size_t _g6_GetNumCharsForEncoding(int order); int _g6_GetNumCharsForOrder(int order); size_t _g6_GetExpectedNumPaddingZeroes(const int order, const size_t numChars); // NOTE: this method is used by g6_ReadGraph() to ensure that graphs after the // first one in the file have the same order, and by g6_WriteGraph() to ensure // that the encoding of the graph order at the beginning of the graph encoding // is correct. The G6 format specification does not have this restriction, but // the docs for Nauty's geng utility indicate that its output files will only // contain all graphs up to isomorphism for the single graph order specified // with the positional command line argument. int _g6_ValidateOrderOfEncodedGraph(char *graphBuff, int order); // NOTE: this method is now used to validate each graph we're reading in, as // well as to check the validity of the encoding produced before attempting to // write. int _g6_ValidateGraphEncoding(char *graphBuff, const int order, const size_t numChars); /* Private functions */ size_t _g6_GetMaxEdgeCount(int); size_t _g6_GetMaxEdgeCount(int order) { return ((size_t)order * (order - 1)) / 2; } size_t _g6_GetNumCharsForEncoding(int order) { size_t maxNumEdges = _g6_GetMaxEdgeCount(order); return (maxNumEdges / 6) + (maxNumEdges % 6 ? 1 : 0); } int _g6_GetNumCharsForOrder(int order) { if (order > 0 && order < 63) { return 1; } else if (order >= 63 && order <= 100000) { return 4; } return -1; } size_t _g6_GetExpectedNumPaddingZeroes(const int order, const size_t numChars) { size_t maxNumEdges = _g6_GetMaxEdgeCount(order); size_t expectedNumPaddingZeroes = numChars * 6 - maxNumEdges; return expectedNumPaddingZeroes; } int _g6_ValidateOrderOfEncodedGraph(char *graphBuff, int order) { int n = 0; char currChar = graphBuff[0]; if (currChar == 126) { if (graphBuff[1] == 126) { gp_ErrorMessage("Can only handle graphs of order <= 100,000."); return NOTOK; } else if (graphBuff[1] > 126) { gp_ErrorMessage("Invalid graph order signifier."); return NOTOK; } else { int orderCharIndex = 2; for (int i = 1; i < 4; i++) n |= (graphBuff[i] - 63) << (6 * orderCharIndex--); } } else if (currChar > 62 && currChar < 126) n = currChar - 63; else { gp_ErrorMessage("Character doesn't correspond to a printable ASCII " "character."); return NOTOK; } if (n != order) { gp_ErrorMessage("Graph order %d doesn't match expected graph order %d", n, order); return NOTOK; } return OK; } int _g6_ValidateGraphEncoding(char *graphBuff, const int order, const size_t numChars) { int exitCode = OK; size_t numPaddingZeroes = 0, numCharsForGraphEncoding = 0; size_t expectedNumPaddingZeroes = 0, expectedNumChars = 0; char finalByte = '\0'; if (graphBuff == NULL || strlen(graphBuff) == 0) { gp_ErrorMessage("Invalid encoding: graphBuff is NULL or empty."); return NOTOK; } expectedNumPaddingZeroes = _g6_GetExpectedNumPaddingZeroes(order, numChars); finalByte = graphBuff[numChars - 1] - 63; // Num edges of the graph (and therefore the number of bits) is (n * (n-1))/2, and // since each resulting byte needs to correspond to an ascii character between 63 and 126, // each group is only comprised of 6 bits (to which we add 63 for the final byte value) expectedNumChars = _g6_GetNumCharsForEncoding(order); numCharsForGraphEncoding = strlen(graphBuff); if (expectedNumChars != numCharsForGraphEncoding) { gp_ErrorMessage("Invalid number of bytes for graph of order %d; got %d " "but expected %d", order, (int)numCharsForGraphEncoding, (int)expectedNumChars); return NOTOK; } // Check that characters are valid ASCII characters between 62 and 126 for (size_t i = 0; i < numChars; i++) { if (graphBuff[i] < 63 || graphBuff[i] > 126) { gp_ErrorMessage("Invalid character at index %d: '%c'", (int)i, graphBuff[i]); return NOTOK; } } // Check that there are no extraneous bits in representation (since we pad out to a // multiple of 6 before splitting into bytes and adding 63 to each byte) for (size_t i = 0; i < expectedNumPaddingZeroes; i++) { if (finalByte & (1 << i)) break; numPaddingZeroes++; } if (numPaddingZeroes != expectedNumPaddingZeroes) { gp_ErrorMessage("Expected %d padding zeroes, but got %d.", (int)expectedNumPaddingZeroes, (int)numPaddingZeroes); exitCode = NOTOK; } return exitCode; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-read-iterator.c000066400000000000000000000547411521450711600270220ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include "strOrFile.h" #include "g6-read-iterator.h" // For definition of zero-based IO flag #include "graphIO.h" /* Imported functions */ extern size_t _g6_GetNumCharsForEncoding(int order); extern int _g6_GetNumCharsForOrder(int order); extern size_t _g6_GetExpectedNumPaddingZeroes(const int order, const size_t numChars); extern int _g6_ValidateOrderOfEncodedGraph(char *graphBuff, int order); extern int _g6_ValidateGraphEncoding(char *graphBuff, const int order, const size_t numChars); /* Private function declarations (exported within system) */ int _g6_ReadGraphFromStrOrFile(graphP theGraph, strOrFileP *pInputContainer); /* Private functions */ int _g6_InitReaderWithStrOrFile(G6ReadIteratorP theG6ReadIterator, strOrFileP *pInputContainer); int _g6_InitReader(G6ReadIteratorP theG6ReadIterator); int _g6_IsReaderInitialized(G6ReadIteratorP theG6ReadIterator, int reportUninitializedParts); int _g6_ValidateHeader(strOrFileP inputContainer); int _g6_ValidateFirstChar(char c, const int lineNum); int _g6_DetermineOrderFromInput(strOrFileP inputContainer, int *order); int _g6_DecodeGraph(char *graphBuff, const int order, const int numChars, graphP theGraph); int _g6_ReadGraphFromFile(graphP theGraph, char *pathToG6File); int _g6_ReadGraphFromString(graphP theGraph, char *g6EncodedString); /******************************************************************** Package private structure declaration for read iterator ********************************************************************/ typedef struct strOrFileStruct strOrFileStruct; typedef strOrFileStruct *strOrFileP; struct G6ReadIteratorStruct { strOrFileP inputContainer; int numGraphsRead; int order; int numCharsForOrder; size_t numCharsForGraphEncoding; size_t currGraphBuffSize; char *currGraphBuff; graphP currGraph; int endReached; }; /******************************************************************** Public and package private method implementations for read iterator ********************************************************************/ int g6_NewReader(G6ReadIteratorP *pG6ReadIterator, graphP theGraph) { int exitCode = OK; if (pG6ReadIterator == NULL) { gp_ErrorMessage("Unable to allocate G6ReadIterator, as pointer to " "which to assign address of memory allocated for " "G6ReadIterator is NULL."); return NOTOK; } if (pG6ReadIterator != NULL && (*pG6ReadIterator) != NULL) { gp_ErrorMessage("G6ReadIterator is not NULL and therefore can't be " "allocated."); return NOTOK; } // numGraphsRead, order, numCharsForOrder, numCharsForGraphEncoding, and // currGraphBuffSize all set to 0 (*pG6ReadIterator) = (G6ReadIteratorP)calloc(1, sizeof(G6ReadIteratorStruct)); if ((*pG6ReadIterator) == NULL) { gp_ErrorMessage("Unable to allocate memory for G6ReadIterator."); return NOTOK; } (*pG6ReadIterator)->inputContainer = NULL; if (theGraph == NULL) { gp_ErrorMessage("Must allocate graph to be used by G6ReadIterator."); g6_FreeReader(pG6ReadIterator); } else { (*pG6ReadIterator)->currGraph = theGraph; } return exitCode; } int _g6_IsReaderInitialized(G6ReadIteratorP theG6ReadIterator, int reportUninitializedParts) { int readerInitialized = TRUE; if (theG6ReadIterator == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6ReadIterator is NULL."); readerInitialized = FALSE; } else { if (!sf_IsValidStrOrFile(theG6ReadIterator->inputContainer)) { if (reportUninitializedParts) gp_ErrorMessage("G6ReadIterator's inputContainer string-or-file " "container is not valid."); readerInitialized = FALSE; } if (theG6ReadIterator->currGraphBuff == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6ReadIterator's currGraphBuff is NULL."); readerInitialized = FALSE; } if (theG6ReadIterator->currGraph == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6ReadIterator's currGraph is NULL."); readerInitialized = FALSE; } } return readerInitialized; } int g6_EndReached(G6ReadIteratorP theG6ReadIterator) { if (theG6ReadIterator == NULL) return TRUE; return theG6ReadIterator->endReached; } int g6_InitReaderWithString(G6ReadIteratorP theG6ReadIterator, char *inputString) { strOrFileP inputContainer = NULL; if (theG6ReadIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6ReadIterator must be non-NULL."); return NOTOK; } if (_g6_IsReaderInitialized(theG6ReadIterator, FALSE)) { gp_ErrorMessage("Unable to initialize reader, as it was already " "previously initialized."); return NOTOK; } if (inputString == NULL || strlen(inputString) == 0) { gp_ErrorMessage("Unable to initialize reader with empty input string."); return NOTOK; } if ((inputContainer = sf_NewInputContainer(inputString, NULL)) == NULL) { gp_ErrorMessage("Unable to initialize reader with string, as we failed " "to allocate the inputContainer."); return NOTOK; } return _g6_InitReaderWithStrOrFile( theG6ReadIterator, (&inputContainer)); } int g6_InitReaderWithFileName(G6ReadIteratorP theG6ReadIterator, char const *const infileName) { strOrFileP inputContainer = NULL; if (theG6ReadIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6ReadIterator must be non-NULL."); return NOTOK; } if (_g6_IsReaderInitialized(theG6ReadIterator, FALSE)) { gp_ErrorMessage( "Unable to initialize reader, as it was already previously " "initialized.\n"); return NOTOK; } if (infileName == NULL || strlen(infileName) == 0) { gp_ErrorMessage("Unable to initialize reader with empty infile name."); return NOTOK; } if ((inputContainer = sf_NewInputContainer(NULL, infileName)) == NULL) { gp_ErrorMessage("Unable to initialize reader with file name, as we " "failed to allocate the inputContainer."); return NOTOK; } return _g6_InitReaderWithStrOrFile( theG6ReadIterator, (&inputContainer)); } int _g6_InitReaderWithStrOrFile(G6ReadIteratorP theG6ReadIterator, strOrFileP *pInputContainer) { if (theG6ReadIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6ReadIterator must be non-NULL."); return NOTOK; } if (pInputContainer == NULL || !sf_IsValidStrOrFile((*pInputContainer))) { gp_ErrorMessage("Unable to initialize reader with invalid strOrFile " "input container."); return NOTOK; } theG6ReadIterator->inputContainer = (*pInputContainer); // We have taken ownership of the inputContainer, and so we have set the // caller's pointer to NULL. The reader is responsible for freeing this // input container. (*pInputContainer) = NULL; return _g6_InitReader(theG6ReadIterator); } int _g6_InitReader(G6ReadIteratorP theG6ReadIterator) { char charConfirmation = EOF; int firstChar = '\0'; int lineNum = 1; int order = NIL; strOrFileP inputContainer = theG6ReadIterator->inputContainer; if ((firstChar = sf_getc(inputContainer)) == EOF) { gp_ErrorMessage("Unable to initialize reader: .g6 infile is empty."); return NOTOK; } else { charConfirmation = sf_ungetc((char)firstChar, inputContainer); if (charConfirmation != firstChar) { gp_ErrorMessage("Unable to initialize reader due to failure to " "ungetc first character."); return NOTOK; } if (firstChar == '>') { if (_g6_ValidateHeader(inputContainer) != OK) { gp_ErrorMessage("Unable to initialize reader due to inability " "to process and check .g6 infile header."); return NOTOK; } } } firstChar = sf_getc(inputContainer); charConfirmation = sf_ungetc((char)firstChar, inputContainer); if (charConfirmation != firstChar) { gp_ErrorMessage("Unable to initialize reader due to failure to ungetc " "first character."); return NOTOK; } if (_g6_ValidateFirstChar((char)firstChar, lineNum) != OK) return NOTOK; // Despite the general specification indicating that n \in [0, 68,719,476,735], // in practice n will be limited such that an integer will suffice in storing it. if (_g6_DetermineOrderFromInput(inputContainer, &order) != OK) { gp_ErrorMessage("Unable to initialize reader due to invalid graph " "order on line %d of .g6 file.", lineNum); return NOTOK; } if (gp_GetN(theG6ReadIterator->currGraph) == 0) { if (gp_EnsureVertexCapacity(theG6ReadIterator->currGraph, order) != OK) { gp_ErrorMessage("Unable to initialize reader due to failure " "initializing graph datastructure with order %d " "for graph on line %d of the .g6 file.", order, lineNum); return NOTOK; } theG6ReadIterator->order = order; } else { if (gp_GetN(theG6ReadIterator->currGraph) != order) { gp_ErrorMessage("Unable to initialize reader, as graph structure " "passed in was already initialized with order " "%d, which doesn't match the graph order %d " "specified in the file.", gp_GetN(theG6ReadIterator->currGraph), order); return NOTOK; } else { gp_ResetGraphStorage(theG6ReadIterator->currGraph); theG6ReadIterator->order = order; } } // Ensures zero-based flag is set regardless of whether the graph was initialized or reinitialized. theG6ReadIterator->currGraph->graphFlags |= GRAPHFLAGS_ZEROBASEDIO; theG6ReadIterator->numCharsForOrder = _g6_GetNumCharsForOrder(order); theG6ReadIterator->numCharsForGraphEncoding = _g6_GetNumCharsForEncoding(order); // Must add 3 bytes for newline, possible carriage return, and null terminator theG6ReadIterator->currGraphBuffSize = theG6ReadIterator->numCharsForOrder + theG6ReadIterator->numCharsForGraphEncoding + 3; theG6ReadIterator->currGraphBuff = (char *)calloc(theG6ReadIterator->currGraphBuffSize, sizeof(char)); if (theG6ReadIterator->currGraphBuff == NULL) { gp_ErrorMessage("Unable to allocate memory for currGraphBuff."); return NOTOK; } return OK; } int _g6_ValidateHeader(strOrFileP inputContainer) { char const *g6Header = ">>graph6<<"; char const *sparse6Header = ">>sparse6<"; char const *digraph6Header = ">>digraph6"; char headerCandidateChars[11]; headerCandidateChars[0] = '\0'; if (inputContainer == NULL) { gp_ErrorMessage("Invalid .g6 string-or-file container."); return NOTOK; } for (int i = 0; i < 10; i++) { headerCandidateChars[i] = sf_getc(inputContainer); } headerCandidateChars[10] = '\0'; if (strcmp(g6Header, headerCandidateChars) != 0) { if (strcmp(sparse6Header, headerCandidateChars) == 0) gp_ErrorMessage("Graph file is sparse6 format, which is not " "supported."); else if (strcmp(digraph6Header, headerCandidateChars) == 0) gp_ErrorMessage("Graph file is digraph6 format, which is not " "supported."); else gp_ErrorMessage("Invalid header for .g6 file."); return NOTOK; } return OK; } int _g6_ValidateFirstChar(char c, const int lineNum) { if (strchr(":;&", c) != NULL) { gp_ErrorMessage("Invalid first character on line %d, i.e. one of ':', " "';', or '&'.", lineNum); return NOTOK; } return OK; } int _g6_DetermineOrderFromInput(strOrFileP inputContainer, int *order) { int n = 0; int graphChar = '\0'; if (inputContainer == NULL) { gp_ErrorMessage("Invalid string-or-file container for .g6 input."); return NOTOK; } // Since geng: n must be in the range 1..32, and since edge-addition-planarity-suite // processing of random graphs may only handle up to n = 100,000, we will only check // if 1 or 4 bytes are necessary if ((graphChar = sf_getc(inputContainer)) == 126) { if ((graphChar = sf_getc(inputContainer)) == 126) { gp_ErrorMessage("Graphs of order n > 100000 are not supported at " "this time."); return NOTOK; } sf_ungetc((char)graphChar, inputContainer); for (int i = 2; i >= 0; i--) { graphChar = sf_getc(inputContainer) - 63; n |= graphChar << (6 * i); } if (n > 100000) { gp_ErrorMessage("Graph order greater than 100000 not supported."); return NOTOK; } } else if (graphChar > 62 && graphChar < 126) n = graphChar - 63; else { gp_ErrorMessage("Graph order is too small; character doesn't " "correspond to a printable ASCII character."); return NOTOK; } (*order) = n; return OK; } int g6_ReadGraph(G6ReadIteratorP theG6ReadIterator) { strOrFileP inputContainer = NULL; int lineNum = 0; char *currGraphBuff = NULL; char firstChar = '\0'; char *graphEncodingChars = NULL; graphP currGraph = NULL; const int order = theG6ReadIterator == NULL ? 0 : theG6ReadIterator->order; const int numCharsForOrder = theG6ReadIterator == NULL ? 0 : theG6ReadIterator->numCharsForOrder; const int numCharsForGraphEncoding = theG6ReadIterator == NULL ? 0 : theG6ReadIterator->numCharsForGraphEncoding; const int currGraphBuffSize = theG6ReadIterator == NULL ? 0 : theG6ReadIterator->currGraphBuffSize; if (!_g6_IsReaderInitialized(theG6ReadIterator, TRUE)) { gp_ErrorMessage("G6ReadIterator is not initialized."); return NOTOK; } inputContainer = theG6ReadIterator->inputContainer; lineNum = theG6ReadIterator->numGraphsRead + 1; currGraphBuff = theG6ReadIterator->currGraphBuff; currGraph = theG6ReadIterator->currGraph; if (sf_fgets(currGraphBuff, currGraphBuffSize, inputContainer) != NULL) { firstChar = currGraphBuff[0]; if (_g6_ValidateFirstChar(firstChar, lineNum) != OK) return NOTOK; // From https://stackoverflow.com/a/28462221, strcspn finds the index of the first // char in charset; this way, I replace the char at that index with the null-terminator currGraphBuff[strcspn(currGraphBuff, "\n\r")] = '\0'; // works for LF, CR, CRLF, LFCR, ... // If the line was too long, then we would have placed the null terminator at the final // index (where it already was; see strcpn docs), and the length of the string will be // longer than the line should have been, i.e. orderOffset + numCharsForGraphRepr if ((int)strlen(currGraphBuff) != (((lineNum == 1) ? 0 : numCharsForOrder) + numCharsForGraphEncoding)) { gp_ErrorMessage("Invalid line length read on line %d", lineNum); return NOTOK; } if (lineNum > 1) { if (_g6_ValidateOrderOfEncodedGraph(currGraphBuff, order) != OK) { gp_ErrorMessage("Order of graph on line %d is incorrect.", lineNum); return NOTOK; } } // On first line, we have already processed the characters corresponding to the graph // order, so there's no need to apply the offset. On subsequent lines, the orderOffset // must be applied so that we are only starting validation on the byte corresponding to // the encoding of the adjacency matrix. graphEncodingChars = (lineNum == 1) ? currGraphBuff : currGraphBuff + numCharsForOrder; if (_g6_ValidateGraphEncoding(graphEncodingChars, order, numCharsForGraphEncoding) != OK) { gp_ErrorMessage("Graph on line %d is invalid.", lineNum); return NOTOK; } if (lineNum > 1) { gp_ResetGraphStorage(currGraph); // Ensures zero-based flag is set after reinitializing graph. currGraph->graphFlags |= GRAPHFLAGS_ZEROBASEDIO; } if (_g6_DecodeGraph(graphEncodingChars, order, numCharsForGraphEncoding, currGraph) != OK) { gp_ErrorMessage("Unable to interpret bits on line %d to populate " "adjacency matrix.", lineNum); return NOTOK; } theG6ReadIterator->numGraphsRead = lineNum; } else { theG6ReadIterator->endReached = TRUE; } return OK; } // Takes the character array graphBuff, the derived number of vertices order, // and the numChars corresponding to the number of characters after the first byte // and performs the inverse transformation of the graph encoding: we subtract 63 from // each byte, then only process the 6 least significant bits of the resulting byte. For // the final byte, we determine how many padding zeroes to expect, and exclude them // from being processed. We index into the adjacency matrix by row and column, which // are incremented such that row ranges from 0 to one less than the column index. int _g6_DecodeGraph(char *graphBuff, const int order, const int numChars, graphP theGraph) { int numPaddingZeroes = _g6_GetExpectedNumPaddingZeroes(order, numChars); char currByte = '\0'; int bitValue = 0; int row = 0; int col = 1; if (theGraph == NULL) { gp_ErrorMessage("Must initialize graph datastructure before decoding " "the graph representation."); return NOTOK; } for (int i = 0; i < numChars; i++) { currByte = graphBuff[i] - 63; // j corresponds to the number of places one must bitshift the byte by // to read the next bit in the byte for (int j = sizeof(char) * 5; j >= 0; j--) { // If we are on the final byte, we know that the final // numPaddingZeroes bits can be ignored, so we break out of the loop if ((i == numChars) && j == numPaddingZeroes - 1) break; if (row == col) { row = 0; col++; } bitValue = ((currByte >> j) & 1u) ? 1 : 0; if (bitValue == 1) { // Also add the offset to the first vertex in in-memory storage, // because the G6 file is 0-based, but im-memory storage may not be. if (gp_DynamicAddEdge(theGraph, row + gp_LowerBoundVertexStorage(theGraph), 0, col + gp_LowerBoundVertexStorage(theGraph), 0) != OK) return NOTOK; } row++; } } return OK; } void g6_FreeReader(G6ReadIteratorP *pG6ReadIterator) { if (pG6ReadIterator != NULL && (*pG6ReadIterator) != NULL) { if ((*pG6ReadIterator)->inputContainer != NULL) sf_Free(&((*pG6ReadIterator)->inputContainer)); (*pG6ReadIterator)->numGraphsRead = 0; (*pG6ReadIterator)->order = 0; if ((*pG6ReadIterator)->currGraphBuff != NULL) { free((*pG6ReadIterator)->currGraphBuff); (*pG6ReadIterator)->currGraphBuff = NULL; } (*pG6ReadIterator)->currGraph = NULL; free((*pG6ReadIterator)); (*pG6ReadIterator) = NULL; } } int _g6_ReadGraphFromFile(graphP theGraph, char *pathToG6File) { strOrFileP inputContainer = NULL; if (pathToG6File == NULL || strlen(pathToG6File) == 0) { gp_ErrorMessage("Unable to read graph from file, as pathToG6File is " "NULL or empty string."); return NOTOK; } if ((inputContainer = sf_NewInputContainer(NULL, pathToG6File)) == NULL) { gp_ErrorMessage("Unable to allocate strOrFile container for infile " "\"%.*s\".", FILENAME_MAX, pathToG6File); return NOTOK; } return _g6_ReadGraphFromStrOrFile(theGraph, (&inputContainer)); } int _g6_ReadGraphFromString(graphP theGraph, char *g6EncodedString) { strOrFileP inputContainer = NULL; if (g6EncodedString == NULL || strlen(g6EncodedString) == 0) { gp_ErrorMessage("Unable to proceed with empty .g6 input string."); return NOTOK; } if ((inputContainer = sf_NewInputContainer(g6EncodedString, NULL)) == NULL) { gp_ErrorMessage("Unable to allocate strOrFile container for .g6 input " "string."); return NOTOK; } return _g6_ReadGraphFromStrOrFile(theGraph, (&inputContainer)); } int _g6_ReadGraphFromStrOrFile(graphP theGraph, strOrFileP *pInputContainer) { G6ReadIteratorP theG6ReadIterator = NULL; if (!sf_IsValidStrOrFile((*pInputContainer))) { gp_ErrorMessage("Invalid G6 output container."); return NOTOK; } if (g6_NewReader((&theG6ReadIterator), theGraph) != OK) { gp_ErrorMessage("Unable to allocate G6ReadIterator."); return NOTOK; } // NOTE: (*pInputContainer) will be NULL after we return from this call, // since the read iterator will take ownership of the input container. if (_g6_InitReaderWithStrOrFile(theG6ReadIterator, pInputContainer) != OK) { gp_ErrorMessage("Unable to initialize G6ReadIterator."); g6_FreeReader((&theG6ReadIterator)); return NOTOK; } if (g6_ReadGraph(theG6ReadIterator) != OK) gp_ErrorMessage("Unable to read graph from .g6 read iterator."); g6_FreeReader((&theG6ReadIterator)); return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-read-iterator.h000066400000000000000000000015371521450711600270220ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef G6_READ_ITERATOR #define G6_READ_ITERATOR #ifdef __cplusplus extern "C" { #endif #include #include "../graph.h" typedef struct G6ReadIteratorStruct G6ReadIteratorStruct; typedef G6ReadIteratorStruct *G6ReadIteratorP; int g6_NewReader(G6ReadIteratorP *pG6ReadIterator, graphP theGraph); int g6_InitReaderWithString(G6ReadIteratorP theG6ReadIterator, char *inputString); int g6_InitReaderWithFileName(G6ReadIteratorP theG6ReadIterator, char const *const infileName); int g6_ReadGraph(G6ReadIteratorP theG6ReadIterator); int g6_EndReached(G6ReadIteratorP theG6ReadIterator); void g6_FreeReader(G6ReadIteratorP *pG6ReadIterator); #ifdef __cplusplus } #endif #endif /* G6_READ_ITERATOR */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-write-iterator.c000066400000000000000000000514561521450711600272410ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include "strOrFile.h" #include "g6-write-iterator.h" /* Imported functions */ extern size_t _g6_GetNumCharsForEncoding(int order); extern int _g6_GetNumCharsForOrder(int order); extern int _g6_ValidateOrderOfEncodedGraph(char *graphBuff, int order); extern int _g6_ValidateGraphEncoding(char *graphBuff, const int order, const size_t numChars); /* Private function declarations (exported within system) */ int _g6_WriteGraphToStrOrFile(graphP theGraph, strOrFileP *pOutputContainer); /* Private functions */ int _g6_InitWriterWithStrOrFile(G6WriteIteratorP theG6WriteIterator, strOrFileP *pOutputContainer); int _g6_InitWriter(G6WriteIteratorP theG6WriteIterator); int _g6_IsWriterInitialized(G6WriteIteratorP theG6WriteIterator, int reportUninitializedParts); void _g6_PrecomputeColumnOffsets(size_t *columnOffsets, int order); void _g6_EncodeAdjMatAsG6(G6WriteIteratorP theG6WriteIterator); void _g6_GetFirstEdgeInUse(graphP theGraph, int *e, int *u, int *v); void _g6_GetNextEdgeInUse(graphP theGraph, int *e, int *u, int *v); int _g6_WriteEncodedGraph(G6WriteIteratorP theG6WriteIterator); int _g6_WriteGraphToFile(graphP theGraph, char *g6OutputFileName); int _g6_WriteGraphToString(graphP theGraph, char **pOutputStr); /******************************************************************** Package private structure declaration for write iterator ********************************************************************/ typedef struct strOrFileStruct strOrFileStruct; typedef strOrFileStruct *strOrFileP; struct G6WriteIteratorStruct { strOrFileP outputContainer; int order; int numCharsForOrder; size_t numCharsForGraphEncoding; size_t currGraphBuffSize; char *currGraphBuff; size_t *columnOffsets; graphP currGraph; }; /******************************************************************** Public and package private method implementations for write iterator ********************************************************************/ int g6_NewWriter(G6WriteIteratorP *pG6WriteIterator, graphP theGraph) { if (pG6WriteIterator == NULL) { gp_ErrorMessage( "Unable to allocate G6WriteIterator, as pointer to which to assign " "address of memory allocated for G6WriteIterator is NULL.\n"); return NOTOK; } if (pG6WriteIterator != NULL && (*pG6WriteIterator) != NULL) { gp_ErrorMessage("G6WriteIterator is not NULL and therefore can't be " "allocated."); return NOTOK; } if (theGraph == NULL || gp_GetN(theGraph) <= 0) { gp_ErrorMessage("Must allocate and initialize graph with an order " "greater than 0 to use the G6WriteIterator."); return NOTOK; } // order, numCharsForOrder, numCharsForGraphEncoding, and currGraphBuffSize // all set to 0 (*pG6WriteIterator) = (G6WriteIteratorP)calloc(1, sizeof(G6WriteIteratorStruct)); if ((*pG6WriteIterator) == NULL) { gp_ErrorMessage("Unable to allocate memory for G6WriteIterator."); return NOTOK; } (*pG6WriteIterator)->outputContainer = NULL; (*pG6WriteIterator)->currGraphBuff = NULL; (*pG6WriteIterator)->columnOffsets = NULL; (*pG6WriteIterator)->currGraph = theGraph; return OK; } int _g6_IsWriterInitialized(G6WriteIteratorP theG6WriteIterator, int reportUninitializedParts) { int writerIsInitialized = TRUE; if (theG6WriteIterator == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator is NULL."); writerIsInitialized = FALSE; } else { if (!sf_IsValidStrOrFile(theG6WriteIterator->outputContainer)) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator's outputContainer is not " "valid."); writerIsInitialized = FALSE; } if (theG6WriteIterator->currGraphBuff == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator's currGraphBuff is NULL."); writerIsInitialized = FALSE; } if (theG6WriteIterator->columnOffsets == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator's columnOffsets is NULL."); writerIsInitialized = FALSE; } if (theG6WriteIterator->currGraph == NULL) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator's currGraph is NULL."); writerIsInitialized = FALSE; } else { if (gp_GetN(theG6WriteIterator->currGraph) == 0) { if (reportUninitializedParts) gp_ErrorMessage("G6WriteIterator's currGraph does not " "contain a valid graph."); writerIsInitialized = FALSE; } } } return writerIsInitialized; } int g6_InitWriterWithString(G6WriteIteratorP theG6WriteIterator, char **pOutputString) { strOrFileP outputContainer = NULL; if (theG6WriteIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6WriteIterator must be " "non-NULL."); return NOTOK; } if (_g6_IsWriterInitialized(theG6WriteIterator, FALSE)) { gp_ErrorMessage( "Unable to initialize writer, as it was already previously " "initialized."); return NOTOK; } if (pOutputString == NULL) { gp_ErrorMessage("Unable to initialize writer with string, as pointer " "to which to assign address of output string is NULL."); return NOTOK; } if ((*pOutputString) != NULL) { gp_ErrorMessage("Unable to initialize writer with string, as pointer " "to which to assign address of output string points to " "allocated memory."); return NOTOK; } if ((outputContainer = sf_NewOutputContainer(pOutputString, NULL)) == NULL) { gp_ErrorMessage("Unable to initialize writer with string, as we failed " "to allocate the outputContainer."); return NOTOK; } return _g6_InitWriterWithStrOrFile( theG6WriteIterator, (&outputContainer)); } int g6_InitWriterWithFileName(G6WriteIteratorP theG6WriteIterator, char *outputFileName) { strOrFileP outputContainer = NULL; if (theG6WriteIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6WriteIterator must be " "non-NULL."); return NOTOK; } if (_g6_IsWriterInitialized(theG6WriteIterator, FALSE)) { gp_ErrorMessage("Unable to initialize writer, as it was already " "previously initialized."); return NOTOK; } if (outputFileName == NULL || strlen(outputFileName) == 0) { gp_ErrorMessage("Unable to initialize writer with NULL or empty output " "file name."); return NOTOK; } if ((outputContainer = sf_NewOutputContainer(NULL, outputFileName)) == NULL) { gp_ErrorMessage("Unable to initialize writer with file name, as we " "failed to allocate the outputContainer."); return NOTOK; } return _g6_InitWriterWithStrOrFile( theG6WriteIterator, (&outputContainer)); } int _g6_InitWriterWithStrOrFile(G6WriteIteratorP theG6WriteIterator, strOrFileP *pOutputContainer) { if (theG6WriteIterator == NULL) { gp_ErrorMessage("Invalid parameter: theG6WriteIterator must be " "non-NULL."); return NOTOK; } if (_g6_IsWriterInitialized(theG6WriteIterator, FALSE)) { gp_ErrorMessage("Unable to initialize writer, as it was already " "previously initialized."); return NOTOK; } if (!sf_IsValidStrOrFile((*pOutputContainer))) { gp_ErrorMessage("Unable to initialize writer with invalid strOrFile " "output container."); return NOTOK; } theG6WriteIterator->outputContainer = (*pOutputContainer); // We have taken ownership of the outputContainer, and so we have set the // caller's pointer to NULL. The writer is responsible for freeing this // output container. (*pOutputContainer) = NULL; return _g6_InitWriter(theG6WriteIterator); } int _g6_InitWriter(G6WriteIteratorP theG6WriteIterator) { char const *g6Header = ">>graph6<<"; theG6WriteIterator->order = gp_GetN(theG6WriteIterator->currGraph); if (theG6WriteIterator->order > 100000) { gp_ErrorMessage("Graphs of order n > 100000 are not supported at this " "time."); return NOTOK; } if (sf_fputs(g6Header, theG6WriteIterator->outputContainer) < 0) { gp_ErrorMessage("Unable to initialize writer due to failure to fputs " "header to outputContainer."); return NOTOK; } theG6WriteIterator->columnOffsets = (size_t *)calloc(theG6WriteIterator->order + 1, sizeof(size_t)); if (theG6WriteIterator->columnOffsets == NULL) { gp_ErrorMessage("Unable to initialize writer due to failure to " "allocate memory for column offsets."); return NOTOK; } _g6_PrecomputeColumnOffsets(theG6WriteIterator->columnOffsets, theG6WriteIterator->order); theG6WriteIterator->numCharsForOrder = _g6_GetNumCharsForOrder(theG6WriteIterator->order); theG6WriteIterator->numCharsForGraphEncoding = _g6_GetNumCharsForEncoding(theG6WriteIterator->order); // Must add 3 bytes for newline, possible carriage return, and null terminator theG6WriteIterator->currGraphBuffSize = theG6WriteIterator->numCharsForOrder + theG6WriteIterator->numCharsForGraphEncoding + 3; theG6WriteIterator->currGraphBuff = (char *)calloc(theG6WriteIterator->currGraphBuffSize, sizeof(char)); if (theG6WriteIterator->currGraphBuff == NULL) { gp_ErrorMessage("Unable to initialize writer due to failure to " "allocate memory for currGraphBuff."); return NOTOK; } return OK; } /* * NOTE: columnOffsets is an array of size_t rather than of int, because for a * graph with N <= 100000, the index for an edge can be as large as * (100000 * 99999) / 2, which overflows the size of a signed integer. */ void _g6_PrecomputeColumnOffsets(size_t *columnOffsets, int order) { if (columnOffsets == NULL) { gp_ErrorMessage("Must allocate columnOffsets memory before " "precomputation."); return; } columnOffsets[0] = 0; columnOffsets[1] = 0; for (int i = 2; i <= order; i++) columnOffsets[i] = columnOffsets[i - 1] + (i - 1); } int g6_WriteGraph(G6WriteIteratorP theG6WriteIterator) { char *graphEncodingChars = NULL; if (!_g6_IsWriterInitialized(theG6WriteIterator, TRUE)) { gp_ErrorMessage("Unable to write graph because G6WriteIterator is not " "initialized."); return NOTOK; } _g6_EncodeAdjMatAsG6(theG6WriteIterator); if (_g6_ValidateOrderOfEncodedGraph(theG6WriteIterator->currGraphBuff, theG6WriteIterator->order) != OK) { gp_ErrorMessage("Unable to write graph, as constructed encoding has " "incorrect order."); return NOTOK; } graphEncodingChars = theG6WriteIterator->currGraphBuff + theG6WriteIterator->numCharsForOrder; if (_g6_ValidateGraphEncoding(graphEncodingChars, theG6WriteIterator->order, theG6WriteIterator->numCharsForGraphEncoding) != OK) { gp_ErrorMessage("Unable to write graph, as constructed encoding is " "invalid."); return NOTOK; } if (_g6_WriteEncodedGraph(theG6WriteIterator) != OK) { gp_ErrorMessage("Unable to write g6 encoded graph to output " "container."); return NOTOK; } return OK; } void _g6_EncodeAdjMatAsG6(G6WriteIteratorP theG6WriteIterator) { char *g6Encoding = NULL; size_t *columnOffsets = NULL; graphP theGraph = NULL; int order = 0; int numCharsForOrder = 0; size_t numCharsForGraphEncoding = 0; size_t totalNumCharsForOrderAndGraph = 0; int u = NIL, v = NIL, e = NIL; size_t charOffset = 0; int bitPositionPower = 0; int bitPosition = 0; g6Encoding = theG6WriteIterator->currGraphBuff; columnOffsets = theG6WriteIterator->columnOffsets; theGraph = theG6WriteIterator->currGraph; // memset ensures all bits are zero, which means we only need to set the bits // that correspond to an edge; this also takes care of padding zeroes for us memset(theG6WriteIterator->currGraphBuff, 0, (theG6WriteIterator->currGraphBuffSize) * sizeof(char)); order = theG6WriteIterator->order; numCharsForOrder = theG6WriteIterator->numCharsForOrder; numCharsForGraphEncoding = theG6WriteIterator->numCharsForGraphEncoding; totalNumCharsForOrderAndGraph = numCharsForOrder + numCharsForGraphEncoding; if (order > 62) { // bytes 1 through 3 will be populated with the 18-bit representation of the graph order int intermediate = -1; g6Encoding[0] = 126; for (int i = 0; i < 3; i++) { intermediate = order >> (6 * i); g6Encoding[3 - i] = intermediate & 63; g6Encoding[3 - i] += 63; } } else if (order > 1 && order < 63) { g6Encoding[0] = (char)(order + 63); } u = v = e = NIL; _g6_GetFirstEdgeInUse(theGraph, &e, &u, &v); charOffset = bitPositionPower = 0; while (u != NIL && v != NIL) { // The in-memory vertex storage may 1-based or 0-based, so we subtract the index // of the first vertex in storage because the .g6 format is 0-based u -= gp_LowerBoundVertexStorage(theGraph); v -= gp_LowerBoundVertexStorage(theGraph); // The columnOffset machinery assumes that we are traversing the edges represented in // the upper-triangular matrix. Since we are dealing with simple graphs, if (v, u) // exists, then (u, v) exists, and so the edge is indicated by a 1 in row = min(u, v) // and col = max(u, v) in the upper-triangular adjacency matrix. if (v < u) { int tempVert = v; v = u; u = tempVert; } // (columnOffsets[v] + u) describes the bit index of the current edge // given the column and row in the adjacency matrix representation; // the byte is floor((columnOffsets[v] + u) / 6) and the we determine which // bit to set in that byte by left-shifting 1 by (5 - ((columnOffsets[v] + u) % 6)) // (transforming the ((columnOffsets[v] + u) % 6)th bit from the left to the // (5 - ((columnOffsets[v] + u) % 6))th bit from the right) // NOTE: We've made columnOffsets an array of size_t because as N approaches // 100000, the size of this calculation can exceed the limit of a signed // integer charOffset = numCharsForOrder + ((columnOffsets[v] + u) / 6); bitPositionPower = 5 - ((columnOffsets[v] + u) % 6); bitPosition = (1u << bitPositionPower); g6Encoding[charOffset] |= bitPosition; _g6_GetNextEdgeInUse(theGraph, &e, &u, &v); } // Bytes corresponding to graph order have already been modified to // correspond to printable ascii character (i.e. by adding 63); must // now do the same for bytes corresponding to edge lists for (size_t i = numCharsForOrder; i < totalNumCharsForOrderAndGraph; i++) g6Encoding[i] += 63; } void _g6_GetFirstEdgeInUse(graphP theGraph, int *e, int *u, int *v) { (*e) = NIL; _g6_GetNextEdgeInUse(theGraph, e, u, v); } void _g6_GetNextEdgeInUse(graphP theGraph, int *e, int *u, int *v) { (*u) = NIL; (*v) = NIL; if ((*e) == NIL) (*e) = gp_LowerBoundEdges(theGraph); else (*e) += 2; if ((*e) < gp_UpperBoundEdges(theGraph)) { while (!gp_EdgeInUse(theGraph, (*e))) { (*e) += 2; if ((*e) >= gp_UpperBoundEdges(theGraph)) break; } if ((*e) < gp_UpperBoundEdges(theGraph) && gp_EdgeInUse(theGraph, (*e))) { (*u) = gp_GetNeighbor(theGraph, (*e)); (*v) = gp_GetNeighbor(theGraph, gp_GetTwin(theGraph, (*e))); } } } int _g6_WriteEncodedGraph(G6WriteIteratorP theG6WriteIterator) { if (sf_fputs(theG6WriteIterator->currGraphBuff, theG6WriteIterator->outputContainer) < 0) { gp_ErrorMessage("Failed to output all characters of g6 encoding."); return NOTOK; } if (sf_fputs("\n", theG6WriteIterator->outputContainer) < 0) { gp_ErrorMessage("Failed to put line terminator after g6 encoding."); return NOTOK; } return OK; } // If the writer is initialized with string, then when we free the writer this // method will give the allocated string back to the user. // NOTE: This setting will occur if any writer operations returned NOTOK, so the // caller is responsible for checking if the string is NULL and freeing it in // all cases. void g6_FreeWriter(G6WriteIteratorP *pG6WriteIterator) { if (pG6WriteIterator != NULL && (*pG6WriteIterator) != NULL) { if ((*pG6WriteIterator)->outputContainer != NULL) sf_Free((&((*pG6WriteIterator)->outputContainer))); (*pG6WriteIterator)->order = 0; if ((*pG6WriteIterator)->currGraphBuff != NULL) { free((*pG6WriteIterator)->currGraphBuff); (*pG6WriteIterator)->currGraphBuff = NULL; } if ((*pG6WriteIterator)->columnOffsets != NULL) { free(((*pG6WriteIterator)->columnOffsets)); (*pG6WriteIterator)->columnOffsets = NULL; } // N.B. The G6WriteIterator doesn't "own" the graph, so we don't free it. (*pG6WriteIterator)->currGraph = NULL; free((*pG6WriteIterator)); (*pG6WriteIterator) = NULL; } } int _g6_WriteGraphToFile(graphP theGraph, char *g6OutputFileName) { strOrFileP outputContainer = NULL; if (g6OutputFileName == NULL || strlen(g6OutputFileName) == 0) { gp_ErrorMessage("Unable to write graph to file, as output file name " "supplied is NULL or empty."); return NOTOK; } if ((outputContainer = sf_NewOutputContainer(NULL, g6OutputFileName)) == NULL) { gp_ErrorMessage("Unable to allocate outputContainer to which to " "write."); return NOTOK; } return _g6_WriteGraphToStrOrFile(theGraph, (&outputContainer)); } int _g6_WriteGraphToString(graphP theGraph, char **pOutputStr) { strOrFileP outputContainer = NULL; if (pOutputStr == NULL) { gp_ErrorMessage("If writing G6 to string, must provide pointer-pointer " "to allow _g6_WriteGraphToString() to assign the " "address of the output string."); return NOTOK; } if ((*pOutputStr) != NULL) { gp_ErrorMessage("(*pOutputStr) should not point to allocated memory."); return NOTOK; } if ((outputContainer = sf_NewOutputContainer(pOutputStr, NULL)) == NULL) { gp_ErrorMessage("Unable to allocate outputContainer to which to " "write."); return NOTOK; } // N.B. Once the graph is successfully written, the string is taken from // the G6WriteIterator's outputContainer and assigned to (*pOutputStr) // before freeing the G6 write iterator. return _g6_WriteGraphToStrOrFile(theGraph, (&outputContainer)); } int _g6_WriteGraphToStrOrFile(graphP theGraph, strOrFileP *pOutputContainer) { G6WriteIteratorP theG6WriteIterator = NULL; if (!sf_IsValidStrOrFile((*pOutputContainer))) { gp_ErrorMessage("Invalid G6 output container."); return NOTOK; } if (g6_NewWriter((&theG6WriteIterator), theGraph) != OK) { gp_ErrorMessage("Unable to allocate G6WriteIterator."); g6_FreeWriter((&theG6WriteIterator)); return NOTOK; } // NOTE: (*pOutputContainer) will be NULL after we return from this call, // since the write iterator will take ownership of the output container. if (_g6_InitWriterWithStrOrFile(theG6WriteIterator, pOutputContainer) != OK) { gp_ErrorMessage("Unable to initialize G6WriteIterator."); g6_FreeWriter((&theG6WriteIterator)); return NOTOK; } if (g6_WriteGraph(theG6WriteIterator) != OK) { gp_ErrorMessage("Unable to write graph using G6WriteIterator."); g6_FreeWriter((&theG6WriteIterator)); return NOTOK; } g6_FreeWriter((&theG6WriteIterator)); return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-write-iterator.h000066400000000000000000000014621521450711600272360ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef G6_WRITE_ITERATOR #define G6_WRITE_ITERATOR #ifdef __cplusplus extern "C" { #endif #include #include "../graph.h" typedef struct G6WriteIteratorStruct G6WriteIteratorStruct; typedef G6WriteIteratorStruct *G6WriteIteratorP; int g6_NewWriter(G6WriteIteratorP *pG6WriteIterator, graphP theGraph); int g6_InitWriterWithString(G6WriteIteratorP theG6WriteIterator, char **pOutputString); int g6_InitWriterWithFileName(G6WriteIteratorP theG6WriteIterator, char *outputFileName); int g6_WriteGraph(G6WriteIteratorP theG6WriteIterator); void g6_FreeWriter(G6WriteIteratorP *pG6WriteIterator); #ifdef __cplusplus } #endif #endif /* G6_WRITE_ITERATOR */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/graphIO.c000066400000000000000000001130771521450711600252750ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include #include "graphIO.h" #include "strOrFile.h" #include "../extensionSystem/graphExtensions.private.h" // To help with writing debug info #include "../planarityRelated/graphPlanarity.private.h" #include "../graphDFSUtils.h" // To help with graph construction #include "../graph.private.h" /* Imported functions */ extern int _g6_ReadGraphFromStrOrFile(graphP theGraph, strOrFileP *pG6InputContainer); extern int _g6_WriteGraphToStrOrFile(graphP theGraph, strOrFileP *pOutputContainer); /* Private functions (exported to system) */ int _ReadGraph(graphP theGraph, strOrFileP *pInputContainer); int _ReadAdjMatrix(graphP theGraph, strOrFileP inputContainer); int _ReadAdjList(graphP theGraph, strOrFileP inputContainer); int _ReadLEDAGraph(graphP theGraph, strOrFileP inputContainer); int _ReadPostprocess(graphP theGraph, char *extraData); int _WriteGraph(graphP theGraph, strOrFileP *outputContainer, int Mode); int _WriteAdjList(graphP theGraph, strOrFileP outputContainer); int _WriteAdjMatrix(graphP theGraph, strOrFileP outputContainer); int _WriteDebugInfo(graphP theGraph, strOrFileP outputContainer); int _WritePostprocess(graphP theGraph, char **pExtraData); /* Private functions */ char _GetEdgeTypeChar(graphP theGraph, int e); char _GetObstructionMarkChar(graphP theGraph, int v); /******************************************************************** _ReadAdjMatrix() This function reads the undirected graph in upper triangular matrix format. Though O(N^2) time is required, this routine is useful during reliability testing due to the wealth of graph generating software that uses this format for output. Returns: OK, NOTOK on internal error ********************************************************************/ int _ReadAdjMatrix(graphP theGraph, strOrFileP inputContainer) { int N = 0; int v = NIL, w = NIL, Flag = NIL; if (!sf_IsValidStrOrFile(inputContainer)) return NOTOK; // Read the number of vertices from the first line of the file if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&N, inputContainer) != OK) return NOTOK; // Initialize the graph based on the number of vertices if (gp_EnsureVertexCapacity(theGraph, N) != OK) return NOTOK; // Read an upper-triangular matrix row for each vertex // Note that for the last vertex, zero flags are read, per the upper triangular format for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { gp_SetIndex(theGraph, v, v); for (w = v + 1; w < gp_UpperBoundVertices(theGraph); w++) { // Read each of v's w-neighbor flags if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadSingleDigit(&Flag, inputContainer) != OK) return NOTOK; // N.B. Currently do not allow edge-weights in Adjacency // Matrix format if (Flag != 0 && Flag != 1) return NOTOK; // Add the edge (v, w) if the flag is raised if (Flag) { if (gp_DynamicAddEdge(theGraph, v, 0, w, 0) != OK) return NOTOK; } } } return OK; } /******************************************************************** _ReadAdjList() This function reads the graph in adjacency list format. The graph format is: On the first line : N= On N subsequent lines: #: a b c ... -1 where # is a vertex number and a, b, c, ... are its neighbors. NOTE: The vertex number is for file documentation only. It is an error if the vertices are not in sorted order in the file. NOTE: If a loop edge is found, it is ignored without error. NOTE: This routine supports digraphs. For a directed edge (v -> W), an edge record is created in both vertices, v and W, and the edge record in v's adjacency list is marked OUTONLY while the edge record in W's list is marked INONLY. This makes it easy to used edge directedness when appropriate but also seamlessly process the corresponding undirected graph. Returns: OK on success, NOTOK on file content error (or internal error) ********************************************************************/ int _ReadAdjList(graphP theGraph, strOrFileP inputContainer) { int ErrorCode = OK; int N = 0, v = NIL, W = NIL, adjList = NIL, e = NIL, indexValue = NIL; int zeroBased = FALSE; if (!sf_IsValidStrOrFile(inputContainer)) return NOTOK; // Skip the "N=" and then read the N value for number of vertices if (sf_ReadSkipChar(inputContainer) != OK) return NOTOK; if (sf_ReadSkipChar(inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&N, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; // Initialize theGraph based on the number of vertices in the input if (gp_EnsureVertexCapacity(theGraph, N) != OK) return NOTOK; // Clear the visited members of the vertices so they can be used // during the adjacency list read operation for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) gp_SetVertexVisitedInfo(theGraph, v, NIL); // Do the adjacency list read operation for each vertex in order for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { // Read the vertex number if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&indexValue, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (indexValue == 0 && v == gp_LowerBoundVertexStorage(theGraph)) zeroBased = TRUE; // If we are reading a zero-based input file, then we have to add to the // indexValue for v the offset of the first vertex in storage, which is // usually 1 (because we compile with USE_1BASEDARRAYS by default) but // which may be 0 if this library was compiled with USE_0BASEDARRAYS. if (zeroBased) indexValue += gp_LowerBoundVertexStorage(theGraph); gp_SetIndex(theGraph, v, indexValue); // The vertices are expected to be in numeric ascending order if (gp_GetIndex(theGraph, v) != v) return NOTOK; // Skip the colon after the vertex number if (sf_ReadSkipChar(inputContainer) != OK) return NOTOK; // If the vertex already has a non-empty adjacency list, then it is // the result of adding edges during processing of preceding vertices. // The list is removed from the current vertex v and saved for use // during the read operation for v. Adjacencies to preceding vertices // are pulled from this list, if present, or added as directed edges // if not. Adjacencies to succeeding vertices are added as undirected // edges, and will be corrected later if the succeeding vertex does not // have the matching adjacency using the following mechanism. After the // read operation for a vertex v, any adjacency nodes left in the saved // list are converted to directed edges from the preceding vertex to v. adjList = gp_GetFirstEdge(theGraph, v); if (gp_IsEdge(theGraph, adjList)) { // Store the adjacency node location in the visited member of each // of the preceding vertices to which v is adjacent so that we can // efficiently detect the adjacency during the read operation and // efficiently find the adjacency node. e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { gp_SetVertexVisitedInfo(theGraph, gp_GetNeighbor(theGraph, e), e); e = gp_GetNextEdge(theGraph, e); } // Make the adjacency list circular, for later ease of processing gp_SetPrevEdge(theGraph, adjList, gp_GetLastEdge(theGraph, v)); gp_SetNextEdge(theGraph, gp_GetLastEdge(theGraph, v), adjList); // Remove the list from the vertex gp_SetFirstEdge(theGraph, v, NIL); gp_SetLastEdge(theGraph, v, NIL); } // Read the adjacency list. while (1) { // Read the value indicating the next adjacent vertex (or the list end) if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&W, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; // If we are reading a zero-based input file, then we have to add to W // the offset of the first vertex in storage, which is usually 1 // (because we compile with USE_1BASEDARRAYS by default) but which may // be 0 if this library was compiled with USE_0BASEDARRAYS. if (zeroBased) W += gp_LowerBoundVertexStorage(theGraph); // A value below the valid range indicates the adjacency list end // This was written before gp_IsNotVertex() existed if (W < gp_LowerBoundVertices(theGraph)) break; // A value above the valid range is an error if (W >= gp_UpperBoundVertices(theGraph)) return NOTOK; // Loop edges are not supported else if (W == v) return NOTOK; // If the adjacency is to a succeeding, higher numbered vertex, // then we'll add an undirected edge for now else if (v < W) { if ((ErrorCode = gp_DynamicAddEdge(theGraph, v, 0, W, 0)) != OK) return ErrorCode; } // If the adjacency is to a preceding, lower numbered vertex, then // we have to pull the adjacency node from the preexisting adjList, // if it is there, and if not then we have to add a directed edge. else { // If the directed edge already exists, then we add it // as the new first edge of the vertex and delete it from adjList if (gp_IsEdge(theGraph, gp_GetVertexVisitedInfo(theGraph, W))) { e = gp_GetVertexVisitedInfo(theGraph, W); // Remove the directed edge e from the adjList construct gp_SetVertexVisitedInfo(theGraph, W, NIL); if (adjList == e) { if ((adjList = gp_GetNextEdge(theGraph, e)) == e) adjList = NIL; } gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, e), gp_GetPrevEdge(theGraph, e)); gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, e), gp_GetNextEdge(theGraph, e)); gp_AttachFirstEdge(theGraph, v, e); } // If an adjacency node to the lower numbered vertex W does not // already exist, then we make a new directed edge from the current // vertex v to W. else { // It is added as the new first edge in both vertices if ((ErrorCode = gp_DynamicAddEdge(theGraph, v, 0, W, 0)) != OK) return ErrorCode; // Note that this call also sets OUTONLY on the twin edge record gp_SetDirection(theGraph, gp_GetFirstEdge(theGraph, W), EDGEFLAG_DIRECTION_INONLY); // This macro expands to constant conditional expression, but it's the proper use of the API } } } // If there are still adjList entries after the read operation // then those entries are not representative of full undirected edges. // Rather, they represent incoming directed edge from other vertices // into vertex v. They need to be added back into v's adjacency list but // marked as "INONLY", while the twin is marked "OUTONLY" (by the same function). while (gp_IsEdge(theGraph, adjList)) { e = adjList; gp_SetVertexVisitedInfo(theGraph, gp_GetNeighbor(theGraph, e), NIL); if ((adjList = gp_GetNextEdge(theGraph, e)) == e) adjList = NIL; gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, e), gp_GetPrevEdge(theGraph, e)); gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, e), gp_GetNextEdge(theGraph, e)); gp_AttachFirstEdge(theGraph, v, e); gp_SetDirection(theGraph, e, EDGEFLAG_DIRECTION_INONLY); // This macro expands to constant conditional expression, but it's the proper use of the API } } if (zeroBased) theGraph->graphFlags |= GRAPHFLAGS_ZEROBASEDIO; return OK; } /******************************************************************** _ReadLEDAGraph() Reads the edge list from a LEDA file containing a simple undirected graph. LEDA files use a one-based numbering system, which is converted to zero-based numbers if the graph reports starting at zero as the first vertex. Returns: OK on success, NOTOK on file content error (or internal error) ********************************************************************/ int _ReadLEDAGraph(graphP theGraph, strOrFileP inputContainer) { int ErrorCode = OK; int graphType = 0; int N = 0, M = 0, u = NIL, v = NIL; int zeroBasedOffset = gp_LowerBoundVertexStorage(theGraph) == 0 ? 1 : 0; char Line[MAXLINE + 1]; memset(Line, '\0', (MAXLINE + 1)); if (!sf_IsValidStrOrFile(inputContainer)) return NOTOK; /* N.B. Skip the lines that say LEDA.GRAPH and give the node and edge types then determine if graph is directed (-1) or undirected (-2); we only support undirected graphs at this time. */ for (int i = 0; i < 3; i++) if (sf_ReadSkipLineRemainder(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&graphType, inputContainer) != OK) return NOTOK; // N.B. We currently only support undirected graphs if (graphType != -2) return NOTOK; /* Skip any preceding whitespace, read the number of vertices N, and skip any subsequent whitespace. */ if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&N, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (gp_EnsureVertexCapacity(theGraph, N) != OK) return NOTOK; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (sf_fgets(Line, MAXLINE, inputContainer) == NULL) return NOTOK; /* Read the number of edges */ if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&M, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; /* Read and add each edge, omitting loops and parallel edges */ for (int m = 0; m < M; m++) { if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&u, inputContainer) != OK) return NOTOK; if (sf_ReadSkipWhitespace(inputContainer) != OK) return NOTOK; if (sf_ReadInteger(&v, inputContainer) != OK) return NOTOK; if (sf_ReadSkipLineRemainder(inputContainer) != OK) return NOTOK; if (u != v && !gp_IsNeighbor(theGraph, u - zeroBasedOffset, v - zeroBasedOffset)) { if ((ErrorCode = gp_DynamicAddEdge(theGraph, u - zeroBasedOffset, 0, v - zeroBasedOffset, 0)) != OK) return ErrorCode; } } if (zeroBasedOffset) theGraph->graphFlags |= GRAPHFLAGS_ZEROBASEDIO; return OK; } /******************************************************************** gp_Read() Populates theGraph from the contents of the input file with path and name given by fileName. Pass "stdin" for the fileName to read from the stdin stream. Returns: OK, NOTOK on internal error ********************************************************************/ int gp_Read(graphP theGraph, char const *fileName) { strOrFileP inputContainer = NULL; if (theGraph == NULL || fileName == NULL || strlen(fileName) == 0) return NOTOK; if ((inputContainer = sf_NewInputContainer(NULL, fileName)) == NULL) return NOTOK; return _ReadGraph(theGraph, (&inputContainer)); } /******************************************************************** gp_ReadFromString() Populates theGraph using the information stored in inputStr. The caller owns the memory of inputStr, as the contents of inputStr are copied into the inputContainer's internal strBuf, and therefore is responsible for freeing the inputStr after gp_ReadFromString(). Returns NOTOK for any error, or OK otherwise ********************************************************************/ int gp_ReadFromString(graphP theGraph, char *inputStr) { strOrFileP inputContainer = NULL; if (theGraph == NULL || inputStr == NULL || strlen(inputStr) == 0) return NOTOK; if ((inputContainer = sf_NewInputContainer(inputStr, NULL)) == NULL) return NOTOK; return _ReadGraph(theGraph, (&inputContainer)); } /******************************************************************** _ReadGraph() Determines the graph input format by parsing the first line of the input stream or string contained by the inputContainer and calling the appropriate read function, then then cleans up the inputContainer and returns the graph. Digraphs and loop edges are not supported in the adjacency matrix format, which is upper triangular. In the adjacency list format, digraphs are supported. Loop edges are ignored without producing an error. ********************************************************************/ int _ReadGraph(graphP theGraph, strOrFileP *pInputContainer) { int RetVal = OK; int extraDataAllowed = FALSE; char lineBuff[MAXLINE + 1]; memset(lineBuff, '\0', (MAXLINE + 1)); if (!sf_IsValidStrOrFile((*pInputContainer))) return NOTOK; if (sf_fgets(lineBuff, MAXLINE, (*pInputContainer)) == NULL) { sf_Free(pInputContainer); return NOTOK; } if (sf_ungets(lineBuff, (*pInputContainer)) != OK) { sf_Free(pInputContainer); return NOTOK; } if (strncmp(lineBuff, "LEDA.GRAPH", strlen("LEDA.GRAPH")) == 0) { RetVal = _ReadLEDAGraph(theGraph, (*pInputContainer)); } else if (strncmp(lineBuff, "N=", strlen("N=")) == 0) { RetVal = _ReadAdjList(theGraph, (*pInputContainer)); if (RetVal == OK) extraDataAllowed = TRUE; } else if (isdigit(lineBuff[0])) { RetVal = _ReadAdjMatrix(theGraph, (*pInputContainer)); if (RetVal == OK) extraDataAllowed = TRUE; } else { // N.B. Unlike the other _Read functions, we are relinquishing // ownership of inputContainer to the G6ReadIterator, which // calls sf_Free() when ending iteration. This will mean that // (*pInputContainer) is NULL after we return from this call. RetVal = _g6_ReadGraphFromStrOrFile(theGraph, pInputContainer); } // The possibility of "extra data" is not allowed for .g6 format: // .g6 files may contain multiple graphs, which are not valid input // for the extra data readers (i.e. fpReadPostProcess) Additionally, // we don't want to process extra data unless the graph reading // was OK. if (extraDataAllowed) { char charAfterGraphRead = EOF; if ((charAfterGraphRead = sf_getc((*pInputContainer))) != EOF) { if (sf_ungetc(charAfterGraphRead, (*pInputContainer)) != charAfterGraphRead) RetVal = NOTOK; else { strBufP extraData = sb_New(0); if (extraData == NULL) RetVal = NOTOK; else { while (sf_fgets(lineBuff, MAXLINE, (*pInputContainer)) != NULL) { if (sb_ConcatString(extraData, lineBuff) != OK) { RetVal = NOTOK; break; } } if (sb_GetSize(extraData) > 0) RetVal = theGraph->functions->fpReadPostprocess(theGraph, sb_GetReadString(extraData)); sb_Free(&extraData); extraData = NULL; } } } } // This is a no-op if pInputContainer or *pInputContainer is already NULL, // such as in the case of G6 file processing. This cleans up for the other // file types. sf_Free(pInputContainer); return RetVal; } int _ReadPostprocess(graphP theGraph, char *extraData) { return OK; } /******************************************************************** _WriteAdjList() For each vertex, we write its number, a colon, the list of adjacent vertices, then a NIL. The vertices occupy the first N positions of theGraph. Each vertex is also has indicators of the first and last adjacency nodes (edge records) in its adjacency list. Returns: NOTOK for parameter errors; OK otherwise. ********************************************************************/ int _WriteAdjList(graphP theGraph, strOrFileP outputContainer) { int v = NIL, e = NIL; int zeroBasedVertexOffset = 0, adjacencyListTerminator = NIL; char numberStr[MAXCHARSFOR32BITINT + 1]; memset(numberStr, '\0', (MAXCHARSFOR32BITINT + 1) * sizeof(char)); if (theGraph == NULL || !sf_IsValidStrOrFile(outputContainer)) return NOTOK; // Write the number of vertices of the graph to the file or string buffer if (sprintf(numberStr, "N=%d\n", gp_GetN(theGraph)) < 1) return NOTOK; if (sf_fputs(numberStr, outputContainer) == EOF) return NOTOK; // If we are supposed to write 0-based output, then we have to adjust the vertex offset and the // adjacency list terminator based on whether this library has been compiled with 0-based or // 1-based array indexing for the in-memory data structure (i.e., compiled with // USE_1BASEDARRAYS USE_0BASEDARRAYS). The macro invoked is responsive to the difference. if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO) { zeroBasedVertexOffset = gp_LowerBoundVertexStorage(theGraph); // If the graph must be written 0-based, then the adjacency list terminator must be -1, // even if the internal representation is 1-based (i.e. when USE_1BASEDARRAYS, NIL == 0, // but the output needs to be -1 for 0-based output) adjacencyListTerminator = -1; } // Write the adjacency list of each vertex for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (sprintf(numberStr, "%d:", v - zeroBasedVertexOffset) < 1) return NOTOK; if (sf_fputs(numberStr, outputContainer) == EOF) return NOTOK; e = gp_GetLastEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetDirection(theGraph, e) != EDGEFLAG_DIRECTION_INONLY) { if (sprintf(numberStr, " %d", gp_GetNeighbor(theGraph, e) - zeroBasedVertexOffset) < 1) return NOTOK; if (sf_fputs(numberStr, outputContainer) == EOF) return NOTOK; } e = gp_GetPrevEdge(theGraph, e); } // Write NIL at the end of the adjacency list (in zero-based I/O, NIL was -1) if (sprintf(numberStr, " %d\n", adjacencyListTerminator) < 1) return NOTOK; if (sf_fputs(numberStr, outputContainer) == EOF) return NOTOK; } return OK; } /******************************************************************** _WriteAdjMatrix() Outputs upper triangular matrix representation capable of being read by _ReadAdjMatrix(). theGraph and one of Outfile or theStrBuf must be non-NULL. Note: This routine does not support digraphs and will return an error if a directed edge is found. returns OK for success, NOTOK for failure ********************************************************************/ int _WriteAdjMatrix(graphP theGraph, strOrFileP outputContainer) { int v = NIL, e = NIL; char *Row = NULL; char numberStr[MAXCHARSFOR32BITINT + 1]; memset(numberStr, '\0', (MAXCHARSFOR32BITINT + 1) * sizeof(char)); if (theGraph == NULL || !sf_IsValidStrOrFile(outputContainer)) return NOTOK; // Write the number of vertices in the graph to the file or string buffer if (sprintf(numberStr, "%d\n", gp_GetN(theGraph)) < 1) return NOTOK; if (sf_fputs(numberStr, outputContainer) == EOF) return NOTOK; // Allocate memory for storing a string expression of one row at a time Row = (char *)malloc((gp_GetN(theGraph) + 2) * sizeof(char)); if (Row == NULL) return NOTOK; // Construct the upper triangular matrix representation one row at a time for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { for (int i = gp_LowerBoundVertices(theGraph); i <= v; i++) Row[i - gp_LowerBoundVertices(theGraph)] = ' '; for (int i = v + 1; i < gp_UpperBoundVertices(theGraph); i++) Row[i - gp_LowerBoundVertices(theGraph)] = '0'; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetDirection(theGraph, e) == EDGEFLAG_DIRECTION_INONLY) return NOTOK; if (gp_GetNeighbor(theGraph, e) > v) Row[gp_GetNeighbor(theGraph, e) - gp_LowerBoundVertices(theGraph)] = '1'; e = gp_GetNextEdge(theGraph, e); } Row[gp_GetN(theGraph)] = '\n'; Row[gp_GetN(theGraph) + 1] = '\0'; // Write the row to the file or string buffer if (sf_fputs(Row, outputContainer) == EOF) return NOTOK; } free(Row); Row = NULL; return OK; } /******************************************************************** ********************************************************************/ char _GetEdgeTypeChar(graphP theGraph, int e) { char type = 'U'; if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) type = 'C'; else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_FORWARD) type = 'F'; else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_PARENT) type = 'P'; else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_BACK) type = 'B'; else if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_TREE) type = 'T'; return type; } /******************************************************************** ********************************************************************/ char _GetObstructionMarkChar(graphP theGraph, int v) { char type = 'U'; if (gp_GetObstructionMark(theGraph, v) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW) type = 'X'; else if (gp_GetObstructionMark(theGraph, v) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW) type = 'x'; if (gp_GetObstructionMark(theGraph, v) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW) type = 'Y'; else if (gp_GetObstructionMark(theGraph, v) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW) type = 'y'; return type; } /******************************************************************** _WriteDebugInfo() Writes adjacency list, but also includes the type value of each edge, e.g. is it an edge record to a DFS child, a descendant (forward edge), or ancestor (back edge), and the L, A and DFSParent of each vertex. ********************************************************************/ int _WriteDebugInfo(graphP theGraph, strOrFileP outputContainer) { int v = NIL, e = NIL; char lineBuf[MAXLINE + 1]; memset(lineBuf, '\0', (MAXLINE + 1) * sizeof(char)); if (theGraph == NULL || !sf_IsValidStrOrFile(outputContainer)) return NOTOK; /* Print parent copy vertices and their adjacency lists */ if (sprintf(lineBuf, "DEBUG N=%d M=%d\n", gp_GetN(theGraph), gp_GetM(theGraph)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (sprintf(lineBuf, "%d(P=%d,lA=%d,LowPt=%d,v=%d):", v, gp_GetVertexParent(theGraph, v), gp_GetVertexLeastAncestor(theGraph, v), gp_GetVertexLowpoint(theGraph, v), gp_GetIndex(theGraph, v)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (sprintf(lineBuf, " %d(e=%d)", gp_GetNeighbor(theGraph, e), e) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; e = gp_GetNextEdge(theGraph, e); } if (sprintf(lineBuf, " %d\n", NIL) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; } /* Print any root copy vertices and their adjacency lists */ for (v = gp_LowerBoundVirtualVertices(theGraph); v < gp_UpperBoundVirtualVertices(theGraph); ++v) { if (!gp_VirtualVertexInUse(theGraph, v)) continue; if (sprintf(lineBuf, "%d(copy of=%d, DFS child=%d):", v, gp_GetIndex(theGraph, v), gp_GetDFSChildFromBicompRoot(theGraph, v)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (sprintf(lineBuf, " %d(e=%d)", gp_GetNeighbor(theGraph, e), e) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; e = gp_GetNextEdge(theGraph, e); } if (sprintf(lineBuf, " %d\n", NIL) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; } /* Print information about vertices and root copy (virtual) vertices */ if (sf_fputs("\nVERTEX INFORMATION\n", outputContainer) == EOF) return NOTOK; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (sprintf(lineBuf, "V[%3d] index=%3d, type=%c, first edge=%3d, last edge=%3d\n", v, gp_GetIndex(theGraph, v), (gp_IsVirtualVertex(theGraph, v) ? 'X' : _GetObstructionMarkChar(theGraph, v)), gp_GetFirstEdge(theGraph, v), gp_GetLastEdge(theGraph, v)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; } for (v = gp_LowerBoundVirtualVertices(theGraph); v < gp_UpperBoundVirtualVertices(theGraph); ++v) { if (gp_VirtualVertexNotInUse(theGraph, v)) continue; if (sprintf(lineBuf, "V[%3d] index=%3d, type=%c, first edge=%3d, last edge=%3d\n", v, gp_GetIndex(theGraph, v), (gp_IsVirtualVertex(theGraph, v) ? 'X' : _GetObstructionMarkChar(theGraph, v)), gp_GetFirstEdge(theGraph, v), gp_GetLastEdge(theGraph, v)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; } /* Print information about edges */ if (sf_fputs("\nEDGE INFORMATION\n", outputContainer) == EOF) return NOTOK; for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); ++e) { if (gp_EdgeInUse(theGraph, e)) { if (sprintf(lineBuf, "E[%3d] neighbor=%3d, type=%c, next edge=%3d, prev edge=%3d\n", e, gp_GetNeighbor(theGraph, e), _GetEdgeTypeChar(theGraph, e), gp_GetNextEdge(theGraph, e), gp_GetPrevEdge(theGraph, e)) < 1) return NOTOK; if (sf_fputs(lineBuf, outputContainer) == EOF) return NOTOK; } } return OK; } /******************************************************************** gp_Write() Writes theGraph into the file. Pass "stdout" or "stderr" to fileName to write to the corresponding stream Pass WRITE_G6, WRITE_ADJLIST, WRITE_ADJMATRIX, or WRITE_DEBUGINFO for writeMode NOTE: For digraphs, it is an error to use a writeMode other than WRITE_ADJLIST Returns NOTOK on error, OK on success. ********************************************************************/ int gp_Write(graphP theGraph, char const *fileName, int writeMode) { int RetVal = OK; strOrFileP outputContainer = NULL; if (theGraph == NULL || fileName == NULL || strlen(fileName) == 0) return NOTOK; if (strcmp(fileName, "nullwrite") == 0) return OK; if ((outputContainer = sf_NewOutputContainer(NULL, fileName)) == NULL) return NOTOK; RetVal = _WriteGraph(theGraph, &outputContainer, writeMode); sf_Free(&outputContainer); return RetVal; } /******************************************************************** * gp_WriteToString() * * Writes the information of theGraph into a string that is returned * to the caller via the pointer pointer pOutputStr. * The string is owned by the caller and should be released with * free() when the caller doesn't need the string anymore. * The format of the content written into the returned string is based * on writeMode: WRITE_G6, WRITE_ADJLIST, or WRITE_ADJMATRIX * (the WRITE_DEBUGINFO writeMode is not supported at this time) NOTE: For digraphs, it is an error to use a mode other than WRITE_ADJLIST Returns NOTOK on error, or OK on success along with an allocated string *pOutputStr that the caller must free() ********************************************************************/ int gp_WriteToString(graphP theGraph, char **pOutputStr, int writeMode) { int RetVal = OK; strOrFileP outputContainer = NULL; if (theGraph == NULL || pOutputStr == NULL || (*pOutputStr) != NULL) return NOTOK; if ((outputContainer = sf_NewOutputContainer(pOutputStr, NULL)) == NULL) return NOTOK; RetVal = _WriteGraph(theGraph, &outputContainer, writeMode); sf_Free(&outputContainer); // NOTE: (#56) If an error was encountered when we _WriteGraph(), we do not // want to return garbage to the caller. When we free the output container, // if writing to string, this means that we will have taken the string from // the internal theStrBuf and have assigned it to the container's // pointer-pointer pOutputStr for output; if the RetVal is not OK, we // must free the string and set the pointer-pointer to NULL. if (RetVal != OK) { if (pOutputStr != NULL && (*pOutputStr) != NULL) { free((*pOutputStr)); pOutputStr = NULL; } } // NOTE: If the output string is NULL or empty, need to report NOTOK if (pOutputStr != NULL && (*pOutputStr) == NULL) RetVal = NOTOK; else if (pOutputStr != NULL && strlen(*pOutputStr) == 0) { free((*pOutputStr)); pOutputStr = NULL; RetVal = NOTOK; } return RetVal; } /******************************************************************** _WriteGraph() Writes theGraph into the strOrFile container. Pass WRITE_G6, WRITE_ADJLIST, WRITE_ADJMATRIX, or WRITE_DEBUGINFO for the Mode NOTE: For digraphs, it is an error to use a mode other than WRITE_ADJLIST Returns NOTOK on error, OK on success. ********************************************************************/ int _WriteGraph(graphP theGraph, strOrFileP *pOutputContainer, int Mode) { int RetVal = OK; switch (Mode) { case WRITE_G6: // This call takes ownership of the outputContainer, so (*pOutputContainer) // will be NULL upon return from this function. RetVal = _g6_WriteGraphToStrOrFile(theGraph, pOutputContainer); break; case WRITE_ADJLIST: RetVal = _WriteAdjList(theGraph, (*pOutputContainer)); break; case WRITE_ADJMATRIX: RetVal = _WriteAdjMatrix(theGraph, (*pOutputContainer)); break; case WRITE_DEBUGINFO: RetVal = _WriteDebugInfo(theGraph, (*pOutputContainer)); break; default: RetVal = NOTOK; break; } if (RetVal == OK) { char *extraData = NULL; RetVal = theGraph->functions->fpWritePostprocess(theGraph, &extraData); if (extraData != NULL) { if (sf_fputs(extraData, (*pOutputContainer)) == EOF) RetVal = NOTOK; free(extraData); extraData = NULL; } } return RetVal; } /******************************************************************** _WritePostprocess() By default, no additional information is written. ********************************************************************/ int _WritePostprocess(graphP theGraph, char **pExtraData) { return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/graphIO.h000066400000000000000000000016341521450711600252750ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHIO_H #define GRAPHIO_H #ifdef __cplusplus extern "C" { #endif #include "../graph.h" int gp_Read(graphP theGraph, char const *fileName); int gp_ReadFromString(graphP theGraph, char *inputStr); int gp_Write(graphP theGraph, char const *fileName, int writeMode); int gp_WriteToString(graphP theGraph, char **pOutputStr, int writeMode); // Mode values for gp_Write() and gp_WriteToString() #define WRITE_ADJLIST 1 #define WRITE_ADJMATRIX 2 #define WRITE_DEBUGINFO 3 #define WRITE_G6 4 // Graph Flags: see gp_GetGraphFlags() // GRAPHFLAGS_ZEROBASEDIO is typically set by gp_Read() to indicate that the // adjacency list representation in a file began with index 0. #define GRAPHFLAGS_ZEROBASEDIO 16 #ifdef __cplusplus } #endif #endif /* GRAPHIO_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/strOrFile.c000066400000000000000000000605701521450711600256540ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include #include #include "../lowLevelUtils/appconst.h" #include "strOrFile.h" /******************************************************************** sf_NewInputContainer() The string-or-file object supports two IO modes: reading input (ioMode == READTEXT), and writing output (ioMode == WRITETEXT). The string-or-file object also may only contain a string (stored using a strBufP to leverage its string manipulation functions) XOR a FILE *. For the input mode, sf_NewInputContainer() should either receive a non-NULL and nonempty input string (which the container *does not own*, but rather copies into the internal strBufP) XOR a non-NULL and nonempty fileName (which may correspond to stdin). Returns the allocated string-or-file container, or NULL on error. ********************************************************************/ strOrFileP sf_NewInputContainer(char const *const inputStr, char const *const fileName) { strOrFileP theStrOrFile; if ( ((fileName != NULL) && (inputStr != NULL)) || ((fileName == NULL) && (inputStr == NULL))) return NULL; theStrOrFile = (strOrFileP)calloc(1, sizeof(strOrFileStruct)); if (theStrOrFile != NULL) { theStrOrFile->containerType = INPUT_CONTAINER; if (fileName != NULL) { // N.B. If the fileName indicates a stream, then input containers // may only contain stdin. if (strcmp(fileName, "stdin") == 0) theStrOrFile->pFile = stdin; else if ( (strcmp(fileName, "stdout") == 0) || (strcmp(fileName, "stderr") == 0)) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } else { if ((theStrOrFile->pFile = fopen(fileName, READTEXT)) == NULL) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } } } else { if ((theStrOrFile->theStrBuf = sb_New(0)) == NULL) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } if ( (inputStr == NULL || (strlen(inputStr) == 0)) || sb_ConcatString(theStrOrFile->theStrBuf, inputStr) != OK) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } } theStrOrFile->ungetBuf = sp_New(MAXLINE); if (theStrOrFile->ungetBuf == NULL) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } } return theStrOrFile; } /******************************************************************** sf_NewOutputContainer() The string-or-file object supports two IO modes: reading input (ioMode == READTEXT), and writing output (ioMode == WRITETEXT). The string-or-file object also may only contain a string (stored using a strBufP to leverage its string manipulation functions) XOR a FILE *. For the output mode, sf_NewOutputContainer() should receive either a non-NULL pointer-pointer that initially points to NULL (to which we will assign the output string upon freeing the container) XOR a non-NULL and nonempty fileName (which may correspond to stdout or stderr). If theStr is not NULL, then the desired output string will be constructed internally in the string-or-file object, which will only be returned to the caller when freeing the strOrFile via this pointer-pointer. Note that if the fileName is not NULL, then the internal strBufP will not be constructed. Returns the allocated string-or-file container, or NULL on error. ********************************************************************/ strOrFileP sf_NewOutputContainer(char **pOutputStr, char const *const fileName) { strOrFileP theStrOrFile; if ( ((fileName != NULL) && (pOutputStr != NULL)) || ((fileName == NULL) && (pOutputStr == NULL)) || ((pOutputStr != NULL) && ((*pOutputStr) != NULL))) return NULL; theStrOrFile = (strOrFileP)calloc(1, sizeof(strOrFileStruct)); if (theStrOrFile != NULL) { theStrOrFile->containerType = OUTPUT_CONTAINER; if (fileName != NULL) { if (strcmp(fileName, "stdin") == 0) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } else if (strcmp(fileName, "stdout") == 0) theStrOrFile->pFile = stdout; else if (strcmp(fileName, "stderr") == 0) theStrOrFile->pFile = stderr; else { if ( (theStrOrFile->pFile = fopen(fileName, WRITETEXT)) == NULL) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } } } else { if ((theStrOrFile->theStrBuf = sb_New(0)) == NULL) { sf_Free(&theStrOrFile); theStrOrFile = NULL; return NULL; } theStrOrFile->pOutputStr = pOutputStr; } } return theStrOrFile; } /******************************************************************** sf_ValidateStrOrFile() Ensures that theStrOrFile: 1. Is not NULL 2. Has ungetBuf allocated 3. Both pFile and theStrBuf are not NULL 4. Both pFile and theStrBuf are not both assigned (since this container should only contain one source). 5. containerType is either set to INPUT_CONTAINER or OUTPUT_CONTAINER Returns FALSE if any of these conditions are not met, otherwise TRUE. ********************************************************************/ int sf_IsValidStrOrFile(strOrFileP theStrOrFile) { if (theStrOrFile == NULL || (theStrOrFile->pFile == NULL && theStrOrFile->theStrBuf == NULL) || (theStrOrFile->pFile != NULL && theStrOrFile->theStrBuf != NULL) || (theStrOrFile->containerType != INPUT_CONTAINER && theStrOrFile->containerType != OUTPUT_CONTAINER)) return FALSE; if (theStrOrFile->containerType == INPUT_CONTAINER) { if ( (theStrOrFile->ungetBuf == NULL) || (theStrOrFile->theStrBuf != NULL && sb_GetSize(theStrOrFile->theStrBuf) == 0)) { return FALSE; } } else // Otherwise, due to the above validation, can only be an output container { if (theStrOrFile->ungetBuf != NULL) { return FALSE; } } return TRUE; } /******************************************************************** sf_getc() If strOrFileP has a non-empty ungetBuf, pop and return the character. If the ungetBuf is empty, then we'll read from pFile using getc() OR from theStrBuf by fetching the character at theStrPos and incrementing theStrPos. ********************************************************************/ char sf_getc(strOrFileP theStrOrFile) { char theChar = EOF; if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return EOF; if ((theStrOrFile->ungetBuf != NULL) && (sp_GetCurrentSize(theStrOrFile->ungetBuf) > 0)) { int currChar = 0; // Technically, this returns NOTOK on error, but the error is underflow, which is // checked above and so cannot happen. Therefore, safe to use sp_Pop() here. #ifndef DEBUG sp_Pop(theStrOrFile->ungetBuf, currChar); #else // In DEBUG, MSVC cl is misreading the printf() generated by sp_Pop(), // so we cut to the underlying debug method to stop its warnings sp__Pop(theStrOrFile->ungetBuf, &(currChar)); #endif theChar = (char)currChar; } else if (theStrOrFile->pFile != NULL) theChar = (char)getc(theStrOrFile->pFile); else if (theStrOrFile->theStrBuf != NULL && sb_GetUnreadCharCount(theStrOrFile->theStrBuf) > 0) { theChar = sb_GetReadString(theStrOrFile->theStrBuf) != NULL ? sb_GetReadString(theStrOrFile->theStrBuf)[0] : EOF; if (theChar != EOF) sb_ReadSkipChar(theStrOrFile->theStrBuf); } return theChar; } /******************************************************************** sf_ReadSkipChar() Calls sf_getc() and does nothing with the returned character ********************************************************************/ int sf_ReadSkipChar(strOrFileP theStrOrFile) { if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return NOTOK; if (sf_getc(theStrOrFile) == EOF) return NOTOK; return OK; } /******************************************************************** sf_ReadSkipWhitespace() Repeatedly calls sf_getc() to find the next non-space character before hitting EOF ********************************************************************/ int sf_ReadSkipWhitespace(strOrFileP theStrOrFile) { char currChar = EOF; if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return NOTOK; while ((currChar = sf_getc(theStrOrFile)) != EOF && isspace(currChar)) { continue; } if (sf_ungetc(currChar, theStrOrFile) != currChar) return NOTOK; return OK; } /******************************************************************** sf_ReadSingleDigit() Calls sf_getc() and tests whether the character read corresponds to a digit. Returns NOTOK if the character returned from sf_getc() is not a digit. Assigns the digit read to the int * and returns OK upon success. ********************************************************************/ int sf_ReadSingleDigit(int *digitToRead, strOrFileP theStrOrFile) { int candidateDigit = EOF; if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return NOTOK; candidateDigit = sf_getc(theStrOrFile); if (!isdigit(candidateDigit)) return NOTOK; // N.B. Subtract '0' = 48 to convert the digit // char to the corresponding int (*digitToRead) = candidateDigit - '0'; return OK; } /******************************************************************** sf_ReadInteger() Repeatedly calls sf_getc() to obtain the characters corresponding to an int, then parses that char* using sscanf() to extract the integer. Returns OK if successfully extracted the digits of and produced the int from theStrOrFile, or NOTOK otherwise. ********************************************************************/ int sf_ReadInteger(int *intToRead, strOrFileP theStrOrFile) { int exitCode = OK; int intCandidate = 0, intCandidateIndex = 0; char currChar = '\0', nextChar = '\0'; int startedReadingInt = FALSE, isNegative = FALSE; char intCandidateStr[MAXCHARSFOR32BITINT + 1]; memset(intCandidateStr, '\0', (MAXCHARSFOR32BITINT + 1) * sizeof(char)); if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return NOTOK; do { currChar = sf_getc(theStrOrFile); if (currChar == '-') { if (startedReadingInt) { exitCode = NOTOK; break; } else { nextChar = sf_getc(theStrOrFile); if (sf_ungetc(nextChar, theStrOrFile) != nextChar) { exitCode = NOTOK; break; } if (!isdigit(nextChar)) { exitCode = NOTOK; break; } else { intCandidateStr[intCandidateIndex++] = currChar; isNegative = TRUE; } } } else if (isdigit(currChar)) { intCandidateStr[intCandidateIndex++] = currChar; startedReadingInt = TRUE; } else { if (sf_ungetc(currChar, theStrOrFile) != currChar) exitCode = NOTOK; break; } if ( (!isNegative && (intCandidateIndex == (MAXCHARSFOR32BITINT - 2))) || (isNegative && (intCandidateIndex == (MAXCHARSFOR32BITINT - 1)))) { if (sscanf(intCandidateStr, "%d", &intCandidate) != 1) exitCode = NOTOK; else { nextChar = sf_getc(theStrOrFile); if (isdigit(nextChar)) { // N.B. The only way we could be here is if `i` is less than the max possible // length of the string representation of a signed 32-bit integer (10 for // positive and 11 for negative) int underflowThreshold = (INT32_MIN / 10), overflowThreshold = (INT32_MAX / 10); if (isNegative) { if (intCandidate < underflowThreshold) exitCode = NOTOK; else if (intCandidate == underflowThreshold && nextChar == '9') exitCode = NOTOK; } else { if (intCandidate > overflowThreshold) exitCode = NOTOK; else if (intCandidate == overflowThreshold && (nextChar == '8' || nextChar == '9')) exitCode = NOTOK; } if (exitCode == OK) { intCandidateStr[intCandidateIndex++] = nextChar; } } else if (sf_ungetc(nextChar, theStrOrFile) != nextChar) exitCode = NOTOK; } break; } } while (currChar != EOF); if (exitCode == OK) { if (sscanf(intCandidateStr, "%d", &intCandidate) != 1) exitCode = NOTOK; else (*intToRead) = intCandidate; } return exitCode; } /******************************************************************** sf_ReadSkipInteger() Calls sf_ReadInteger() and discards the result. ********************************************************************/ int sf_ReadSkipInteger(strOrFileP theStrOrFile) { int temp = 0; if (sf_ReadInteger(&temp, theStrOrFile) != OK) return NOTOK; return OK; } /******************************************************************** sf_ReadSkipLineRemainder() Calls sf_fgets() and discards the result. ********************************************************************/ int sf_ReadSkipLineRemainder(strOrFileP theStrOrFile) { char lineRemainderToSkip[MAXLINE + 1]; memset(lineRemainderToSkip, '\0', (MAXLINE + 1)); if (sf_fgets(lineRemainderToSkip, MAXLINE, theStrOrFile) == NULL) return NOTOK; return OK; } /******************************************************************** sf_ungetc() Order of parameters matches stdio ungetc(). For both the case where the strOrFile contains a FILE * and the case where it contains a strBufP, we unget to the ungetBuf; this ungetBuf is consumed first when we sf_getc(), sf_fgets(), etc. Like ungetc() in stdio, on success theChar is returned. On failure, EOF is returned. ********************************************************************/ char sf_ungetc(char theChar, strOrFileP theStrOrFile) { if (theChar == EOF || !sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER || sp_GetCurrentSize(theStrOrFile->ungetBuf) >= sp_GetCapacity(theStrOrFile->ungetBuf)) return EOF; // Acceptable downcast, allowing char rather than int return type #ifndef DEBUG sp_Push(theStrOrFile->ungetBuf, theChar); #else // In DEBUG, MSVC cl is misreading the printf() generated by sp_Push(), // so we cut to the underlying debug method to stop its warnings sp__Push(theStrOrFile->ungetBuf, theChar); #endif return theChar; } /******************************************************************** sf_ungets() Pushes characters of strToUnget in reverse order to the ungetBuf so that they can be fetched from the ungetBuf in the order of the original string. Returns OK on success and NOTOK on failure. ********************************************************************/ int sf_ungets(char *strToUnget, strOrFileP theStrOrFile) { if (!sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER || (int)strlen(strToUnget) > (sp_GetCapacity(theStrOrFile->ungetBuf) - sp_GetCurrentSize(theStrOrFile->ungetBuf))) return NOTOK; for (int i = (strlen(strToUnget) - 1); i >= 0; i--) sp_Push(theStrOrFile->ungetBuf, strToUnget[i]); return OK; } /******************************************************************** sf_fgets() Order of parameters matches stdio fgets(). First param is the string to populate (assumes allocated (count + 1) bytes), second param is the max number of characters to read, and third param is the pointer to the string-or-file container from which we wish to read count characters (or up to and including \n). Like fgets() in stdio, this function doesn't check that enough memory is allocated for str to contain count characters plus \0. Like fgets() in stdio, on success the pointer to the buffer is returned. On failure, NULL is returned. ********************************************************************/ char *sf_fgets(char *str, int count, strOrFileP theStrOrFile) { int charsToReadFromUngetBuf = 0; int charsToReadFromStrOrFile = count; if (str == NULL || count < 0 || !sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != INPUT_CONTAINER) return NULL; if (theStrOrFile->ungetBuf != NULL) { int numCharsInUngetBuf = sp_GetCurrentSize(theStrOrFile->ungetBuf); if (numCharsInUngetBuf > 0) { char currChar = '\0'; int encounteredNewline = FALSE; charsToReadFromUngetBuf = (count > numCharsInUngetBuf) ? numCharsInUngetBuf : count; for (int i = 0; i < charsToReadFromUngetBuf; i++) { currChar = sf_getc(theStrOrFile); if (currChar == EOF) return NULL; str[i] = currChar; str[i + 1] = '\0'; // N.B. fgets() includes the \n in the string returned, and // no further characters shall be read if (currChar == '\n') { encounteredNewline = TRUE; break; } } // N.B. If we broke out of the loop early due to \n, do not read // any further characters from stream charsToReadFromStrOrFile = (encounteredNewline) ? 0 : ((count > numCharsInUngetBuf) ? (count - charsToReadFromUngetBuf) : 0); } } if (charsToReadFromStrOrFile > 0) { if (theStrOrFile->pFile != NULL) { // N.B. if fgets() returns NULL (can't read more characters) AND the ungetBuf was empty, // then return NULL (error trying to read from empty stream). Otherwise, return str (that // was read from ungetBuf) if (fgets(str + charsToReadFromUngetBuf, charsToReadFromStrOrFile, theStrOrFile->pFile) == NULL) { if (charsToReadFromUngetBuf == 0) return NULL; } } else if (theStrOrFile->theStrBuf != NULL) { char *theStrBuf = sb_GetReadString(theStrOrFile->theStrBuf); if (theStrBuf != NULL && sb_GetUnreadCharCount(theStrOrFile->theStrBuf) > 0) { if (strncpy( str + charsToReadFromUngetBuf, theStrBuf, charsToReadFromStrOrFile) == NULL) return NULL; sb_SetReadPos(theStrOrFile->theStrBuf, (sb_GetReadPos(theStrOrFile->theStrBuf) + charsToReadFromStrOrFile)); } else if (charsToReadFromUngetBuf == 0) return NULL; } } return str; } /******************************************************************** sf_fputs() Order of parameters matches stdio fputs(). First param is the string to append, and the second param is the string-or-file container to which we wish to append. On success, returns the number of characters written. On failure, returns EOF. ********************************************************************/ int sf_fputs(char const *strToWrite, strOrFileP theStrOrFile) { int outputLen = EOF; if (strToWrite == NULL || !sf_IsValidStrOrFile(theStrOrFile) || theStrOrFile->containerType != OUTPUT_CONTAINER) return EOF; // N.B. fputs() will fail and return EOF if pFile doesn't correspond // to an output stream if (theStrOrFile->pFile != NULL) outputLen = fputs(strToWrite, theStrOrFile->pFile); else if (theStrOrFile->theStrBuf != NULL) { if (sb_ConcatString(theStrOrFile->theStrBuf, strToWrite) == OK) outputLen = strlen(strToWrite); else outputLen = EOF; } return outputLen; } /******************************************************************** sf_closeFile() If the strOrFile container contains a string, degenerately returns OK. If the strOrFile container contains a FILE pointer: - if the FILE pointer is one of stdin, stdout, or stderr, calls fflush() on the stream and captures the errorCode - else, closes pFile and sets the internal pointer to NULL, then captures the errorCode from fclose() If the errorCode is less than 0, returns NOTOK, otherwise returns OK. ********************************************************************/ int sf_closeFile(strOrFileP theStrOrFile) { FILE *pFile = theStrOrFile->pFile; theStrOrFile->pFile = NULL; if (pFile != NULL) { int errorCode = 0; if (pFile == stdin || pFile == stdout || pFile == stderr) errorCode = fflush(pFile); else errorCode = fclose(pFile); if (errorCode < 0) return NOTOK; } sp_Free(&(theStrOrFile->ungetBuf)); return OK; } /******************************************************************** sf_Free() Receives a pointer-pointer to a string-or-file container. If the strOrFile contains a string which has not yet been "taken" using sf_takeTheStr() (i.e. we want inputStr to be freed, and in an error state we want to free pOutputStr), the string is freed, the internal pointer is set to NULL, and theStrPos is set to 0. If the strOrFile contains a FILE pointer, we call sf_closeFile() and set the internal pointer to NULL. Note that unless we are in an error state when sf_Free() is called, sf_closeFile should have already been called. Finally, we use the indirection operator to free the strOrFile container and set the pointer to NULL. ********************************************************************/ void sf_Free(strOrFileP *pStrOrFile) { if (pStrOrFile != NULL && (*pStrOrFile) != NULL) { if ((*pStrOrFile)->theStrBuf != NULL) { // TODO: (#56) If in an error state, just don't sb_TakeString() // before freeing it if ((*pStrOrFile)->pOutputStr != NULL) { (*((*pStrOrFile)->pOutputStr)) = sb_TakeString((*pStrOrFile)->theStrBuf); (*pStrOrFile)->pOutputStr = NULL; } sb_Free(&((*pStrOrFile)->theStrBuf)); } // TODO: (#56) if the strOrFile container's FILE pointer // corresponds to an output file, i.e. ioMode is 'w', // we should try to remove the file since the error state // means the contents are invalid if ((*pStrOrFile)->pFile != NULL) sf_closeFile((*pStrOrFile)); (*pStrOrFile)->pFile = NULL; if ((*pStrOrFile)->ungetBuf != NULL) { sp_Free(&((*pStrOrFile)->ungetBuf)); } (*pStrOrFile)->ungetBuf = NULL; free(*pStrOrFile); (*pStrOrFile) = NULL; } } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/strOrFile.h000066400000000000000000000031461521450711600256550ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef STR_OR_FILE_H #define STR_OR_FILE_H #ifdef __cplusplus extern "C" { #endif #include #include "../lowLevelUtils/stack.h" #include "strbuf.h" #define INPUT_CONTAINER 1 #define OUTPUT_CONTAINER 2 struct strOrFileStruct { char **pOutputStr; strBufP theStrBuf; FILE *pFile; int containerType; stackP ungetBuf; }; typedef struct strOrFileStruct strOrFileStruct; typedef strOrFileStruct *strOrFileP; strOrFileP sf_NewInputContainer(char const *const inputStr, char const *const fileName); strOrFileP sf_NewOutputContainer(char **pOutputStr, char const *const fileName); int sf_IsValidStrOrFile(strOrFileP theStrOrFile); char sf_getc(strOrFileP theStrOrFile); int sf_ReadSkipChar(strOrFileP theStrOrFile); int sf_ReadSkipWhitespace(strOrFileP theStrOrFile); int sf_ReadSingleDigit(int *digitToRead, strOrFileP theStrOrFile); int sf_ReadInteger(int *intToRead, strOrFileP theStrOrFile); int sf_ReadSkipInteger(strOrFileP theStrOrFile); int sf_ReadSkipLineRemainder(strOrFileP theStrOrFile); char sf_ungetc(char theChar, strOrFileP theStrOrFile); int sf_ungets(char *contentsToUnget, strOrFileP theStrOrFile); char *sf_fgets(char *str, int count, strOrFileP theStrOrFile); int sf_fputs(char const *strToWrite, strOrFileP theStrOrFile); int sf_closeFile(strOrFileP theStrOrFile); void sf_Free(strOrFileP *pStrOrFile); #ifdef __cplusplus } #endif #endif /* STR_OR_FILE_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/strbuf.c000066400000000000000000000165441521450711600252520ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../lowLevelUtils/appconst.h" #include "strbuf.h" #include #include /******************************************************************** sb_New() Allocates a string buffer with space for capacity characters, plus one for a null terminator Returns the allocated string buffer, or NULL on error. ********************************************************************/ strBufP sb_New(int capacity) { strBufP theStrBuf; if (capacity < 0) return NULL; theStrBuf = (strBufP)malloc(sizeof(strBuf)); if (theStrBuf != NULL) { theStrBuf->buf = (char *)malloc((capacity + 1) * sizeof(char)); if (theStrBuf->buf == NULL) { free(theStrBuf); theStrBuf = NULL; } } if (theStrBuf != NULL) { theStrBuf->capacity = capacity; sb_ClearBuf(theStrBuf); } return theStrBuf; } /******************************************************************** sb_Free() Receives a pointer-pointer to a string buffer. Frees the memory of the string buffer structure and the string buffer it contains. Using the pointer-pointer, sets the pointer to NULL. ********************************************************************/ void sb_Free(strBufP *pStrBuf) { if (pStrBuf != NULL && *pStrBuf != NULL) { (*pStrBuf)->capacity = (*pStrBuf)->size = (*pStrBuf)->readPos = 0; if ((*pStrBuf)->buf != NULL) free((*pStrBuf)->buf); (*pStrBuf)->buf = NULL; free(*pStrBuf); *pStrBuf = NULL; } } /******************************************************************** sb_CleareBuf() The string buffer is changed, if not NULL, to hold an empty string, including setting the size and position to 0. The capacity is not changed. ********************************************************************/ void sb_ClearBuf(strBufP theStrBuf) { if (theStrBuf != NULL) { if (theStrBuf->buf != NULL) theStrBuf->buf[0] = '\0'; theStrBuf->size = theStrBuf->readPos = 0; } } /******************************************************************** sb_Copy() Receives strBuf pointers for a destination and source. Ensures that the destination has the capacity of the source. Copies the source string content into the destination, and sets the size correctly. Returns OK for success, NOTOK on param or memory allocation error. ********************************************************************/ int sb_Copy(strBufP strBufDst, strBufP strBufSrc) { char *tempBuf; strBufP newStrBuf = sb_Duplicate(strBufSrc); if (strBufDst == NULL || strBufSrc == NULL || newStrBuf == NULL) { sb_Free(&newStrBuf); return NOTOK; } tempBuf = strBufDst->buf; strBufDst->buf = newStrBuf->buf; newStrBuf->buf = tempBuf; sb_Free(&newStrBuf); strBufDst->size = strBufSrc->size; strBufDst->capacity = strBufSrc->capacity; return OK; } /******************************************************************** sb_Duplicate() Receives a strBuf pointer. Allocates a new strBuf structure with the same capacity, and initially clears it. If the received strBuf has a non-empty string, then it is copied into the duplicate, and the duplicates size is set correctly. Returns the duplicate, or NULL on memory allocation error. ********************************************************************/ strBufP sb_Duplicate(strBufP theStrBuf) { strBufP newStrBuf = sb_New(theStrBuf->capacity); if (newStrBuf == NULL) return NULL; if (theStrBuf->size > 0) { strcpy(newStrBuf->buf, theStrBuf->buf); newStrBuf->size = theStrBuf->size; } return newStrBuf; } /******************************************************************** sb_ReadSkipWhitespace() Advances the read position managed by the string buffer by skipping any number of whitespace characters, if present. ********************************************************************/ void sb_ReadSkipWhitespace(strBufP theStrBuf) { if (theStrBuf != NULL && theStrBuf->buf != NULL) { while (isspace(theStrBuf->buf[theStrBuf->readPos])) theStrBuf->readPos++; } } /******************************************************************** sb_ReadSkipInteger() Advances the read position managed by the string buffer by skipping an initial negative sign, if present, then skipping any number of numeric digits, if present. ********************************************************************/ void sb_ReadSkipInteger(strBufP theStrBuf) { if (theStrBuf != NULL && theStrBuf->buf != NULL) { if (theStrBuf->buf[theStrBuf->readPos] == '-') theStrBuf->readPos++; while (isdigit(theStrBuf->buf[theStrBuf->readPos])) theStrBuf->readPos++; } } /******************************************************************** sb_ConcatString() Appends the content of string s to the end of the content already in the string buffer. If the append would exceed the capacity of the buffer, then the buffer capacity is first increased. It is increased to one greater than the sum of the current capacity and the new string size or double the capacity (to ensure the memory space is big enough for both strings and linear time performance over many small concatenations). Returns OK on success, NOTOK on error ********************************************************************/ int sb_ConcatString(strBufP theStrBuf, char const *s) { int slen = s == NULL ? 0 : strlen(s); if (slen == 0) return OK; if (theStrBuf == NULL || theStrBuf->buf == NULL) return NOTOK; if (theStrBuf->size + slen > theStrBuf->capacity) { int newLen = theStrBuf->size + slen > 2 * theStrBuf->capacity ? theStrBuf->size + slen : 2 * theStrBuf->capacity; char *newBuf = (char *)malloc((newLen + 1) * sizeof(char)); if (newBuf == NULL) return NOTOK; strcpy(newBuf, theStrBuf->buf); free(theStrBuf->buf); theStrBuf->buf = newBuf; theStrBuf->capacity = newLen; } strcpy(theStrBuf->buf + theStrBuf->size, s); theStrBuf->size += slen; return OK; } /******************************************************************** sb_ConcatChar() Converts ch into a one-character string containing ch, then invokes sb_ConcatStr(). Returns Same as sb_ConcatStr() ********************************************************************/ int sb_ConcatChar(strBufP theStrBuf, char ch) { char s[2]; s[0] = ch; s[1] = '\0'; return sb_ConcatString(theStrBuf, s); } /******************************************************************** sb_TakeString() Extracts the buffer string from the received string buffer and returns it. The string buffer is changed to contain a zero length string, so that the buffer can be reused or freed with sb_Free(). Returns NULL on error or a string to be freed by the caller on success. ********************************************************************/ char *sb_TakeString(strBufP theStrBuf) { char *theBuf = NULL; if (theStrBuf == NULL) return NULL; theBuf = theStrBuf->buf; theStrBuf->buf = (char *)malloc(sizeof(char)); theStrBuf->buf[0] = '\0'; theStrBuf->size = theStrBuf->capacity = 0; return theBuf; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/strbuf.h000066400000000000000000000033571521450711600252550ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef STRBUF_H #define STRBUF_H #ifdef __cplusplus extern "C" { #endif // includes mem functions like memcpy #include typedef struct { char *buf; int size, capacity, readPos; } strBuf; typedef strBuf *strBufP; strBufP sb_New(int); void sb_Free(strBufP *); void sb_ClearBuf(strBufP); int sb_Copy(strBufP, strBufP); strBufP sb_Duplicate(strBufP); #define sb_GetFullString(theStrBuf) (theStrBuf->buf) #define sb_GetSize(theStrBuf) (theStrBuf->size) #define sb_GetCapacity(theStrBuf) (theStrBuf->capacity) #define sb_GetUnreadCharCount(theStrBuf) (theStrBuf->size - theStrBuf->readPos) #define sb_GetReadString(theStrBuf) ((theStrBuf != NULL && theStrBuf->buf != NULL) ? (theStrBuf->buf + theStrBuf->readPos) : NULL) #define sb_GetReadPos(theStrBuf) (theStrBuf->readPos) #define sb_SetReadPos(theStrBuf, theReadPos) \ { \ theStrBuf->readPos = theReadPos; \ } void sb_ReadSkipWhitespace(strBufP); void sb_ReadSkipInteger(strBufP); #define sb_ReadSkipChar(theStrBuf) \ { \ theStrBuf->readPos++; \ } int sb_ConcatString(strBufP, char const *); int sb_ConcatChar(strBufP, char); char *sb_TakeString(strBufP); #ifndef SPEED_MACROS // Optimized SPEED_MACROS versions of larger methods are not used in this module #else // Optimized SPEED_MACROS versions of larger methods are not used in this module #endif #ifdef __cplusplus } #endif #endif /* STRBUF_H */ edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/000077500000000000000000000000001521450711600257725ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/apiutils.c000066400000000000000000000124331521450711600277730ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include #include #include #include "appconst.h" #include "apiutils.h" #include "apiutils.private.h" // The graphLib gp_ErrorMessage() and gp_Message() calls are suppressed by // default, but an application can turn them on if desired. unsigned quietMode = QUIETMODE_ALL; unsigned gp_GetQuietMode(void) { return quietMode; } void gp_SetQuietMode(unsigned newQuietMode) { quietMode = newQuietMode; } void gp_Message(const char *message, ...) { if (!(gp_GetQuietMode() & QUIETMODE_MESSAGES)) { va_list args; va_start(args, message); vfprintf(stdout, message, args); va_end(args); fprintf(stdout, "\n"); fflush(stdout); } } void gp_MessagePrompt(const char *message, ...) { if (!(gp_GetQuietMode() & QUIETMODE_MESSAGES)) { va_list args; va_start(args, message); vfprintf(stdout, message, args); va_end(args); fprintf(stdout, " "); fflush(stdout); } } void gp_LogErrorMessage(int lineNum, const char *srcFileName, const char *message, ...) { if (!(gp_GetQuietMode() & QUIETMODE_ERRORS)) { va_list args; fprintf(stderr, "[ERROR] "); va_start(args, message); vfprintf(stderr, message, args); va_end(args); if (lineNum > 0 && srcFileName != NULL) fprintf(stderr, "\n\ton line %d of '%s'", lineNum, srcFileName); fprintf(stderr, "\n"); fflush(stderr); } } /******************************************************************** debugNOTOK() This function returns the literal value of NOTOK. In debug mode, NOTOK is redefined to first use printf() to emit information about where in the code a NOTOK has occurred. Then, this method is invoked so that the debug version of NOTOK still returns the NOTOK value. Rather than just returning 0 in the debug-mode NOTOK macro, we invoke this method because it gives the option (with recompilation) of having the program exit on the first NOTOK occurrence. That option is off by default, so we normally get a stack trace of the NOTOK occcurences, but on an exhaustive, long-run test, it can be handy to stop on the first error since otherwise the error message might not be seen. ********************************************************************/ #ifdef DEBUG int debugNOTOK(void) { // exit(-1); return 0; // NOTOK is normally defined to be zero } #endif // LOGGING is not defined in the standard compile configuration. // A graphLib developer can uncomment LOGGING in apiutils.private.h #ifdef LOGGING /******************************************************************** _Log() When the project is compiled with LOGGING enabled, this method writes a string to the file Edge_Addition_Planarity_Suite.LOG in the current working directory. On first write, the file is created or cleared. Call this method with NULL to close the log file. ********************************************************************/ void closeLogFileAtExit(void); void _Log(char const *Str) { static FILE *logfile = NULL; static int triedlogfile = FALSE; if (logfile == NULL && !triedlogfile) { triedlogfile = TRUE; if (atexit(closeLogFileAtExit) != 0) gp_ErrorMessage("Unable to set up atexit() to close Edge_Addition_Planarity_Suite log file on exit"); else { if ((logfile = fopen("Edge_Addition_Planarity_Suite.LOG", WRITETEXT)) == NULL) gp_ErrorMessage("Unable to open the Edge_Addition_Planarity_Suite log file"); } } if (logfile != NULL) { if (Str != NULL) { fprintf(logfile, "%s", Str); fflush(logfile); } else { fclose(logfile); logfile = NULL; } } } void _LogLine(char const *Str) { _Log(Str); _Log("\n"); } void closeLogFileAtExit(void) { _gp_Log(NULL); } static char LogStr[MAXLINE + 1]; char *_MakeLogStr1(const char *format, int one) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(LogStr, format, one); #pragma GCC diagnostic pop return LogStr; } char *_MakeLogStr2(const char *format, int one, int two) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(LogStr, format, one, two); #pragma GCC diagnostic pop return LogStr; } char *_MakeLogStr3(const char *format, int one, int two, int three) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(LogStr, format, one, two, three); #pragma GCC diagnostic pop return LogStr; } char *_MakeLogStr4(const char *format, int one, int two, int three, int four) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(LogStr, format, one, two, three, four); #pragma GCC diagnostic pop return LogStr; } char *_MakeLogStr5(const char *format, int one, int two, int three, int four, int five) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(LogStr, format, one, two, three, four, five); #pragma GCC diagnostic pop return LogStr; } #endif // LOGGING edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/apiutils.h000066400000000000000000000034011521450711600277730ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef APIUTILS_H #define APIUTILS_H #ifdef __cplusplus extern "C" { #endif #include "stdio.h" #define MAXLINE 1024 // The string representation for an integer must account for: an optional '-', // then 10 digits (max signed 32-bit int), and a null-terminator #define MAXCHARSFOR32BITINT 11 #if defined(_MSC_VER) && !defined(__llvm__) && !defined(__INTEL_COMPILER) #define APPLY_FORMAT_ATTRIBUTE 0 #elif defined(__has_attribute) #define APPLY_FORMAT_ATTRIBUTE __has_attribute(format) #elif defined(__GNUC__) || defined(__clang__) #define APPLY_FORMAT_ATTRIBUTE 1 #else #define APPLY_FORMAT_ATTRIBUTE 0 #endif #if APPLY_FORMAT_ATTRIBUTE #if defined(__GNUC__) && !defined(__clang__) #define FORMAT_PRINTF(formatIndex, firstArg) __attribute__((format(gnu_printf, formatIndex, firstArg))) #else #define FORMAT_PRINTF(formatIndex, firstArg) __attribute__((format(printf, formatIndex, firstArg))) #endif #else #define FORMAT_PRINTF(formatIndex, firstArg) #endif // These methods control whether gp_ErrorMessage() and gp_Message() calls // emit output or skip producing output (the default) unsigned gp_GetQuietMode(void); void gp_SetQuietMode(unsigned newQuietMode); #define QUIETMODE_NONE 0 #define QUIETMODE_ERRORS 1 #define QUIETMODE_MESSAGES 2 #define QUIETMODE_ALL 0XFFFFFFFF void gp_Message(const char *message, ...) FORMAT_PRINTF(1, 2); void gp_MessagePrompt(const char *message, ...) FORMAT_PRINTF(1, 2); #define gp_ErrorMessage(...) (gp_LogErrorMessage(__LINE__, __FILE__, __VA_ARGS__)) void gp_LogErrorMessage(int lineNum, const char *srcFileName, const char *message, ...) FORMAT_PRINTF(3, 4); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/apiutils.private.h000066400000000000000000000030361521450711600314500ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef APIUTILS_PRIVATE_H #define APIUTILS_PRIVATE_H #ifdef __cplusplus extern "C" { #endif /* PRIVATE FUNCTIONS FOR ADDITIONAL INFORMATIONAL LOGGING. If LOGGING is defined by uncommenting it below, then log-related lines write to the log, and otherwise they no-op. By default, neither release nor DEBUG builds including LOGGING. Logging is used to see more details of how various algorithms handle a particular graph. */ // #define LOGGING #ifdef LOGGING #define _gp_LogLine _LogLine #define _gp_Log _Log void _LogLine(const char *Line); void _Log(const char *Line); #define _gp_MakeLogStr1 _MakeLogStr1 #define _gp_MakeLogStr2 _MakeLogStr2 #define _gp_MakeLogStr3 _MakeLogStr3 #define _gp_MakeLogStr4 _MakeLogStr4 #define _gp_MakeLogStr5 _MakeLogStr5 char *_MakeLogStr1(const char *format, int); char *_MakeLogStr2(const char *format, int, int); char *_MakeLogStr3(const char *format, int, int, int); char *_MakeLogStr4(const char *format, int, int, int, int); char *_MakeLogStr5(const char *format, int, int, int, int, int); #else #define _gp_LogLine(Line) #define _gp_Log(Line) #define _gp_MakeLogStr1(format, one) #define _gp_MakeLogStr2(format, one, two) #define _gp_MakeLogStr3(format, one, two, three) #define _gp_MakeLogStr4(format, one, two, three, four) #define _gp_MakeLogStr5(format, one, two, three, four, five) #endif #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/appconst.h000066400000000000000000000041331521450711600277730ustar00rootroot00000000000000#ifndef APPCONST_H #define APPCONST_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "apiutils.h" // NOTE: This is defined on 32- and 64-bit Windows systems; see // https://sourceforge.net/p/predef/wiki/OperatingSystems #if defined(WIN32) || defined(_WIN32) #define WINDOWS #endif /* Defines fopen strings for reading and writing text files on PC and UNIX */ #ifdef WINDOWS #define READTEXT "rt" #define WRITETEXT "wt" #define FILE_DELIMITER '\\' #else #define READTEXT "r" #define WRITETEXT "w" #define FILE_DELIMITER '/' #endif /* Define DEBUG to get additional debugging. The default is to define it when MSC does */ #ifdef _DEBUG #define DEBUG #endif /* Some low-level functions are replaced by faster macros, except when debugging */ #define SPEED_MACROS #ifdef DEBUG #undef SPEED_MACROS #endif /* Return status values; OK/NOTOK behave like Boolean true/false, not like program exit codes. */ #define OK 1 #define NOTOK 0 #ifdef DEBUG #undef NOTOK extern int debugNOTOK(void); #include #define NOTOK (printf("NOTOK on Line %d of %s\n", __LINE__, __FILE__), debugNOTOK()) #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef NULL #define NULL ((void *)0) #endif // Define one of these to use 1-based arrays or the original 0-based arrays // It used to be true that the 1-based arrays were faster, but compiler // optimizations have come a long way in two decades. // // The main advantages of 1-based arrays are readability of the data, // that NIL is still an index within array bounds, that 1-based supports // 0-based files but not the reverse, and continuing the long-time default. #define USE_1BASEDARRAYS // #define USE_0BASEDARRAYS #ifdef USE_0BASEDARRAYS #undef USE_1BASEDARRAYS #endif /* Array indices are used as pointers, and NIL means bad pointer */ #ifdef USE_1BASEDARRAYS // This definition is used with 1-based array indexing #define NIL 0 #define NIL_CHAR 0x00 #else // This definition is used with 0-based array indexing #define NIL -1 #define NIL_CHAR 0xFF #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/listcoll.c000066400000000000000000000223111521450711600277620ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #define _LISTCOLL_C #include "appconst.h" #include "listcoll.h" #include /***************************************************************************** The data structure defined by this module manages a set of N objects arranged as a collection of circular lists, each containing distinct elements from the set. On construction, LCNew() creates an array of N nodes, each containing a prev and next pointer. The identity of the node is given by its array index. Each node's prev and next pointers are set to NIL, indicating that the node is not currently part of a list. LCReset() can be called to reset all pointers to NIL. The function LCFree() deallocates the collection of lists and clears the pointer variable used to pass the collection. An empty list is indicated by NIL. To begin a list with node I, call LCPrepend() or LCAppend() with the NIL list and with I as the node. The prev and next pointers in node I are set to I and I is returned as the head of the list. Future calls to LCPrepend() add a node J as the new first element of the list, so the list given as input is pointed to by J's next, and J is returned as the head of the list. Future calls to LCAppend() add a node J as the new last element, so the prev pointer of the list given as input will indicate node J, and the input list is returned as the head of the list. LCInsertAfter() adds a node immediately after a given anchor node. LCInsertBefore() adds a node immediately before a given anchor node and has the same effect on a list as LCPrepend(). The function LCDelete() removes a node I from a list L. If node I is in the list alone, then its pointers are set to NIL, and NIL is returned as the list. If node I is not alone in the list, but it is the head of the list (in other words, I is equal to L), then L's successor is returned as the new head of the list. Whether or not I equals L, node I is deleted by joining its predecessor and successor nodes. LCCopy() copies the contents of one collection to another if both are of equal size. LCGetNext() is used for forward iteration through a list in the collection. The expected iteration pattern is first to process the node one has, then call LCGetNext() to get the next node, so if the result of LCGetNext() would be the head of the list, then NIL is returned instead. This simplifies most coding operations involving LCGetNext(). LCGetPrev() is used for backward iteration through a list in the collection. The expected iteration pattern is that the last list element will be obtained by an initial call to LCGetPrev() with theNode equal to NIL. This call should appear outside of the iteration loop. The iteration loop then proceeds while the current node is not NIL. The loop body processes the current node, then LCGetPrev() is called with theNode equal to the current node. LCGetPrev() returns NIL if theNode is equal to theList. Otherwise, the predecessor of theNode is returned. *****************************************************************************/ /***************************************************************************** LCNew() *****************************************************************************/ listCollectionP LCNew(int N) { listCollectionP theListColl = NULL; if (N <= 0) return theListColl; theListColl = (listCollectionP)malloc(sizeof(listCollectionStruct)); if (theListColl != NULL) { theListColl->List = (lcnode *)malloc(N * sizeof(lcnode)); if (theListColl->List == NULL) { free(theListColl); theListColl = NULL; } else { theListColl->N = N; LCReset(theListColl); } } return theListColl; } /***************************************************************************** LCFree() *****************************************************************************/ void LCFree(listCollectionP *pListColl) { if (pListColl == NULL || *pListColl == NULL) return; if ((*pListColl)->List != NULL) free((*pListColl)->List); free(*pListColl); *pListColl = NULL; } /***************************************************************************** LCInsertAfter() *****************************************************************************/ void LCInsertAfter(listCollectionP listColl, int theAnchor, int theNewNode) { listColl->List[theNewNode].prev = theAnchor; listColl->List[theNewNode].next = listColl->List[theAnchor].next; listColl->List[listColl->List[theAnchor].next].prev = theNewNode; listColl->List[theAnchor].next = theNewNode; } /***************************************************************************** LCInsertBefore() *****************************************************************************/ void LCInsertBefore(listCollectionP listColl, int theAnchor, int theNewNode) { LCPrepend(listColl, theAnchor, theNewNode); } #ifndef SPEED_MACROS /***************************************************************************** LCReset() *****************************************************************************/ void LCReset(listCollectionP listColl) { int K; for (K = 0; K < listColl->N; K++) listColl->List[K].prev = listColl->List[K].next = NIL; } /***************************************************************************** LCCopy() *****************************************************************************/ void LCCopy(listCollectionP dst, listCollectionP src) { int K; if (dst == NULL || src == NULL || dst->N != src->N) return; for (K = 0; K < dst->N; K++) dst->List[K] = src->List[K]; } /***************************************************************************** LCGetNext() *****************************************************************************/ int LCGetNext(listCollectionP listColl, int theList, int theNode) { int next; if (listColl == NULL || theList == NIL || theNode == NIL) return NIL; next = listColl->List[theNode].next; return next == theList ? NIL : next; } /***************************************************************************** LCGetPrev() *****************************************************************************/ int LCGetPrev(listCollectionP listColl, int theList, int theNode) { if (listColl == NULL || theList == NIL) return NIL; if (theNode == NIL) return listColl->List[theList].prev; if (theNode == theList) return NIL; return listColl->List[theNode].prev; } /***************************************************************************** LCPrepend() *****************************************************************************/ int LCPrepend(listCollectionP listColl, int theList, int theNode) { /* If the append worked, then theNode is last, which in a circular list is the direct predecessor of the list head node, so we just back up one. For singletons, the result is unchanged. */ return listColl->List[LCAppend(listColl, theList, theNode)].prev; } /***************************************************************************** LCAppend() *****************************************************************************/ int LCAppend(listCollectionP listColl, int theList, int theNode) { /* If the given list is empty, then the given node becomes the singleton list output */ if (theList == NIL) { listColl->List[theNode].prev = listColl->List[theNode].next = theNode; theList = theNode; } /* Otherwise, make theNode the predecessor of head node of theList, which is where the last node goes in a circular list. */ else { int pred = listColl->List[theList].prev; listColl->List[theList].prev = theNode; listColl->List[theNode].next = theList; listColl->List[theNode].prev = pred; listColl->List[pred].next = theNode; } /* Return the list (only really important if it was NIL) */ return theList; } /***************************************************************************** LCDelete() *****************************************************************************/ int LCDelete(listCollectionP listColl, int theList, int theNode) { /* If the list is a singleton, then NIL its pointers and return NIL for theList*/ if (listColl->List[theList].next == theList) { listColl->List[theList].prev = listColl->List[theList].next = NIL; theList = NIL; } /* Join predecessor and successor, dropping theNode from the list. If theNode is the head of the list, then return the successor as the new head node. */ else { int pred = listColl->List[theNode].prev, succ = listColl->List[theNode].next; listColl->List[pred].next = succ; listColl->List[succ].prev = pred; listColl->List[theNode].prev = listColl->List[theNode].next = NIL; if (theList == theNode) theList = succ; } return theList; } #endif // SPEED_MACROS edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/listcoll.h000066400000000000000000000125611521450711600277750ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef _LISTCOLL_H #define _LISTCOLL_H #ifdef __cplusplus extern "C" { #endif /* This include is needed for memset and memcpy */ #include typedef struct { int prev, next; } lcnode; struct listCollectionStruct { int N; lcnode *List; }; typedef struct listCollectionStruct listCollectionStruct; typedef listCollectionStruct *listCollectionP; listCollectionP LCNew(int N); void LCFree(listCollectionP *pListColl); void LCInsertAfter(listCollectionP listColl, int theAnchor, int theNewNode); void LCInsertBefore(listCollectionP listColl, int theAnchor, int theNewNode); #ifndef SPEED_MACROS void LCReset(listCollectionP listColl); void LCCopy(listCollectionP dst, listCollectionP src); int LCGetNext(listCollectionP listColl, int theList, int theNode); int LCGetPrev(listCollectionP listColl, int theList, int theNode); int LCPrepend(listCollectionP listColl, int theList, int theNode); int LCAppend(listCollectionP listColl, int theList, int theNode); int LCDelete(listCollectionP listColl, int theList, int theNode); #else /* void LCReset(listCollectionP listColl); */ #define LCReset(listColl) memset(listColl->List, NIL_CHAR, listColl->N * sizeof(lcnode)) /* void LCCopy(listCollectionP dst, listCollectionP src) */ #define LCCopy(dst, src) memcpy(dst->List, src->List, src->N * sizeof(lcnode)) /* int LCGetNext(listCollectionP listColl, int theList, int theNode); Return theNode's successor, unless it is theList head pointer */ #define LCGetNext(listColl, theList, theNode) listColl->List[theNode].next == theList ? NIL : listColl->List[theNode].next /* int LCGetPrev(listCollectionP listColl, int theList, int theNode); Return theNode's predecessor unless theNode is theList head. To start going backwards, use NIL for theNode, which returns theList head's predecessor Usage: Obtain last node, loop while NIL not returned, process node then get predecessor. After theList head processed, get predecessor returns NIL because we started with theList head's predecessor. */ #define LCGetPrev(listColl, theList, theNode) \ (theNode == NIL \ ? listColl->List[theList].prev \ : theNode == theList ? NIL \ : listColl->List[theNode].prev) /* int LCPrepend(listCollectionP listColl, int theList, int theNode); If theList is empty, then theNode becomes its only member and is returned. Otherwise, theNode is placed before theList head, and theNode is returned as the new head. */ #define LCPrepend(listColl, theList, theNode) \ (theList == NIL \ ? (listColl->List[theNode].prev = listColl->List[theNode].next = theNode) \ : (listColl->List[theNode].next = theList, \ listColl->List[theNode].prev = listColl->List[theList].prev, \ listColl->List[listColl->List[theNode].prev].next = theNode, \ listColl->List[theList].prev = theNode, \ listColl->List[theList].prev)) /* int LCAppend(listCollectionP listColl, int theList, int theNode); If theList is empty, then theNode becomes its only member and is returned. Otherwise, theNode is placed before theList head, and then theList head is returned. */ #define LCAppend(listColl, theList, theNode) \ (theList == NIL \ ? (listColl->List[theNode].prev = listColl->List[theNode].next = theNode) \ : (listColl->List[theNode].next = theList, \ listColl->List[theNode].prev = listColl->List[theList].prev, \ listColl->List[listColl->List[theNode].prev].next = theNode, \ listColl->List[theList].prev = theNode, \ theList)) /* int LCDelete(listCollectionP listColl, int theList, int theNode); If theList contains only one node, then NIL it out and return NIL meaning empty list Otherwise, join the predecessor and successor, then return either the list head or its successor if the deleted node is the list head (in that case, the caller makes the successor become the new list head).*/ #define LCDelete(listColl, theList, theNode) \ listColl->List[theList].next == theList \ ? (listColl->List[theList].prev = listColl->List[theList].next = NIL) \ : (listColl->List[listColl->List[theNode].prev].next = listColl->List[theNode].next, \ listColl->List[listColl->List[theNode].next].prev = listColl->List[theNode].prev, \ (theList == theNode ? listColl->List[theNode].next : theList)) #endif #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/stack.c000066400000000000000000000102671521450711600272510ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "appconst.h" #include "stack.h" #include stackP sp_New(int capacity) { stackP theStack; theStack = (stackP)malloc(sizeof(stackStruct)); if (theStack != NULL) { theStack->S = (int *)malloc(capacity * sizeof(int)); if (theStack->S == NULL) { free(theStack); theStack = NULL; } } if (theStack != NULL) { theStack->capacity = capacity; sp_ClearStack(theStack); } return theStack; } void sp_Free(stackP *pStack) { if (pStack == NULL || *pStack == NULL) return; (*pStack)->capacity = (*pStack)->size = 0; if ((*pStack)->S != NULL) free((*pStack)->S); (*pStack)->S = NULL; free(*pStack); *pStack = NULL; } int sp_CopyContent(stackP stackDst, stackP stackSrc) { if (stackDst->capacity < stackSrc->size) return NOTOK; if (stackSrc->size > 0) memcpy(stackDst->S, stackSrc->S, stackSrc->size * sizeof(int)); stackDst->size = stackSrc->size; return OK; } stackP sp_Duplicate(stackP theStack) { stackP newStack = sp_New(theStack->capacity); if (newStack == NULL) return NULL; if (theStack->size > 0) { memcpy(newStack->S, theStack->S, theStack->size * sizeof(int)); newStack->size = theStack->size; } return newStack; } int sp_Copy(stackP stackDst, stackP stackSrc) { if (sp_CopyContent(stackDst, stackSrc) != OK) { stackP newStack = sp_Duplicate(stackSrc); int *p; if (newStack == NULL) return NOTOK; p = stackDst->S; stackDst->S = newStack->S; newStack->S = p; newStack->capacity = stackDst->capacity; sp_Free(&newStack); stackDst->size = stackSrc->size; stackDst->capacity = stackSrc->capacity; } return OK; } #ifndef SPEED_MACROS int sp_ClearStack(stackP theStack) { theStack->size = 0; return OK; } int sp_GetCurrentSize(stackP theStack) { return theStack->size; } int sp_SetCurrentSize(stackP theStack, int size) { return size > theStack->capacity ? NOTOK : (theStack->size = size, OK); } int sp_IsEmpty(stackP theStack) { return !theStack->size; } int sp_NonEmpty(stackP theStack) { return theStack->size; } int sp__Push(stackP theStack, int a) { if (theStack->size >= theStack->capacity) return NOTOK; theStack->S[theStack->size++] = a; return OK; } int sp__Push2(stackP theStack, int a, int b) { if (theStack->size + 1 >= theStack->capacity) return NOTOK; theStack->S[theStack->size++] = a; theStack->S[theStack->size++] = b; return OK; } int sp__Pop(stackP theStack, int *pA) { if (theStack->size <= 0) return NOTOK; *pA = theStack->S[--theStack->size]; return OK; } int sp__Pop_Discard(stackP theStack) { if (theStack->size <= 0) return NOTOK; --theStack->size; return OK; } int sp__Pop2(stackP theStack, int *pA, int *pB) { if (theStack->size <= 1) return NOTOK; *pB = theStack->S[--theStack->size]; *pA = theStack->S[--theStack->size]; return OK; } int sp__Pop2_Discard1(stackP theStack, int *pA) { if (theStack->size <= 1) return NOTOK; // When a pair of the form (main, secondary) are pushed in order, // it is sometimes necessary to pop the secondary and discard, // then pop and store the main datum. --theStack->size; *pA = theStack->S[--theStack->size]; return OK; } int sp__Pop2_Discard(stackP theStack) { if (theStack->size <= 1) return NOTOK; --theStack->size; --theStack->size; return OK; } int sp_Top(stackP theStack) { return theStack->size ? theStack->S[theStack->size - 1] : NIL; } int sp_Get(stackP theStack, int pos) { if (theStack == NULL || pos < 0 || pos >= theStack->size) return NOTOK; return (theStack->S[pos]); } int sp_Set(stackP theStack, int pos, int val) { if (theStack == NULL || pos < 0 || pos >= theStack->size) return NOTOK; return (theStack->S[pos] = val); } #endif // not defined SPEED_MACROS edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/stack.h000066400000000000000000000105451521450711600272550ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef STACK_H #define STACK_H #ifdef __cplusplus extern "C" { #endif // includes mem functions like memcpy #include struct stackStruct { int *S; int size, capacity; }; typedef struct stackStruct stackStruct; typedef stackStruct *stackP; stackP sp_New(int); void sp_Free(stackP *); int sp_Copy(stackP, stackP); int sp_CopyContent(stackP stackDst, stackP stackSrc); stackP sp_Duplicate(stackP theStack); #define sp_GetCapacity(theStack) (theStack->capacity) #ifndef SPEED_MACROS int sp_ClearStack(stackP); int sp_GetCurrentSize(stackP theStack); int sp_SetCurrentSize(stackP theStack, int top); int sp_IsEmpty(stackP); int sp_NonEmpty(stackP); #define sp_Push(theStack, a) \ { \ if (sp__Push(theStack, (a)) != OK) \ return NOTOK; \ } #define sp_Push2(theStack, a, b) \ { \ if (sp__Push2(theStack, (a), (b)) != OK) \ return NOTOK; \ } int sp__Push(stackP, int); int sp__Push2(stackP, int, int); #define sp_Pop(theStack, a) \ { \ if (sp__Pop(theStack, &(a)) != OK) \ return NOTOK; \ } #define sp_Pop_Discard(theStack) \ { \ if (sp__Pop_Discard(theStack) != OK) \ return NOTOK; \ } #define sp_Pop2(theStack, a, b) \ { \ if (sp__Pop2(theStack, &(a), &(b)) != OK) \ return NOTOK; \ } #define sp_Pop2_Discard1(theStack, a) \ { \ if (sp__Pop2_Discard1(theStack, &(a)) != OK) \ return NOTOK; \ } #define sp_Pop2_Discard(theStack) \ { \ if (sp__Pop2_Discard(theStack) != OK) \ return NOTOK; \ } int sp__Pop(stackP, int *); int sp__Pop_Discard(stackP theStack); int sp__Pop2(stackP, int *, int *); int sp__Pop2_Discard1(stackP theStack, int *pA); int sp__Pop2_Discard(stackP theStack); int sp_Top(stackP); int sp_Get(stackP, int); int sp_Set(stackP, int, int); #else #define sp_ClearStack(theStack) theStack->size = 0 #define sp_GetCurrentSize(theStack) (theStack->size) #define sp_SetCurrentSize(theStack, Size) ((Size) > theStack->capacity ? NOTOK : (theStack->size = (Size), OK)) #define sp_IsEmpty(theStack) !theStack->size #define sp_NonEmpty(theStack) theStack->size #define sp_Push(theStack, a) theStack->S[theStack->size++] = a #define sp_Push2(theStack, a, b) \ { \ sp_Push(theStack, a); \ sp_Push(theStack, b); \ } #define sp_Pop(theStack, a) a = theStack->S[--theStack->size] #define sp_Pop_Discard(theStack) --theStack->size #define sp_Pop2(theStack, a, b) \ { \ sp_Pop(theStack, b); \ sp_Pop(theStack, a); \ } #define sp_Pop2_Discard1(theStack, a) \ { \ sp_Pop_Discard(theStack); \ sp_Pop(theStack, a); \ } #define sp_Pop2_Discard(theStack) \ { \ sp_Pop_Discard(theStack); \ sp_Pop_Discard(theStack); \ } #define sp_Top(theStack) (theStack->size ? theStack->S[theStack->size - 1] : NIL) #define sp_Get(theStack, pos) (theStack->S[pos]) #define sp_Set(theStack, pos, val) (theStack->S[pos] = val) #endif #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/000077500000000000000000000000001521450711600264645ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphDrawPlanar.c000066400000000000000000001357331521450711600317210ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphDrawPlanar.h" #include "graphDrawPlanar.private.h" // For definition of zero-based IO flag #include "../io/graphIO.h" // For LOGGING-related declarations #include "../lowLevelUtils/apiutils.private.h" #include #include #include extern void _ClearAllVisitedFlagsInGraph(graphP theGraph); /* Private functions exported to system */ void _CollectDrawingData(DrawPlanarContext *context, int RootVertex, int W, int WPrevLink); int _BreakTie(DrawPlanarContext *context, int BicompRoot, int W, int WPrevLink); int _ComputeVisibilityRepresentation(DrawPlanarContext *context); int _CheckVisibilityRepresentationIntegrity(DrawPlanarContext *context); /* Private functions */ int _ComputeVertexPositions(DrawPlanarContext *context); int _ComputeVertexPositionsInComponent(DrawPlanarContext *context, int root, int *pIndex); int _ComputeEdgePositions(DrawPlanarContext *context); int _ComputeVertexRanges(DrawPlanarContext *context); int _ComputeEdgeRanges(DrawPlanarContext *context); int _GetNextExternalFaceVertex(graphP theGraph, int curVertex, int *pPrevLink); char *_RenderToString(graphP theEmbedding); #ifdef LOGGING void _LogEdgeList(graphP theEmbedding, listCollectionP edgeList, int edgeListHead); #endif /******************************************************************** _ComputeVisibilityRepresentation() Compute vertex positions Compute edge positions Assign horizontal ranges of vertices Assign vertical ranges of edges ********************************************************************/ int _ComputeVisibilityRepresentation(DrawPlanarContext *context) { if (sp_NonEmpty(context->theGraph->edgeHoles)) return NOTOK; if (_ComputeVertexPositions(context) != OK) return NOTOK; if (_ComputeEdgePositions(context) != OK) return NOTOK; if (_ComputeVertexRanges(context) != OK) return NOTOK; if (_ComputeEdgeRanges(context) != OK) return NOTOK; return OK; } /******************************************************************** _ComputeVertexPositions() Computes the vertex positions in the graph. This method accounts for disconnected graphs by finding the DFS tree roots and then, for each, invoking _ComputeVertexPositionsInComponent(). The index variable for the positioning is maintained by this method so that the vertices in separate components still get distinct vertex positions. ********************************************************************/ int _ComputeVertexPositions(DrawPlanarContext *context) { graphP theEmbedding = context->theGraph; int v, vertpos; vertpos = 0; for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) { // For each DFS tree root in the embedding, we // compute the vertex positions if (gp_IsDFSTreeRoot(theEmbedding, v)) { if (_ComputeVertexPositionsInComponent(context, v, &vertpos) != OK) return NOTOK; } } return OK; } /******************************************************************** _ComputeVertexPositionsInComponent() The vertical positions of the vertices are computed based in part on the information compiled during the planar embedding. Each vertex is marked as being between its parent and some ancestor or beyond the parent relative to the ancestor. The localized, intuitive notion is that the vertex is either below the parent or above the parent, but the bicomp containing the vertex, its parent and the ancestor may be turned upside-down as the result of a global sequence of operations, resulting in a between or beyond generalization. As the core planarity algorithm constructs successively larger bicomps out of smaller ones, the bicomp root and its DFS child are marked as 'tied' in vertex position using markers along the external face. The marking of the DFS child may be indirect. Since the child may not be on the external face, its descendant that is next along the external face is marked instead. Later (possibly in the same step or possibly many vertices later), the Walkdown proceeds around the bicomp and returns to each merge point, and the tie is broken based on the direction of approach. As the Walkdown passes a vertex to its successor, the external face is short-circuited to remove the vertex from it. Immediately before this occurs, the new drawing method resolves the tie. Since the vertex is going to the internal face, its vertex position should be 'between' its successor and the current vertex being processed by the Walkdown. If the vertex is a child of its external face successor, then it is simply marked as being 'between' that successor and the current vertex being processed by the planarity method. But if the vertex is the parent of its external face successor, then the successor is placed 'beyond' the vertex. Recall that the successor is either the DFS child of the vertex or a descendant of that DFS child that was specially marked because it, not the DFS child, was on the external face. This explains the information that has been collected by the planarity embedder, which will now be turned into a vertex ordering system. The idea is to proceed with a pre-order traversal of the DFS tree, determining the relative orders of the ancestors of a vertex by the time we get to a vertex. This will allow us to convert between/beyond into above/below based on the known relative order of the parent and some given ancestor of the vertex. A vertex would then be added immediately above or below its parent in the total ordering, and then the algorithm proceeds to the descendants. Consider a depth-first pre-order visitation of vertices. If the full order of all vertices visited so far is dynamically maintained, then it is easy to decide whether a vertex goes above or below its parent based on the between/beyond indicator and the relative positions in the order of the parent and given ancestor of the vertex. If the ancestor is above the parent, then 'between' means put the vertex immediately above its parent and 'beyond' means put the vertex immediately below its parent in the order. And if the ancestor is below the parent, then the meaning of between and beyond are simply reversed. Once a vertex is known to be above or below its parent, the drawing flag is changed from between/beyond to above/below, and processing proceeds to the next vertex in pre-order depth first search. The difficulty lies in keeping an up-to-date topological ordering that can be queried in constant time to find the relative positions of two vertices. By itself, this is an instance of "online" or dynamic topological sorting and has been proven not to be achievable in linear total time. But this is a special case of the problem and is therefore solvable through the collection and maintenance of some additional information. Recall that the ancestor V of a vertex is recorded when the setting for between/beyond is made for a vertex. However, the Walkdown is invoked on the bicomp rooted by edge (V', C), so the child C of V that roots the subtree containing the vertex being marked is known. Note that when a DFS child is placed above its parent, the entire DFS subtree of vertices is placed above the parent. Hence, to determine whether the parent P of a vertex W is above the ancestor V, where W is marked either between or beyond P and V, we need only determine the relationship between V and C, which has already been directly determined due to previous steps of the algorithm (because V and C are ancestors of P and W). If C is above/below V then so is P. As mentioned above, once the position of P is known relative to V, it is a simple matter to decide whether to put W above or below P based on the between/beyond indicator stored in W during embedding. ********************************************************************/ int _ComputeVertexPositionsInComponent(DrawPlanarContext *context, int root, int *pVertpos) { graphP theEmbedding = context->theGraph; listCollectionP theOrder = LCNew(gp_UpperBoundVertices(theEmbedding)); int W, P, C, V, e; if (theOrder == NULL) return NOTOK; // Determine the vertex order using a depth first search with // pre-order visitation. sp_ClearStack(theEmbedding->theStack); sp_Push(theEmbedding->theStack, root); while (!sp_IsEmpty(theEmbedding->theStack)) { sp_Pop(theEmbedding->theStack, W); P = gp_GetVertexParent(theEmbedding, W); V = context->VI[W].ancestor; C = context->VI[W].ancestorChild; // For the special case that we just popped the DFS tree root, // we simply add the root to its own position. if (gp_IsNotVertex(theEmbedding, P)) { // Put the DFS root in the list by itself LCAppend(theOrder, NIL, W); // The children of the DFS root have the root as their // ancestorChild and 'beyond' as the drawingFlag, so this // causes the root's children to be placed below the root context->VI[W].drawingFlag = DRAWINGFLAG_BELOW; } // Determine vertex W position relative to P else { // An unresolved tie is an error if (context->VI[W].drawingFlag == DRAWINGFLAG_TIE) return NOTOK; // If W is the child of a DFS root, then there is no vertex C // between it and some ancestor V. Both V and C are not a vertex, // and W will simply take the default of being below its parent. // If C is a vertex, then it has already been absolutely positioned // and can be used to help position W relative to its parent P, // which is equal to or descendant to C. If C below V, then P below V, // so interpret 'W between P and V' as 'W above P', and interpret // 'W beyond P relative to V' as 'W below P'. if (gp_IsNotVertex(theEmbedding, C) || context->VI[C].drawingFlag == DRAWINGFLAG_BELOW) { if (context->VI[W].drawingFlag == DRAWINGFLAG_BETWEEN) context->VI[W].drawingFlag = DRAWINGFLAG_ABOVE; else context->VI[W].drawingFlag = DRAWINGFLAG_BELOW; } // If C above V, then P above V, so interpret W between // P and V as W below P, and interpret W beyond P relative // to V as W above P. else { if (context->VI[W].drawingFlag == DRAWINGFLAG_BETWEEN) context->VI[W].drawingFlag = DRAWINGFLAG_BELOW; else context->VI[W].drawingFlag = DRAWINGFLAG_ABOVE; } if (context->VI[W].drawingFlag == DRAWINGFLAG_BELOW) LCInsertAfter(theOrder, P, W); else LCInsertBefore(theOrder, P, W); } // Push DFS children e = gp_GetFirstEdge(theEmbedding, W); while (gp_IsEdge(theEmbedding, e)) { if (gp_GetEdgeType(theEmbedding, e) == EDGE_TYPE_CHILD) sp_Push(theEmbedding->theStack, gp_GetNeighbor(theEmbedding, e)); e = gp_GetNextEdge(theEmbedding, e); } } // Use the order to assign vertical positions V = root; while (gp_IsVertex(theEmbedding, V)) { context->VI[V].pos = *pVertpos; (*pVertpos)++; V = LCGetNext(theOrder, root, V); } // Clean up and return LCFree(&theOrder); return OK; } #ifdef LOGGING /******************************************************************** _LogEdgeList() Used to show the progressive calculation of the edge position list. ********************************************************************/ void _LogEdgeList(graphP theEmbedding, listCollectionP edgeList, int edgeListHead) { int eIndex = edgeListHead, e, eTwin; _gp_Log("EdgeList: [ "); while (gp_IsEdge(theEmbedding, eIndex)) { e = (eIndex << 1); eTwin = gp_GetTwin(theEmbedding, e); _gp_Log(_gp_MakeLogStr2("(%d, %d) ", gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, e)), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, eTwin)))); eIndex = LCGetNext(edgeList, edgeListHead, eIndex); } _gp_LogLine("]"); } #endif /******************************************************************** _ComputeEdgePositions() Performs a vertical sweep of the combinatorial planar embedding, developing the edge order in the horizontal sweep line as it advances through the vertices according to their assigned vertical positions. The 'visitedInfo' member of each vertex is used to indicate the location in the edge order list of the generator edge for the vertex. The generator edge is the first edge used to visit the vertex from a higher vertex in the drawing (i.e. a vertex with an earlier, or lower, position number). All edges added from this vertex to the neighbors below it are added immediately after the generator edge for the vertex. ********************************************************************/ int _ComputeEdgePositions(DrawPlanarContext *context) { graphP theEmbedding = context->theGraph; int *vertexOrder = NULL; listCollectionP edgeList = NULL; int edgeListHead, edgeListInsertPoint; int e, eTwin, eCur, v, vpos, epos, eIndex; _gp_LogLine("\ngraphDrawPlanar.c/_ComputeEdgePositions() start"); // Sort the vertices by vertical position (in linear time) if ((vertexOrder = (int *)malloc(gp_GetN(theEmbedding) * sizeof(int))) == NULL) { return NOTOK; } for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) vertexOrder[context->VI[v].pos] = v; // Allocate the edge list of size M. // This is an array of (prev, next) pointers. // An edge at position X corresponds to the edge // at position X in the graph structure, which is // represented by a pair of adjacent edge records // at index 2X. if (gp_GetM(theEmbedding) > 0 && (edgeList = LCNew(gp_LowerBoundEdges(theEmbedding) / 2 + gp_GetM(theEmbedding))) == NULL) { free(vertexOrder); vertexOrder = NULL; return NOTOK; } edgeListHead = NIL; // Each vertex starts out with a NIL generator edge. for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) gp_SetVertexVisitedInfo(theEmbedding, v, NIL); // Perform the vertical sweep of the combinatorial embedding, using // the vertex ordering to guide the sweep. // For each vertex, each edge leading to a vertex with a higher number in // the vertex order is recorded as the "generator edge", or the edge of // first discovery of that higher numbered vertex, unless the vertex already has // a recorded generator edge for (vpos = 0; vpos < gp_GetN(theEmbedding); vpos++) { // Get the vertex associated with the position v = vertexOrder[vpos]; _gp_LogLine(_gp_MakeLogStr3("Processing vertex %d with DFI=%d at position=%d", gp_GetIndex(theEmbedding, v), v, vpos)); // The DFS tree root of a connected component is always the least // number vertex in the vertex ordering. We have to give it a // false generator edge so that it is still "visited" and then // all of its edges are generators for its neighbor vertices because // they all have greater numbers in the vertex order. if (gp_IsDFSTreeRoot(theEmbedding, v)) { // Set a false generator edge, so the vertex is distinguishable from // a vertex with no generator edge when its neighbors are visited // This way, an edge from a neighbor won't get recorded as the // generator edge of the DFS tree root. gp_SetVertexVisitedInfo(theEmbedding, v, NIL - 1); // Now we traverse the adjacency list of the DFS tree root and // record each edge as the generator edge of the neighbors e = gp_GetFirstEdge(theEmbedding, v); while (gp_IsEdge(theEmbedding, e)) { eIndex = (e >> 1); // div by 2 since each edge is a pair of edge records edgeListHead = LCAppend(edgeList, edgeListHead, eIndex); _gp_LogLine(_gp_MakeLogStr2("Append generator edge (%d, %d) to edgeList", gp_GetIndex(theEmbedding, v), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, e)))); // Set the generator edge for the root's neighbor gp_SetVertexVisitedInfo(theEmbedding, gp_GetNeighbor(theEmbedding, e), e); // Go to the next node of the root's adj list e = gp_GetNextEdge(theEmbedding, e); } } // Else, if we are not on a DFS tree root... else { // Get the generator edge of the vertex // Note that this never gets the false generator edge of a DFS tree root eTwin = gp_GetVertexVisitedInfo(theEmbedding, v); if (gp_IsNotEdge(theEmbedding, eTwin)) return NOTOK; e = gp_GetTwin(theEmbedding, eTwin); // Traverse the edges of the vertex, starting // from the generator edge and going counterclockwise... eIndex = (e >> 1); edgeListInsertPoint = eIndex; eCur = gp_GetNextEdgeCircular(theEmbedding, e); while (eCur != e) { // If the neighboring vertex's position is greater // than the current vertex (meaning it is lower in the // diagram), then add that edge to the edge order. if (context->VI[gp_GetNeighbor(theEmbedding, eCur)].pos > vpos) { eIndex = eCur >> 1; LCInsertAfter(edgeList, edgeListInsertPoint, eIndex); _gp_LogLine(_gp_MakeLogStr4("Insert (%d, %d) after (%d, %d)", gp_GetIndex(theEmbedding, v), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, eCur)), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, gp_GetTwin(theEmbedding, e))), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, e)))); edgeListInsertPoint = eIndex; // If the vertex does not yet have a generator edge, then set it. // Note that a DFS tree root has a false generator edge, so this if // test avoids setting a generator edge for a DFS tree root if (gp_IsNotEdge(theEmbedding, gp_GetVertexVisitedInfo(theEmbedding, gp_GetNeighbor(theEmbedding, eCur)))) { gp_SetVertexVisitedInfo(theEmbedding, gp_GetNeighbor(theEmbedding, eCur), eCur); _gp_LogLine(_gp_MakeLogStr2("Generator edge (%d, %d)", gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, gp_GetTwin(theEmbedding, e))), gp_GetIndex(theEmbedding, gp_GetNeighbor(theEmbedding, eCur)))); } } // Go to the next node in v's adjacency list eCur = gp_GetNextEdgeCircular(theEmbedding, eCur); } } #ifdef LOGGING _LogEdgeList(theEmbedding, edgeList, edgeListHead); #endif } // Now iterate through the edgeList and assign positions to the edges. epos = 0; eIndex = edgeListHead; e = eIndex == NIL ? NIL : (eIndex << 1); while (gp_IsEdge(theEmbedding, e)) { eTwin = gp_GetTwin(theEmbedding, e); context->E[e].pos = context->E[eTwin].pos = epos; epos++; eIndex = LCGetNext(edgeList, edgeListHead, eIndex); e = eIndex == NIL ? NIL : (eIndex << 1); } // Clean up and return LCFree(&edgeList); free(vertexOrder); vertexOrder = NULL; _gp_LogLine("graphDrawPlanar.c/_ComputeEdgePositions() end\n"); return OK; } /******************************************************************** _ComputeVertexRanges() Assumes edge positions are known (see _ComputeEdgePositions()). A vertex spans horizontally the positions of the edges incident to it. ********************************************************************/ int _ComputeVertexRanges(DrawPlanarContext *context) { graphP theEmbedding = context->theGraph; int v = NIL, e = NIL, min = NIL, max = NIL; for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) { min = gp_GetM(theEmbedding) + 1; max = NIL; // Iterate the edges, except in the isolated vertex case we just // set the min and max to 1 since there no edges controlling where // it gets drawn. e = gp_GetFirstEdge(theEmbedding, v); if (gp_IsNotEdge(theEmbedding, e)) { min = max = 0; } else { while (gp_IsEdge(theEmbedding, e)) { if (min > context->E[e].pos) min = context->E[e].pos; if (max < context->E[e].pos) max = context->E[e].pos; e = gp_GetNextEdge(theEmbedding, e); } } context->VI[v].start = min; context->VI[v].end = max; } return OK; } /******************************************************************** _ComputeEdgeRanges() Assumes vertex positions are known (see _ComputeVertexPositions()). An edges spans the vertical range of its endpoints. ********************************************************************/ int _ComputeEdgeRanges(DrawPlanarContext *context) { graphP theEmbedding = context->theGraph; int e, eTwin, v1, v2, pos1, pos2; // Deleted edges are not supported, nor should they be in the embedding, so // this is just a reality check that avoids an in-use test inside the loop if (sp_NonEmpty(theEmbedding->edgeHoles)) return NOTOK; for (e = gp_LowerBoundEdges(theEmbedding); e < gp_UpperBoundEdges(theEmbedding); e += 2) { eTwin = gp_GetTwin(theEmbedding, e); v1 = gp_GetNeighbor(theEmbedding, e); v2 = gp_GetNeighbor(theEmbedding, eTwin); pos1 = context->VI[v1].pos; pos2 = context->VI[v2].pos; if (pos1 < pos2) { context->E[e].start = pos1; context->E[e].end = pos2; } else { context->E[e].start = pos2; context->E[e].end = pos1; } context->E[eTwin].start = context->E[e].start; context->E[eTwin].end = context->E[e].end; } return OK; } /******************************************************************** _GetNextExternalFaceVertex() Uses the extFace links to traverse to the next vertex on the external face given a current vertex and the link that points to its predecessor. ********************************************************************/ int _GetNextExternalFaceVertex(graphP theGraph, int curVertex, int *pPrevLink) { int nextVertex = gp_GetExtFaceVertex(theGraph, curVertex, 1 ^ *pPrevLink); // If the two links in the new vertex are not equal, then only one points // back to the current vertex, and it is the new prev link. // Otherwise, the vertex is in a consistently oriented single-edge bicomp, so // no adjustment of the prev link is needed (due to the consistent orientation). if (gp_GetExtFaceVertex(theGraph, nextVertex, 0) != gp_GetExtFaceVertex(theGraph, nextVertex, 1)) { *pPrevLink = gp_GetExtFaceVertex(theGraph, nextVertex, 0) == curVertex ? 0 : 1; } return nextVertex; } /******************************************************************** _CollectDrawingData() To be called by core planarity Walkdown immediately before merging bicomps and embedding a new back edge. Each bicomp is rooted by a DFS tree edge. The parent vertex in that edge is the bicomp root, and the bicomp contains one DFS child of the vertex, which is on the child end of the 'root edge'. Here we decide whether the DFS child is to be embedded between or beyond its parent relative to vertex v, the one currently being processed (and the ancestor endpoint of a back edge being embedded, where the descendant endpoint is also an endpoint of the bicomp root being merged). ********************************************************************/ void _CollectDrawingData(DrawPlanarContext *context, int RootVertex, int W, int WPrevLink) { graphP theEmbedding = context->theGraph; int K, Parent, BicompRoot, DFSChild, direction, descendant; _gp_LogLine("\ngraphDrawPlanar.c/_CollectDrawingData() start"); _gp_LogLine(_gp_MakeLogStr3("_CollectDrawingData(RootVertex=%d, W=%d, W_in=%d)", RootVertex, W, WPrevLink)); /* Process all of the merge points to set their drawing flags. */ for (K = 0; K < sp_GetCurrentSize(theEmbedding->theStack); K += 4) { /* Get the parent and child that are about to be merged from the 4-tuple in the merge stack */ Parent = theEmbedding->theStack->S[K]; BicompRoot = theEmbedding->theStack->S[K + 2]; DFSChild = gp_GetDFSChildFromBicompRoot(theEmbedding, BicompRoot); /* We get the active descendant vertex in the child bicomp that will be adjacent to the parent along the external face. This vertex is guaranteed to be found in one step due to external face 'short-circuiting' that was done in step 'Parent' of the planarity algorithm. */ direction = theEmbedding->theStack->S[K + 3]; descendant = _GetNextExternalFaceVertex(theEmbedding, BicompRoot, &direction); /* Now we set the tie flag in the DFS child, and mark the descendant and parent with non-NIL pointers to the child whose tie flag is to be resolved as soon as one of the two is connected to by an edge or child bicomp merge. */ context->VI[DFSChild].drawingFlag = DRAWINGFLAG_TIE; context->VI[descendant].tie[direction] = DFSChild; direction = theEmbedding->theStack->S[K + 1]; context->VI[Parent].tie[direction] = DFSChild; _gp_LogLine(_gp_MakeLogStr5("V[Parent=%d]=.tie[%d] = V[descendant=%d].tie[%d] = (child=%d)", Parent, direction, descendant, theEmbedding->theStack->S[K + 3], DFSChild)); } _gp_LogLine("graphDrawPlanar.c/_CollectDrawingData() end\n"); } /******************************************************************** _BreakTie() The given vertex W has just been arrived at by the core planarity algorithm. Using WPrevLink, we seek its predecessor WPred on the external face and test whether the two are involved in a tie that can be resolved. Since the planarity algorithm has just passed by WPred, it is safe to conclude that WPred can go between W and the current vertex. Of course, if W was the parent to some DFS child whose subtree contains WPred, then the DFS child is marked 'between', placing the whole subtree including WPred between W and the current vertex. On the other hand, if WPred was the parent of some DFS child whose subtree contained W, then we achieve the same effect of putting WPred 'between' W and the current vertex by marking the DFS child 'beyond'. Since the DFS child and hence W are beyond W relative to the current vertex, WPred is also between W and the current vertex. Thus the certain positional relationship between W and WPred relative to a specific ancestor, the current vertex, is used to indirectly break the positional tie between MIN(W, WPred) and the DFS child of MIN(W, WPred) whose subtree contains MAX(W, WPred). The ancestorChild is the DFS child of the current vertex whose DFS subtree contains W and WPred, and it is recorded here in order to optimize the post-processing calculation of vertex positions. ********************************************************************/ int _BreakTie(DrawPlanarContext *context, int BicompRoot, int W, int WPrevLink) { graphP theEmbedding = context->theGraph; /* First we get the predecessor of W. */ int WPredNextLink = 1 ^ WPrevLink, WPred = _GetNextExternalFaceVertex(theEmbedding, W, &WPredNextLink); _gp_LogLine("\ngraphDrawPlanar.c/::_BreakTie() start"); _gp_LogLine(_gp_MakeLogStr4("_BreakTie(BicompRoot=%d, W=%d, W_in=%d) WPred=%d", BicompRoot, W, WPrevLink, WPred)); /* Ties happen only within a bicomp (i.e. between two non-root vertices) */ if (gp_IsVirtualVertex(theEmbedding, W) || gp_IsVirtualVertex(theEmbedding, WPred)) { _gp_LogLine("graphDrawPlanar.c/_BreakTie() end\n"); return OK; } /* The two vertices are either tied or not; having one tied and the other not is an error */ if (context->VI[W].tie[WPrevLink] != context->VI[WPred].tie[WPredNextLink]) return NOTOK; /* If there is a tie, it can now be resolved. */ if (gp_IsVertex(theEmbedding, context->VI[W].tie[WPrevLink])) { int DFSChild = context->VI[W].tie[WPrevLink]; /* Set the two ancestor variables that contextualize putting W 'between' or 'beyond' its parent relative to what. */ context->VI[DFSChild].ancestorChild = gp_GetDFSChildFromBicompRoot(theEmbedding, BicompRoot); context->VI[DFSChild].ancestor = gp_GetVertexFromBicompRoot(theEmbedding, BicompRoot); _gp_LogLine(_gp_MakeLogStr4("V[child=%d]=.ancestorChild = %d, V[child=%d]=.ancestor = %d", DFSChild, context->VI[DFSChild].ancestorChild, DFSChild, context->VI[DFSChild].ancestor)); /* If W is the ancestor of WPred, then the DFSChild subtree contains WPred, and so must go between W and some ancestor. */ if (W < WPred) { context->VI[DFSChild].drawingFlag = DRAWINGFLAG_BETWEEN; _gp_LogLine(_gp_MakeLogStr3("Child=%d is 'between' ancestorChild=%d and ancestor=%d", DFSChild, context->VI[DFSChild].ancestorChild, context->VI[DFSChild].ancestor)); } /* If W is the descendant, so we achieve the effect of putting WPred between DFSChild and ancestor by putting the DFSChild 'beyond' WPred. */ else { context->VI[DFSChild].drawingFlag = DRAWINGFLAG_BEYOND; _gp_LogLine(_gp_MakeLogStr3("Child=%d is 'beyond' ancestorChild=%d relative to ancestor=%d", DFSChild, context->VI[DFSChild].ancestorChild, context->VI[DFSChild].ancestor)); } /* The tie is resolved so clear the flags*/ context->VI[W].tie[WPrevLink] = NIL; context->VI[WPred].tie[WPredNextLink] = NIL; } _gp_LogLine("graphDrawPlanar.c/_BreakTie() end\n"); return OK; } /******************************************************************** _RenderToString() Draws the previously calculated visibility representation in a string of size (M+1)*2N + 1 characters, which should be deallocated with free(). Returns NULL on failure, or the string containing the visibility representation otherwise. The string can be printed using %s, ********************************************************************/ char *_RenderToString(graphP theEmbedding) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { int N = gp_GetN(theEmbedding); int M = gp_GetM(theEmbedding); int zeroBasedVertexOffset = 0; int n, m, v, vRange, eRange, Mid, Pos; int e; char *visRep = (char *)malloc(sizeof(char) * ((M + 1) * 2 * N + 1)); char numBuffer[32]; if (visRep == NULL) return NULL; // If edges were deleted from the embedding, then the visibility representation is // no longer valid. (This is a necessary condition but not sufficient to guarantee // no embedding mutations, because adding edges uses the holes, and other APIs // allow edge changes). if (sp_NonEmpty(theEmbedding->edgeHoles)) { free(visRep); visRep = NULL; return NULL; } // If we are supposed to write 0-based output, then we have to set this variable to indicate // how much to subtract from each vertex index based on whether this library has been // compiled with 0-based or 1-based array indexing for the in-memory data structure (i.e., // compiled with USE_1BASEDARRAYS versus USE_0BASEDARRAYS). // The macro invoked is responsive to the compile-time difference. if (gp_GetGraphFlags(theEmbedding) & GRAPHFLAGS_ZEROBASEDIO) zeroBasedVertexOffset = gp_LowerBoundVertexStorage(theEmbedding); // Clear the space for (n = 0; n < N; n++) { for (m = 0; m < M; m++) { visRep[(2 * n) * (M + 1) + m] = ' '; visRep[(2 * n + 1) * (M + 1) + m] = ' '; } visRep[(2 * n) * (M + 1) + M] = '\n'; visRep[(2 * n + 1) * (M + 1) + M] = '\n'; } // Draw the vertices for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) { Pos = context->VI[v].pos; for (vRange = context->VI[v].start; vRange <= context->VI[v].end; vRange++) visRep[(2 * Pos) * (M + 1) + vRange] = '-'; // Draw vertex label Mid = (context->VI[v].start + context->VI[v].end) / 2; sprintf(numBuffer, "%d", v - zeroBasedVertexOffset); if ((unsigned)(context->VI[v].end - context->VI[v].start + 1) >= strlen(numBuffer)) { memcpy((char *)visRep + (2 * Pos) * (M + 1) + Mid, (char *)numBuffer, strlen(numBuffer)); } // If the vertex width is less than the label width, then fail gracefully else { if (strlen(numBuffer) == 2) visRep[(2 * Pos) * (M + 1) + Mid] = numBuffer[0]; else visRep[(2 * Pos) * (M + 1) + Mid] = '*'; visRep[(2 * Pos + 1) * (M + 1) + Mid] = numBuffer[strlen(numBuffer) - 1]; } } // Draw the edges for (e = gp_LowerBoundEdges(theEmbedding); e < gp_UpperBoundEdges(theEmbedding); e += 2) { Pos = context->E[e].pos; for (eRange = context->E[e].start; eRange < context->E[e].end; eRange++) { if (eRange > context->E[e].start) visRep[(2 * eRange) * (M + 1) + Pos] = '|'; visRep[(2 * eRange + 1) * (M + 1) + Pos] = '|'; } } // Null terminate string and return it visRep[(M + 1) * 2 * N] = '\0'; return visRep; } return NULL; } /******************************************************************** gp_DrawPlanar_RenderToString() Creates a rendition of the planar graph visibility representation as a string, and return it via the pRenditionString parameter. The caller can use free() to get rid of the returned string after use. Returns NOTOK for any error, OK otherwise. ********************************************************************/ int gp_DrawPlanar_RenderToString(graphP theEmbedding, char **pRenditionString) { if (theEmbedding != NULL && sp_IsEmpty(theEmbedding->edgeHoles) && pRenditionString != NULL) { *pRenditionString = _RenderToString(theEmbedding); return *pRenditionString != NULL ? OK : NOTOK; } return NOTOK; } /******************************************************************** gp_DrawPlanar_RenderToFile() Creates a rendition of the planar graph visibility representation as a string, then dumps the string to the file. theFileName - can be "stdout", "stderr" or a file system file name Returns NOTOK for any error, OK otherwise. ********************************************************************/ int gp_DrawPlanar_RenderToFile(graphP theEmbedding, char *theFileName) { int Result = OK; if (theEmbedding != NULL && sp_IsEmpty(theEmbedding->edgeHoles)) { FILE *outfile; char *theRendition; if (strcmp(theFileName, "stdout") == 0) outfile = stdout; else if (strcmp(theFileName, "stderr") == 0) outfile = stderr; else outfile = fopen(theFileName, WRITETEXT); if (outfile == NULL) return NOTOK; theRendition = _RenderToString(theEmbedding); Result = theRendition ? OK : NOTOK; if (theRendition != NULL) { fprintf(outfile, "%s", theRendition); free(theRendition); theRendition = NULL; } if (strcmp(theFileName, "stdout") == 0 || strcmp(theFileName, "stderr") == 0) fflush(outfile); else if (fclose(outfile) != 0) return NOTOK; return Result; } return NOTOK; } /******************************************************************** _CheckVisibilityRepresentationIntegrity() ********************************************************************/ int _CheckVisibilityRepresentationIntegrity(DrawPlanarContext *context) { graphP theEmbedding = context->theGraph; int v, e, eTwin, epos, eposIndex; if (sp_NonEmpty(context->theGraph->edgeHoles)) return NOTOK; _ClearAllVisitedFlagsInGraph(theEmbedding); /* Test whether the vertex values make sense and whether the vertex positions are unique. */ for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) { if (gp_GetM(theEmbedding) > 0) { if (context->VI[v].pos < 0 || context->VI[v].pos >= gp_GetN(theEmbedding) || context->VI[v].start < 0 || context->VI[v].start > context->VI[v].end || context->VI[v].end >= gp_GetM(theEmbedding)) return NOTOK; } // Has the vertex position been used by a vertex before vertex v? if (gp_GetVisited(theEmbedding, context->VI[v].pos + gp_LowerBoundVertexStorage(theEmbedding))) return NOTOK; // Mark the vertex position as used by vertex v. // Note that this marking is made on some other vertex unrelated to v // We're just reusing the vertex visited array as cheap storage for a // detector of reusing vertex position integers. gp_SetVisited(theEmbedding, context->VI[v].pos + gp_LowerBoundVertexStorage(theEmbedding)); } /* Test whether the edge values make sense and whether the edge positions are unique */ for (e = gp_LowerBoundEdges(theEmbedding); e < gp_UpperBoundEdges(theEmbedding); e += 2) { /* Each edge has two index locations in the edge information array */ eTwin = gp_GetTwin(theEmbedding, e); if (context->E[e].pos != context->E[eTwin].pos || context->E[e].start != context->E[eTwin].start || context->E[e].end != context->E[eTwin].end || context->E[e].pos < 0 || context->E[e].pos >= gp_GetM(theEmbedding) || context->E[e].start < 0 || context->E[e].start > context->E[e].end || context->E[e].end >= gp_GetN(theEmbedding)) return NOTOK; /* Get the recorded horizontal position of that edge, a number between 0 and M-1 */ epos = context->E[e].pos; /* Convert that to an index in the graph structure so we can use the visited flags in the graph's edges to tell us whether the positions are being reused. */ eposIndex = (epos << 1) + gp_LowerBoundEdgeStorage(theEmbedding); eTwin = gp_GetTwin(theEmbedding, eposIndex); if (gp_GetEdgeVisited(theEmbedding, eposIndex) || gp_GetEdgeVisited(theEmbedding, eTwin)) return NOTOK; gp_SetEdgeVisited(theEmbedding, eposIndex); gp_SetEdgeVisited(theEmbedding, eTwin); } /* Test whether any edge intersects any vertex position for a vertex that is not an endpoint of the edge. */ for (e = gp_LowerBoundEdges(theEmbedding); e < gp_UpperBoundEdges(theEmbedding); e += 2) { eTwin = gp_GetTwin(theEmbedding, e); for (v = gp_LowerBoundVertices(theEmbedding); v < gp_UpperBoundVertices(theEmbedding); ++v) { /* If the vertex is an endpoint of the edge, then... */ if (gp_GetNeighbor(theEmbedding, e) == v || gp_GetNeighbor(theEmbedding, eTwin) == v) { /* The vertical position of the vertex must be at the top or bottom of the edge, */ if (context->E[e].start != context->VI[v].pos && context->E[e].end != context->VI[v].pos) return NOTOK; /* The horizontal edge position must be in the range of the vertex */ if (context->E[e].pos < context->VI[v].start || context->E[e].pos > context->VI[v].end) return NOTOK; } /* If the vertex is not an endpoint of the edge... */ else // if (gp_GetNeighbor(theEmbedding, e) != v && gp_GetNeighbor(theEmbedding, eTwin) != v) { /* If the vertical position of the vertex is in the vertical range of the edge ... */ if (context->E[e].start <= context->VI[v].pos && context->E[e].end >= context->VI[v].pos) { /* And if the horizontal position of the edge is in the horizontal range of the vertex, then return an error. */ if (context->VI[v].start <= context->E[e].pos && context->VI[v].end >= context->E[e].pos) return NOTOK; } } } } /* All tests passed */ return OK; } /******************************************************************** gp_DrawPlanar_GetVertexPosition() Gets the vertical position of the horizontal bar that represents vertex v in the visibility representation. Returns the vertex's vertical position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetVertexPosition(graphP theEmbedding, int v) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->VI == NULL || v < gp_LowerBoundVertices(theEmbedding) || v >= gp_UpperBoundVertices(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->VI[v].pos; } /******************************************************************** gp_DrawPlanar_GetVertexStart() Gets the horizontal start position of the horizontal bar representing vertex v in the visibility representation. Returns the vertex's horizontal start position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetVertexStart(graphP theEmbedding, int v) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->VI == NULL || v < gp_LowerBoundVertices(theEmbedding) || v >= gp_UpperBoundVertices(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->VI[v].start; } /******************************************************************** gp_DrawPlanar_GetVertexEnd() Gets the horizontal end position of the horizontal bar representing vertex v in the visibility representation. Returns the vertex's horizontal end position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetVertexEnd(graphP theEmbedding, int v) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->VI == NULL || v < gp_LowerBoundVertices(theEmbedding) || v >= gp_UpperBoundVertices(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->VI[v].end; } /******************************************************************** gp_DrawPlanar_GetEdgePosition() Gets the horizontal position of the vertical bar that represents edge e in the visibility representation. Returns the edge's horizontal position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetEdgePosition(graphP theEmbedding, int e) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->E == NULL || e < gp_LowerBoundEdges(theEmbedding) || e >= gp_UpperBoundEdges(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->E[e].pos; } /******************************************************************** gp_DrawPlanar_GetEdgeStart() Gets the vertical start position of the vertical bar representing edge e in the visibility representation. Returns the edge's vertical start position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetEdgeStart(graphP theEmbedding, int e) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->E == NULL || e < gp_LowerBoundEdges(theEmbedding) || e >= gp_UpperBoundEdges(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->E[e].start; } /******************************************************************** gp_DrawPlanar_GetEdgeEnd() Gets the vertical end position of the vertical bar representing edge e in the visibility representation. Returns the edge's vertical end position or -1 on error ********************************************************************/ int gp_DrawPlanar_GetEdgeEnd(graphP theEmbedding, int e) { DrawPlanarContext *context = NULL; gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *)&context); if (theEmbedding == NULL || context == NULL || context->E == NULL || e < gp_LowerBoundEdges(theEmbedding) || e >= gp_UpperBoundEdges(theEmbedding)) { gp_ErrorMessage("Invalid parameters"); return -1; } return context->E[e].end; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphDrawPlanar.h000066400000000000000000000017171521450711600317200ustar00rootroot00000000000000#ifndef GRAPH_DRAWPLANAR_H #define GRAPH_DRAWPLANAR_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphPlanarity.h" #ifdef __cplusplus extern "C" { #endif #define DRAWPLANAR_NAME "DrawPlanar" int gp_ExtendWith_DrawPlanar(graphP theGraph); int gp_Detach_DrawPlanar(graphP theGraph); int gp_DrawPlanar_RenderToFile(graphP theEmbedding, char *theFileName); int gp_DrawPlanar_RenderToString(graphP theEmbedding, char **pRenditionString); int gp_DrawPlanar_GetVertexPosition(graphP theEmbedding, int v); int gp_DrawPlanar_GetVertexStart(graphP theEmbedding, int v); int gp_DrawPlanar_GetVertexEnd(graphP theEmbedding, int v); int gp_DrawPlanar_GetEdgePosition(graphP theEmbedding, int e); int gp_DrawPlanar_GetEdgeStart(graphP theEmbedding, int e); int gp_DrawPlanar_GetEdgeEnd(graphP theEmbedding, int e); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphDrawPlanar.private.h000066400000000000000000000066571521450711600334010ustar00rootroot00000000000000#ifndef GRAPH_DRAWPLANAR_PRIVATE_H #define GRAPH_DRAWPLANAR_PRIVATE_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../planarityRelated/graphPlanarity.private.h" #ifdef __cplusplus extern "C" { #endif // Additional equipment for each edge /* pos, start, end: used to store a visibility representation, or horvert diagram of a planar graph. For edges, horizontal position, vertical range */ typedef struct { int pos, start, end; } DrawPlanar_EdgeRec; typedef DrawPlanar_EdgeRec *DrawPlanar_EdgeRecP; // Additional equipment for each vertex /* pos, start, end: used to store a visibility representation, or horvert diagram, of a planar graph. For vertices, vertical position, horizontal range drawingFlag, ancestor, ancestorChild: used to collect information needed to help 'draw' a visibility representation. During planar embedding, a vertex is determined to be between its DFS parent and a given ancestor (the vertex being processed) or beyond the parent relative to the ancestor. In post processing, the relative orientation of the parent and ancestor are determined, then the notion of between/beyond resolves to above/below or below/above depending on whether the ancestor is above or below, respectively, the parent. The ancestorChild are used to help r esolve this latter question. tie[2] stores information along the external face during embedding that is pertinent to helping break ties in the decisions about vertical vertex positioning. When vertices are first merged together into a bicomp, we cannot always decide right away which vertices will be above or below others. But as we traverse the external face removing inactive vertices, these positional ties can be resolved. */ typedef struct { int pos, start, end; int drawingFlag, ancestor, ancestorChild; int tie[2]; } DrawPlanar_VertexInfo; typedef DrawPlanar_VertexInfo *DrawPlanar_VertexInfoP; #define DRAWINGFLAG_BEYOND 0 #define DRAWINGFLAG_TIE 1 #define DRAWINGFLAG_BETWEEN 2 #define DRAWINGFLAG_BELOW 3 #define DRAWINGFLAG_ABOVE 4 typedef struct { // Helps distinguish initialize from re-initialize int initialized; // The graph that this context augments graphP theGraph; // Parallel array for additional edge level equipment DrawPlanar_EdgeRecP E; // Parallel array for additional vertex level equipment DrawPlanar_VertexInfoP VI; // Overloaded function pointers graphFunctionTableStruct functions; } DrawPlanarContext; extern int DRAWPLANAR_ID; int gp_GetDrawPlanarExtensionIdentifier(void); #ifdef __cplusplus } #endif #endif graphDrawPlanar_Extensions.c000066400000000000000000000676721521450711600340670ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphDrawPlanar.h" #include "graphDrawPlanar.private.h" // Need to save and restore a graph flag related to IO #include "../io/graphIO.h" #include extern void _ClearVertexVisitedFlags(graphP theGraph, int); extern void _CollectDrawingData(DrawPlanarContext *context, int RootVertex, int W, int WPrevLink); extern int _BreakTie(DrawPlanarContext *context, int BicompRoot, int W, int WPrevLink); extern int _ComputeVisibilityRepresentation(DrawPlanarContext *context); extern int _CheckVisibilityRepresentationIntegrity(DrawPlanarContext *context); /* Forward declarations of local functions */ void _DrawPlanar_ClearStructures(DrawPlanarContext *context); int _DrawPlanar_CreateStructures(DrawPlanarContext *context); int _DrawPlanar_InitStructures(DrawPlanarContext *context); void _DrawPlanar_InitEdgeRec(DrawPlanarContext *context, int v); void _DrawPlanar_InitVertexInfo(DrawPlanarContext *context, int v); /* Forward declarations of overloading functions */ int _DrawPlanar_MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink); int _DrawPlanar_HandleInactiveVertex(graphP theGraph, int BicompRoot, int *pW, int *pWPrevLink); int _DrawPlanar_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); int _DrawPlanar_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); int _DrawPlanar_CheckObstructionIntegrity(graphP theGraph, graphP origGraph); int _DrawPlanar_EnsureVertexCapacity(graphP theGraph, int N); void _DrawPlanar_ResetGraphStorage(graphP theGraph); int _DrawPlanar_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity); int _DrawPlanar_SortVertices(graphP theGraph); int _DrawPlanar_ReadPostprocess(graphP theGraph, char *extraData); int _DrawPlanar_WritePostprocess(graphP theGraph, char **pExtraData); /* Forward declarations of functions used by the extension system */ void *_DrawPlanar_DupContext(void *pContext, void *theGraph); int _DrawPlanar_CopyData(void *dstContext, void *srcContext); void _DrawPlanar_FreeContext(void *); /**************************************************************************** * DRAWPLANAR_ID - the variable used to hold the integer identifier for this * extension, enabling this feature's extension context to be distinguished * from other features' extension contexts that may be attached to a graph. ****************************************************************************/ int DRAWPLANAR_ID = 0; /**************************************************************************** gp_ExtendWith_DrawPlanar() This function adjusts the graph data structure to attach the planar graph drawing feature. To activate this feature during gp_Embed(), use EMBEDFLAGS_DRAWPLANAR. This method may be called immediately after gp_New() in the case of invoking gp_Read(). For generating graphs, gp_EnsureVertexCapacity() can be invoked before or after this enabling method. This method detects if the vertex capacity has already been set, and if so, it will also create the additional data structures specific to planar graph drawing. This makes it possible to invoke gp_New() and gp_EnsureVertexCapacity() together, and then attach this feature only if it is requested at run-time. Returns OK for success, NOTOK for failure. ****************************************************************************/ int gp_ExtendWith_DrawPlanar(graphP theGraph) { DrawPlanarContext *context = NULL; if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the drawing feature has already been attached to the graph, // then there is no need to attach it again gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { return OK; } // Ensure theGraph is a Planarity Graph if (gp_ExtendWith_Planarity(theGraph) != OK) return NOTOK; // Allocate a new extension context context = (DrawPlanarContext *)malloc(sizeof(DrawPlanarContext)); if (context == NULL) { return NOTOK; } // First, tell the context that it is not initialized context->initialized = 0; // Save a pointer to theGraph in the context context->theGraph = theGraph; // Put the overload functions into the context function table. // gp_AddExtension will overload the graph's functions with these, and // return the base function pointers in the context function table memset(&context->functions, 0, sizeof(graphFunctionTableStruct)); context->functions.fpMergeBicomps = _DrawPlanar_MergeBicomps; context->functions.fpHandleInactiveVertex = _DrawPlanar_HandleInactiveVertex; context->functions.fpEmbedPostprocess = _DrawPlanar_EmbedPostprocess; context->functions.fpCheckEmbeddingIntegrity = _DrawPlanar_CheckEmbeddingIntegrity; context->functions.fpCheckObstructionIntegrity = _DrawPlanar_CheckObstructionIntegrity; context->functions.fpEnsureVertexCapacity = _DrawPlanar_EnsureVertexCapacity; context->functions.fpResetGraphStorage = _DrawPlanar_ResetGraphStorage; context->functions.fpEnsureEdgeCapacity = _DrawPlanar_EnsureEdgeCapacity; context->functions.fpSortVertices = _DrawPlanar_SortVertices; context->functions.fpReadPostprocess = _DrawPlanar_ReadPostprocess; context->functions.fpWritePostprocess = _DrawPlanar_WritePostprocess; _DrawPlanar_ClearStructures(context); // Store the Draw context, including the data structure and the // function pointers, as an extension of the graph if (gp_AddExtension(theGraph, &DRAWPLANAR_ID, (void *)context, _DrawPlanar_DupContext, _DrawPlanar_CopyData, _DrawPlanar_FreeContext, &context->functions) != OK) { _DrawPlanar_FreeContext(context); return NOTOK; } // Create the Draw-specific structures if the size of the graph is known // Attach functions are typically invoked after gp_New(), but if a graph // extension must be attached before gp_Read(), then the attachment // also happens before gp_EnsureVertexCapacity(), which means N==0. // However, a feature can be attached after gp_EnsureVertexCapacity(), // in which case there is extra work to do when N > 0. if (gp_GetN(theGraph) > 0) { if (_DrawPlanar_CreateStructures(context) != OK || _DrawPlanar_InitStructures(context) != OK) { _DrawPlanar_FreeContext(context); return NOTOK; } } return OK; } /******************************************************************** gp_Detach_DrawPlanar() ********************************************************************/ int gp_Detach_DrawPlanar(graphP theGraph) { return gp_RemoveExtension(theGraph, DRAWPLANAR_ID); } /******************************************************************** gp_GetDrawPlanarExtensionIdentifier() A private function that returns the DRAWPLANAR_ID. ********************************************************************/ int gp_GetDrawPlanarExtensionIdentifier(void) { return DRAWPLANAR_ID; } /******************************************************************** _DrawPlanar_ClearStructures() ********************************************************************/ void _DrawPlanar_ClearStructures(DrawPlanarContext *context) { if (!context->initialized) { // Before initialization, the pointers are stray, not NULL // Once NULL or allocated, free() or LCFree() can do the job context->E = NULL; context->VI = NULL; context->initialized = 1; } else { if (context->E != NULL) { free(context->E); context->E = NULL; } if (context->VI != NULL) { free(context->VI); context->VI = NULL; } } } /******************************************************************** _DrawPlanar_CreateStructures() Create uninitialized structures for the vertex and edge levels, and initialized structures for the graph level ********************************************************************/ int _DrawPlanar_CreateStructures(DrawPlanarContext *context) { graphP theGraph = context->theGraph; int VIsize = gp_UpperBoundVertices(theGraph); int Esize = gp_UpperBoundEdgeStorage(theGraph); if (gp_GetN(theGraph) <= 0) return NOTOK; if ((context->E = (DrawPlanar_EdgeRecP)malloc(Esize * sizeof(DrawPlanar_EdgeRec))) == NULL || (context->VI = (DrawPlanar_VertexInfoP)malloc(VIsize * sizeof(DrawPlanar_VertexInfo))) == NULL) { return NOTOK; } return OK; } /******************************************************************** _DrawPlanar_InitStructures() Intended to be called when N>0. Initializes vertex and edge levels only. Graph level is already initialized in _CreateStructures() ********************************************************************/ int _DrawPlanar_InitStructures(DrawPlanarContext *context) { #ifdef USE_1BASEDARRAYS memset(context->VI, NIL_CHAR, gp_UpperBoundVertices(context->theGraph) * sizeof(DrawPlanar_VertexInfo)); #else graphP theGraph = context->theGraph; if (gp_GetN(theGraph) <= 0) return NOTOK; for (int v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) _DrawPlanar_InitVertexInfo(context, v); #endif memset(context->E, 0, gp_UpperBoundEdgeStorage(context->theGraph) * sizeof(DrawPlanar_EdgeRec)); return OK; } /******************************************************************** _DrawPlanar_DupContext() ********************************************************************/ void *_DrawPlanar_DupContext(void *pContext, void *theGraph) { DrawPlanarContext *context = (DrawPlanarContext *)pContext; DrawPlanarContext *newContext = (DrawPlanarContext *)malloc(sizeof(DrawPlanarContext)); if (newContext != NULL) { int VIsize = gp_UpperBoundVertices((graphP)theGraph); int Esize = gp_UpperBoundEdgeStorage((graphP)theGraph); *newContext = *context; newContext->theGraph = (graphP)theGraph; newContext->initialized = 0; _DrawPlanar_ClearStructures(newContext); if (((graphP)theGraph)->N > 0) { if (_DrawPlanar_CreateStructures(newContext) != OK) { _DrawPlanar_FreeContext(newContext); return NULL; } // Initialize custom data structures by copying memcpy(newContext->E, context->E, Esize * sizeof(DrawPlanar_EdgeRec)); memcpy(newContext->VI, context->VI, VIsize * sizeof(DrawPlanar_VertexInfo)); } } return newContext; } /******************************************************************** _DrawPlanar_CopyData() ********************************************************************/ int _DrawPlanar_CopyData(void *dstContext, void *srcContext) { DrawPlanarContext *dstDrawPlanarContext = (DrawPlanarContext *)dstContext; DrawPlanarContext *srcDrawPlanarContext = (DrawPlanarContext *)srcContext; int dstEdgeStorage, srcEdgeStorage; if (dstContext == NULL) return NOTOK; // If the srcContext is NULL, then the caller wants the data // structures in the dstContext to be reset/reinitialized if (srcContext == NULL) return _DrawPlanar_InitStructures(dstDrawPlanarContext); // ELSE: If there is also a srcContext, then we copy data from it dstEdgeStorage = gp_UpperBoundEdgeStorage(dstDrawPlanarContext->theGraph); srcEdgeStorage = gp_UpperBoundEdgeStorage(srcDrawPlanarContext->theGraph); // The caller (ultimately gp_CopyGraph()) is responsible for making sure that the // destination graph has enough edge capacity to receive the source graph content if (dstEdgeStorage < srcEdgeStorage) return NOTOK; // If the destination graph has more edge capacity, then we make sure that the // extra edge capacity is reinitialized if (dstEdgeStorage > srcEdgeStorage) { memset(dstDrawPlanarContext->E, NIL_CHAR, gp_UpperBoundEdgeStorage(dstDrawPlanarContext->theGraph) * sizeof(DrawPlanar_EdgeRec)); } memcpy(dstDrawPlanarContext->E, srcDrawPlanarContext->E, gp_UpperBoundEdgeStorage(dstDrawPlanarContext->theGraph) * sizeof(DrawPlanar_EdgeRec)); memcpy(dstDrawPlanarContext->VI, srcDrawPlanarContext->VI, gp_UpperBoundVertices(dstDrawPlanarContext->theGraph) * sizeof(DrawPlanar_VertexInfo)); return OK; } /******************************************************************** _DrawPlanar_FreeContext() ********************************************************************/ void _DrawPlanar_FreeContext(void *pContext) { DrawPlanarContext *context = (DrawPlanarContext *)pContext; _DrawPlanar_ClearStructures(context); free(pContext); } /******************************************************************** ********************************************************************/ int _DrawPlanar_EnsureVertexCapacity(graphP theGraph, int N) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context == NULL) { return NOTOK; } theGraph->N = N; theGraph->NV = N; if (theGraph->edgeCapacity == 0) theGraph->edgeCapacity = DEFAULT_EDGE_CAPACITY_FACTOR * N; if (_DrawPlanar_CreateStructures(context) != OK || _DrawPlanar_InitStructures(context) != OK) return NOTOK; context->functions.fpEnsureVertexCapacity(theGraph, N); return OK; } /******************************************************************** ********************************************************************/ void _DrawPlanar_ResetGraphStorage(graphP theGraph) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { // Reset the graph storage in base class(es) context->functions.fpResetGraphStorage(theGraph); // Do the reset that is specific to this module _DrawPlanar_InitStructures(context); } } /******************************************************************** _DrawPlanar_EnsureEdgeCapacity() ********************************************************************/ int _DrawPlanar_EnsureEdgeCapacity(graphP theGraph, int requiredEdgeCapacity) { DrawPlanarContext *context = NULL; DrawPlanar_EdgeRecP oldE = NULL, newE = NULL; int oldEsize = gp_UpperBoundEdgeStorage(theGraph), newEsize = 0; // If the requirement is already satisfied, then no work to do if (gp_GetEdgeCapacity(theGraph) >= requiredEdgeCapacity) return OK; // Get the graph's extension context so we can work on it gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context == NULL) return NOTOK; // Call the superclass function to make sure lower levels of parallel // edge arrays can successfully meet the new capacity requirement if (context->functions.fpEnsureEdgeCapacity(theGraph, requiredEdgeCapacity) != OK) return NOTOK; // Save the current E so it can be freed once we replace it oldE = context->E; // The superclass EnsureEdgeCapacity method succeeded, so the graph's // new edge capacity is already set, which means we the upper bound of // the graph's edge storage gives the new parallel array size we need. newEsize = gp_UpperBoundEdgeStorage(theGraph); // We must successfully allocate the new parallel edge array newE = (DrawPlanar_EdgeRecP)malloc(newEsize * sizeof(DrawPlanar_EdgeRec)); if (newE == NULL) return NOTOK; // Clear all new edge records memset(newE, NIL_CHAR, newEsize * sizeof(DrawPlanar_EdgeRec)); // Copy the old edge records to the new edge records memcpy(newE, oldE, oldEsize * sizeof(DrawPlanar_EdgeRec)); // Set the new edge array into the context and free the old one context->E = newE; free(oldE); return OK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_SortVertices(graphP theGraph) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { // If this is a planarity-based algorithm to which graph drawing has been attached, // and if the embedding process has already been completed if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_DRAWPLANAR) { int v, vIndex; DrawPlanar_VertexInfo temp; // Relabel the context data members that indicate vertices for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (gp_IsVertex(theGraph, context->VI[v].ancestor)) { context->VI[v].ancestor = gp_GetIndex(theGraph, context->VI[v].ancestor); context->VI[v].ancestorChild = gp_GetIndex(theGraph, context->VI[v].ancestorChild); } } // "Sort" the extra vertex info associated with each vertex so that it is rearranged according // to the index values of the vertices. This could be done very easily with an extra array in // which, for each v, newVI[index of v] = VI[v]. However, this loop avoids memory allocation // by performing the operation (almost) in-place, except for the pre-existing visitation flags. _ClearVertexVisitedFlags(theGraph, FALSE); for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { // If the correct data has already been placed into position v // by prior steps, then skip to the next vertex if (gp_GetVisited(theGraph, v)) continue; // At the beginning of processing position v, the data in position v // corresponds to data that belongs at the index of v. vIndex = gp_GetIndex(theGraph, v); // Iterate on position v until it receives the correct data while (!gp_GetVisited(theGraph, v)) { // Place the data at position v into its proper location at position // vIndex, and move vIndex's data into position v. temp = context->VI[v]; context->VI[v] = context->VI[vIndex]; context->VI[vIndex] = temp; // The data at position vIndex is now marked as being correct. gp_SetVisited(theGraph, vIndex); // The data now in position v is the data from position vIndex, // whose index we now take as the new vIndex vIndex = gp_GetIndex(theGraph, vIndex); } } } if (context->functions.fpSortVertices(theGraph) != OK) return NOTOK; return OK; } return NOTOK; } /******************************************************************** Returns OK for a successful merge, NOTOK on an internal failure, or NONEMBEDDABLE if the merge is blocked ********************************************************************/ int _DrawPlanar_MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_DRAWPLANAR) { _CollectDrawingData(context, RootVertex, W, WPrevLink); } return context->functions.fpMergeBicomps(theGraph, v, RootVertex, W, WPrevLink); } return NOTOK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_HandleInactiveVertex(graphP theGraph, int BicompRoot, int *pW, int *pWPrevLink) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { int RetVal = context->functions.fpHandleInactiveVertex(theGraph, BicompRoot, pW, pWPrevLink); if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_DRAWPLANAR) { if (_BreakTie(context, BicompRoot, *pW, *pWPrevLink) != OK) return NOTOK; } return RetVal; } return NOTOK; } /******************************************************************** ********************************************************************/ void _DrawPlanar_InitEdgeRec(DrawPlanarContext *context, int e) { context->E[e].pos = 0; context->E[e].start = 0; context->E[e].end = 0; } /******************************************************************** ********************************************************************/ void _DrawPlanar_InitVertexInfo(DrawPlanarContext *context, int v) { context->VI[v].pos = 0; context->VI[v].start = 0; context->VI[v].end = 0; context->VI[v].drawingFlag = DRAWINGFLAG_BEYOND; context->VI[v].ancestorChild = NIL; context->VI[v].ancestor = NIL; context->VI[v].tie[0] = NIL; context->VI[v].tie[1] = NIL; } /******************************************************************** ********************************************************************/ int _DrawPlanar_EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { int RetVal = context->functions.fpEmbedPostprocess(theGraph, v, edgeEmbeddingResult); if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_DRAWPLANAR) { if (RetVal == OK) { RetVal = _ComputeVisibilityRepresentation(context); } } return RetVal; } return NOTOK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { if (context->functions.fpCheckEmbeddingIntegrity(theGraph, origGraph) != OK) return NOTOK; return _CheckVisibilityRepresentationIntegrity(context); } return NOTOK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_CheckObstructionIntegrity(graphP theGraph, graphP origGraph) { return OK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_ReadPostprocess(graphP theGraph, char *extraData) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { if (context->functions.fpReadPostprocess(theGraph, extraData) != OK) return NOTOK; else if (extraData != NULL && strlen(extraData) > 0) { int v, tempInt, e; char line[64], tempChar; sprintf(line, "<%s>", DRAWPLANAR_NAME); // Find the start of the data for this feature extraData = strstr(extraData, line); if (extraData == NULL) return NOTOK; // Advance past the start tag extraData = extraData + strlen(line) + 1; // Read the N lines of vertex information for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { sscanf(extraData, " %d%c %d %d %d", &tempInt, &tempChar, &context->VI[v].pos, &context->VI[v].start, &context->VI[v].end); extraData = strchr(extraData, '\n') + 1; } // Read the lines that contain edge information for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); ++e) { sscanf(extraData, " %d%c %d %d %d", &tempInt, &tempChar, &context->E[e].pos, &context->E[e].start, &context->E[e].end); extraData = strchr(extraData, '\n') + 1; } } return OK; } return NOTOK; } /******************************************************************** ********************************************************************/ int _DrawPlanar_WritePostprocess(graphP theGraph, char **pExtraData) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) { (*pExtraData) = NULL; if (context->functions.fpWritePostprocess(theGraph, pExtraData) != OK) return NOTOK; else if ((*pExtraData) != NULL) { // NOTE: We currently do not support stacking WritePostprocess calls // from multiple extensions; it wouldn't be hard, we just don't ;) free((*pExtraData)); (*pExtraData) = NULL; return NOTOK; } else { int v, e; char line[64]; int maxLineSize = 64, extraDataPos = 0; char *extraData = (char *)calloc((1 + gp_GetN(theGraph) + 2 * gp_GetM(theGraph) + 1) * maxLineSize, sizeof(char)); int zeroBasedVertexOffset = 0; int zeroBasedEdgeOffset = 0; if (extraData == NULL) return NOTOK; // If we are supposed to write 0-based output, then we have to set these two variables to indicate // how much to subtract from each vertex and edge index based on whether this library has been // compiled with 0-based or 1-based array indexing for the in-memory data structure (i.e., compiled // with USE_1BASEDARRAYS USE_0BASEDARRAYS). The macros invoked are responsive to the difference. if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO) { zeroBasedVertexOffset = gp_LowerBoundVertexStorage(theGraph); zeroBasedEdgeOffset = gp_LowerBoundEdgeStorage(theGraph); } // Bit of an unlikely case, but for safety, a bigger maxLineSize // and line array size are needed to handle very large graphs if (gp_GetN(theGraph) > 2000000000) { free(extraData); extraData = NULL; return NOTOK; } sprintf(line, "<%s>\n", DRAWPLANAR_NAME); strcpy(extraData + extraDataPos, line); extraDataPos += (int)strlen(line); for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { sprintf(line, "%d: %d %d %d\n", v - zeroBasedVertexOffset, context->VI[v].pos, context->VI[v].start, context->VI[v].end); strcpy(extraData + extraDataPos, line); extraDataPos += (int)strlen(line); } for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); e++) { if (gp_EdgeInUse(theGraph, e)) { sprintf(line, "%d: %d %d %d\n", e - zeroBasedEdgeOffset, context->E[e].pos, context->E[e].start, context->E[e].end); strcpy(extraData + extraDataPos, line); extraDataPos += (int)strlen(line); } } sprintf(line, "\n", DRAWPLANAR_NAME); strcpy(extraData + extraDataPos, line); extraDataPos += (int)strlen(line); *pExtraData = extraData; } return OK; } return NOTOK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphEmbed.c000066400000000000000000002027351521450711600306770ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include // This source file implements the main graph planarity/outerplanarity method, gp_Embed() #include "../planarityRelated/graphPlanarity.h" #include "../planarityRelated/graphPlanarity.private.h" #include "../planarityRelated/graphOuterplanarity.h" #include "../planarityRelated/graphOuterplanarity.private.h" // Includes needed by _gp_EmbedFlagsValid() #include "graphDrawPlanar.private.h" #include "../homeomorphSearch/graphK23Search.private.h" #include "../homeomorphSearch/graphK33Search.private.h" #include "../homeomorphSearch/graphK4Search.private.h" // For LOGGING-related declarations #include "../lowLevelUtils/apiutils.private.h" /* Imported functions */ extern void _ClearVertexVisitedFlags(graphP theGraph, int); extern int _IsolateKuratowskiSubgraph(graphP theGraph, int v, int R); extern int _IsolateOuterplanarObstruction(graphP theGraph, int v, int R); extern void _InitVertexRec(graphP theGraph, int v); extern int _gp_FindEdge(graphP theGraph, int u, int v); /* Private functions (some are exported to system only) */ int _gp_EmbedFlagsValid(graphP theGraph, int embedFlags); int _EmbeddingInitialize(graphP theGraph); void _EmbedBackEdgeToDescendant(graphP theGraph, int RootSide, int RootVertex, int W, int WPrevLink); void _InvertVertex(graphP theGraph, int V); void _MergeVertex(graphP theGraph, int W, int WPrevLink, int R); int _MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink); void _WalkUp(graphP theGraph, int v, int e); int _WalkDown(graphP theGraph, int v, int RootVertex); int _HandleInactiveVertex(graphP theGraph, int BicompRoot, int *pW, int *pWPrevLink); int _HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R); void _AdvanceFwdEdgeList(graphP theGraph, int v, int child, int nextChild); int _EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult); int _OrientVerticesInEmbedding(graphP theGraph); int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns); int _OrientExternalFacePath(graphP theGraph, int u, int v, int w, int x); int _JoinBicomps(graphP theGraph); /******************************************************************** gp_Embed() Either a planar embedding is created in theGraph, or a Kuratowski subgraph is isolated. Either way, theGraph remains sorted by DFI since that is the most common desired result. The original vertex numbers are available in the 'index' members of the vertex records. Moreover, gp_SortVertices() can be invoked to put the vertices in the order of the input graph, at which point the 'index' members of the vertex records will contain the vertex DFIs. return OK if the embedding was successfully created or no subgraph homeomorphic to a topological obstruction was found. NOTOK on failure (e.g., NULL graph, gp_Embed already called, failure to attach algorithm extension) NONEMBEDDABLE if the embedding couldn't be created due to the existence of a subgraph homeomorphic to a topological obstruction. For core planarity, OK is returned when theGraph contains a planar embedding of the input graph, and NONEMBEDDABLE is returned when a subgraph homeomorphic to K5 or K3,3 has been isolated in theGraph. Extension modules can overload functions used by gp_Embed to achieve alternate algorithms. In those cases, the return results are similar. For example, a K3,3 search algorithm would return NONEMBEDDABLE if it finds the K3,3 obstruction, and OK if the graph is planar or only contains K5 homeomorphs. Similarly, an outerplanarity module can return OK for an outerplanar embedding or NONEMBEDDABLE when a subgraph homeomorphic to K2,3 or K4 has been isolated. The algorithm extension for gp_Embed() is encoded in the embedFlags, and the details of the return value can be found in the extension module that defines the embedding flag. ********************************************************************/ int gp_Embed(graphP theGraph, unsigned embedFlags) { int v, e, c; int RetVal = OK; // Basic safety checks if (theGraph == NULL || embedFlags == 0 || gp_GetEmbedFlags(theGraph) != 0) return NOTOK; // Preprocessing if (!_gp_EmbedFlagsValid(theGraph, embedFlags)) { // For historical reasons, the graph will be automatically extended with // Planarity or Outerplanarity if not already done. if (embedFlags == EMBEDFLAGS_PLANAR) { if (gp_ExtendWith_Planarity(theGraph) != OK) return NOTOK; } else if (embedFlags == EMBEDFLAGS_OUTERPLANAR) { if (gp_ExtendWith_Outerplanarity(theGraph) != OK) return NOTOK; } // For other Graph subclasses, the caller must have invoked their // ExtendWith method prior to calling gp_Embed() else return NOTOK; } theGraph->embedFlags = embedFlags; // Initialize embedding data structures and allow extension algorithms // that overload the function to postprocess the DFS if (theGraph->functions->fpEmbeddingInitialize(theGraph) != OK) return NOTOK; // In reverse DFI order, embed the back edges from each vertex to its DFS descendants. for (v = gp_UpperBoundVertices(theGraph) - 1; v >= gp_LowerBoundVertices(theGraph); --v) { RetVal = OK; // Walkup calls establish Pertinence in Step v // Do the Walkup for each cycle edge from v to a DFS descendant W. e = gp_GetVertexFwdEdgeList(theGraph, v); while (gp_IsEdge(theGraph, e)) { theGraph->functions->fpWalkUp(theGraph, v, e); e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, v)) e = NIL; } gp_SetVertexPertinentRootsList(theGraph, v, NIL); // Work systematically through the DFS children of vertex v, using Walkdown // to add the back edges from v to its descendants in each of the DFS subtrees c = gp_GetVertexSortedDFSChildList(theGraph, v); while (gp_IsVertex(theGraph, c)) { if (gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, c))) { RetVal = theGraph->functions->fpWalkDown(theGraph, v, gp_GetBicompRootFromDFSChild(theGraph, c)); // If Walkdown returns OK, then it is OK to proceed with edge addition. // Otherwise, if Walkdown returns NONEMBEDDABLE then we stop edge addition. if (RetVal != OK) break; } c = gp_GetVertexNextDFSChild(theGraph, v, c); } // If the Walkdown determined that the graph is NONEMBEDDABLE, // then the guiding embedder loop can be stopped now. if (RetVal != OK) break; } // Postprocessing to orient the embedding and merge any remaining separated bicomps. // Some extension algorithms may overload this function, e.g. to do nothing if they // have no need of an embedding. return theGraph->functions->fpEmbedPostprocess(theGraph, v, RetVal); } /******************************************************************** _gp_EmbedFlagsValid() Returns TRUE the theGraph has been extended to a subclass that supports the value in embedFlags and if embedFlags has a value that is supportable by extensions available in the graphLib. Returns FALSE otherwise. NOTE: Returns TRUE/FALSE rather than OK/NOTOK so the caller can decide if it is an error or if they want to try to take corrective actions on theGraph. ********************************************************************/ int _gp_EmbedFlagsValid(graphP theGraph, int embedFlags) { // Currently, planar and outerplanar graph embedding and obstruction // isolation do not require an explicit extension. if (embedFlags == EMBEDFLAGS_PLANAR) { if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_PLANARITY) return TRUE; } else if (embedFlags == EMBEDFLAGS_OUTERPLANAR) { if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY) return TRUE; } // For other algorithms that are supported by explicit extensions, we // ensure they are attached (the attach methods exit early if it has // already been done). else if (embedFlags == EMBEDFLAGS_DRAWPLANAR) { DrawPlanarContext *context = NULL; gp_FindExtension(theGraph, DRAWPLANAR_ID, (void *)&context); if (context != NULL) return TRUE; } else if (embedFlags == EMBEDFLAGS_SEARCHFORK23) { K23SearchContext *context = NULL; gp_FindExtension(theGraph, K23SEARCH_ID, (void *)&context); if (context != NULL) return TRUE; } else if (embedFlags == EMBEDFLAGS_SEARCHFORK33) { K33SearchContext *context = NULL; gp_FindExtension(theGraph, K33SEARCH_ID, (void *)&context); if (context != NULL) return TRUE; } else if (embedFlags == EMBEDFLAGS_SEARCHFORK4) { K4SearchContext *context = NULL; gp_FindExtension(theGraph, K4SEARCH_ID, (void *)&context); if (context != NULL) return TRUE; } // The embedFlags are not valid if they indicate an algorithm for // which there is no graph extension, or even if they indicate // multiple supported algorithm extensions at the same time. return FALSE; } /******************************************************************** _EmbeddingInitialize() This method performs the following tasks: (1) Assign depth first index (DFI) and DFS parentvalues to vertices (2) Assign DFS edge types (3) Create a sortedDFSChildList for each vertex, sorted by child DFI (4) Create a sorted fwdEdgeList for each vertex, sorted by descendant DFI (5) Assign leastAncestor values to vertices (6) Sort the vertices by their DFIs (7) Initialize for pertinence and future pertinence management (8) Embed each tree edge as a singleton biconnected component The first five of these are performed in a single-pass DFS of theGraph. Afterward, the vertices are sorted by their DFIs, the lowpoint values are assigned and then the DFS tree edges stored in virtual vertices during the DFS are used to create the DFS tree embedding. ********************************************************************/ int _EmbeddingInitialize(graphP theGraph) { stackP theStack; int DFI, v, R, uparent, u, uneighbor, e, f, eTwin, ePrev, eNext; int leastValue, child; _gp_LogLine("graphEmbed.c/_EmbeddingInitialize() start\n"); theStack = theGraph->theStack; // At most we push 2 integers per edge from a vertex to each *unvisited* neighbor // plus one extra (NIL, NIL) at the beginning to represent arriving at a DFS tree // root. We ensure that theGraph's stack has this capacity and, if so, we clear // the stack for use in the depth-first search (DFS). if (sp_GetCapacity(theStack) < 2 * 2 * gp_GetM(theGraph) + 2) return NOTOK; sp_ClearStack(theStack); // We clear the visited flags of vertices because they are used to determine // which vertices have already been visited as the DFS traverses theGraph. _ClearVertexVisitedFlags(theGraph, FALSE); // This outer loop processes each connected component of a disconnected graph // No need to compare v < N since DFI will reach N when inner loop processes the // last connected component in the graph for (DFI = v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { // Skip numbered vertices to cause the outerloop to find the // next DFS tree root in a disconnected graph if (gp_IsVertex(theGraph, gp_GetVertexParent(theGraph, v))) continue; // DFS a connected component sp_Push2(theStack, NIL, NIL); while (sp_NonEmpty(theStack)) { sp_Pop2(theStack, uparent, e); // For vertex uparent and edge e, obtain the opposing endpoint u of e // If uparent is NIL, then e is also NIL and we have encountered the // false edge to the DFS tree root as pushed above. u = gp_IsNotVertex(theGraph, uparent) ? v : gp_GetNeighbor(theGraph, e); // We popped an edge to an unvisited vertex, so it is either a DFS tree edge // or a false edge to the DFS tree root (u). if (!gp_GetVisited(theGraph, u)) { _gp_LogLine(_gp_MakeLogStr3("v=%d, DFI=%d, parent=%d", u, DFI, uparent)); // (1) Set the DFI and DFS parent gp_SetVisited(theGraph, u); gp_SetIndex(theGraph, u, DFI++); gp_SetVertexParent(theGraph, u, uparent); if (gp_IsEdge(theGraph, e)) { // (2) Set the edge type values for tree edges gp_SetEdgeType(theGraph, e, EDGE_TYPE_CHILD); gp_SetEdgeType(theGraph, gp_GetTwin(theGraph, e), EDGE_TYPE_PARENT); // (3) Record u in the sortedDFSChildList of uparent gp_SetVertexSortedDFSChildList(theGraph, uparent, gp_AppendDFSChild(theGraph, uparent, gp_GetIndex(theGraph, u))); // (8) Record e as the first and last edges of the virtual vertex R, // a root copy of uparent uniquely associated with child u R = gp_GetBicompRootFromDFSChild(theGraph, gp_GetIndex(theGraph, u)); gp_SetFirstEdge(theGraph, R, e); gp_SetLastEdge(theGraph, R, e); } // (5) Initialize the least ancestor value gp_SetVertexLeastAncestor(theGraph, u, gp_GetIndex(theGraph, u)); // Push edges to all unvisited neighbors. These will be either // tree edges to children or forward edge records to descendants // Edges that are not pushed are either marked as back edges or as // a tree edge if it leads back to the immediate DFS parent. e = gp_GetFirstEdge(theGraph, u); while (gp_IsEdge(theGraph, e)) { if (!gp_GetVisited(theGraph, gp_GetNeighbor(theGraph, e))) { sp_Push2(theStack, u, e); } else if (gp_GetEdgeType(theGraph, e) != EDGE_TYPE_PARENT) { // (2) Set the edge type values for back edges gp_SetEdgeType(theGraph, e, EDGE_TYPE_BACK); eTwin = gp_GetTwin(theGraph, e); gp_SetEdgeType(theGraph, eTwin, EDGE_TYPE_FORWARD); // (4) Move the twin of back edge record e to the sorted FwdEdgeList of the ancestor uneighbor = gp_GetNeighbor(theGraph, e); ePrev = gp_GetPrevEdge(theGraph, eTwin); eNext = gp_GetNextEdge(theGraph, eTwin); if (gp_IsEdge(theGraph, ePrev)) gp_SetNextEdge(theGraph, ePrev, eNext); else gp_SetFirstEdge(theGraph, uneighbor, eNext); if (gp_IsEdge(theGraph, eNext)) gp_SetPrevEdge(theGraph, eNext, ePrev); else gp_SetLastEdge(theGraph, uneighbor, ePrev); if (gp_IsEdge(theGraph, f = gp_GetVertexFwdEdgeList(theGraph, uneighbor))) { ePrev = gp_GetPrevEdge(theGraph, f); gp_SetPrevEdge(theGraph, eTwin, ePrev); gp_SetNextEdge(theGraph, eTwin, f); gp_SetPrevEdge(theGraph, f, eTwin); gp_SetNextEdge(theGraph, ePrev, eTwin); } else { gp_SetVertexFwdEdgeList(theGraph, uneighbor, eTwin); gp_SetPrevEdge(theGraph, eTwin, eTwin); gp_SetNextEdge(theGraph, eTwin, eTwin); } // (5) Update the leastAncestor value for the vertex u uneighbor = gp_GetIndex(theGraph, uneighbor); if (uneighbor < gp_GetVertexLeastAncestor(theGraph, u)) gp_SetVertexLeastAncestor(theGraph, u, uneighbor); } e = gp_GetNextEdge(theGraph, e); } } } } // The graph is now DFS numbered theGraph->graphFlags |= GRAPHFLAGS_DFSNUMBERED; // (6) Now that all vertices have a DFI in the index member, we can sort vertices if (gp_SortVertices(theGraph) != OK) return NOTOK; // Loop through the vertices to... for (v = gp_UpperBoundVertices(theGraph) - 1; v >= gp_LowerBoundVertices(theGraph); --v) { // (7) Initialize for pertinence management gp_SetVertexVisitedInfo(theGraph, v, gp_GetN(theGraph)); // (7) Initialize for future pertinence management child = gp_GetVertexSortedDFSChildList(theGraph, v); gp_SetVertexFuturePertinentChild(theGraph, v, child); leastValue = gp_GetVertexLeastAncestor(theGraph, v); while (gp_IsVertex(theGraph, child)) { if (leastValue > gp_GetVertexLowpoint(theGraph, child)) leastValue = gp_GetVertexLowpoint(theGraph, child); child = gp_GetVertexNextDFSChild(theGraph, v, child); } gp_SetVertexLowpoint(theGraph, v, leastValue); // (8) Create the DFS tree embedding using the child edge records stored in the virtual vertices // For each vertex v that is a DFS child, the virtual vertex R that will represent v's parent // in the singleton bicomp with v is at location v + N in the vertex array. if (gp_IsDFSTreeRoot(theGraph, v)) { gp_SetFirstEdge(theGraph, v, NIL); gp_SetLastEdge(theGraph, v, NIL); } else { R = gp_GetBicompRootFromDFSChild(theGraph, v); // Make the child edge the only edge in the virtual vertex adjacency list e = gp_GetFirstEdge(theGraph, R); gp_SetPrevEdge(theGraph, e, NIL); gp_SetNextEdge(theGraph, e, NIL); // Reset the twin's neighbor value to point to the virtual vertex eTwin = gp_GetTwin(theGraph, e); gp_SetNeighbor(theGraph, eTwin, R); // Make its twin the only edge in the child's adjacency list gp_SetFirstEdge(theGraph, v, eTwin); gp_SetLastEdge(theGraph, v, eTwin); gp_SetPrevEdge(theGraph, eTwin, NIL); gp_SetNextEdge(theGraph, eTwin, NIL); // Set up the external face management data structure to match gp_SetExtFaceVertex(theGraph, R, 0, v); gp_SetExtFaceVertex(theGraph, R, 1, v); gp_SetExtFaceVertex(theGraph, v, 0, R); gp_SetExtFaceVertex(theGraph, v, 1, R); } } _gp_LogLine("graphEmbed.c/_EmbeddingInitialize() end\n"); return OK; } /******************************************************************** _EmbedBackEdgeToDescendant() The Walkdown has found a descendant vertex W to which it can attach a back edge up to the root of the bicomp it is processing. The RootSide and WPrevLink indicate the parts of the external face that will be replaced at each endpoint of the back edge. ********************************************************************/ void _EmbedBackEdgeToDescendant(graphP theGraph, int RootSide, int RootVertex, int W, int WPrevLink) { int fwdEdgeRec, backEdgeRec, parentCopy; /* We get the two edge records of the back edge (v, W) to embed. The Walkup recorded in W's adjacentTo the index of the forward edge record that goes from the root's parent copy, v, to the descendant W. */ fwdEdgeRec = gp_GetVertexPertinentEdge(theGraph, W); backEdgeRec = gp_GetTwin(theGraph, fwdEdgeRec); /* The forward edge record is removed from the fwdEdgeList of the root's parent copy. */ parentCopy = gp_GetVertexFromBicompRoot(theGraph, RootVertex); _gp_LogLine(_gp_MakeLogStr5("graphEmbed.c/_EmbedBackEdgeToDescendant() V=%d, R=%d, R_out=%d, W=%d, W_in=%d", parentCopy, RootVertex, RootSide, W, WPrevLink)); if (gp_GetVertexFwdEdgeList(theGraph, parentCopy) == fwdEdgeRec) { gp_SetVertexFwdEdgeList(theGraph, parentCopy, gp_GetNextEdge(theGraph, fwdEdgeRec)); if (gp_GetVertexFwdEdgeList(theGraph, parentCopy) == fwdEdgeRec) gp_SetVertexFwdEdgeList(theGraph, parentCopy, NIL); } gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, fwdEdgeRec), gp_GetNextEdge(theGraph, fwdEdgeRec)); gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, fwdEdgeRec), gp_GetPrevEdge(theGraph, fwdEdgeRec)); // The forward edge record is added to the adjacency list of the RootVertex. // Note that we're guaranteed that the RootVertex adjacency list is non-empty, // so tests for NIL are not needed gp_SetAdjacentEdge(theGraph, fwdEdgeRec, 1 ^ RootSide, NIL); gp_SetAdjacentEdge(theGraph, fwdEdgeRec, RootSide, gp_GetEdgeByLink(theGraph, RootVertex, RootSide)); gp_SetAdjacentEdge(theGraph, gp_GetEdgeByLink(theGraph, RootVertex, RootSide), 1 ^ RootSide, fwdEdgeRec); gp_SetEdgeByLink(theGraph, RootVertex, RootSide, fwdEdgeRec); // The back edge record is added to the adjacency list of W. // The adjacency list of W is also guaranteed non-empty gp_SetAdjacentEdge(theGraph, backEdgeRec, 1 ^ WPrevLink, NIL); gp_SetAdjacentEdge(theGraph, backEdgeRec, WPrevLink, gp_GetEdgeByLink(theGraph, W, WPrevLink)); gp_SetAdjacentEdge(theGraph, gp_GetEdgeByLink(theGraph, W, WPrevLink), 1 ^ WPrevLink, backEdgeRec); gp_SetEdgeByLink(theGraph, W, WPrevLink, backEdgeRec); gp_SetNeighbor(theGraph, backEdgeRec, RootVertex); /* Link the two endpoint vertices together on the external face */ gp_SetExtFaceVertex(theGraph, RootVertex, RootSide, W); gp_SetExtFaceVertex(theGraph, W, WPrevLink, RootVertex); } /******************************************************************** _InvertVertex() This function flips the orientation of a single vertex such that instead of using link successors to go clockwise (or counterclockwise) around a vertex's adjacency list, link predecessors would be used. ********************************************************************/ void _InvertVertex(graphP theGraph, int W) { int e, temp; _gp_LogLine(_gp_MakeLogStr1("graphEmbed.c/_InvertVertex() W=%d", W)); // Swap the links in all of the edge records of the adjacency list e = gp_GetFirstEdge(theGraph, W); while (gp_IsEdge(theGraph, e)) { temp = gp_GetNextEdge(theGraph, e); gp_SetNextEdge(theGraph, e, gp_GetPrevEdge(theGraph, e)); gp_SetPrevEdge(theGraph, e, temp); e = temp; } // Swap the first/last edge record indicators in the vertex temp = gp_GetFirstEdge(theGraph, W); gp_SetFirstEdge(theGraph, W, gp_GetLastEdge(theGraph, W)); gp_SetLastEdge(theGraph, W, temp); // Swap the first/last external face indicators in the vertex temp = gp_GetExtFaceVertex(theGraph, W, 0); gp_SetExtFaceVertex(theGraph, W, 0, gp_GetExtFaceVertex(theGraph, W, 1)); gp_SetExtFaceVertex(theGraph, W, 1, temp); } /******************************************************************** _MergeVertex() The merge step joins the vertex W to the root R of a child bicompRoot, which is a root copy of W appearing in the region N to 2N-1. Actually, the first step of this is to redirect all of the edges leading into R so that they indicate W as the neighbor instead of R. For each edge node pointing to R, we set the 'v' field to W. Once an edge is redirected from a root copy R to a parent copy W, the edge is never redirected again, so we associate the cost of the redirection as constant per edge, which maintains linear time performance. After this is done, a regular circular list union occurs. The only consideration is that WPrevLink is used to indicate the two edge records e_w and e_r that will become consecutive in the resulting adjacency list of W. We set e_w to W's link [WPrevLink] and e_r to R's link [1^WPrevLink] so that e_w and e_r indicate W and R with opposing links, which become free to be cross-linked. Finally, the edge record e_ext, set equal to R's link [WPrevLink], is the edge that, with e_r, held R to the external face. Now, e_ext will be the new link [WPrevLink] edge record for W. If e_w and e_r become part of a proper face, then e_ext and W's link [1^WPrevLink] are the two edges that attach W to the external face cycle of the containing bicomp. ********************************************************************/ void _MergeVertex(graphP theGraph, int W, int WPrevLink, int R) { int e, eTwin, e_w, e_r, e_ext; _gp_LogLine(_gp_MakeLogStr4("graphEmbed.c/_MergeVertex() W=%d, W_in=%d, R=%d, R_out=%d", W, WPrevLink, R, 1 ^ WPrevLink)); // All edge records leading _into_ R _from_ its neighbors must be changed // to say that they are leading into W. e = gp_GetFirstEdge(theGraph, R); while (gp_IsEdge(theGraph, e)) { eTwin = gp_GetTwin(theGraph, e); gp_GetNeighbor(theGraph, eTwin) = W; e = gp_GetNextEdge(theGraph, e); } // Obtain the edge records that will be involved in the adjacency list union e_w = gp_GetEdgeByLink(theGraph, W, WPrevLink); e_r = gp_GetEdgeByLink(theGraph, R, 1 ^ WPrevLink); e_ext = gp_GetEdgeByLink(theGraph, R, WPrevLink); // If W has any edges, then join its adjacency list with that of R if (gp_IsEdge(theGraph, e_w)) { // The WPrevLink edge of W is e_w, so the 1^WPrevLink edge in e_w leads back to W. // Now it must lead to e_r. Likewise, e_r needs to lead back to e_w with the // opposing link, which is WPrevLink // Note that the adjacency lists of W and R are guaranteed non-empty, which is // why these linkages can be made without NIL tests. gp_SetAdjacentEdge(theGraph, e_w, 1 ^ WPrevLink, e_r); gp_SetAdjacentEdge(theGraph, e_r, WPrevLink, e_w); // Cross-link W's WPrevLink edge record and the 1^WPrevLink edge record in e_ext gp_SetEdgeByLink(theGraph, W, WPrevLink, e_ext); gp_SetAdjacentEdge(theGraph, e_ext, 1 ^ WPrevLink, NIL); } // Otherwise, W just receives R's adjacency list. This can happen, for example, on // a DFS tree root vertex during JoinBicomps() else { // Cross-link W's 1^WPrevLink edge record and the WPrevLink edge record in e_r gp_SetEdgeByLink(theGraph, W, 1 ^ WPrevLink, e_r); gp_SetAdjacentEdge(theGraph, e_r, WPrevLink, NIL); // Cross-link W's WPrevLink edge record and the 1^WPrevLink edge record in e_ext gp_SetEdgeByLink(theGraph, W, WPrevLink, e_ext); gp_SetAdjacentEdge(theGraph, e_ext, 1 ^ WPrevLink, NIL); } // Erase the entries in R, which is a root copy that is no longer needed _InitVertexRec(theGraph, R); } /******************************************************************** _MergeBicomps() Merges all biconnected components at the cut vertices indicated by entries on the stack. theGraph contains the stack of bicomp roots and cut vertices to merge v, RootVertex, W and WPrevLink are not used in this routine, but are used by overload extensions Returns OK, but an extension function may return a value other than OK in order to cause Walkdown to terminate immediately. ********************************************************************/ int _MergeBicomps(graphP theGraph, int v, int RootVertex, int W, int WPrevLink) { int R, Rout, Z, ZPrevLink, e, extFaceVertex; while (sp_NonEmpty(theGraph->theStack)) { sp_Pop2(theGraph->theStack, R, Rout); sp_Pop2(theGraph->theStack, Z, ZPrevLink); /* The external faces of the bicomps containing R and Z will form two corners at Z. One corner will become part of the internal face formed by adding the new back edge. The other corner will be the new external face corner at Z. We first want to update the links at Z to reflect this. */ extFaceVertex = gp_GetExtFaceVertex(theGraph, R, 1 ^ Rout); gp_SetExtFaceVertex(theGraph, Z, ZPrevLink, extFaceVertex); if (gp_GetExtFaceVertex(theGraph, extFaceVertex, 0) == gp_GetExtFaceVertex(theGraph, extFaceVertex, 1)) // When (R, extFaceVertex) form a singleton bicomp, they have the same orientation, so the Rout link in extFaceVertex // is the one that has to now point back to Z gp_SetExtFaceVertex(theGraph, extFaceVertex, Rout, Z); else // When R and extFaceVertex are not alone in the bicomp, then they may not have the same orientation, so the // ext face link that should point to Z is whichever one pointed to R, since R is a root copy of Z. gp_SetExtFaceVertex(theGraph, extFaceVertex, gp_GetExtFaceVertex(theGraph, extFaceVertex, 0) == R ? 0 : 1, Z); /* If the path used to enter Z is opposed to the path used to exit R, then we have to flip the bicomp rooted at R, which we signify by inverting R then setting the sign on its DFS child edge to indicate that its descendants must be flipped later */ if (ZPrevLink == Rout) { Rout = 1 ^ ZPrevLink; if (gp_GetFirstEdge(theGraph, R) != gp_GetLastEdge(theGraph, R)) _InvertVertex(theGraph, R); e = gp_GetFirstEdge(theGraph, R); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { // The core planarity algorithm could simply "set" the inverted flag // because a bicomp root edge cannot be already inverted in the core // planarity algorithm at the time of this merge. // However, extensions may perform edge reductions on tree edges, resulting // in an inversion sign being promoted to the root edge of a bicomp before // it gets merged. So, xor is used to reverse the inversion flag on the // root edge if the bicomp root must be inverted before it is merged. gp_XorEdgeFlagInverted(theGraph, e); break; } e = gp_GetNextEdge(theGraph, e); } } // R is no longer pertinent to Z since we are about to merge R into Z, so we delete R // from Z's pertinent bicomp list (Walkdown gets R from the head of the list). gp_DeleteVertexPertinentRoot(theGraph, Z, R); // If the merge will place the current future pertinence child into the same bicomp as Z, // then we advance to the next child (or NIL) because future pertinence is if (gp_GetDFSChildFromBicompRoot(theGraph, R) == gp_GetVertexFuturePertinentChild(theGraph, Z)) { gp_SetVertexFuturePertinentChild(theGraph, Z, gp_GetVertexNextDFSChild(theGraph, Z, gp_GetVertexFuturePertinentChild(theGraph, Z))); } // Now we push R into Z, eliminating R theGraph->functions->fpMergeVertex(theGraph, Z, ZPrevLink, R); } return OK; } /******************************************************************** _WalkUp() v is the vertex currently being embedded e is the forward edge record of the "back" edge between v and a descendant W of v The Walkup establishes pertinence for step v. It marks W with e as a way of indicating it is pertinent because it should be made 'adjacent to' v by adding a back edge (v', W), which will occur when the Walkdown encounters W. The Walkup also determines the pertinent child bicomps that should be set up as a result of the need to embed edge (v, W). It does this by recording the pertinent child biconnected components of all cut vertices between W and the child of v that is an ancestor of W. Note that it stops the traversal if it finds a visited info value set to v, which indicates that a prior walkup call in step v has already done the work. This ensures work is not duplicated. A second technique used to maintain a total linear time bound for the whole planarity method is that of parallel external face traversal. This ensures that the cost of determining pertinence in step v is linearly commensurate with the length of the path that ultimately is removed from the external face. Zig and Zag are so named because one goes around one side of a bicomp and the other goes around the other side, yet we have as yet no notion of orientation for the bicomp. The edge record e from vertex v gestures to a descendant vertex W in some other bicomp. Zig and Zag start out at W. They go around alternate sides of the bicomp until its root is found. We then hop from the root copy to the parent copy of the vertex in order to record which bicomp we just came from and also to continue the walk-up at the parent copy as if it were the new W. We reiterate this process until the parent copy actually is v, at which point the Walkup is done. ********************************************************************/ void _WalkUp(graphP theGraph, int v, int e) { int W = gp_GetNeighbor(theGraph, e); int Zig = W, Zag = W, ZigPrevLink = 1, ZagPrevLink = 0; int nextZig, nextZag, R; // Start by marking W as being directly pertinent gp_SetVertexPertinentEdge(theGraph, W, e); // Zig and Zag are initialized at W, and we continue looping around // the external faces of bicomps up from W until we reach vertex v // (or until the visited info optimization breaks the loop) while (Zig != v) { // Obtain the next vertex in a first direction and determine if it is a bicomp root if (gp_IsVirtualVertex(theGraph, (nextZig = gp_GetExtFaceVertex(theGraph, Zig, 1 ^ ZigPrevLink)))) { // If the current vertex along the external face was visited in this step v, // then the bicomp root and its ancestor roots have already been added. if (gp_GetVertexVisitedInfo(theGraph, Zig) == v) break; // Store the bicomp root that was found R = nextZig; // Since the bicomp root was the next vertex on the path from Zig, determine the // vertex on the opposing path that enters the bicomp root. nextZag = gp_GetExtFaceVertex(theGraph, R, gp_GetExtFaceVertex(theGraph, R, 0) == Zig ? 1 : 0); // If the opposing vertex was already marked visited in this step, then a prior // Walkup already recorded as pertinent the bicomp root and its ancestor roots. if (gp_GetVertexVisitedInfo(theGraph, nextZag) == v) break; } // Obtain the next vertex in the parallel direction and perform the analogous logic else if (gp_IsVirtualVertex(theGraph, (nextZag = gp_GetExtFaceVertex(theGraph, Zag, 1 ^ ZagPrevLink)))) { if (gp_GetVertexVisitedInfo(theGraph, Zag) == v) break; R = nextZag; nextZig = gp_GetExtFaceVertex(theGraph, R, gp_GetExtFaceVertex(theGraph, R, 0) == Zag ? 1 : 0); if (gp_GetVertexVisitedInfo(theGraph, nextZig) == v) break; } // The bicomp root was not found in either direction. else { if (gp_GetVertexVisitedInfo(theGraph, Zig) == v) break; if (gp_GetVertexVisitedInfo(theGraph, Zag) == v) break; R = NIL; } // This Walkup has now finished with another vertex along each of the parallel // paths, so they are marked visited in step v so that future Walkups in this // step v can break if these vertices are encountered again. gp_SetVertexVisitedInfo(theGraph, Zig, v); gp_SetVertexVisitedInfo(theGraph, Zag, v); // If both directions found new non-root vertices, then proceed with parallel external face traversal if (gp_IsNotVirtualVertex(theGraph, R)) { ZigPrevLink = gp_GetExtFaceVertex(theGraph, nextZig, 0) == Zig ? 0 : 1; Zig = nextZig; ZagPrevLink = gp_GetExtFaceVertex(theGraph, nextZag, 0) == Zag ? 0 : 1; Zag = nextZag; } // The bicomp root was found and not previously recorded as pertinent, // so walk up to the parent bicomp and continue else { // Step up from the bicomp root vertex (virtual) to the parent copy of // the vertex (non-virtual; called parent copy because it is the one that // is in a bicomp with a virtual or non-virtual copy of its DFS parent) Zig = Zag = gp_GetVertexFromBicompRoot(theGraph, R); ZigPrevLink = 1; ZagPrevLink = 0; // Add the new bicomp root o the list of pertinent bicomp roots of the parent copy vertex. // The new root vertex is appended if future pertinent and prepended if only pertinent // so that, by virtue of storage, the Walkdown will process all pertinent bicomps that // are not future pertinent before any future pertinent bicomps. // NOTE: Unlike vertices, the activity status of a bicomp is computed solely using // the lowpoint of the DFS child in the bicomp's root edge, which indicates // whether the DFS child or any of its descendants connect by a back edge to // ancestors of v. If so, then the bicomp rooted at RootVertex must contain a // future pertinent vertex that must be kept on the external face. if (gp_GetVertexLowpoint(theGraph, gp_GetDFSChildFromBicompRoot(theGraph, R)) < v) gp_AppendVertexPertinentRoot(theGraph, Zig, R); else gp_PrependVertexPertinentRoot(theGraph, Zag, R); } } } /******************************************************************** _WalkDown() Consider a circular shape with small circles and squares along its perimeter. The small circle at the top is the root vertex of the bicomp. The other small circles represent active vertices, and the squares represent future pertinent vertices. The root vertex is a root copy of v, the vertex currently being processed. The Walkup previously marked all vertices adjacent to v by setting their pertinentEdge members with the forward edge records of the back edges to embed. Two Walkdown traversals are performed to visit all reachable vertices along each of the external face paths emanating from RootVertex (a root copy of vertex v) to embed back edges to descendants of vertex v that have their pertinentEdge members marked. During each Walkdown traversal, it is sometimes necessary to hop from a vertex to one of its child biconnected components in order to reach the desired vertices. In such cases, the biconnected components are merged such that adding the back edge forms a new proper face in the biconnected component rooted at RootVertex (which, again, is a root copy of v). The outer loop performs both walks, unless the first walk got all the way around to RootVertex (only happens when bicomp contains no external activity, such as when processing the last vertex), or when non-planarity is discovered (in a pertinent child bicomp such that the stack is non-empty). For the inner loop, each iteration visits a vertex W. If W is marked as requiring a back edge, then MergeBicomps is called to merge the biconnected components whose cut vertices have been collecting in merge stack. Then, the back edge (RootVertex, W) is added, and the pertinentEdge of W is cleared. Next, we check whether W has a pertinent child bicomp. If so, then we figure out which path down from the root of the child bicomp leads to the next vertex to be visited, and we push onto the stack information on the cut vertex and the paths used to enter into it and exit from it. Alternately, if W had no pertinent child bicomps, then we check to see if it is inactive. If so, we find the next vertex along the external face, then short-circuit its inactive predecessor (under certain conditions). Finally, if W is not inactive, but it has no pertinent child bicomps, then we already know its adjacentTo flag is clear so both criteria for internal activity also fail. Therefore, W must be a stopping vertex. A stopping vertex X is a future pertinent vertex that has no pertinent child bicomps and no unembedded back edge to the current vertex v. The inner loop of Walkdown stops walking when it reaches a stopping vertex X because if it were to proceed beyond X and embed a back edge, then X would be surrounded by the bounding cycle of the bicomp. This would clearly be incorrect because X has a path leading from it to an ancestor of v, which would have to cross the bounding cycle. Either Walkdown traversal can halt the Walkdown and return if a pertinent child biconnected component to which the traversal has descended is blocked, i.e. has stopping vertices on both paths emanating from the root. This indicates an obstruction to embedding. In core planarity it is evidence of a K_{3,3}, but some extension algorithms are able to clear the blockage and proceed with embedding. If both Walkdown traversals successfully completed, then the outer loop ends. Post-processing code tests whether the Walkdown embedded all the back edges from v to its descendants in the subtree rooted by c, a DFS child of v uniquely associated with the RootVertex. If not, then embedding was obstructed. In core planarity it is evidence of a K_{3,3} or K_5, but some extension algorithms are able to clear the blockage and proceed with embedding. Returns OK if all possible edges were embedded, NONEMBEDDABLE if less than all possible edges were embedded, NOTOK for an internal code failure ********************************************************************/ int _WalkDown(graphP theGraph, int v, int RootVertex) { int RetVal, W, WPrevLink, R, X, XPrevLink, Y, YPrevLink, RootSide, e; int RootEdgeChild = gp_GetDFSChildFromBicompRoot(theGraph, RootVertex); sp_ClearStack(theGraph->theStack); for (RootSide = 0; RootSide < 2; RootSide++) { W = gp_GetExtFaceVertex(theGraph, RootVertex, RootSide); // Determine the link used to enter W based on which side points back to RootVertex // Implicitly handled special case: In core planarity, the first Walkdown traversal // Will be on a singleton edge. In this case, RootVertex and W are *consistently* // oriented, and the RootSide is 0, so WPrevLink should be 1. This calculation is // written to implicitly produce that result. WPrevLink = gp_GetExtFaceVertex(theGraph, W, 1) == RootVertex ? 1 : 0; while (W != RootVertex) { // Detect unembedded back edge descendant endpoint W if (gp_IsEdge(theGraph, gp_GetVertexPertinentEdge(theGraph, W))) { // Merge any bicomps whose cut vertices were traversed to reach W, then add the // edge to W to form a new proper face in the embedding. if (sp_NonEmpty(theGraph->theStack)) { if ((RetVal = theGraph->functions->fpMergeBicomps(theGraph, v, RootVertex, W, WPrevLink)) != OK) return RetVal; } theGraph->functions->fpEmbedBackEdgeToDescendant(theGraph, RootSide, RootVertex, W, WPrevLink); // Clear W's pertinentEdge since the forward edge record it contained has been embedded gp_SetVertexPertinentEdge(theGraph, W, NIL); } // If W has a pertinent child bicomp, then we descend to the first one... // NOTE: Each pertinent root is stored as the DFS child with which it is // associated, so we test gp_IsVertex, not gp_IsVirtualVertex here. if (gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, W))) { // Push the vertex W and the direction of entry, then descend to a root copy R of W sp_Push2(theGraph->theStack, W, WPrevLink); R = gp_GetVertexFirstPertinentRoot(theGraph, W); // Get the next active vertices X and Y on the external face paths emanating from R X = gp_GetExtFaceVertex(theGraph, R, 0); XPrevLink = gp_GetExtFaceVertex(theGraph, X, 1) == R ? 1 : 0; Y = gp_GetExtFaceVertex(theGraph, R, 1); YPrevLink = gp_GetExtFaceVertex(theGraph, Y, 0) == R ? 0 : 1; // Now we implement the Walkdown's simple path selection rules! // Select a direction from the root to a pertinent vertex, // preferentially toward a vertex that is not future pertinent gp_UpdateVertexFuturePertinentChild(theGraph, X, v); gp_UpdateVertexFuturePertinentChild(theGraph, Y, v); if (PERTINENT(theGraph, X) && NOTFUTUREPERTINENT(theGraph, X, v)) { W = X; WPrevLink = XPrevLink; sp_Push2(theGraph->theStack, R, 0); } else if (PERTINENT(theGraph, Y) && NOTFUTUREPERTINENT(theGraph, Y, v)) { W = Y; WPrevLink = YPrevLink; sp_Push2(theGraph->theStack, R, 1); } else if (PERTINENT(theGraph, X)) { W = X; WPrevLink = XPrevLink; sp_Push2(theGraph->theStack, R, 0); } else if (PERTINENT(theGraph, Y)) { W = Y; WPrevLink = YPrevLink; sp_Push2(theGraph->theStack, R, 1); } else { // Both the X and Y sides of the descendant bicomp are blocked. // Let the application decide whether it can unblock the bicomp. // The core planarity/outerplanarity embedder simply isolates a // planarity/outerplanary obstruction and returns NONEMBEDDABLE if ((RetVal = theGraph->functions->fpHandleBlockedBicomp(theGraph, v, RootVertex, R)) != OK) return RetVal; // If an extension algorithm cleared the blockage, then we pop W and WPrevLink // back off the stack and let the Walkdown traversal try descending again sp_Pop2(theGraph->theStack, W, WPrevLink); } } else { // The vertex W is known to be non-pertinent, so if it is future pertinent // (or if the algorithm is based on outerplanarity), then the vertex is // a stopping vertex for the Walkdown traversal. gp_UpdateVertexFuturePertinentChild(theGraph, W, v); if (FUTUREPERTINENT(theGraph, W, v) || (gp_GetEmbedFlags(theGraph) & EMBEDFLAGS_OUTERPLANAR)) { // Create an external face short-circuit between RootVertex and the stopping vertex W // so that future steps do not walk down a long path of inactive vertices between them. // As a special case, we ensure that the external face is not reduced to just two // vertices, W and RootVertex, because it would then become a challenge to determine // whether W has the same orientation as RootVertex. // So, if the other side of RootVertex is already attached to W, then we simply push // W back one vertex so that the external face will have at least three vertices. if (gp_GetExtFaceVertex(theGraph, RootVertex, 1 ^ RootSide) == W) { X = W; W = gp_GetExtFaceVertex(theGraph, W, WPrevLink); WPrevLink = gp_GetExtFaceVertex(theGraph, W, 0) == X ? 1 : 0; } gp_SetExtFaceVertex(theGraph, RootVertex, RootSide, W); gp_SetExtFaceVertex(theGraph, W, WPrevLink, RootVertex); // Terminate the Walkdown traversal since it encountered the stopping vertex break; } // If the vertex is neither pertinent nor future pertinent, then it is inactive. // The default handler planarity handler simply skips inactive vertices by traversing // to the next vertex on the external face. // Once upon a time, false edges called short-circuit edges were added to eliminate // inactive vertices, but the extFace links above achieve the same result with less work. else { if (theGraph->functions->fpHandleInactiveVertex(theGraph, RootVertex, &W, &WPrevLink) != OK) return NOTOK; } } } } // Detect and handle the case in which Walkdown was blocked from embedding all the back edges from v // to descendants in the subtree of the child of v associated with the bicomp RootVertex. if (gp_IsEdge(theGraph, e = gp_GetVertexFwdEdgeList(theGraph, v)) && RootEdgeChild < gp_GetNeighbor(theGraph, e)) { int nextChild = gp_GetVertexNextDFSChild(theGraph, v, RootEdgeChild); // We finish detecting that the Walkdown was blocked from embedding all forward edge records into // the RootEdgeChild subtree if there the next child's DFI is greater than the descendant endpoint // of the next forward edge record, or if there is no next child. if (gp_IsNotVertex(theGraph, nextChild) || nextChild > gp_GetNeighbor(theGraph, e)) { // If an extension to core planarity indicates it is OK to proceed despite having detected // unembedded forward edges, then advance to the forward edges for the next child, if any if ((RetVal = theGraph->functions->fpHandleBlockedBicomp(theGraph, v, RootVertex, RootVertex)) == OK) _AdvanceFwdEdgeList(theGraph, v, RootEdgeChild, nextChild); return RetVal; } } return OK; } /******************************************************************** _HandleBlockedBicomp() A biconnected component has blocked the Walkdown from embedding back edges. Each external face path emanating from the root is blocked by a stopping vertex. The core planarity/outerplanarity algorithm handles the blockage by isolating an embedding obstruction (a subgraph homeomorphic to K_{3,3} or K_5 for planarity, or a subgraph homeomorphic to K_{2,3} or K_4 for outerplanarity). Then NONEMBEDDABLE is returned so that the WalkDown can terminate. Extension algorithms are able to clear some of the blockages, in which case OK is returned to indicate that the WalkDown can proceed. Returns OK to proceed with WalkDown at W, NONEMBEDDABLE to terminate WalkDown of Root Vertex NOTOK for internal error ********************************************************************/ int _HandleBlockedBicomp(graphP theGraph, int v, int RootVertex, int R) { int RetVal = NONEMBEDDABLE; if (R != RootVertex) sp_Push2(theGraph->theStack, R, 0); if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_PLANAR) { if (_IsolateKuratowskiSubgraph(theGraph, v, RootVertex) != OK) RetVal = NOTOK; } else if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_OUTERPLANAR) { if (_IsolateOuterplanarObstruction(theGraph, v, RootVertex) != OK) RetVal = NOTOK; } return RetVal; } /******************************************************************** _AdvanceFwdEdgeList() If an extension determines that it is OK to leave some forward edges unembedded, then we advance the forward edge list head pointer past the unembedded edges for the current child so that it points to the first forward edge for the next child, if any. There are two meanings of the phrase "if any". First, there may be no next child, in which case nextChild is NIL, and the forward edge list need not be advanced. If there is a next child, then the forward edge list head needs to be advanced to the first edge whose descendant endpoint is greater than the nextChild, if any. However, the tail end of the forward edge list may include unembedded forward edge records to a preceding sibling of the child vertex. So, we advance an edge pointer e until one of the following happens: 1) e gets all the way around to the head of the forward edge list 2) e finds an edge whose descendant endpoint is less than the child 3) e finds an edge whose descendant endpoint is greater than the next child In case 1, all the forward edges belong in the subtree of the child, so there is no need to change the forward edge list head. In case 2, there are no more forward edges to any following siblings of the child, only left-behind unembedded forward edges that we advanced past in previous calls to this method from Walkdowns of the preceding children of v. So the forward edge list head should be set to e so that it is set to the forward edge with the least numbered descendant endpoint. In case 3, the desired forward edge into the subtree of a following sibling of the child has been found, so again the forward edge list head should be set to e to indicate that edge. After all Walkdowns of the children of a vertex, the forward edge list will be NIL if all edges were embedded, or it will indicate the unembedded forward edge whose descendant endpoint has the least number. Cases 1 and 2 directly implement this in cases where a Walkdown for the given child fails to embed an edge, and case 3 indirectly finishes the job by making sure the forward edge list head has the right value at the beginning of a Walkdown for a particular child. If the Walkdown of that child succeeds at embedding all the forward edges into that child's subtree, then each embedding advances the forward edge list head. So, even if the Walkdown of the last pertinent child embeds all forward edges, then the Walkdown itself advances the head of the forward edge list to the first unembedded forward edge, or to NIL. ********************************************************************/ void _AdvanceFwdEdgeList(graphP theGraph, int v, int child, int nextChild) { int e = gp_GetVertexFwdEdgeList(theGraph, v); while (gp_IsEdge(theGraph, e)) { // 2) e finds an edge whose descendant endpoint is less than the child if (gp_GetNeighbor(theGraph, e) < child) { gp_SetVertexFwdEdgeList(theGraph, v, e); break; } // 3) e finds an edge whose descendant endpoint is greater than the next child else if (gp_IsVertex(theGraph, nextChild) && nextChild < gp_GetNeighbor(theGraph, e)) { gp_SetVertexFwdEdgeList(theGraph, v, e); break; } e = gp_GetNextEdge(theGraph, e); // 1) e gets all the way around to the head of the forward edge list if (e == gp_GetVertexFwdEdgeList(theGraph, v)) e = NIL; } } /******************************************************************** _HandleInactiveVertex() Although it is possible to short-circuit every inactive vertex from the external face, for efficiency the Walkdown traversal now just does a single short-circuit between the bicomp root and a stopping vertex. This is because the main thing that short-circuiting needs to optimize is the Walkdown's choice of direction after descending to the root of a pertinent biconnected component. So, the Walkdown just optimizes the external face of a biconnected component as it finishes processing it so it will be ready in future steps when it becomes pertinent. Hence, when traversing the face of a bicomp during the current step, we only need to skip an inactive vertex and traverse to the next vertex on the external face. ********************************************************************/ int _HandleInactiveVertex(graphP theGraph, int BicompRoot, int *pW, int *pWPrevLink) { int X = gp_GetExtFaceVertex(theGraph, *pW, 1 ^ *pWPrevLink); *pWPrevLink = gp_GetExtFaceVertex(theGraph, X, 0) == *pW ? 0 : 1; *pW = X; return OK; } /******************************************************************** _EmbedPostprocess() After the loop that embeds the cycle edges from each vertex to its DFS descendants, this method is invoked to postprocess the graph. If the graph is planar or outerplanar, then a consistent orientation is imposed on the vertices of the embedding, and any remaining separated biconnected components are joined together. If the graph is non-planar or non-outerplanar, then an obstruction to planarity or outerplanarity has already been isolated. Extensions may override this function to provide alternate behavior. @param theGraph - the graph ready for postprocessing @param v - the last vertex processed by the edge embedding loop @param edgeEmbeddingResult - OK if all edge embedding iterations returned OK NONEMBEDDABLE if an embedding iteration failed to embed all edges for a vertex @return NOTOK on internal failure NONEMBEDDABLE if a subgraph homeomorphic to a topological obstruction is isolated in the graph OK otherwise (e.g. if the graph contains an embedding) *****************************************************************/ int _EmbedPostprocess(graphP theGraph, int v, int edgeEmbeddingResult) { int RetVal = edgeEmbeddingResult; // If an embedding was found, then post-process the embedding structure give // a consistent orientation to all vertices then eliminate virtual vertices if (edgeEmbeddingResult == OK) { if (_OrientVerticesInEmbedding(theGraph) != OK || _JoinBicomps(theGraph) != OK) RetVal = NOTOK; } // If the graph is embedded (OK) or NONEMBEDDABLE, we pass the result back return RetVal; } /******************************************************************** _OrientVerticesInEmbedding() Each vertex will then have an orientation, either clockwise or counterclockwise. All vertices in each bicomp need to have the same orientation. This method clears the stack, and the stack is clear when it is finished. Returns OK on success, NOTOK on implementation failure. ********************************************************************/ int _OrientVerticesInEmbedding(graphP theGraph) { sp_ClearStack(theGraph->theStack); // For each vertex, obtain the associated bicomp root location and, // if it is still in use as a bicomp root, orient the vertices in the bicomp for (int R = gp_LowerBoundVirtualVertices(theGraph); R < gp_UpperBoundVirtualVertices(theGraph); ++R) { if (gp_VirtualVertexInUse(theGraph, R)) { if (_OrientVerticesInBicomp(theGraph, R, 0) != OK) return NOTOK; } } return OK; } /******************************************************************** _OrientVerticesInBicomp() As a result of the work done so far, the edges around each vertex have been put in order, but the orientation may be counterclockwise or clockwise for different vertices within the same bicomp. We need to reverse the orientations of those vertices that are not oriented the same way as the root of the bicomp. During embedding, a bicomp with root edge (v', c) may need to be flipped. We do this by inverting the root copy v' and implicitly inverting the orientation of the vertices in the subtree rooted by c by assigning -1 to the sign of the DFSCHILD edge record leading to c. We now use these signs to help propagate a consistent vertex orientation throughout all vertices that have been merged into the given bicomp. The bicomp root contains the orientation to be imposed on all parent copy vertices. We perform a standard depth first search to visit each vertex. A vertex must be inverted if the product of the edge signs along the tree edges between the bicomp root and the vertex is -1. Finally, the PreserveSigns flag, if set, performs the inversions but does not change any of the edge signs. This allows a second invocation of this function to restore the state of the bicomp as it was before the first call. This method uses the stack but preserves whatever may have been on it. In debug mode, it will return NOTOK if the stack overflows. This method pushes at most two integers per vertext in the bicomp. Returns OK on success, NOTOK on implementation failure. ********************************************************************/ int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns) { int W, e, invertedFlag; int stackBottom = sp_GetCurrentSize(theGraph->theStack); sp_Push2(theGraph->theStack, BicompRoot, 0); while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { /* Pop a vertex to orient */ sp_Pop2(theGraph->theStack, W, invertedFlag); /* Invert the vertex if the inverted flag is set */ if (invertedFlag) _InvertVertex(theGraph, W); /* Push the vertex's DFS children that are in the bicomp */ e = gp_GetFirstEdge(theGraph, W); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_CHILD) { sp_Push2(theGraph->theStack, gp_GetNeighbor(theGraph, e), invertedFlag ^ gp_GetEdgeFlagInverted(theGraph, e)); if (!PreserveSigns) gp_ClearEdgeFlagInverted(theGraph, e); } e = gp_GetNextEdge(theGraph, e); } } return OK; } /******************************************************************** _JoinBicomps() The embedding algorithm works by only joining bicomps once the result forms a larger bicomp. However, if the original graph was separable or disconnected, then the result of the embed function will be a graph that contains each bicomp as a distinct entity. This function merges the bicomps into one connected graph. ********************************************************************/ int _JoinBicomps(graphP theGraph) { for (int R = gp_LowerBoundVirtualVertices(theGraph); R < gp_UpperBoundVirtualVertices(theGraph); ++R) { // If the bicomp root is still active (i.e. an in-use virtual vertex) // then merge it with its parent copy vertex (non-virtual) if (gp_VirtualVertexInUse(theGraph, R)) _MergeVertex(theGraph, gp_GetVertexFromBicompRoot(theGraph, R), 0, R); } return OK; } /**************************************************************************** _OrientExternalFacePath() The vertices along the path (v ... w) are assumed to be degree two vertices in an external face path connecting u and x. This method imparts the orientation of u and x onto the vertices v ... w. The work done is on the order of the path length. Returns OK if the external face path was oriented, NOTOK on implementation error (i.e. if a condition arises providing the path is not on the external face). ****************************************************************************/ int _OrientExternalFacePath(graphP theGraph, int u, int v, int w, int x) { int e_u, e_v, e_ulink, e_vlink; // Get the edge record in u that indicates v; uses the "get twin" method // to ensure the cost is dominated by the degree of v (which is 2), not u // (which can be any degree). e_u = gp_GetTwin(theGraph, _gp_FindEdge(theGraph, v, u)); do { // Get the external face link in vertex u that indicates the // edge e_u which connects to the next vertex v in the path // As a sanity check, we determine whether e_u is an // external face edge, because there would be an internal // implementation error if not if (gp_GetFirstEdge(theGraph, u) == e_u) e_ulink = 0; else if (gp_GetLastEdge(theGraph, u) == e_u) e_ulink = 1; else return NOTOK; v = gp_GetNeighbor(theGraph, e_u); // Now get the external face link in vertex v that indicates the // edge e_v which connects back to the prior vertex u. e_v = gp_GetTwin(theGraph, e_u); if (gp_GetFirstEdge(theGraph, v) == e_v) e_vlink = 0; else if (gp_GetLastEdge(theGraph, v) == e_v) e_vlink = 1; else return NOTOK; // The vertices u and v are inversely oriented if they // use the same link to indicate the edge [e_u, e_v]. if (e_vlink == e_ulink) { _InvertVertex(theGraph, v); e_vlink = 1 ^ e_vlink; } // This update of the extFace short-circuit is polite but unnecessary. // This orientation only occurs once we know we can isolate a K_{3,3}, // at which point the extFace data structure is not used. gp_SetExtFaceVertex(theGraph, u, e_ulink, v); gp_SetExtFaceVertex(theGraph, v, e_vlink, u); u = v; e_u = gp_GetEdgeByLink(theGraph, v, 1 ^ e_vlink); } while (u != x); return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphIsolator.c000066400000000000000000000741221521450711600314540ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #define GRAPHISOLATOR_C // This source file implements subroutines of the main Planarity method #include "../planarityRelated/graphPlanarity.h" #include "../planarityRelated/graphPlanarity.private.h" /* Imported functions */ extern void _ClearAllVisitedFlagsInGraph(graphP); extern int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink); extern int _JoinBicomps(graphP theGraph); extern int _ChooseTypeOfNonplanarityMinor(graphP theGraph, int v, int R); /* Private function declarations (exported within system) */ int _IsolateKuratowskiSubgraph(graphP theGraph, int v, int R); int _FindUnembeddedEdgeToAncestor(graphP theGraph, int cutVertex, int *pAncestor, int *pDescendant); int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant); int _FindUnembeddedEdgeToSubtree(graphP theGraph, int ancestor, int SubtreeRoot, int *pDescendant); int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert); int _MarkDFSPath(graphP theGraph, int ancestor, int descendant); int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant); void _AddBackEdge(graphP theGraph, int ancestor, int descendant); int _DeleteUnmarkedVerticesAndEdges(graphP theGraph); int _InitializeIsolatorContext(graphP theGraph); int _IsolateMinorA(graphP theGraph); int _IsolateMinorB(graphP theGraph); int _IsolateMinorC(graphP theGraph); int _IsolateMinorD(graphP theGraph); int _IsolateMinorE(graphP theGraph); int _IsolateMinorE1(graphP theGraph); int _IsolateMinorE2(graphP theGraph); int _IsolateMinorE3(graphP theGraph); int _IsolateMinorE4(graphP theGraph); int _GetLeastAncestorConnection(graphP theGraph, int cutVertex); int _MarkDFSPathsToDescendants(graphP theGraph); int _AddAndMarkUnembeddedEdges(graphP theGraph); /**************************************************************************** gp_IsolateKuratowskiSubgraph() ****************************************************************************/ int _IsolateKuratowskiSubgraph(graphP theGraph, int v, int R) { int RetVal; /* A subgraph homeomorphic to K_{3,3} or K_5 will be isolated by using the visited flags, set=keep edge/vertex and clear=omit. Here we initialize to omit all, then we subsequently set visited on all edges and vertices in the homeomorph. */ _ClearAllVisitedFlagsInGraph(theGraph); /* Next, we determine which of the non-planarity Minors was encountered and the principal bicomp on which the isolator will focus attention. */ if (_ChooseTypeOfNonplanarityMinor(theGraph, v, R) != OK) return NOTOK; if (_InitializeIsolatorContext(theGraph) != OK) return NOTOK; /* Call the appropriate isolator */ if (theGraphIC(theGraph)->minorType & MINORTYPE_A) RetVal = _IsolateMinorA(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_B) RetVal = _IsolateMinorB(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_C) RetVal = _IsolateMinorC(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_D) RetVal = _IsolateMinorD(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_E) RetVal = _IsolateMinorE(theGraph); else RetVal = NOTOK; /* Delete the unmarked edges and vertices, and return */ if (RetVal == OK) RetVal = _DeleteUnmarkedVerticesAndEdges(theGraph); return RetVal; } /**************************************************************************** _InitializeIsolatorContext() ****************************************************************************/ int _InitializeIsolatorContext(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); /* Obtains the edges connecting X and Y to ancestors of the current vertex */ if (_FindUnembeddedEdgeToAncestor(theGraph, IC->x, &IC->ux, &IC->dx) != TRUE || _FindUnembeddedEdgeToAncestor(theGraph, IC->y, &IC->uy, &IC->dy) != TRUE) return NOTOK; /* For Minor B, we seek the last pertinent child biconnected component, which is also future pertinent, and obtain the DFS child in its root edge. This child is the subtree root containing vertices with connections to both the current vertex and an ancestor of the current vertex. */ if (theGraphIC(theGraph)->minorType & MINORTYPE_B) { int SubtreeRoot = gp_GetVertexLastPertinentRootChild(theGraph, IC->w); IC->uz = gp_GetVertexLowpoint(theGraph, SubtreeRoot); if (_FindUnembeddedEdgeToSubtree(theGraph, IC->v, SubtreeRoot, &IC->dw) != TRUE || _FindUnembeddedEdgeToSubtree(theGraph, IC->uz, SubtreeRoot, &IC->dz) != TRUE) return NOTOK; } /* For all other minors, we obtain an unembedded connecting the current vertex to the pertinent vertex W, and for minor E we collect the additional unembedded ancestor connection for the future pertinent vertex Z. */ else { if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; if (theGraphIC(theGraph)->minorType & MINORTYPE_E) if (_FindUnembeddedEdgeToAncestor(theGraph, IC->z, &IC->uz, &IC->dz) != TRUE) return NOTOK; } return OK; } /**************************************************************************** _IsolateMinorA(): Isolate a K3,3 homeomorph ****************************************************************************/ int _IsolateMinorA(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN(IC->ux, IC->uy), IC->r) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorB(): Isolate a K3,3 homeomorph ****************************************************************************/ int _IsolateMinorB(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN3(IC->ux, IC->uy, IC->uz), MAX3(IC->ux, IC->uy, IC->uz)) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorC(): Isolate a K3,3 homeomorph ****************************************************************************/ int _IsolateMinorC(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (gp_GetObstructionMark(theGraph, IC->px) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW) { int highY = gp_GetObstructionMark(theGraph, IC->py) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW ? IC->py : IC->y; if (_MarkPathAlongBicompExtFace(theGraph, IC->r, highY) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->r) != OK) return NOTOK; } // Note: The x-y path is already marked, due to identifying the type of non-planarity minor if (_MarkDFSPathsToDescendants(theGraph) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN(IC->ux, IC->uy), IC->r) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorD(): Isolate a K3,3 homeomorph ****************************************************************************/ int _IsolateMinorD(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); // Note: The x-y and v-z paths are already marked, due to identifying the type of non-planarity minor if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->y) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN(IC->ux, IC->uy), IC->r) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorE() ****************************************************************************/ int _IsolateMinorE(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); /* Minor E1: Isolate a K3,3 homeomorph */ if (IC->z != IC->w) return _IsolateMinorE1(theGraph); /* Minor E2: Isolate a K3,3 homeomorph */ if (IC->uz > MAX(IC->ux, IC->uy)) return _IsolateMinorE2(theGraph); /* Minor E3: Isolate a K3,3 homeomorph */ if (IC->uz < MAX(IC->ux, IC->uy) && IC->ux != IC->uy) return _IsolateMinorE3(theGraph); /* Minor E4: Isolate a K3,3 homeomorph */ else if (IC->x != IC->px || IC->y != IC->py) return _IsolateMinorE4(theGraph); /* Minor E: Isolate a K5 homeomorph */ // Note: The x-y path is already marked, due to identifying the type of non-planarity minor if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, MIN3(IC->ux, IC->uy, IC->uz), IC->r) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateMinorE1() Reduce to Minor C if the vertex Z responsible for external activity below the X-Y path does not equal the pertinent vertex W. ****************************************************************************/ int _IsolateMinorE1(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (gp_GetObstructionMark(theGraph, IC->z) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW) { gp_ResetObstructionMark(theGraph, IC->px, ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW); IC->x = IC->z; IC->ux = IC->uz; IC->dx = IC->dz; } else if (gp_GetObstructionMark(theGraph, IC->z) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW) { gp_ResetObstructionMark(theGraph, IC->py, ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW); IC->y = IC->z; IC->uy = IC->uz; IC->dy = IC->dz; } else return NOTOK; // Note: The x-y path is already marked, due to identifying E as the type of non-planarity minor, // but the x-y path is also included in minor C, so we let it stay marked since the minor C // isolator also assumes the x-y path has been marked by non-planarity minor type identification IC->z = IC->uz = IC->dz = NIL; theGraphIC(theGraph)->minorType ^= MINORTYPE_E; theGraphIC(theGraph)->minorType |= (MINORTYPE_C | MINORTYPE_E1); return _IsolateMinorC(theGraph); } /**************************************************************************** _IsolateMinorE2() If uZ (which is the ancestor of v that is adjacent to Z) is a descendant of both uY and uX, then we reduce to Minor A ****************************************************************************/ int _IsolateMinorE2(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); // Note: The x-y path was already marked, due to identifying E as the type of non-planarity minor, // but we're reducing to Minor A, which does not include the x-y path, so the visited flags are // cleared as a convenient, if somewhat wasteful, way to clear the marking on the x-y path _ClearAllVisitedFlagsInGraph(theGraph); IC->v = IC->uz; IC->dw = IC->dz; IC->z = IC->uz = IC->dz = NIL; theGraphIC(theGraph)->minorType ^= MINORTYPE_E; theGraphIC(theGraph)->minorType |= (MINORTYPE_A | MINORTYPE_E2); return _IsolateMinorA(theGraph); } /**************************************************************************** _IsolateMinorE3() ****************************************************************************/ int _IsolateMinorE3(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (IC->ux < IC->uy) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->px) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->w, IC->y) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->x, IC->w) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->py, IC->r) != OK) return NOTOK; } // Note: The x-y path is already marked, due to identifying E as the type of non-planarity minor if (theGraph->functions->fpMarkDFSPath(theGraph, MIN3(IC->ux, IC->uy, IC->uz), IC->r) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; theGraphIC(theGraph)->minorType |= MINORTYPE_E3; return OK; } /**************************************************************************** _IsolateMinorE4() ****************************************************************************/ int _IsolateMinorE4(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (IC->px != IC->x) { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->w) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->py, IC->r) != OK) return NOTOK; } else { if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->px) != OK || _MarkPathAlongBicompExtFace(theGraph, IC->w, IC->r) != OK) return NOTOK; } // Note: The x-y path is already marked, due to identifying E as the type of non-planarity minor if (theGraph->functions->fpMarkDFSPath(theGraph, MIN3(IC->ux, IC->uy, IC->uz), MAX3(IC->ux, IC->uy, IC->uz)) != OK || _MarkDFSPathsToDescendants(theGraph) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkUnembeddedEdges(theGraph) != OK) return NOTOK; theGraphIC(theGraph)->minorType |= MINORTYPE_E4; return OK; } /**************************************************************************** _GetLeastAncestorConnection() This function searches for an ancestor of the current vertex v adjacent by a cycle edge to the given cutVertex or one of its DFS descendants appearing in a separated bicomp. The given cutVertex is assumed to be future pertinent such that either the leastAncestor or the lowpoint of a separated DFS child is less than v. We obtain the minimum possible connection from the cutVertex to an ancestor of v. ****************************************************************************/ int _GetLeastAncestorConnection(graphP theGraph, int cutVertex) { int child; int ancestor = gp_GetVertexLeastAncestor(theGraph, cutVertex); child = gp_GetVertexFuturePertinentChild(theGraph, cutVertex); while (gp_IsVertex(theGraph, child)) { if (gp_IsSeparatedDFSChild(theGraph, child) && ancestor > gp_GetVertexLowpoint(theGraph, child)) ancestor = gp_GetVertexLowpoint(theGraph, child); child = gp_GetVertexNextDFSChild(theGraph, cutVertex, child); } return ancestor; } /**************************************************************************** _FindUnembeddedEdgeToAncestor() This function searches for an ancestor of the current vertex v adjacent by a cycle edge to the given cutVertex or one of its DFS descendants appearing in a separated bicomp. The given cutVertex is assumed to be future pertinent such that either the leastAncestor or the lowpoint of a separated DFS child is less than v. We obtain the minimum possible connection from the cutVertex to an ancestor of v, then compute the descendant accordingly. Returns TRUE if found, FALSE otherwise. ****************************************************************************/ int _FindUnembeddedEdgeToAncestor(graphP theGraph, int cutVertex, int *pAncestor, int *pDescendant) { int child, foundChild; int ancestor = gp_GetVertexLeastAncestor(theGraph, cutVertex); child = gp_GetVertexFuturePertinentChild(theGraph, cutVertex); foundChild = NIL; while (gp_IsVertex(theGraph, child)) { if (gp_IsSeparatedDFSChild(theGraph, child) && ancestor > gp_GetVertexLowpoint(theGraph, child)) { ancestor = gp_GetVertexLowpoint(theGraph, child); foundChild = child; } child = gp_GetVertexNextDFSChild(theGraph, cutVertex, child); } *pAncestor = ancestor; // If the least ancestor connection was direct, then return the cutVertex as the descendant if (ancestor == gp_GetVertexLeastAncestor(theGraph, cutVertex)) { *pDescendant = cutVertex; return TRUE; } // Otherwise find the descendant based on the separated child with least lowpoint return _FindUnembeddedEdgeToSubtree(theGraph, *pAncestor, foundChild, pDescendant); } /**************************************************************************** _FindUnembeddedEdgeToCurVertex() Given the current vertex v, we search for an edge connecting v to either a given pertinent vertex W or one of its DFS descendants in the subtree indicated by the the last pertinent child biconnected component. Returns TRUE if founds, FALSE otherwise. ****************************************************************************/ int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant) { if (gp_IsEdge(theGraph, gp_GetVertexPertinentEdge(theGraph, cutVertex))) { *pDescendant = cutVertex; return TRUE; } else { int subtreeRoot = gp_GetVertexFirstPertinentRootChild(theGraph, cutVertex); return _FindUnembeddedEdgeToSubtree(theGraph, theGraphIC(theGraph)->v, subtreeRoot, pDescendant); } } /**************************************************************************** _FindUnembeddedEdgeToSubtree() Given the root vertex of a DFS subtree and an ancestor of that subtree, find a vertex in the subtree that is adjacent to the ancestor by a cycle edge. Returns TRUE if found, FALSE if not found. ****************************************************************************/ int _FindUnembeddedEdgeToSubtree(graphP theGraph, int ancestor, int SubtreeRoot, int *pDescendant) { int e, Z, ZNew; *pDescendant = NIL; /* If SubtreeRoot is a root copy, then we change to the DFS child in the DFS tree root edge of the bicomp rooted by SubtreeRoot. */ SubtreeRoot = gp_IsVirtualVertex(theGraph, SubtreeRoot) ? gp_GetDFSChildFromBicompRoot(theGraph, SubtreeRoot) : SubtreeRoot; /* Find the least descendant of the cut vertex incident to the ancestor. */ e = gp_GetVertexFwdEdgeList(theGraph, ancestor); while (gp_IsEdge(theGraph, e)) { if (gp_GetNeighbor(theGraph, e) >= SubtreeRoot) { if (gp_IsNotVertex(theGraph, *pDescendant) || *pDescendant > gp_GetNeighbor(theGraph, e)) *pDescendant = gp_GetNeighbor(theGraph, e); } e = gp_GetNextEdge(theGraph, e); if (e == gp_GetVertexFwdEdgeList(theGraph, ancestor)) e = NIL; } if (gp_IsNotVertex(theGraph, *pDescendant)) return FALSE; /* Make sure the identified descendant actually descends from the cut vertex */ Z = *pDescendant; while (Z != SubtreeRoot) { ZNew = gp_GetVertexParent(theGraph, Z); if (gp_IsNotVertex(theGraph, ZNew) || ZNew == Z) return FALSE; Z = ZNew; } /* Return successfully */ return TRUE; } /**************************************************************************** _MarkPathAlongBicompExtFace() Sets the visited flags of vertices and edges on the external face of a bicomp from startVert to endVert, inclusive, by following the 'first' edge link out of each visited vertex. ****************************************************************************/ int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert) { int Z, ZPrevLink, ZPrevEdge; /* Mark the start vertex (and if it is a root copy, mark the parent copy too. */ gp_SetVisited(theGraph, startVert); /* For each vertex visited after the start vertex, mark the vertex and the edge used to get there. Stop after marking the ending vertex. */ Z = startVert; ZPrevLink = 1; do { Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); ZPrevEdge = gp_GetEdgeByLink(theGraph, Z, ZPrevLink); gp_SetEdgeVisited(theGraph, ZPrevEdge); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, ZPrevEdge)); gp_SetVisited(theGraph, Z); } while (Z != endVert); return OK; } /**************************************************************************** _MarkDFSPath() Sets visited flags of vertices and edges from descendant to ancestor, including root copy vertices, and including the step of hopping from a root copy to its parent copy. At each vertex, the edge record is obtained whose type indicates that it leads to the DFS parent. An earlier implementation just used the DFS parent member of the vertex, but then had to find the edge to mark anyway. This method is more generalized because some extension algorithms reduce DFS paths to single DFS tree edges, in which case the edge record with type EDGE_TYPE_PARENT may indicate the DFS paent or an ancestor. ****************************************************************************/ int _MarkDFSPath(graphP theGraph, int ancestor, int descendant) { int e, parent; // If we are marking from a root (virtual) vertex upward, then go up to the // non-virtual parent copy before starting the loop if (gp_IsVirtualVertex(theGraph, descendant)) descendant = gp_GetVertexFromBicompRoot(theGraph, descendant); // Mark the lowest vertex (the one with the highest number). gp_SetVisited(theGraph, descendant); // Mark all ancestors of the lowest vertex, and the edges used to reach // them, up to the given ancestor vertex. while (descendant != ancestor) { // This loop traverses all vertices from descendant to ancestor, // including intervening bicomp roots (which are virtual vertices) if (descendant == NIL) return NOTOK; // If we are at a bicomp root, then ascend to its non-virtual // counterpart, so that can also be marked as visited. if (gp_IsVirtualVertex(theGraph, descendant)) { parent = gp_GetVertexFromBicompRoot(theGraph, descendant); } // If we are on a regular, non-virtual vertex then get the edge to the parent, // mark the edge, then fall through to the code that marks the parent vertex. else { // Scan the edges for the one marked as the DFS parent parent = NIL; e = gp_GetFirstEdge(theGraph, descendant); while (gp_IsEdge(theGraph, e)) { if (gp_GetEdgeType(theGraph, e) == EDGE_TYPE_PARENT) { parent = gp_GetNeighbor(theGraph, e); break; } e = gp_GetNextEdge(theGraph, e); } // Sanity check on the data structure integrity // The found parent may be a non-virtual or a virtual vertex. // If the latter, then it will be marked visited, and then the // next iteration of the loop will hop up to its non-virtual if (parent == NIL) return NOTOK; // Mark the edge gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); } // Mark the parent, then hop to the parent and reiterate gp_SetVisited(theGraph, parent); descendant = parent; } return OK; } /**************************************************************************** _MarkDFSPathsToDescendants() ****************************************************************************/ int _MarkDFSPathsToDescendants(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (theGraph->functions->fpMarkDFSPath(theGraph, IC->x, IC->dx) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->y, IC->dy) != OK) return NOTOK; if (gp_IsVertex(theGraph, IC->dw)) if (theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK) return NOTOK; if (gp_IsVertex(theGraph, IC->dz)) if (theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dz) != OK) return NOTOK; return OK; } /**************************************************************************** _AddAndMarkUnembeddedEdges() ****************************************************************************/ int _AddAndMarkUnembeddedEdges(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_AddAndMarkEdge(theGraph, IC->ux, IC->dx) != OK || _AddAndMarkEdge(theGraph, IC->uy, IC->dy) != OK) return NOTOK; if (gp_IsVertex(theGraph, IC->dw)) if (_AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; if (gp_IsVertex(theGraph, IC->dz)) if (_AddAndMarkEdge(theGraph, IC->uz, IC->dz) != OK) return NOTOK; return OK; } /**************************************************************************** _AddAndMarkEdge() Adds edge records for the edge (ancestor, descendant) and marks the edge records and vertex structures that represent the edge. ****************************************************************************/ int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant) { _AddBackEdge(theGraph, ancestor, descendant); /* Mark the edge so it is not deleted */ gp_SetVisited(theGraph, ancestor); gp_SetEdgeVisited(theGraph, gp_GetFirstEdge(theGraph, ancestor)); gp_SetEdgeVisited(theGraph, gp_GetFirstEdge(theGraph, descendant)); gp_SetVisited(theGraph, descendant); return OK; } /**************************************************************************** _AddBackEdge() This function transfers the edge records for the edge between the ancestor and descendant from the forward edge list of the ancestor to the adjacency lists of the ancestor and descendant. ****************************************************************************/ void _AddBackEdge(graphP theGraph, int ancestor, int descendant) { int fwdEdgeRec, backEdgeRec; /* We get the two edge records of the back edge to embed. */ fwdEdgeRec = gp_GetVertexFwdEdgeList(theGraph, ancestor); while (gp_IsEdge(theGraph, fwdEdgeRec)) { if (gp_GetNeighbor(theGraph, fwdEdgeRec) == descendant) break; fwdEdgeRec = gp_GetNextEdge(theGraph, fwdEdgeRec); if (fwdEdgeRec == gp_GetVertexFwdEdgeList(theGraph, ancestor)) fwdEdgeRec = NIL; } if (gp_IsNotEdge(theGraph, fwdEdgeRec)) { #ifdef DEBUG NOTOK; #endif return; } backEdgeRec = gp_GetTwin(theGraph, fwdEdgeRec); /* The forward edge record is removed from the fwdEdgeList of the ancestor. */ if (gp_GetVertexFwdEdgeList(theGraph, ancestor) == fwdEdgeRec) { if (gp_GetNextEdge(theGraph, fwdEdgeRec) == fwdEdgeRec) gp_SetVertexFwdEdgeList(theGraph, ancestor, NIL); else gp_SetVertexFwdEdgeList(theGraph, ancestor, gp_GetNextEdge(theGraph, fwdEdgeRec)); } gp_SetNextEdge(theGraph, gp_GetPrevEdge(theGraph, fwdEdgeRec), gp_GetNextEdge(theGraph, fwdEdgeRec)); gp_SetPrevEdge(theGraph, gp_GetNextEdge(theGraph, fwdEdgeRec), gp_GetPrevEdge(theGraph, fwdEdgeRec)); /* The forward edge record is added to the adjacency list of the ancestor. */ gp_SetPrevEdge(theGraph, fwdEdgeRec, NIL); gp_SetNextEdge(theGraph, fwdEdgeRec, gp_GetFirstEdge(theGraph, ancestor)); gp_SetPrevEdge(theGraph, gp_GetFirstEdge(theGraph, ancestor), fwdEdgeRec); gp_SetFirstEdge(theGraph, ancestor, fwdEdgeRec); /* The back edge record is added to the adjacency list of the descendant. */ gp_SetPrevEdge(theGraph, backEdgeRec, NIL); gp_SetNextEdge(theGraph, backEdgeRec, gp_GetFirstEdge(theGraph, descendant)); gp_SetPrevEdge(theGraph, gp_GetFirstEdge(theGraph, descendant), backEdgeRec); gp_SetFirstEdge(theGraph, descendant, backEdgeRec); gp_SetNeighbor(theGraph, backEdgeRec, ancestor); } /**************************************************************************** _DeleteUnmarkedVerticesAndEdges() For each vertex, traverse its adjacency list and delete all unvisited edges. ****************************************************************************/ int _DeleteUnmarkedVerticesAndEdges(graphP theGraph) { int v, e, eNext; /* All of the forward and back edge records of all of the "back" edges were removed from the adjacency lists in the planarity algorithm preprocessing. We now put them back into the adjacency lists (and we do not mark them), so they can be properly deleted below. */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { while (gp_IsEdge(theGraph, e = gp_GetVertexFwdEdgeList(theGraph, v))) _AddBackEdge(theGraph, v, gp_GetNeighbor(theGraph, e)); } /* Now we delete all unmarked edges. We don't delete vertices from the embedding, but the ones we should delete will become degree zero. */ for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { eNext = gp_GetNextEdge(theGraph, e); if (!gp_GetEdgeVisited(theGraph, e)) gp_DeleteEdge(theGraph, e); e = eNext; } } return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphNonplanar.c000066400000000000000000001042411521450711600316040ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #define GRAPHNONPLANAR_C // This source file implements subroutines of the main graph planarity method #include "../planarityRelated/graphPlanarity.h" #include "../planarityRelated/graphPlanarity.private.h" /* Imported functions */ extern void _InitIsolatorContext(graphP theGraph); extern int _ClearAllVisitedFlagsInBicomp(graphP theGraph, int BicompRoot); extern int _ClearObstructionMarksInBicomp(graphP theGraph, int BicompRoot); extern int _HideInternalEdges(graphP theGraph, int vertex); extern int _RestoreInternalEdges(graphP theGraph, int stackBottom); // extern int _OrientVerticesInEmbedding(graphP theGraph); extern int _OrientVerticesInBicomp(graphP theGraph, int BicompRoot, int PreserveSigns); /* Private functions (exported to system) */ int _ChooseTypeOfNonplanarityMinor(graphP theGraph, int v, int R); int _InitializeNonplanarityContext(graphP theGraph, int v, int R); int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink); void _FindActiveVertices(graphP theGraph, int R, int *pX, int *pY); int _FindPertinentVertex(graphP theGraph); int _SetVertexTypesForMarkingXYPath(graphP theGraph); int _PopAndUnmarkVerticesAndEdges(graphP theGraph, int Z, int stackBottom); int _MarkHighestXYPath(graphP theGraph); int _MarkLowestXYPath(graphP theGraph); int _MarkClosestXYPath(graphP theGraph, int targetVertex); int _MarkZtoRPath(graphP theGraph); int _FindFuturePertinenceBelowXYPath(graphP theGraph); /**************************************************************************** _gp_GetObstructionMinorType() ****************************************************************************/ unsigned gp_GetObstructionMinorType(graphP theGraph) { if (theGraph == NULL || theGraphIC(theGraph) == NULL) return MINORTYPE_NONE; return theGraphIC(theGraph)->minorType; } /**************************************************************************** _ChooseTypeOfNonplanarityMinor() ****************************************************************************/ int _ChooseTypeOfNonplanarityMinor(graphP theGraph, int v, int R) { int W, Px, Py, Z; /* Create the initial non-planarity minor state in the isolator context */ if (_InitializeNonplanarityContext(theGraph, v, R) != OK) return NOTOK; R = theGraphIC(theGraph)->r; W = theGraphIC(theGraph)->w; /* If the root copy is not a root copy of the current vertex v, then the Walkdown terminated because it couldn't find a viable path along a child bicomp, which is Minor A. */ if (gp_GetVertexFromBicompRoot(theGraph, R) != v) { theGraphIC(theGraph)->minorType |= MINORTYPE_A; return OK; } /* If W has a pertinent and future pertinent child bicomp, then we've found Minor B */ // NOTE: Each pertinent root is stored as the DFS child with which it is // associated, so we test gp_IsVertex, not gp_IsVirtualVertex here. if (gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, W))) { if (gp_GetVertexLowpoint(theGraph, gp_GetVertexLastPertinentRootChild(theGraph, W)) < v) { theGraphIC(theGraph)->minorType |= MINORTYPE_B; return OK; } } /* Find the highest obstructing X-Y path */ if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; Px = theGraphIC(theGraph)->px; Py = theGraphIC(theGraph)->py; /* If either point of attachment is 'high' (P_x closer to R than X or P_y closer to R than Y along external face), then we've matched Minor C. */ if (gp_GetObstructionMark(theGraph, Px) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW || gp_GetObstructionMark(theGraph, Py) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW) { theGraphIC(theGraph)->minorType |= MINORTYPE_C; return OK; } /* For Minor D, we search for a path from an internal vertex Z along the X-Y path up to the root R of the bicomp. */ if (_MarkZtoRPath(theGraph) != OK) return NOTOK; if (gp_IsVertex(theGraph, theGraphIC(theGraph)->z)) { theGraphIC(theGraph)->minorType |= MINORTYPE_D; return OK; } /* For Minor E, we search for an future pertinent vertex Z below the points of attachment of the X-Y path */ Z = _FindFuturePertinenceBelowXYPath(theGraph); if (gp_IsVertex(theGraph, Z)) { theGraphIC(theGraph)->z = Z; theGraphIC(theGraph)->minorType |= MINORTYPE_E; return OK; } return NOTOK; } /**************************************************************************** _InitializeNonplanarityContext() This method finds the stopping vertices X and Y, and the pertinent vertex W of a bicomp rooted by vertex R. If R is NIL, the routine first determines which bicomp produced non-planarity condition. If the stack is non-empty, then R is on the top of the stack. Otherwise, an unembedded edge from the fwdEdgeList of vertex v is used in combination with the sortedDFSChildList of v to determine R. If the parameter R was not NIL, then this method assumes it must operate only on the bicomp rooted by R, and it also assumes that the caller has not cleared the visited flags in the bicomp, so they are cleared. This routine imparts consistent orientation to all vertices in bicomp R since several subroutines count on this. The edge signs are preserved so that the original orientations of all vertices can be restored. If the vertices of the embedding are already consistently oriented, then this operation simply has no effect. Finally, in the bicomp R, the vertex types of all non-root vertices on the external face are classified according to whether or not they are closer to the root R than X and Y along the external face paths (R X W) and (R Y W). ****************************************************************************/ int _InitializeNonplanarityContext(graphP theGraph, int v, int R) { // Blank out the isolator context, then assign the input graph reference // and the current vertext v into the context. _InitIsolatorContext(theGraph); theGraphIC(theGraph)->v = v; // The bicomp root provided was the one on which the WalkDown was performed, // but in the case of Minor A, the central bicomp of the minor is at the top // of the stack, so R must be changed to that value. if (sp_NonEmpty(theGraph->theStack)) { // The top of stack has the pair R and 0/1 direction Walkdown traversal proceeds from R // Need only R, so pop and discard the direction, then pop R sp_Pop2_Discard1(theGraph->theStack, R); } theGraphIC(theGraph)->r = R; // A number of subroutines require the main bicomp of the minor to be // consistently oriented and its visited flags clear. if (_OrientVerticesInBicomp(theGraph, R, 1) != OK) { return NOTOK; } if (_ClearAllVisitedFlagsInBicomp(theGraph, R) != OK) return NOTOK; // Now we find the active vertices along both external face paths // extending from R. _FindActiveVertices(theGraph, R, &theGraphIC(theGraph)->x, &theGraphIC(theGraph)->y); // Now, we obtain the pertinent vertex W on the lower external face // path between X and Y (that path that does not include R). theGraphIC(theGraph)->w = _FindPertinentVertex(theGraph); // Now we can classify the vertices along the external face of the bicomp // rooted at R as 'high RXW', 'low RXW', 'high RXY', 'low RXY' // NOTE: We do not need to set up for xy path identification when we // have minor A, which occurs when the parent copy vertex of // bicomp root R is a descendant of v (not v). if (gp_GetVertexFromBicompRoot(theGraph, R) == v) { if (_SetVertexTypesForMarkingXYPath(theGraph) != OK) return NOTOK; } // All work is done, so return success return OK; } /******************************************************************** _GetNeighborOnExtFace() Each vertex contains two 'link' index pointers that indicate the first and last adjacency list edges. If the vertex is on the external face, then these two edges are also on the external face. We want to take one of those edges to get to the next vertex on the external face. On input *pPrevLink indicates which link we followed to arrive at curVertex. On output *pPrevLink will be set to the link we follow to get into the next vertex. To get to the next vertex, we use the opposite link from the one used to get into curVertex. This takes us to an edge record. The twin of that edge record carries us to an edge record in the next vertex. At least one of the two links in that edge record will lead to a vertex node in G, which is the next vertex. Once we arrive at the next vertex, at least one of its links will lead back to the edge record, and that link becomes the output value of *pPrevLink. NOTE: This method intentionally ignores the extFace optimization links. It is invoked when the "real" external face must be traversed and hence when the constant time guarantee is not needed from the extFace short-circuit that connects the bicomp root to the first active vertices along each external face path emanating from the bicomp root. ********************************************************************/ int _GetNeighborOnExtFace(graphP theGraph, int curVertex, int *pPrevLink) { /* Exit curVertex from whichever link was not previously used to enter it */ int e = gp_GetEdgeByLink(theGraph, curVertex, 1 ^ (*pPrevLink)); int nextVertex = gp_GetNeighbor(theGraph, e); /* This if stmt assigns the new prev link that tells us which edge record was used to enter nextVertex (so that we exit from the opposing edge record). However, if we are in a singleton bicomp, then both links in nextVertex lead back to curVertex. We want the two edge records of a singleton bicomp to act like a cycle, so we just don't change the prev link in this case. But when nextVertex has more than one edge, we need to figure out whether the first edge or last edge (which are the two on the external face) was used to enter nextVertex so we can exit from the other one as traversal of the external face continues later. */ if (gp_GetFirstEdge(theGraph, nextVertex) != gp_GetLastEdge(theGraph, nextVertex)) *pPrevLink = gp_GetTwin(theGraph, e) == gp_GetFirstEdge(theGraph, nextVertex) ? 0 : 1; return nextVertex; } /**************************************************************************** _FindActiveVertices() Descends from the root of a bicomp R along both external face paths (which are indicated by the first and last edge records in R's adjacency list), returning the first active vertex appearing in each direction. ****************************************************************************/ void _FindActiveVertices(graphP theGraph, int R, int *pX, int *pY) { int XPrevLink = 1, YPrevLink = 0, v = theGraphIC(theGraph)->v; *pX = _GetNeighborOnExtFace(theGraph, R, &XPrevLink); *pY = _GetNeighborOnExtFace(theGraph, R, &YPrevLink); // For planarity algorithms, advance past inactive vertices // For outerplanarity algorithms, ignore the notion of inactive vertices // since all vertices must remain on the external face. if (!(gp_GetEmbedFlags(theGraph) & EMBEDFLAGS_OUTERPLANAR)) { gp_UpdateVertexFuturePertinentChild(theGraph, *pX, v); while (INACTIVE(theGraph, *pX, v)) { *pX = _GetNeighborOnExtFace(theGraph, *pX, &XPrevLink); gp_UpdateVertexFuturePertinentChild(theGraph, *pX, v); } gp_UpdateVertexFuturePertinentChild(theGraph, *pY, v); while (INACTIVE(theGraph, *pY, v)) { *pY = _GetNeighborOnExtFace(theGraph, *pY, &YPrevLink); gp_UpdateVertexFuturePertinentChild(theGraph, *pY, v); } } } /**************************************************************************** _FindPertinentVertex() Get the first vertex after x. Since x was obtained using a prevlink of 1 on r, we use the same prevlink so we don't go back to R. Then, we proceed around the lower path until we find a vertex W that either has pertinent child bicomps or is directly adjacent to the current vertex v. ****************************************************************************/ int _FindPertinentVertex(graphP theGraph) { int W = theGraphIC(theGraph)->x, WPrevLink = 1; W = _GetNeighborOnExtFace(theGraph, W, &WPrevLink); while (W != theGraphIC(theGraph)->y) { if (PERTINENT(theGraph, W)) return W; W = _GetNeighborOnExtFace(theGraph, W, &WPrevLink); } return NIL; } /**************************************************************************** _SetVertexTypesForMarkingXYPath() Label the vertices along the external face of the bicomp rooted at R as 'high RXW', 'low RXW', 'high RXY', 'low RXY' ****************************************************************************/ int _SetVertexTypesForMarkingXYPath(graphP theGraph) { int R, X, Y, W, Z, ZPrevLink, ZType; // Unpack the context for efficiency of loops R = theGraphIC(theGraph)->r; X = theGraphIC(theGraph)->x; Y = theGraphIC(theGraph)->y; W = theGraphIC(theGraph)->w; // Ensure basic preconditions of this routine are met if (gp_IsNotVirtualVertex(theGraph, R) || gp_IsNotVertex(theGraph, X) || gp_IsNotVertex(theGraph, Y) || gp_IsNotVertex(theGraph, W)) { return NOTOK; } // Clear the type member of each vertex in the bicomp if (_ClearObstructionMarksInBicomp(theGraph, R) != OK) return NOTOK; // Traverse from R to W in the X direction ZPrevLink = 1; Z = _GetNeighborOnExtFace(theGraph, R, &ZPrevLink); ZType = ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW; while (Z != W) { if (Z == X) ZType = ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW; gp_ResetObstructionMark(theGraph, Z, ZType); Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } // Traverse from R to W in the Y direction ZPrevLink = 0; Z = _GetNeighborOnExtFace(theGraph, R, &ZPrevLink); ZType = ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW; while (Z != W) { if (Z == Y) ZType = ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW; gp_ResetObstructionMark(theGraph, Z, ZType); Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } return OK; } /**************************************************************************** _PopAndUnmarkVerticesAndEdges() Pop all vertex/edge pairs from the top of the stack up to a terminating vertex Z and mark as unvisited. If Z is NIL, then all vertex/edge pairs are popped and marked as unvisited. The stackBottom indicates where other material besides the vertex/edge pairs may appear. ****************************************************************************/ int _PopAndUnmarkVerticesAndEdges(graphP theGraph, int Z, int stackBottom) { int V, e; // Pop vertex/edge pairs until all have been popped from the stack, // and all that's left is what was under the pairs, or until... while (sp_GetCurrentSize(theGraph->theStack) > stackBottom) { sp_Pop(theGraph->theStack, V); // If we pop the terminating vertex Z, then put it back and break if (V == Z) { sp_Push(theGraph->theStack, V); break; } // Otherwise, pop the edge part of the vertex/edge pair sp_Pop(theGraph->theStack, e); // Now unmark the vertex and edge (i.e. revert to "unvisited") gp_ClearVisited(theGraph, V); gp_ClearEdgeVisited(theGraph, e); gp_ClearEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); } return OK; } /**************************************************************************** _MarkHighestXYPath() Sets the visited flags on the highest X-Y path, i.e. the one closest to the root vertex R of the biconnected component containing X and Y as well as a pertinent vertex W that the Walkdown could not reach due to the future pertinent vertices X and Y being along both external face paths emanating from the root R. The caller receives an OK result if the method succeeded in operating or NOTOK on an internal failure. However, there may or may not be any X-Y path, in which case no visitation flags are set and the return result is still OK because the caller must decide whether the absence of an X-Y path is an operational error. This method also sets the isolator context's points of attachment on the external face of the marked X-Y path, if there was an X-Y path. So, the caller can also use this call to decide if there was an X-Y path by testing whether theGraphIC(theGraph)->px and py have been set to non-NIL values. ****************************************************************************/ int _MarkHighestXYPath(graphP theGraph) { return _MarkClosestXYPath(theGraph, theGraphIC(theGraph)->r); } /**************************************************************************** _MarkLowestXYPath() Sets the visited flags on the lowest X-Y path, i.e. the one closest to the pertinent vertex W that the Walkdown could not reach due to future pertinent vertices X and Y along both external face paths emanating from the root R of the biconnected component containing W, X and Y. The caller receives an OK result if the method succeeded in operating or NOTOK on an internal failure. However, there may or may not be any X-Y path, in which case no visitation flags are set and the return result is still OK because the caller must decide whether the absence of an X-Y path is an operational error. This method also sets the isolator context's points of attachment on the external face of the marked X-Y path, if there was an X-Y path. So, the caller can also use this call to decide if there was an X-Y path by testing whether theGraphIC(theGraph)->px and py have been set to non-NIL values. ****************************************************************************/ int _MarkLowestXYPath(graphP theGraph) { return _MarkClosestXYPath(theGraph, theGraphIC(theGraph)->w); } /**************************************************************************** _MarkClosestXYPath() This method searches for and marks the X-Y path in the bicomp rooted by R that is either closest to R (highest) or closest to W (lowest). This method will return NOTOK if the targetVertex parameter is other than R or W. The closest X-Y path is the path through the inside of the bicomp that only attaches to the external face at its two endpoint vertices and those endpoints are closer along the external face path emanating from the targetVertex than the attachment points of any other X-Y path. This method returns NOTOK if there is an internal processing error and otherwise it returns OK. If there is no X-Y path, the method still returns OK to indicate no internal failures, but on return the caller can detect whether there was an X-Y path by testing whether the attachment points in the isolator context have been set to non-NIL values. Specifically, test whether theGraphIC(theGraph)->px and py have been set to non-NIL values. The caller must decide whether the absence of an X-Y path is an error. For example, in core planarity, the proof of correctness guarantees an X-Y path exists by the time this method is called, so that caller would decide to return NOTOK since there should be an X-Y path. If there is an X-Y path, it is marked using the visited flags on the vertices and edges. PRECONDITION: During non-planarity context initialization, the vertices along the external face (other than R and W) have been classified as 'high RXW', 'low RXW', 'high RXY', or 'low RXY'. Once the vertices have been categorized, we proceed with trying to set the visitation flags of vertices and edges. First, we remove all edges incident to the targetVertex, except the two edges that join it to the external face. The result is that the targetVertex and its two remaining edges are a 'corner' in the external face but also in a single proper face whose boundary includes the X-Y path with the closest attachment points. Thus, we simply need to walk this proper face to find the desired X-Y path. Note, however, that the resulting face boundary may have attached cut vertices. Any such separable component contains a vertex neighbor of the targetVertex, but the edge to the targetVertex has been temporarily hidden. The algorithm removes loops of vertices and edges along the proper face so that only the desired path is identified. To walk the proper face containing the targetVertex, we first identify an edge record that will be considered to be the one used to enter the targetVertex. When the first loop iteration exits the targetVertex, it comes out on the RXW side (even though it may be an internal vertex not marked RXW). Then we take either the next edge (if targetVertex==W) or previous edge (if targetVertex==R) at every subsequent corner to determine the edge record to use to exit the vertex. Then, we use the twin of the exit edge record to determine the edge record to use to enter the next vertex. For each vertex, we mark as visited the vertex as well as both edge records of the edge used to enter the vertex. We also push the visited vertices and edges onto a stack. Each time the traversal lands on an external face vertex on the RXW side, it is recorded as a candidate point of attachment Px. We also pop and unmark all previously visited vertices and edges because they are now known to not be part of the internal X-Y path after encountering the point of attachment. Instead, these preceding edges and vertices were part of a path parallel to the external face that does not obstruct the space between R and W inside the bicomp. As we walk the proper face, we keep track of the last vertex P_x we visited of type RXW (high or low). If we encounter antipodalVertex of the targetVertex (i.e., W if the targetVertex is R, or R if the targetVertex is W), then there is no obstructing X-Y path since we removed only edges incident to the targetVertex, so we pop the stack unmarking everything then clear the X-Y path points of attachment in the isolator context so the caller will know that no X-Y path was found (and then we return OK since there was no internal error). If, during the traversal, we encounter a vertex Z previously visited, then we pop the stack, unmarking the vertices and edges popped, until we find the prior occurrence of Z on the stack. This is because we have traversed a path that would connect back to the targetVertex had we not hidden the internal edges of the targetVertex. Otherwise, the first time we encounter a vertex of type 'RYW', we stop because the obstructing X-Y path has been marked visited and its points of attachment to the external face have been found. This second point of attachment Py is stored in the isolator context. Once the X-Y path is identified (or once we have found that there is no X-Y path), we restore the previously hidden edges incident to the targetVertex. This method uses the stack, but it preserves any prior content. The stack space used is no greater than 3N. The first N accounts for hiding the edges incident to the targetVertex. The other 2N accounts for the fact that each iteration of the main loop visits a vertex, pushing its index and the location of an edge record. If a vertex is encountered that is already on the stack, then it is not pushed again (and in fact part of the stack is removed). Returns OK on successful operation and NOTOK on internal failure. Also, if an X-Y path is found (which will be the closest), then the graph isolator context contains its attachment points on the external face of the bicomp rooted by R, and the edges and vertices in the X-Y path will have been marked visited. ****************************************************************************/ int _MarkClosestXYPath(graphP theGraph, int targetVertex) { int e, Z; int R, W, antipodalVertex; int stackBottom1, stackBottom2; /* Initialization */ R = theGraphIC(theGraph)->r; W = theGraphIC(theGraph)->w; theGraphIC(theGraph)->px = theGraphIC(theGraph)->py = NIL; /* This method only makes sense for a targetVertex of R or W */ if (targetVertex != R && targetVertex != W) return NOTOK; /* The vertex opposite the targetVertex is needed to detect when there is no X-Y path */ antipodalVertex = targetVertex == R ? W : R; /* Save the stack bottom before we start hiding internal edges, so we will know how many edges to restore */ stackBottom1 = sp_GetCurrentSize(theGraph->theStack); /* Remove the internal edges incident to targetVertex (R or W) */ if (_HideInternalEdges(theGraph, targetVertex) != OK) return NOTOK; /* Now we're going to use the stack to collect the vertices of potential * X-Y paths, so we need to store where the hidden internal edges are * located because we must, at times, pop the collected vertices if * the path being collected doesn't work out. */ stackBottom2 = sp_GetCurrentSize(theGraph->theStack); /* Walk the proper face containing targetVertex to find and mark the closest X-Y path. */ Z = targetVertex; // Now we will get the edge considered to be the one used to enter // the targetVertex. This will be an edge on the RYW side, and the // first line of the loop code will get the previous or next edge // that will be used to exit the targetVertex on the RXW side of the // bicomp (that one operation to get previous or next, circular, will // switch sides because all internal edges of targetVertex are hidden). e = targetVertex == R ? gp_GetLastEdge(theGraph, R) : gp_GetFirstEdge(theGraph, W); while (gp_GetObstructionMark(theGraph, Z) != ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW && gp_GetObstructionMark(theGraph, Z) != ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW) { /* Advance e and Z along the proper face containing the targetVertex */ // Get the opposing edge of the corner at vertex Z, as the edge to exit Z e = targetVertex == R ? gp_GetPrevEdgeCircular(theGraph, e) : gp_GetNextEdgeCircular(theGraph, e); // Now use the exit edge record to get the next Z to visit Z = gp_GetNeighbor(theGraph, e); // And get the entry edge record of the new Z being visited e = gp_GetTwin(theGraph, e); /* If Z is already visited, then pop everything since the last time we visited Z because its all part of a separable component. */ if (gp_GetVisited(theGraph, Z)) { if (_PopAndUnmarkVerticesAndEdges(theGraph, Z, stackBottom2) != OK) return NOTOK; } /* If we have not visited this vertex before... */ else { /* If we find the antipodalVertex of the targetVertex (W if R, R if W), then there is no X-Y path. Never happens for Kuratowski subgraph isolator, but this routine is also used to test for certain X-Y paths. So, we clean up and bail out in that case. */ if (Z == antipodalVertex) { if (_PopAndUnmarkVerticesAndEdges(theGraph, NIL, stackBottom2) != OK) return NOTOK; break; } /* If we found another vertex along the RXW path, then blow off all the vertices we visited so far because they're not part of the obstructing path */ if (gp_GetObstructionMark(theGraph, Z) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW || gp_GetObstructionMark(theGraph, Z) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW) { theGraphIC(theGraph)->px = Z; if (_PopAndUnmarkVerticesAndEdges(theGraph, NIL, stackBottom2) != OK) return NOTOK; } /* Push the current vertex onto the stack of vertices visited since the last RXW vertex was encountered */ sp_Push(theGraph->theStack, e); sp_Push(theGraph->theStack, Z); /* Mark the vertex Z as visited as well as its edge of entry (except the entry edge for P_x).*/ gp_SetVisited(theGraph, Z); if (Z != theGraphIC(theGraph)->px) { gp_SetEdgeVisited(theGraph, e); gp_SetEdgeVisited(theGraph, gp_GetTwin(theGraph, e)); } /* If we found an RYW vertex, then we have successfully finished identifying the closest X-Y path, so we record the point of attachment and break the loop. */ if (gp_GetObstructionMark(theGraph, Z) == ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW || gp_GetObstructionMark(theGraph, Z) == ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW) { theGraphIC(theGraph)->py = Z; break; } } } /* Remove any remaining vertex-edge pairs on the top of the stack, then Restore the internal edges incident to R that were previously removed. */ sp_SetCurrentSize(theGraph->theStack, stackBottom2); if (_RestoreInternalEdges(theGraph, stackBottom1) != OK) return NOTOK; /* Return the result */ if (!gp_IsVertex(theGraph, theGraphIC(theGraph)->py)) theGraphIC(theGraph)->px = NIL; return OK; } /**************************************************************************** _MarkZtoRPath() This function assumes that _MarkHighestXYPath() has already been called, which marked as visited the vertices and edges along the X-Y path. We begin at the point of attachment P_x, take its last edge and traverse the predecessor edges until we find one marked visited, which leads to the first internal vertex along the X-Y path. We begin with this vertex (and its edge of entry), and we run until we find P_y. For each internal vertex Z and its edge of entry ZPrevEdge, we take the predecessor edge record of ZPrevEdge, called ZNextEdge. If ZNextEdge is marked visited then it is along the X-Y path, so we use it to exit Z and go to the next vertex on the X-Y path. If ZNextEdge is not visited, then when _MarkHighestXYPath() ran, it exited Z from ZNextedge, then eventually reentered Z. In other words, Z became a cut vertex when we removed the internal edges incident to R. Thus, ZNextEdge indicates the first edge in an internal path to R. When we find an unvisited ZNextEdge, we stop running the X-Y path and instead begin marking the Z to R path. We move to successive vertices using a twin edge record then its predecessor in the adjacency list, only this time we have not removed the internal edges incident to R, so this technique does eventually lead us all the way to R. If we do not find an unvisited ZNextEdge for any vertex Z on the X-Y path and inside the bicomp, then there is no Z to R path, so we return. Returns OK if a Z-to-R path has been marked or if it has been found that there is not a Z-to-R path; returns NOTOK on error ****************************************************************************/ int _MarkZtoRPath(graphP theGraph) { int ZPrevEdge, ZNextEdge, Z, R, Px, Py; /* Initialize */ R = theGraphIC(theGraph)->r; Px = theGraphIC(theGraph)->px; Py = theGraphIC(theGraph)->py; theGraphIC(theGraph)->z = NIL; /* Begin at Px and search its adjacency list for the edge leading to the first internal vertex of the X-Y path. */ Z = Px; ZNextEdge = gp_GetLastEdge(theGraph, Z); while (ZNextEdge != gp_GetFirstEdge(theGraph, Z)) { if (gp_GetEdgeVisited(theGraph, ZNextEdge)) break; ZNextEdge = gp_GetPrevEdge(theGraph, ZNextEdge); } if (!gp_GetEdgeVisited(theGraph, ZNextEdge)) return NOTOK; /* For each internal vertex Z, determine whether it has a path to root. */ while (gp_GetEdgeVisited(theGraph, ZNextEdge)) { ZPrevEdge = gp_GetTwin(theGraph, ZNextEdge); ZNextEdge = gp_GetPrevEdgeCircular(theGraph, ZPrevEdge); } ZPrevEdge = gp_GetTwin(theGraph, ZNextEdge); Z = gp_GetNeighbor(theGraph, ZPrevEdge); /* If there is no Z to R path, return */ if (Z == Py) return OK; /* Otherwise, store Z in the isolation context */ theGraphIC(theGraph)->z = Z; /* Walk the proper face starting with (Z, ZNextEdge) until we reach R, marking the vertices and edges encountered along the way, then Return OK. */ while (Z != R) { /* If we ever encounter a non-internal vertex (other than the root R), then corruption has occurred, so we return NOTOK */ if (gp_GetObstructionMark(theGraph, Z) != ANYVERTEX_OBSTRUCTIONMARK_UNMARKED) return NOTOK; /* Go to the next vertex indicated by ZNextEdge */ Z = gp_GetNeighbor(theGraph, ZNextEdge); /* Mark the next vertex and the edge leading to it as visited. */ gp_SetEdgeVisited(theGraph, ZNextEdge); gp_SetEdgeVisited(theGraph, ZPrevEdge); gp_SetVisited(theGraph, Z); /* Go to the next edge in the proper face */ ZNextEdge = gp_GetPrevEdgeCircular(theGraph, ZPrevEdge); ZPrevEdge = gp_GetTwin(theGraph, ZNextEdge); } /* Found Z to R path, so indicate as much to caller */ return OK; } /**************************************************************************** _FindFuturePertinenceBelowXYPath() Get a future pertinent vertex along the lower external face path between the points of attachment P_x and P_y of a 'low' X-Y Path. NOTE: By the time this function is called, Px and Py have already been found to be at or below X and Y. ****************************************************************************/ int _FindFuturePertinenceBelowXYPath(graphP theGraph) { int Z = theGraphIC(theGraph)->px, ZPrevLink = 1, Py = theGraphIC(theGraph)->py, v = theGraphIC(theGraph)->v; Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); while (Z != Py) { gp_UpdateVertexFuturePertinentChild(theGraph, Z, v); if (FUTUREPERTINENT(theGraph, Z, v)) return Z; Z = _GetNeighborOnExtFace(theGraph, Z, &ZPrevLink); } return NIL; } graphOuterplanarObstruction.c000066400000000000000000000163111521450711600343250ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphOuterplanarity.h" #include "graphOuterplanarity.private.h" /* Imported functions */ extern void _ClearAllVisitedFlagsInGraph(graphP); extern int _JoinBicomps(graphP theGraph); extern int _InitializeNonplanarityContext(graphP theGraph, int v, int R); extern int _MarkHighestXYPath(graphP theGraph); // extern int _FindUnembeddedEdgeToAncestor(graphP theGraph, int cutVertex, int *pAncestor, int *pDescendant); extern int _FindUnembeddedEdgeToCurVertex(graphP theGraph, int cutVertex, int *pDescendant); extern int _FindUnembeddedEdgeToSubtree(graphP theGraph, int ancestor, int SubtreeRoot, int *pDescendant); extern int _MarkPathAlongBicompExtFace(graphP theGraph, int startVert, int endVert); extern int _AddAndMarkEdge(graphP theGraph, int ancestor, int descendant); extern int _DeleteUnmarkedVerticesAndEdges(graphP theGraph); /* Private function declarations (exported to system) */ int _IsolateOuterplanarObstruction(graphP theGraph, int v, int R); int _ChooseTypeOfNonOuterplanarityMinor(graphP theGraph, int v, int R); int _IsolateOuterplanarityObstructionA(graphP theGraph); int _IsolateOuterplanarityObstructionB(graphP theGraph); int _IsolateOuterplanarityObstructionE(graphP theGraph); /**************************************************************************** _ChooseTypeOfNonOuterplanarityMinor() A constant time implementation is easily feasible but only constant amortized time is needed for the outerplanarity obstruction isolation, which also benefits from having the bicomp rooted by R oriented. If an extension algorithm requires constant actual time, then this function should not be used and instead the minor should be decided without orienting the bicomp. ****************************************************************************/ int _ChooseTypeOfNonOuterplanarityMinor(graphP theGraph, int v, int R) { int W; // Create the initial non-outerplanarity obstruction isolator state. if (_InitializeNonplanarityContext(theGraph, v, R) != OK) return NOTOK; R = theGraphIC(theGraph)->r; W = theGraphIC(theGraph)->w; // If the root copy is not a root copy of the current vertex v, // then the Walkdown terminated on a descendant bicomp, which is Minor A. if (gp_GetVertexFromBicompRoot(theGraph, R) != v) { theGraphIC(theGraph)->minorType |= MINORTYPE_A; return OK; } // If W has a pertinent child bicomp, then we've found Minor B. // Notice this is different from planarity, in which minor B is indicated // only if the pertinent child bicomp is also future pertinent. // NOTE: Each pertinent root is stored as the DFS child with which it is // associated, so we test gp_IsVertex, not gp_IsVirtualVertex here. if (gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, W))) { theGraphIC(theGraph)->minorType |= MINORTYPE_B; return OK; } // The only other result is minor E (we will search for the X-Y path later) theGraphIC(theGraph)->minorType |= MINORTYPE_E; return OK; } /**************************************************************************** _IsolateOuterplanarObstruction() ****************************************************************************/ int _IsolateOuterplanarObstruction(graphP theGraph, int v, int R) { int RetVal; /* A subgraph homeomorphic to K_{2,3} or K_4 will be isolated by using the visited flags, set=keep edge/vertex and clear=omit. Here we initialize to omit all, then we subsequently set visited on all edges and vertices in the homeomorph. */ _ClearAllVisitedFlagsInGraph(theGraph); /* Next we determineg which of the non-outerplanarity Minors was encountered and the principal bicomp on which the isolator will focus attention. */ if (_ChooseTypeOfNonOuterplanarityMinor(theGraph, v, R) != OK) return NOTOK; /* Find the path connecting the pertinent vertex w with the current vertex v */ if (theGraphIC(theGraph)->minorType & MINORTYPE_B) { isolatorContextP IC = theGraphIC(theGraph); int SubtreeRoot = gp_GetVertexLastPertinentRootChild(theGraph, IC->w); if (_FindUnembeddedEdgeToSubtree(theGraph, IC->v, SubtreeRoot, &IC->dw) != TRUE) return NOTOK; } else { isolatorContextP IC = theGraphIC(theGraph); if (_FindUnembeddedEdgeToCurVertex(theGraph, IC->w, &IC->dw) != TRUE) return NOTOK; } /* For minor E, we need to find and mark an X-Y path */ if (theGraphIC(theGraph)->minorType & MINORTYPE_E) { if (_MarkHighestXYPath(theGraph) != OK || theGraphIC(theGraph)->py == NIL) return NOTOK; } /* Call the appropriate isolator */ if (theGraphIC(theGraph)->minorType & MINORTYPE_A) RetVal = _IsolateOuterplanarityObstructionA(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_B) RetVal = _IsolateOuterplanarityObstructionB(theGraph); else if (theGraphIC(theGraph)->minorType & MINORTYPE_E) RetVal = _IsolateOuterplanarityObstructionE(theGraph); else RetVal = NOTOK; /* Delete the unmarked edges and vertices, and return */ if (RetVal == OK) RetVal = _DeleteUnmarkedVerticesAndEdges(theGraph); return RetVal; } /**************************************************************************** _IsolateOuterplanarityObstructionA(): Isolate a K2,3 homeomorph ****************************************************************************/ int _IsolateOuterplanarityObstructionA(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->v, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateOuterplanarityObstructionB(): Isolate a K2,3 homeomorph ****************************************************************************/ int _IsolateOuterplanarityObstructionB(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; return OK; } /**************************************************************************** _IsolateOuterplanarityObstructionE(): Isolate a K4 homeomorph ****************************************************************************/ int _IsolateOuterplanarityObstructionE(graphP theGraph) { isolatorContextP IC = theGraphIC(theGraph); if (_MarkPathAlongBicompExtFace(theGraph, IC->r, IC->r) != OK || theGraph->functions->fpMarkDFSPath(theGraph, IC->w, IC->dw) != OK || _JoinBicomps(theGraph) != OK || _AddAndMarkEdge(theGraph, IC->v, IC->dw) != OK) return NOTOK; return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphOuterplanarity.h000066400000000000000000000015451521450711600327060ustar00rootroot00000000000000#ifndef GRAPHOUTERPLANARITY_H #define GRAPHOUTERPLANARITY_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphPlanarity.h" #ifdef __cplusplus extern "C" { #endif // Create an Outerplanarity Graph, i.e., subclass a Planarity Graph by extending it // with the ability to perform planar graph embedding and obstruction isolation. #define OUTERPLANARITY_NAME "Outerplanarity" int gp_ExtendWith_Outerplanarity(graphP theGraph); int gp_Detach_Outerplanarity(graphP theGraph); /* Graph Flags: see gp_GetGraphFlags() GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY is set by calling gp_ExtendWith_OuterPlanarity() This is automatically by gp_Embed() if not already done. */ #define GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY 131072 #ifdef __cplusplus } #endif #endif graphOuterplanarity.private.h000066400000000000000000000006001521450711600342670ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHOUTERPLANARITY_PRIVATE_H #define GRAPHOUTERPLANARITY_PRIVATE_H #include "graphPlanarity.private.h" #ifdef __cplusplus extern "C" { #endif // Currently nothing to add here. #ifdef __cplusplus } #endif #endif /* GRAPHPOUTERLANARITY_PRIVATE_H */ graphOuterplanarity_Extensions.c000066400000000000000000000042101521450711600350310ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphOuterplanarity.h" #include "graphOuterplanarity.private.h" #include /**************************************************************************** gp_ExtendWith_Outerplanarity() This function is intended to subclass a Planarity Graph by extending it with the outerplanar graph embedding and obstruction isolation capabilities. If the given graph has not already been extended with Planarity, then gp_ExtendWith_Planarity() is called. To use Outerplanarity during gp_Embed(), use EMBEDFLAGS_OUTERPLANAR. Returns OK for success, NOTOK for failure. ****************************************************************************/ int gp_ExtendWith_Outerplanarity(graphP theGraph) { if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the Graph has already been extended with Outerplanarity, // then just return successfully if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY) return OK; // Ensure theGraph is a Planarity Graph if (gp_ExtendWith_Planarity(theGraph) != OK) return NOTOK; // Allocate supporting data structures as needed // Perform "on success" operations theGraph->graphFlags |= GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY; return OK; } /******************************************************************** gp_Detach_Outerplanarity() This function is intended to disinherit the outerplanar graph embedding and obstruction isolation feature by removing the extension from the graph, which also frees any outerplanarity-specific data structures. Clears GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY after detaching support for Outerplanarity. Returns OK on success, NOTOK on failure ********************************************************************/ int gp_Detach_Outerplanarity(graphP theGraph) { // Free any data structures allocated by the ExtendWith function // Indicate successful detachment of Outerplanarity theGraph->graphFlags &= ~GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY; return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphPlanarity.h000066400000000000000000000054271521450711600316320ustar00rootroot00000000000000#ifndef GRAPHPLANARITY_H #define GRAPHPLANARITY_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "../graphDFSUtils.h" #ifdef __cplusplus extern "C" { #endif // Create a Planarity Graph, i.e., subclass a DFSUtils Graph by extending it with // the ability to perform planar graph embedding and obstruction isolation. #define PLANARITY_NAME "Planarity" int gp_ExtendWith_Planarity(graphP theGraph); int gp_Detach_Planarity(graphP theGraph); /* Graph Flags: see gp_GetGraphFlags() GRAPHFLAGS_EXTENDEDWITH_PLANARITY is set by calling gp_ExtendWith_Planarity() This is automatically by gp_Embed() if not already done. */ #define GRAPHFLAGS_EXTENDEDWITH_PLANARITY 65536 // Graph embedding and result validation methods // The embedResult output by gp_Embed() and input to gp_TestEmbedResultIntegrity() // can be OK if the graph is embedded or embeddable, NONEMBEDDABLE if a minimal // subgraph obstructing embedding has been isolated, or NOTOK on error int gp_Embed(graphP theGraph, unsigned embedFlags); int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult); // A return result value for gp_Embed() to indicate success prior to embedding completion, // due to finding an obstruction to embedding. #define NONEMBEDDABLE -1 // Below are the possible graph embedFlags to pass to gp_Embed() and which are // then set into the graph by gp_Embed() and returned by this method. #define gp_GetEmbedFlags(theGraph) ((theGraph)->embedFlags) #define EMBEDFLAGS_PLANAR 1 #define EMBEDFLAGS_OUTERPLANAR 2 #define EMBEDFLAGS_DRAWPLANAR (4 | EMBEDFLAGS_PLANAR) #define EMBEDFLAGS_SEARCHFORK23 (8 | EMBEDFLAGS_OUTERPLANAR) #define EMBEDFLAGS_SEARCHFORK33 (16 | EMBEDFLAGS_PLANAR) #define EMBEDFLAGS_SEARCHFORK4 (32 | EMBEDFLAGS_OUTERPLANAR) // Reserve flag bits for possible future embedding-related extension modules #define EMBEDFLAGS_SEARCHFORK5 (64 | EMBEDFLAGS_PLANAR) #define EMBEDFLAGS_SEARCHFORK5MINOR (128 | EMBEDFLAGS_PLANAR) #define EMBEDFLAGS_MAXIMALPLANARSUBGRAPH (256 | EMBEDFLAGS_PLANAR) #define EMBEDFLAGS_PROJECTIVEPLANAR 512 #define EMBEDFLAGS_TOROIDAL 1024 // After gp_Embed(), if the result is NONEMBEDDABLE, then this method // returns the obstructing minor type from the list below. // It is best to compare using a bitwise-and operation. unsigned gp_GetObstructionMinorType(graphP theGraph); #define MINORTYPE_NONE 0 #define MINORTYPE_A 1 #define MINORTYPE_B 2 #define MINORTYPE_C 4 #define MINORTYPE_D 8 #define MINORTYPE_E 16 #define MINORTYPE_E1 32 #define MINORTYPE_E2 64 #define MINORTYPE_E3 128 #define MINORTYPE_E4 256 #define MINORTYPE_E5 512 #define MINORTYPE_E6 1024 #define MINORTYPE_E7 2048 #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphPlanarity.private.h000066400000000000000000000420271521450711600333000ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifndef GRAPHPLANARITY_PRIVATE_H #define GRAPHPLANARITY_PRIVATE_H #include "../graphDFSUtils.private.h" #ifdef __cplusplus extern "C" { #endif // PLANARITY-RELATED ONLY VERTEX FLAGS // // The ANYVERTEX_OBSTRUCTIONMARK_MASK bits are bits 2-4, 4+8+16=28 // They are used by planarity-related algorithms to identify the four // regions of the external face cycle of a bicomp, relative to an // XY-path in the bicomp. // Bit 2 - 4 if the OBSTRUCTIONMARK is set, 0 if not // Bit 3 - 8 if the OBSTRUCTIONMARK indicates Y side, 0 if X side // Bit 4 - 16 if the OBSTRUCTIONMARK indicates high, 0 if low #define ANYVERTEX_OBSTRUCTIONMARK_MASK 28 // Call gp_GetObstructionMark, then compare to one of these four possibilities // ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW - On the external face path between vertices R and X // ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW - X or on the external face path between vertices X and W // ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW - On the external face path between vertices R and Y // ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW - Y or on the external face path between vertices Y and W // ANYVERTEX_OBSTRUCTIONMARK_UNMARKED - corresponds to all three bits off #define ANYVERTEX_OBSTRUCTIONMARK_HIGH_RXW 20 #define ANYVERTEX_OBSTRUCTIONMARK_LOW_RXW 4 #define ANYVERTEX_OBSTRUCTIONMARK_HIGH_RYW 28 #define ANYVERTEX_OBSTRUCTIONMARK_LOW_RYW 12 #define ANYVERTEX_OBSTRUCTIONMARK_UNMARKED 0 #define gp_GetObstructionMark(theGraph, v) (theGraph->V[v].flags & ANYVERTEX_OBSTRUCTIONMARK_MASK) #define gp_ClearObstructionMark(theGraph, v) (theGraph->V[v].flags &= ~ANYVERTEX_OBSTRUCTIONMARK_MASK) #define gp_SetObstructionMark(theGraph, v, type) (theGraph->V[v].flags |= type) #define gp_ResetObstructionMark(theGraph, v, type) \ (theGraph->V[v].flags = (theGraph->V[v].flags & ~ANYVERTEX_OBSTRUCTIONMARK_MASK) | type) /******************************************************************** // PLANARITY-RELATED ONLY // This structure defines a pair of links used by each vertex and virtual vertex to create "short circuit" paths that eliminate unimportant vertices from the external face, enabling more efficient traversal of the external face. It is also possible to embed the "short circuit" edges, but this approach creates a better separation of concerns, imparts greater clarity, and removes exceptionalities for handling additional fake "short circuit" edges. vertex[2]: The two adjacent vertices along the external face, possibly short-circuiting paths of inactive vertices. */ struct extFaceLinkRec { int vertex[2]; }; typedef struct extFaceLinkRec extFaceLinkRec; typedef extFaceLinkRec *extFaceLinkRecP; #define gp_GetExtFaceVertex(theGraph, v, link) (theGraphExtFace(theGraph)[v].vertex[link]) #define gp_SetExtFaceVertex(theGraph, v, link, theVertex) (theGraphExtFace(theGraph)[v].vertex[link] = theVertex) /******************************************************************** // PLANARITY-RELATED ONLY // Planarity-specific additional vertex information. visitedInfo: enables algorithms to manage vertex visitation with more than just a flag. For example, the planarity test flags visitation as a step number that implicitly resets on each step, whereas part of the planar drawing method signifies a first visitation by storing the index of the first edge used to reach a vertex pertinentEdge: Used by the planarity method; during Walkup, each vertex that is directly adjacent via a back edge to the vertex v currently being embedded will have the forward edge's index stored in this field. During Walkdown, each vertex for which this field is set will cause a back edge to be embedded. Implicitly resets at each vertex step of the planarity method pertinentRootsList: used by Walkup to store a list of child bicomp roots of a vertex descendant of the current vertex that are pertinent and must be merged by the Walkdown in order to embed the cycle edges of the current vertex. Future pertinent child bicomp roots are placed at the end of the list to ensure bicomps that are only pertinent are processed first. futurePertinentChild: indicates a DFS child with a lowpoint less than the current vertex v. This member is initialized to the start of the sortedDFSChildList and is advanced in a relaxed manner as needed until one with a lowpoint less than v is found or until there are no more children. sortedDFSChildList: at the start of embedding, the list of DFS children of this vertex is calculated in ascending order by DFI (sorted in linear time). The list is used during Walkdown processing of a vertex to process all of its children. It is also used in future pertinence management when processing the ancestors of the vertex. When a child C is merged into the same bicomp as the vertex, it is removed from the list. fwdEdgeList: at the start of embedding, the "back" edges from a vertex to its DFS *descendants* (i.e. the forward edge records) are separated from the main adjacency list and placed in a circular list until they are embedded. The list is sorted in ascending DFI order of the descendants (in linear time). This member indicates (by index) a node in that list. */ struct Planarity_VertexInfo { int visitedInfo; int pertinentEdge, pertinentRoots, futurePertinentChild, sortedDFSChildList, fwdEdgeList; }; typedef struct Planarity_VertexInfo Planarity_VertexInfo; typedef Planarity_VertexInfo *Planarity_VertexInfoP; #define gp_GetVertexVisitedInfo(theGraph, v) (theGraphPVI(theGraph)[v].visitedInfo) #define gp_SetVertexVisitedInfo(theGraph, v, theVisitedInfo) (theGraphPVI(theGraph)[v].visitedInfo = theVisitedInfo) #define gp_GetVertexPertinentEdge(theGraph, v) (theGraphPVI(theGraph)[v].pertinentEdge) #define gp_SetVertexPertinentEdge(theGraph, v, e) (theGraphPVI(theGraph)[v].pertinentEdge = e) #define gp_GetVertexPertinentRootsList(theGraph, v) (theGraphPVI(theGraph)[v].pertinentRoots) #define gp_SetVertexPertinentRootsList(theGraph, v, pertinentRootsHead) (theGraphPVI(theGraph)[v].pertinentRoots = pertinentRootsHead) #define gp_GetVertexFirstPertinentRoot(theGraph, v) gp_GetBicompRootFromDFSChild(theGraph, theGraphPVI(theGraph)[v].pertinentRoots) #define gp_GetVertexFirstPertinentRootChild(theGraph, v) (theGraphPVI(theGraph)[v].pertinentRoots) #define gp_GetVertexLastPertinentRoot(theGraph, v) gp_GetBicompRootFromDFSChild(theGraph, LCGetPrev(theGraphBicompRootLists(theGraph), theGraphPVI(theGraph)[v].pertinentRoots, NIL)) #define gp_GetVertexLastPertinentRootChild(theGraph, v) LCGetPrev(theGraphBicompRootLists(theGraph), theGraphPVI(theGraph)[v].pertinentRoots, NIL) #define gp_DeleteVertexPertinentRoot(theGraph, v, R) \ gp_SetVertexPertinentRootsList(theGraph, v, \ LCDelete(theGraphBicompRootLists(theGraph), \ gp_GetVertexPertinentRootsList(theGraph, v), \ gp_GetDFSChildFromBicompRoot(theGraph, R))) #define gp_PrependVertexPertinentRoot(theGraph, v, R) \ gp_SetVertexPertinentRootsList(theGraph, v, \ LCPrepend(theGraphBicompRootLists(theGraph), \ gp_GetVertexPertinentRootsList(theGraph, v), \ gp_GetDFSChildFromBicompRoot(theGraph, R))) #define gp_AppendVertexPertinentRoot(theGraph, v, R) \ gp_SetVertexPertinentRootsList(theGraph, v, \ LCAppend(theGraphBicompRootLists(theGraph), \ gp_GetVertexPertinentRootsList(theGraph, v), \ gp_GetDFSChildFromBicompRoot(theGraph, R))) #define gp_GetVertexFuturePertinentChild(theGraph, v) (theGraphPVI(theGraph)[v].futurePertinentChild) #define gp_SetVertexFuturePertinentChild(theGraph, v, theFuturePertinentChild) (theGraphPVI(theGraph)[v].futurePertinentChild = theFuturePertinentChild) // Used to advance futurePertinentChild of w to the next separated DFS child with a lowpoint less than v // Once futurePertinentChild advances past a child, no future planarity operation could make that child // relevant to future pertinence. #define gp_UpdateVertexFuturePertinentChild(theGraph, w, v) \ while (gp_IsVertex(theGraph, theGraphPVI(theGraph)[w].futurePertinentChild)) \ { \ /* Skip children that 1) aren't future pertinent, 2) have been merged into the bicomp with w */ \ if (gp_GetVertexLowpoint(theGraph, theGraphPVI(theGraph)[w].futurePertinentChild) >= v || \ gp_IsNotSeparatedDFSChild(theGraph, theGraphPVI(theGraph)[w].futurePertinentChild)) \ { \ theGraphPVI(theGraph)[w].futurePertinentChild = \ gp_GetVertexNextDFSChild(theGraph, w, gp_GetVertexFuturePertinentChild(theGraph, w)); \ } \ else \ break; \ } #define gp_GetVertexSortedDFSChildList(theGraph, v) (theGraphPVI(theGraph)[v].sortedDFSChildList) #define gp_SetVertexSortedDFSChildList(theGraph, v, theSortedDFSChildList) (theGraphPVI(theGraph)[v].sortedDFSChildList = theSortedDFSChildList) #define gp_GetVertexNextDFSChild(theGraph, v, c) LCGetNext(theGraphSortedDFSChildLists(theGraph), gp_GetVertexSortedDFSChildList(theGraph, v), c) #define gp_AppendDFSChild(theGraph, v, c) \ LCAppend(theGraphSortedDFSChildLists(theGraph), gp_GetVertexSortedDFSChildList(theGraph, v), c) #define gp_GetVertexFwdEdgeList(theGraph, v) (theGraphPVI(theGraph)[v].fwdEdgeList) #define gp_SetVertexFwdEdgeList(theGraph, v, theFwdEdgeList) (theGraphPVI(theGraph)[v].fwdEdgeList = theFwdEdgeList) /******************************************************************** // PLANARITY-RELATED ONLY // Variables needed in embedding by Kuratowski subgraph isolator: minorType: the type of planarity obstruction found. v: the current vertex being processed r: the root of the bicomp on which the Walkdown failed x,y: stopping vertices on bicomp rooted by r w: pertinent vertex on ext. face path below x and y px, py: attachment points of x-y path, z: Unused except in minors D and E (not needed in A, B, C). ux,dx: endpoints of unembedded edge that helps connext x with ancestor of v uy,dy: endpoints of unembedded edge that helps connext y with ancestor of v dw: descendant endpoint in unembedded edge to v uz,dz: endpoints of unembedded edge that helps connext z with ancestor of v (for minors B and E, not A, C, D). */ struct isolatorContextStruct { unsigned minorType; int v, r, x, y, w, px, py, z; int ux, dx, uy, dy, dw, uz, dz; }; typedef struct isolatorContextStruct isolatorContextStruct; typedef isolatorContextStruct *isolatorContextP; //******************************************************************** // A few simple integer selection macros for obstruction isolation //******************************************************************** #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN3(x, y, z) MIN(MIN((x), (y)), MIN((y), (z))) #define MAX3(x, y, z) MAX(MAX((x), (y)), MAX((y), (z))) /******************************************************************** PERTINENT() A vertex is pertinent in a partially processed graph if there is an unprocessed back edge between the vertex v whose edges are currently being processed and either the vertex or a DFS descendant D of the vertex not in the same bicomp as the vertex. The vertex is either directly adjacent to v by an unembedded back edge or there is an unembedded back edge (v, D) and the vertex is a cut vertex in the partially processed graph along the DFS tree path from D to v. Pertinence is a dynamic property that can change for a vertex after each edge addition. In other words, a vertex can become non-pertinent during step v as more back edges to v are embedded. NOTE: Pertinent roots are stored using the DFS children with which they are associated, so we test 'is vertex' (rather than virtual). ********************************************************************/ #define PERTINENT(theGraph, theVertex) \ (gp_IsEdge(theGraph, gp_GetVertexPertinentEdge(theGraph, theVertex)) || \ gp_IsVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, theVertex))) #define NOTPERTINENT(theGraph, theVertex) \ (gp_IsNotEdge(theGraph, gp_GetVertexPertinentEdge(theGraph, theVertex)) && \ gp_IsNotVertex(theGraph, gp_GetVertexPertinentRootsList(theGraph, theVertex))) /******************************************************************** FUTUREPERTINENT() A vertex is future-pertinent in a partially processed graph if there is an unprocessed back edge between a DFS ancestor A of the vertex v whose edges are currently being processed and either theVertex or a DFS descendant D of theVertex not in the same bicomp as theVertex. Either theVertex is directly adjacent to A by an unembedded back edge or there is an unembedded back edge (A, D) and theVertex is a cut vertex in the partially processed graph along the DFS tree path from D to A. If no more edges are added to the partially processed graph prior to processing the edges of A, then the vertex would be pertinent. The addition of edges to the partially processed graph can alter both the pertinence and future pertinence of a vertex. For example, if the vertex is pertinent due to an unprocessed back edge (v, D1) and future pertinent due to an unprocessed back edge (A, D2), then the vertex may lose both its pertinence and future pertinence when edge (v, D1) is added if D2 is in the same subtree as D1. Generally, pertinence and future pertinence are dynamic properties that can change for a vertex after each edge addition. Note that gp_UpdateVertexFuturePertinentChild() must be called before this macro. Since it is a statement and not a void expression, the desired commented out version does not compile (except with special compiler extensions not assumed by this code). ********************************************************************/ #define FUTUREPERTINENT(theGraph, theVertex, v) \ (theGraphDVI(theGraph)[theVertex].leastAncestor < v || \ (gp_IsVertex(theGraph, theGraphPVI(theGraph)[theVertex].futurePertinentChild) && \ theGraphDVI(theGraph)[theGraphPVI(theGraph)[theVertex].futurePertinentChild].lowpoint < v)) #define NOTFUTUREPERTINENT(theGraph, theVertex, v) \ (theGraphDVI(theGraph)[theVertex].leastAncestor >= v && \ (gp_IsNotVertex(theGraph, theGraphPVI(theGraph)[theVertex].futurePertinentChild) || \ theGraphDVI(theGraph)[theGraphPVI(theGraph)[theVertex].futurePertinentChild].lowpoint >= v)) /******************************************************************** INACTIVE() For planarity algorithms, a vertex is inactive if it is neither pertinent nor future pertinent. ********************************************************************/ #define INACTIVE(theGraph, theVertex, v) \ (NOTPERTINENT(theGraph, theVertex) && \ NOTFUTUREPERTINENT(theGraph, theVertex, v)) #ifdef __cplusplus } #endif #endif /* GRAPHPLANARITY_PRIVATE_H */ graphPlanarity_Extensions.c000066400000000000000000000041231521450711600337550ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "graphPlanarity.h" #include "graphPlanarity.private.h" #include /**************************************************************************** gp_ExtendWith_Planarity() This function is intended to subclass a DFSUtils Graph by extending it with the planar graph embedding and obstruction isolation capabilities and any additional required data structures. If the given graph has not already been extended with DFSUtils, then gp_ExtendWith_DFSUtils() is called. To use Planarity during gp_Embed(), use EMBEDFLAGS_PLANAR. Returns OK for success, NOTOK for failure. ****************************************************************************/ int gp_ExtendWith_Planarity(graphP theGraph) { if (theGraph == NULL || gp_GetN(theGraph) <= 0) return NOTOK; // If the Graph has already been extended with Planarity, // then just return successfully if (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_EXTENDEDWITH_PLANARITY) return OK; // Ensure theGraph is a DFSUtils Graph if (gp_ExtendWith_DFSUtils(theGraph) != OK) return NOTOK; // Allocate supporting data structures as needed // Perform "on success" operations theGraph->graphFlags |= GRAPHFLAGS_EXTENDEDWITH_PLANARITY; return OK; } /******************************************************************** gp_Detach_Planarity() This function is intended to disinherit the planar graph embedding and obstruction isolation feature by remove the extension from the graph, which also frees any planarity-specific data structures. Clears GRAPHFLAGS_EXTENDEDWITH_PLANARITY after detaching support for Planarity. Returns OK on success, NOTOK on failure ********************************************************************/ int gp_Detach_Planarity(graphP theGraph) { // Free any data structures allocated by the ExtendWith function // Indicate successful detachment of Planarity theGraph->graphFlags &= ~GRAPHFLAGS_EXTENDEDWITH_PLANARITY; return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphTests.c000066400000000000000000001102501521450711600307530ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #define GRAPHTEST_C // This source file implements the main graph planarity result integrity check method // NOTE: Integrity checks for subclasses of Planarity Graph that are extended with // extensions for advanced algorithms are performed by overloads of the // integrity check function in those extensions, e.g., K33Search and DrawPlanar. #include "graphPlanarity.h" #include "graphPlanarity.private.h" extern void _ClearVertexVisitedFlags(graphP theGraph, int); /* Private function declarations (some exported to system) */ int _TestPath(graphP theGraph, int U, int V); int _TryPath(graphP theGraph, int e, int V); void _MarkPath(graphP theGraph, int e); int _TestSubgraph(graphP theSubgraph, graphP theGraph); int _CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph); int _CheckEmbeddingFacialIntegrity(graphP theGraph); int _CheckObstructionIntegrity(graphP theGraph, graphP origGraph); int _getImageVertices(graphP theGraph, int *degrees, int maxDegree, int *imageVerts, int maxNumImageVerts); int _TestForCompleteGraphObstruction(graphP theGraph, int numVerts, int *degrees, int *imageVerts); int _TestForK33GraphObstruction(graphP theGraph, int *degrees, int *imageVerts); int _TestForK23GraphObstruction(graphP theGraph, int *degrees, int *imageVerts); int _CheckKuratowskiSubgraphIntegrity(graphP theGraph); int _CheckOuterplanarObstructionIntegrity(graphP theGraph); int _CheckAllVerticesOnExternalFace(graphP theGraph); void _MarkExternalFaceVertices(graphP theGraph, int startVertex); /******************************************************************** gp_TestEmbedResultIntegrity() This function tests the integrity of the graph result returned from gp_Embed(). The caller of gp_Embed() does not have to save the original graph because, for efficiency, gp_Embed() operates on the input graph. However, to test the integrity of the result relative to the input, a copy of the input graph is required. Modules that extend/alter the behavior of gp_Embed() beyond the core planarity embedder and planarity obstruction isolator should also provide overriding integrity test routines appropriate to the extension algorithm. The main method first calls gp_SortVertices on theGraph, if the origGraph is not in DFI order (the common case). Therefore, extension integrity tests can count on a consistent numbering between theGraph and the origGraph, either DFI order or pre-DFS order if that is the state of the origGraph. After all tests, the main method ensures theGraph is restored to DFI order by invoking gp_SortVertices if needed, thus ensuring that theGraph has the documented post-condition of gp_Embed(). For an embedResult of OK, fpCheckEmbeddingIntegrity is invoked. The core planarity implementation does a face walk of all faces of the embedding. It ensures that all edges were used in the face walk and that the right number of faces exist for the number of vertices and edges. Also, we ensure that all adjacencies expressed in the original graph still exist in the result graph. For an embedResult of NONEMBEDDABLE, fpCheckObstructionIntegrity is invoked. The core planarity algorithm checks that the result graph is homeomorphic to K5 or K3,3 and that it is in fact a subgraph of the input graph. Other algorithms use overloads to make appropriate checks. Returns NOTOK on integrity check failure or embedResult of NOTOK OK for successful integrity check of OK embedResult NONEMBEDDABLE for successful integrity check of an embedResult of NONEMBEDDABLE ********************************************************************/ int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) { int RetVal = embedResult; if (theGraph == NULL || origGraph == NULL) return NOTOK; if (embedResult == OK) { RetVal = theGraph->functions->fpCheckEmbeddingIntegrity(theGraph, origGraph); } else if (embedResult == NONEMBEDDABLE) { RetVal = theGraph->functions->fpCheckObstructionIntegrity(theGraph, origGraph); } if (RetVal == OK) RetVal = embedResult; return RetVal; } /******************************************************************** _CheckEmbeddingIntegrity() The core planarity implementation does a face walk of all faces of the embedding. It ensures that all edges were used in the face walk and that the right number of faces exist for the number of vertices and edges. Also, we ensure that all adjacencies expressed in the original graph still exist in the result graph, accounting for the fact that the result graph is sorted by DFI, but the input may or may not be sorted by DFI. returns OK if all integrity tests passed, NOTOK otherwise ********************************************************************/ int _CheckEmbeddingIntegrity(graphP theGraph, graphP origGraph) { if (theGraph == NULL || origGraph == NULL) return NOTOK; if (_TestSubgraph(theGraph, origGraph) != TRUE) return NOTOK; if (_TestSubgraph(origGraph, theGraph) != TRUE) return NOTOK; if (_CheckEmbeddingFacialIntegrity(theGraph) != OK) return NOTOK; if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_OUTERPLANAR) { if (_CheckAllVerticesOnExternalFace(theGraph) != OK) return NOTOK; } return OK; } /******************************************************************** _CheckEmbeddingFacialIntegrity() This function traverses all faces of a graph structure containing the planar embedding that results from gp_Embed(). The algorithm begins by placing all of the graph's edge records onto a stack and marking all of them as unvisited. For each edge record popped, if it is visited, it is discarded and the next edge record is popped. Popping an unvisited edge record e begins a face traversal. We move to the true twin edge record of e, and obtain its adjacency list successor. This amounts to always going clockwise or counterclockwise (depending on how the graph is drawn on the plane, or alternately whether one is above or below the plane). This traversal continues until we make it back to the original edge record e. Each edge record along the way is marked as visited. Further, if the successor edge record has been visited, then there is an error since an edge record can only appear in one face (the twin edge record in an edge appears in a separate face, which is traversed in the opposing direction). If this algorithm succeeds without doubly visiting any edge records, and it produces the correct face count according to Euler's formula, then the embedding has all vertices oriented the same way. NOTE: In disconnected graphs, the face reader counts the external face of each connected component. So, we adjust the face count by subtracting one for each component, then we add one to count the external face shared by all components. ********************************************************************/ int _CheckEmbeddingFacialIntegrity(graphP theGraph) { stackP theStack = theGraph->theStack; int v, e, eTwin, eStart, eNext, NumFaces, connectedComponents; if (theGraph == NULL) return NOTOK; /* The stack need only contain 2M entries, one for each edge record. With max M at 3N, this amounts to 6N integers of space. The embedding structure already contains this stack, so we just make sure it starts out empty. */ sp_ClearStack(theStack); /* Push all edge records (both parts of each edge) and set them all to unvisited */ for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); e += 2) { // Except skip edge holes if (gp_EdgeInUse(theGraph, e)) { // Push e and clear its visited flag sp_Push(theStack, e); gp_ClearEdgeVisited(theGraph, e); // And now the same for the twin of e eTwin = gp_GetTwin(theGraph, e); sp_Push(theStack, eTwin); gp_ClearEdgeVisited(theGraph, eTwin); } } // There are M edges, so we better have pushed 2M edge records, // i.e., we test that the continue above skipped edge holes and // didn't push edge records beyond the boundary of those in use. if (sp_GetCurrentSize(theStack) != 2 * gp_GetM(theGraph)) return NOTOK; /* Read faces until every edge record is popped */ NumFaces = 0; while (sp_NonEmpty(theStack)) { /* Get an edge record; if it has already been used by a face, then don't use it to traverse a new face */ sp_Pop(theStack, eStart); if (gp_GetEdgeVisited(theGraph, eStart)) continue; e = eStart; do { eNext = gp_GetNextEdgeCircular(theGraph, gp_GetTwin(theGraph, e)); if (gp_GetEdgeVisited(theGraph, eNext)) return NOTOK; gp_SetEdgeVisited(theGraph, eNext); e = eNext; } while (e != eStart); NumFaces++; } /* Count the external face once rather than once per connected component; each connected component is detected by the fact that it has no DFS parent, except in the case of isolated vertices, no face was counted so we do not subtract one. */ connectedComponents = 0; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (gp_IsDFSTreeRoot(theGraph, v)) { if (gp_GetVertexDegree(theGraph, v) > 0) NumFaces--; connectedComponents++; } } NumFaces++; /* Test number of faces using the extended Euler's formula. For connected components, Euler's formula is f=m-n+2, but for disconnected graphs it is extended to f=m-n+1+c where c is the number of connected components.*/ return NumFaces == gp_GetM(theGraph) - gp_GetN(theGraph) + 1 + connectedComponents ? OK : NOTOK; } /******************************************************************** _CheckAllVerticesOnExternalFace() Determines whether or not any vertices have been embedded within the bounding cycle of the external face. The input graph may be disconnected, so this routine walks the external face starting at each vertex with no DFSParent. return OK if all vertices visited on external face walks, NOTOK otherwise ********************************************************************/ int _CheckAllVerticesOnExternalFace(graphP theGraph) { int v; // Mark all vertices unvisited _ClearVertexVisitedFlags(theGraph, FALSE); // For each connected component, walk its external face and // mark the vertices as visited for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { if (gp_IsDFSTreeRoot(theGraph, v)) _MarkExternalFaceVertices(theGraph, v); } // If any vertex is unvisited, then the embedding is not an outerplanar // embedding, so we return NOTOK for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (!gp_GetVisited(theGraph, v)) return NOTOK; // All vertices were found on external faces of the connected components // so the embedding is an outerplanar embedding and we return OK return OK; } /******************************************************************** _MarkExternalFaceVertices() Walks the external face of the connected component containing the start vertex, and marks the visited flag of all vertices found. The start vertex is assumed to be on the external face. This method assumed the embedding integrity has already been verified to be correct. This method correctly handles components that have cut vertices, i.e. it does not assume that the outer face is a simple cycle; it only assumes that all vertices are reachable by walking a single face that starts with startVertex. ********************************************************************/ void _MarkExternalFaceVertices(graphP theGraph, int startVertex) { int nextVertex = startVertex; int e = gp_GetFirstEdge(theGraph, nextVertex); int eTwin; // Handle the case of an isolated vertex if (gp_IsNotEdge(theGraph, e)) { gp_SetVisited(theGraph, startVertex); return; } // Process a non-trivial connected component do { gp_SetVisited(theGraph, nextVertex); // The edge record out of the vertex just visited points to the next vertex nextVertex = gp_GetNeighbor(theGraph, e); // The edge record used to enter the next vertex is needed so we can get // the next edge in rotation order. // Note: for bicomps, first and last edges of all external face vertices // indicate the edges that hold them to the external face // But _JoinBicomps() has already occurred, so cut vertices // will have external face edges other than their first and last // edge records, so we need this more sophisticated traversal method. eTwin = gp_GetTwin(theGraph, e); // Now we get the next edge in rotation order as the new edge out to the // vertex after nextVertex. This sets us up for the next iteration. // NOTE: We cannot simply follow the chain of nextVertex first edges // as we started out doing at the top of this method. This is // because we are no longer dealing with bicomps only. // Since _JoinBicomps() has already been invoked, there may now // be cut vertices on the external face whose adjacency lists // contain external face edges in positions other than their first // and last edges. We will visit those vertices multiple times, // which is OK (just that we have to explain why we're calculating // the out edge to exit a vertex in this way). e = gp_GetNextEdgeCircular(theGraph, eTwin); // Now things get really interesting. The DFS root (startVertex) may // itself be a cut vertex to which multiple bicomps have been joined. // So we cannot simply stop when the external face walk gets back to // startVertex. We must actually get back to startVertex using its // last edge. This ensures that we've looped down into all the DFS // subtrees rooted at startVertex and walked their external faces. // Since we started the whole external face walk with the first edge // of startVertex, we need to proceed until we reenter startVertex // using its last edge. } while (eTwin != gp_GetLastEdge(theGraph, startVertex)); } /******************************************************************** _CheckObstructionIntegrity() Returns OK if theGraph is a subgraph of origGraph and it contains an allowed homeomorph, and NOTOK otherwise. For core planarity, the allowed homeomorphs are K_5 or K_{3,3} Extension modules may overload this method to implement different tests. For example, K_{3,3} search allows only K_{3,3} and outerplanarity allows only K_4 or K_{2,3}. ********************************************************************/ int _CheckObstructionIntegrity(graphP theGraph, graphP origGraph) { if (theGraph == NULL || origGraph == NULL) return NOTOK; if (_TestSubgraph(theGraph, origGraph) != TRUE) { return NOTOK; } if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_PLANAR) return _CheckKuratowskiSubgraphIntegrity(theGraph); else if (gp_GetEmbedFlags(theGraph) == EMBEDFLAGS_OUTERPLANAR) return _CheckOuterplanarObstructionIntegrity(theGraph); return NOTOK; } /******************************************************************** _getImageVertices() Count the number of vertices of each degree and find the locations of the image vertices (also sometimes called the corners of the obstruction). An image vertex is a vertex of degree three or higher because degree 2 vertices are generally internal to the paths between the image vertices. The notable exception is K_{2,3}, an obstruction to outerplanarity. This routine does not know the obstruction it is looking for, so the caller must decide whether there are any degree 2 vertices that should be added to imageVerts. Return NOTOK if any vertex of degree 1 or higher than the max is found NOTOK if more than the max number of image vertices is found. Return OK otherwise. ********************************************************************/ int _getImageVertices(graphP theGraph, int *degrees, int maxDegree, int *imageVerts, int maxNumImageVerts) { int K, v, imageVertPos, degree; for (degree = 0; degree <= maxDegree; degree++) degrees[degree] = 0; for (K = 0; K < maxNumImageVerts; K++) imageVerts[K] = NIL; imageVertPos = 0; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) { degree = gp_GetVertexDegree(theGraph, v); if (degree == 1) return NOTOK; if (degree > maxDegree) return NOTOK; degrees[degree]++; if (imageVertPos < maxNumImageVerts && degree > 2) imageVerts[imageVertPos++] = v; else if (degree > 2) return NOTOK; } return OK; } /******************************************************************** _TestForCompleteGraphObstruction() theGraph - the graph to test numVerts - the number of image vertices (corners) of the complete graph homeomorph being tested for (e.g. 5 for a K5) degrees - array of counts of the number of vertices of each degree given by the array index. Only valid up to numVerts-1 imageVerts - the vertices of degree numVerts-1 This routine tests whether theGraph is a K_{numVerts} homeomorph for numVerts >= 4. returns FALSE if numVerts < 4, if theGraph has other than numVerts image vertices if theGraph contains other than degree 2 vertices plus the image vertices if any pair of image vertices lacks a connecting path if any degree two vertices are not in the connecting paths TRUE otherwise ********************************************************************/ int _TestForCompleteGraphObstruction(graphP theGraph, int numVerts, int *degrees, int *imageVerts) { int v, w; // We need to make sure we have numVerts vertices of degree numVerts-1 // For example, if numVerts==5, then we're looking for a K5, so we // need to have degrees[4] == 5 (5 vertices of degree 4) if (degrees[numVerts - 1] != numVerts) return FALSE; // All vertices need to be degree 0, degree 2 or degree numVerts-1 if (degrees[0] + degrees[2] + degrees[numVerts - 1] != gp_GetN(theGraph)) return FALSE; // We clear all the vertex visited flags _ClearVertexVisitedFlags(theGraph, FALSE); // For each pair of image vertices, we test that there is a path // between the two vertices. If so, the visited flags of the // internal vertices along the path are marked // for (v = 0; v < numVerts; v++) for (w = 0; w < numVerts; w++) if (v != w) if (_TestPath(theGraph, imageVerts[v], imageVerts[w]) != TRUE) return FALSE; // The visited flags should have marked only degree two vertices, // so for every marked vertex, we subtract one from the count of // the degree two vertices. for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (gp_GetVisited(theGraph, v)) degrees[2]--; /* If every degree 2 vertex is used in a path between image vertices, then there are no extra pieces of the graph in theGraph. Specifically, the prior tests identify a K_5 and ensure that nothing else could exist in the graph except extra degree 2 vertices, which must be joined in a cycle so that all are degree 2. */ return degrees[2] == 0 ? TRUE : FALSE; } /******************************************************************** _TestForK33GraphObstruction() theGraph - the graph to test degrees - array of counts of the number of vertices of each degree given by the array index. Only valid up to numVerts-1 imageVerts - the degree 3 vertices of the K3,3 homeomorph This routine tests whether theGraph is a K_{3,3} homeomorph. returns TRUE if so, FALSE if not ********************************************************************/ int _TestForK33GraphObstruction(graphP theGraph, int *degrees, int *imageVerts) { int v, K, imageVertPos, temp, success; if (degrees[4] != 0) return FALSE; if (degrees[3] != 6) return FALSE; /* Partition the six image vertices into two sets of 3 (or report failure) */ for (imageVertPos = 3; imageVertPos < 6; imageVertPos++) { K = 0; success = FALSE; do { if (_TestPath(theGraph, imageVerts[imageVertPos], imageVerts[0]) == TRUE) { success = TRUE; break; } K++; temp = imageVerts[K]; imageVerts[K] = imageVerts[imageVertPos]; imageVerts[imageVertPos] = temp; } while (K < 3); if (!success) return FALSE; } /* Now test the paths between each of the first three vertices and each of the last three vertices */ _ClearVertexVisitedFlags(theGraph, FALSE); for (imageVertPos = 0; imageVertPos < 3; imageVertPos++) for (K = 3; K < 6; K++) if (_TestPath(theGraph, imageVerts[imageVertPos], imageVerts[K]) != TRUE) return FALSE; for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (gp_GetVisited(theGraph, v)) degrees[2]--; /* If every degree 2 vertex is used in a path between image vertices, then there are no extra pieces of the graph in theGraph. Specifically, the prior tests identify a K_{3,3} and ensure that nothing else could exist in the graph except extra degree 2 vertices, which must be joined in a cycle so that all are degree 2. */ return degrees[2] == 0 ? TRUE : FALSE; } /******************************************************************** _CheckKuratowskiSubgraphIntegrity() This function checks whether theGraph received as input contains either a K_5 or K_{3,3} homeomorph. RETURNS: OK if theGraph contains a K_5 or K_{3,3} homeomorph, NOTOK otherwise To be a K_5 homeomorph, there must be exactly 5 vertices of degree 4, which are called 'image' vertices, and all other vertices must be either degree 2 or degree 0. Furthermore, each of the image vertices must be able to reach all of the other image vertices by a single edge or a path of degree two vertices. To be a K_{3,3} homeomorph, there must be exactly 6 vertices of degree 3, which are called 'image' vertices, and all other vertices must be either degree 2 or degree 0. Furthermore, the image vertices must be connected by edges or paths of degree two vertices in the manner suggested by a K_{3,3}. To test this, we select an image vertex U, and determine three image vertices X, Y and Z reachable from U by single edges or paths of degree 2 vertices. Then, we check that the two remaining image vertices, V and W, can also reach X, Y and Z by single edges or paths of degree 2 vertices. It is not necessary to check that the paths between the image vertices are distinct since if the paths had a common vertex, then the common vertex would not be degree 2. ********************************************************************/ int _CheckKuratowskiSubgraphIntegrity(graphP theGraph) { int degrees[5], imageVerts[6]; if (_getImageVertices(theGraph, degrees, 4, imageVerts, 6) != OK) return NOTOK; if (_TestForCompleteGraphObstruction(theGraph, 5, degrees, imageVerts) == TRUE) { return OK; } if (_TestForK33GraphObstruction(theGraph, degrees, imageVerts) == TRUE) { return OK; } return NOTOK; } /******************************************************************** _TestForK23GraphObstruction() theGraph - the graph to test degrees - array of counts of the number of vertices of each degree given by the array index. Only valid up to numVerts-1 imageVerts - the degree 3 vertices of the K2,3 homeomorph This routine tests whether theGraph is a K_{2,3} homeomorph. This routine operates over the results of _getImageVertices() returns TRUE if so, FALSE if not ********************************************************************/ int _TestForK23GraphObstruction(graphP theGraph, int *degrees, int *imageVerts) { int v, e, imageVertPos; // This function operates over the imageVerts results produced by // getImageVertices, which only finds vertices of degree 3 or higher. // So, for a K2,3, there must be exactly two degree 3 vertices and // no degree 4 vertices. if (degrees[3] != 2) return FALSE; // For K_{2,3}, the three vertices of degree 2 were not // detected as image vertices because degree 2 vertices // are indistinguishable from the internal path vertices // between the image vertices. So, here we acknowledge // that more image vertices need to be selected. imageVertPos = 2; // Assign the remaining three image vertices to be the // neighbors of the first degree 3 image vertex. // Ensure that each is distinct from the second // degree 3 image vertex. This must be the case because // the two degree 3 image vertices are in the same partition // and hence must not be adjacent. e = gp_GetFirstEdge(theGraph, imageVerts[0]); while (gp_IsEdge(theGraph, e)) { imageVerts[imageVertPos] = gp_GetNeighbor(theGraph, e); if (imageVerts[imageVertPos] == imageVerts[1]) return FALSE; imageVertPos++; e = gp_GetNextEdge(theGraph, e); } /* The paths from imageVerts[0] to each of the new degree 2 image vertices are the edges we just traversed. Now test the paths between each of the degree 2 image vertices and imageVerts[1]. */ _ClearVertexVisitedFlags(theGraph, FALSE); for (imageVertPos = 2; imageVertPos < 5; imageVertPos++) { if (_TestPath(theGraph, imageVerts[imageVertPos], imageVerts[1]) != TRUE) return FALSE; gp_SetVisited(theGraph, imageVerts[imageVertPos]); } for (v = gp_LowerBoundVertices(theGraph); v < gp_UpperBoundVertices(theGraph); ++v) if (gp_GetVisited(theGraph, v)) degrees[2]--; /* If every degree 2 vertex is used in a path between the two degree 3 image vertices, then there are no extra pieces of the graph in theGraph. Specifically, the prior tests identify a K_{2,3} and ensure that nothing else could exist in the graph... except extra degree 2 vertices joined in a cycle. We return NOTOK in that case. */ return degrees[2] == 0 ? TRUE : FALSE; } /******************************************************************** _CheckOuterplanarObstructionIntegrity() This function checks whether theGraph received as input contains either a K_4 or K_{2,3} homeomorph. RETURNS: OK if theGraph contains a K_4 or K_{2,3} homeomorph, NOTOK otherwise To be a K_4 homeomorph, there must be exactly 4 vertices of degree 3, which are called 'image' vertices, and all other vertices must be either degree 2 or degree 0. Furthermore, each of the image vertices must be able to reach all of the other image vertices by a single edge or a path of degree two vertices. To be a K_{2,3} homeomorph, there must be exactly 2 vertices of degree 3. All other vertices must be degree 2. Furthermore, the two degree 3 vertices must have three internally disjoint paths connecting them, and each path must contain at least two edges (i.e. at least one internal vertex). The two degree 3 vertices are image vertices, and an internal vertex from each of the three paths contributes the remaining three image vertices. It is not necessary to check that the paths between the degree three vertices are distinct since if the paths had a common vertex, then the common vertex would not be degree 2. ********************************************************************/ int _CheckOuterplanarObstructionIntegrity(graphP theGraph) { int degrees[4], imageVerts[5]; if (_getImageVertices(theGraph, degrees, 3, imageVerts, 5) != OK) return NOTOK; if (_TestForCompleteGraphObstruction(theGraph, 4, degrees, imageVerts) == TRUE) { return OK; } if (_TestForK23GraphObstruction(theGraph, degrees, imageVerts) == TRUE) { return OK; } /* We get here only if we failed to recognize an outerplanarity obstruction, so we return failure */ return NOTOK; } /******************************************************************** _TestPath() This function determines whether there exists a path of degree two vertices between two given vertices. The function marks each degree two vertex as visited. It returns TRUE if it finds the path and FALSE otherwise. ********************************************************************/ int _TestPath(graphP theGraph, int U, int V) { int e = gp_GetFirstEdge(theGraph, U); while (gp_IsEdge(theGraph, e)) { if (_TryPath(theGraph, e, V) == OK) { _MarkPath(theGraph, e); return TRUE; } e = gp_GetNextEdge(theGraph, e); } return FALSE; } /******************************************************************** _TryPath() This function seeks a given path to a vertex V starting with a given edge out of a starting vertex U. The path is allowed to contain zero or more degree two vertices, but we stop as soon as a vertex of degree higher than two is encountered. The function returns boolean true if that vertex is V, and boolean false otherwise. ********************************************************************/ int _TryPath(graphP theGraph, int e, int V) { int eTwin, nextVertex; nextVertex = gp_GetNeighbor(theGraph, e); // while nextVertex is strictly degree 2 while (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, nextVertex)) && gp_IsEdge(theGraph, gp_GetLastEdge(theGraph, nextVertex)) && gp_GetNextEdge(theGraph, gp_GetFirstEdge(theGraph, nextVertex)) == gp_GetLastEdge(theGraph, nextVertex)) { eTwin = gp_GetTwin(theGraph, e); e = gp_GetFirstEdge(theGraph, nextVertex); if (e == eTwin) e = gp_GetLastEdge(theGraph, nextVertex); nextVertex = gp_GetNeighbor(theGraph, e); } return nextVertex == V ? TRUE : FALSE; } /******************************************************************** _MarkPath() This function sets the visitation flag on all degree two vertices along a path to a vertex V that starts with a given edge out of a starting vertex U. ********************************************************************/ void _MarkPath(graphP theGraph, int e) { int eTwin, nextVertex; nextVertex = gp_GetNeighbor(theGraph, e); // while nextVertex is strictly degree 2 while (gp_IsEdge(theGraph, gp_GetFirstEdge(theGraph, nextVertex)) && gp_IsEdge(theGraph, gp_GetLastEdge(theGraph, nextVertex)) && gp_GetNextEdge(theGraph, gp_GetFirstEdge(theGraph, nextVertex)) == gp_GetLastEdge(theGraph, nextVertex)) { gp_SetVisited(theGraph, nextVertex); eTwin = gp_GetTwin(theGraph, e); e = gp_GetFirstEdge(theGraph, nextVertex); if (e == eTwin) e = gp_GetLastEdge(theGraph, nextVertex); nextVertex = gp_GetNeighbor(theGraph, e); } } /******************************************************************** _TestSubgraph() Checks whether theSubgraph is in fact a subgraph of theGraph. For each vertex v in graph G and subgraph H, we iterate the adjacency list of H(v) and, for each neighbor w, we mark G(w). Then, we iterate the adjacency list of G(v) and unmark each neighbor. Then, we iterate the adjacency list of H(v) again to ensure that every neighbor w was unmarked. If there exists a marked neighbor, then H(v) contains an incident edge that is not incident to G(v). Returns TRUE if theSubgraph contains only edges from theGraph, FALSE otherwise ********************************************************************/ int _TestSubgraph(graphP theSubgraph, graphP theGraph) { int v, e, degreeCount; int Result = TRUE; int invokeSortOnGraph = FALSE; int invokeSortOnSubgraph = FALSE; // If the graph is not sorted by DFI, but the alleged subgraph is, // then "unsort" the alleged subgraph so both have the same vertex order if (!(gp_GetGraphFlags(theGraph) & GRAPHFLAGS_SORTEDBYDFI) && (gp_GetGraphFlags(theSubgraph) & GRAPHFLAGS_SORTEDBYDFI)) { invokeSortOnSubgraph = TRUE; gp_SortVertices(theSubgraph); } // If the graph is not sorted by DFI, but the alleged subgraph is, // then "unsort" the alleged subgraph so both have the same vertex order if (!(gp_GetGraphFlags(theSubgraph) & GRAPHFLAGS_SORTEDBYDFI) && (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_SORTEDBYDFI)) { invokeSortOnGraph = TRUE; gp_SortVertices(theGraph); } /* We clear all visitation flags */ _ClearVertexVisitedFlags(theGraph, FALSE); /* For each vertex... */ for (v = gp_LowerBoundVertices(theSubgraph), degreeCount = 0; v < gp_UpperBoundVertices(theSubgraph); ++v) { /* For each neighbor w in the adjacency list of vertex v in the subgraph, set the visited flag in w in the graph */ e = gp_GetFirstEdge(theSubgraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_IsNotVertex(theSubgraph, gp_GetNeighbor(theSubgraph, e))) { Result = FALSE; break; } degreeCount++; gp_SetVisited(theGraph, gp_GetNeighbor(theSubgraph, e)); e = gp_GetNextEdge(theSubgraph, e); } if (Result != TRUE) break; /* For each neighbor w in the adjacency list of vertex v in the graph, clear the visited flag in w in the graph */ e = gp_GetFirstEdge(theGraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_IsNotVertex(theGraph, gp_GetNeighbor(theGraph, e))) { Result = FALSE; break; } gp_ClearVisited(theGraph, gp_GetNeighbor(theGraph, e)); e = gp_GetNextEdge(theGraph, e); } if (Result != TRUE) break; /* For each neighbor w in the adjacency list of vertex v in the subgraph, ensure that the visited flag in w was cleared (otherwise, the "subgraph" would incorrectly contain an adjacency not contained in the ("super") graph) */ e = gp_GetFirstEdge(theSubgraph, v); while (gp_IsEdge(theGraph, e)) { if (gp_GetVisited(theGraph, gp_GetNeighbor(theSubgraph, e))) { Result = FALSE; break; } e = gp_GetNextEdge(theSubgraph, e); } if (Result != TRUE) break; } // Restore the DFI sort order of either graph if it had to be reordered at the start if (invokeSortOnSubgraph) gp_SortVertices(theSubgraph); if (invokeSortOnGraph) gp_SortVertices(theGraph); // Assuming theSubgraph is a subgraph, we also do an extra integrity check to ensure // proper edge array utilization if (Result == TRUE) { // If the edge count is wrong, we fail the subgraph test in a way that invokes // the name NOTOK so that in debug mode there is more trace on the failure. if (degreeCount != 2 * gp_GetM(theSubgraph)) Result = NOTOK == FALSE ? NOTOK : FALSE; } return Result; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/000077500000000000000000000000001521450711600240745ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarity.1000066400000000000000000000076611521450711600261730ustar00rootroot00000000000000.TH planarity 1 .SH NAME planarity - The Edge Addition Planarity Suite .SH SYNOPSIS .SS Getting help .B planarity -h|-help [-menu] .B planarity -i|-info .SS Menu-driven mode .B planarity .SS Batch modes .B planarity -s [-q] \fICOMMAND\fR \fIINPUT\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] .B planarity -r [-q] \fICOMMAND\fR \fIK\fR \fIN\fR [\fIOUTPUT\fR] .B planarity -rm [-q] \fIN\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] .B planarity -rn [-q] \fIN\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] .B planarity -x [-q] \fB-(gam)\fR \fIINPUT\fR \fIOUTPUT\fR .B planarity -t [-q] \fICOMMAND\fR \fIINPUT\fR \fIOUTPUT\fR .SH DESCRIPTION Invokes the Edge Addition Planarity Suite command-line tool, either in interactive mode or in batch mode. Without a parameter, the tool presents a menu-driven interactive interface. When a parameter is given, it runs in batch mode and returns 0 or 1 for a successful result and -1 on error. .SH OPTIONS .TP .B -h, -help [-menu] Display some help, with \fB-menu\fR triggering even more detailed output. .TP .B -i, -info Display version, copyright, license, and reference articles information. .TP .B -q Quiet mode optional modifier, see below which options accept it. .TP .B -s [-q] \fICOMMAND\fR \fIINPUT\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] Run the \fICOMMAND\fR (see below) on a specific graph given in the \fIINPUT\fR file, with output in the primary \fIOUTPUT\fR file and complementary information, if any, in the secondary \fICOMPLEMENT\fR file (e.g. an ASCII art rendition for the planar graph drawing command). The return value is 0 if the specific graph is embeddable (e.g. it is planar or doesn't contain a homeomorphic subgraph) and 1 if it isn't embeddable (e.g. non planar or does contain a homeomorphic subgraph). .TP .B -r [-q] \fICOMMAND\fR \fIK\fR \fIN\fR [\fIOUTPUT\fR] Run the \fICOMMAND\fR (see below) on \fIK\fR random graphs with \fIN\fR vertices. Optionally, write all generated graphs to .g6 \fIOUTPUT\fR. .TP .B -rm [-q] \fIN\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] Generate a random maximal planar graph with \fIN\fR vertices, then output its planar embedding in the primary \fIOUTPUT\fR file and optionally the generated graph in the \fICOMPLEMENT\fR file. .TP .B -rn [-q] \fIN\fR \fIOUTPUT\fR [\fICOMPLEMENT\fR] Generate a random nonplanar graph (maximal planar plus one edge) with \fIN\fR vertices, then output a Kuratowski subgraph of the generated graph in the primary \fIOUTPUT\fR file and optionally the generated graph in the \fICOMPLEMENT\fR file. .TP .B [-q] \fB-(gam)\fR \fIINPUT\fR \fIOUTPUT\fR Transform single graph in \fIINPUT\fR file (any supported format) to .g6 (\fBg\fR), adjacency list (\fBa\fR), or adjacency matrix (\fBm\fR) format and output to \fIOUTPUT\fR file. .TP .B [-q] \fICOMMAND\fR \fIINPUT\fR \fIOUTPUT\fR Run the \fICOMMAND\fR (see below) on graphs in .g6 encoded \fIINPUT\fR file, then output summary statistics to \fIOUTPUT\fR file. .SH COMMANDS Determine which algorithm implementation to run: .TP .B -p Planar embedding and Kuratowski subgraph isolation .TP .B -d Planar graph drawing by visibility representation (and optional ASCII art rendition) .TP .B -o Outerplanar embedding and obstruction isolation .TP .B -2 Search for subgraph homeomorphic to K_{2,3} .TP .B -3 Search for subgraph homeomorphic to K_{3,3} .TP .B -4 Search for subgraph homeomorphic to K_4 .SH EXAMPLES .TP .B planarity -s -q -p infile.txt embedding.out [obstruction.out] Process infile.txt in quiet mode (-q), putting planar embedding in embedding.out or (optionally) a Kuratowski subgraph in obstruction.out. Process returns 0=planar, 1=nonplanar, -1=error .TP .B planarity -s -q -d infile.txt embedding.out [drawing.out] If graph in infile.txt is planar, then put embedding in embedding.out and (optionally) an ASCII art drawing in drawing.out. Process returns 0=planar, 1=nonplanar, -1=error .SH SEE ALSO The full inline help: \fBplanarity -h -menu\fR The project homepage: \fBhttps://github.com/graph-algorithms/edge-addition-planarity-suite\fR edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarity.c000066400000000000000000000020701521450711600262420ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" /**************************************************************************** MAIN ****************************************************************************/ int main(int argc, char *argv[]) { int retVal = 0; // Although the graphLib is quiet by default, this planarity app is only quiet if // the user requests quiet mode on the command line gp_SetQuietMode(QUIETMODE_NONE); // If there are no command-line arguments, then the planarity app enters menu mode if (argc <= 1) retVal = menu(); // If there are command-line arguments that start with hyphens, then // we enter command-line mode else if (argv[1][0] == '-') retVal = commandLine(argc, argv); // If there are command-line arguments not starting with a hyphen, then // we enter a very old backward compatibility command-line mode else retVal = legacyCommandLine(argc, argv); return retVal; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarity.h000066400000000000000000000061521521450711600262540ustar00rootroot00000000000000#ifndef PLANARITY_H #define PLANARITY_H /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef __cplusplus extern "C" { #endif #include "../graphLib/graphLib.h" #include "platformTime.h" #define FILENAMEMAXLENGTH 128 #define ALGORITHMNAMEMAXLENGTH 32 #define SUFFIXMAXLENGTH 32 #define COMMANDSTRINGMAXLENGTH 2 #define YESNOCHOICECHARS "yYnN" char const *GetProjectTitle(void); char const *GetAlgorithmFlags(void); char const *GetAlgorithmSpecifiers(void); char const *GetAlgorithmChoices(void); char const *GetSupportedOutputChoices(void); char const *GetSupportedOutputFormats(void); int helpMessage(char *param); /* Functions that call the Graph Library */ int SpecificGraph( char const *const commandString, char const *infileName, char *outfileName, char *outfile2Name, char *inputStr, char **pOutputStr, char **pOutput2Str); int RandomGraph(char const *const commandString, int extraEdges, int numVertices, char *outfileName, char *outfile2Name); int RandomGraphs(char const *const commandString, int NumGraphs, int SizeOfGraphs, char *outfileName); int TransformGraph(char const *const commandString, char const *const infileName, char *inputStr, int *outputBase, char const *outfileName, char **pOutputStr); int TestAllGraphs(char const *const commandString, char const *const infileName, char *outfileName, char **pOutputStr); /* Command line, Menu, and Configuration */ int menu(void); int commandLine(int argc, char *argv[]); int legacyCommandLine(int argc, char *argv[]); extern char Mode, OrigOut, OrigOutFormat, EmbeddableOut, ObstructedOut, AdjListsForEmbeddingsOut; int Reconfigure(void); /* Low-level Utilities */ int GetLineFromStdin(char *lineBuff, int lineBuffSize); void FlushConsole(FILE *f); void SaveAsciiGraph(graphP theGraph, char *fileName); char *ReadTextFileIntoString(char const *infileName); int TextFileMatchesString(char const *theFileName, char const *theString); int TextFilesEqual(char *file1Name, char *file2Name); int BinaryFilesEqual(char *file1Name, char *file2Name); int GetCommandAndOptionalModifier(const char *commandString, char *command, char *modifier); int GetEmbedFlags(char command, char modifier, int *embedFlags); char const *GetAlgorithmName(char command); char const *GetTransformationName(char command); char const *GetBaseName(int baseFlag); int ExtendGraph(graphP theGraph, char command); char *ConstructInputFileName(char const *infileName); char *ConstructPrimaryOutputFileName(char const *infileName, char const *outfileName, char command); int ConstructTransformationExpectedResultFileName(char const *infileName, char **outfileName, char command, int actualOrExpectedFlag); void WriteAlgorithmResults(graphP theGraph, int Result, char command, platform_time start, platform_time end, char const *infileName); int GetNumCharsToReprInt(int theNum, int *numCharsRequired); #ifdef __cplusplus } #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityCommandLine.c000066400000000000000000000761261521450711600303660ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" #if defined(_MSC_VER) && !defined(__llvm__) && !defined(__INTEL_COMPILER) // MSVC under Windows doesn't have unistd.h, but does define functions like getcwd and chdir #include #define getcwd _getcwd #define chdir _chdir #else #include #endif int runQuickRegressionTests(int argc, char *argv[]); int callRandomGraphs(int argc, char *argv[]); int callSpecificGraph(int argc, char *argv[]); int callRandomMaxPlanarGraph(int argc, char *argv[]); int callRandomNonplanarGraph(int argc, char *argv[]); int callTestAllGraphs(int argc, char *argv[]); int callTransformGraph(int argc, char *argv[]); int runSpecificGraphTests(void); int runGraphTransformationTests(void); int runTestAllGraphsTests(void); int runSpecificGraphTest(char const *command, char const *infileName, int inputInMemFlag); int runGraphTransformationTest(char const *command, char const *infileName, int inputInMemFlag); int runTestAllGraphsTest(char const *commandString, char const *infileName); /**************************************************************************** Command Line Processor ****************************************************************************/ int commandLine(int argc, char *argv[]) { int Result = OK; #ifdef DEBUG char lineBuff[MAXLINE + 1]; #endif if (argc >= 3 && strcmp(argv[2], "-q") == 0) gp_SetQuietMode(QUIETMODE_ALL); if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-help") == 0) { Result = helpMessage(argc >= 3 ? argv[2] : NULL); } else if (strcmp(argv[1], "-i") == 0 || strcmp(argv[1], "-info") == 0) { Result = helpMessage(argv[1]); } else if (strcmp(argv[1], "-test") == 0) Result = runQuickRegressionTests(argc, argv); else if (strcmp(argv[1], "-r") == 0) Result = callRandomGraphs(argc, argv); else if (strcmp(argv[1], "-s") == 0) Result = callSpecificGraph(argc, argv); else if (strcmp(argv[1], "-rm") == 0) Result = callRandomMaxPlanarGraph(argc, argv); else if (strcmp(argv[1], "-rn") == 0) Result = callRandomNonplanarGraph(argc, argv); else if (strncmp(argv[1], "-x", 2) == 0) Result = callTransformGraph(argc, argv); else if (strncmp(argv[1], "-t", 2) == 0) Result = callTestAllGraphs(argc, argv); else { gp_ErrorMessage("Unsupported command line. Here is the help for this " "program."); helpMessage(NULL); Result = NOTOK; } #ifdef DEBUG // When one builds and runs the executable in an external console from an IDE // such as VSCode, the external console window will close immediately upon // exit 0 being returned. This means that one may miss gp_Message() and // gp_ErrorMessage() outputs that are crucial to the debugging process. // Hence, if we compile with the DDEBUG flag, this means that in appconst.h // we #define DEBUG. That way, this prompt will appear only for debug builds, // and will ensure the console window stays open until the user proceeds. printf("\n\tPress return key to exit...\n"); fflush(stdout); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch from stdin; exiting."); Result = NOTOK; } #endif // NOTE: Translates internal planarity codes to appropriate exit codes return Result == OK ? 0 : (Result == NONEMBEDDABLE ? 1 : -1); } /**************************************************************************** Legacy Command Line Processor from version 1.x ****************************************************************************/ int legacyCommandLine(int argc, char *argv[]) { int Result = OK; graphP theGraph = gp_New(); if (theGraph == NULL) { gp_ErrorMessage("Unable to allocate memory for theGraph."); Result = NOTOK; } if (Result == OK) { Result = gp_Read(theGraph, argv[1]); if (Result != OK) { gp_ErrorMessage("Failed to read graph \"%.*s\"", FILENAME_MAX, argv[1]); Result = NOTOK; } } if (Result == OK) { Result = gp_Embed(theGraph, EMBEDFLAGS_PLANAR); if (Result == OK) { if ((Result = gp_SortVertices(theGraph)) != OK) gp_ErrorMessage("Failed to restore original vertex labelling."); if (Result == OK && (Result = gp_Write(theGraph, argv[2], WRITE_ADJLIST)) != OK) gp_ErrorMessage("Failed to write embedding."); } else if (Result == NONEMBEDDABLE) { if (argc >= 5 && strcmp(argv[3], "-n") == 0) { if ((Result = gp_SortVertices(theGraph)) != OK) gp_ErrorMessage("Failed to restore original vertex " "labelling."); if (Result == OK && (Result = gp_Write(theGraph, argv[4], WRITE_ADJLIST)) != OK) gp_ErrorMessage("Failed to write obstruction."); } } else Result = NOTOK; } gp_Free(&theGraph); // In the legacy 1.x versions, OK/NONEMBEDDABLE was 0 and NOTOK was -2 return Result == OK || Result == NONEMBEDDABLE ? 0 : -2; } /**************************************************************************** Quick regression test ****************************************************************************/ int runQuickRegressionTests(int argc, char *argv[]) { char const *samplesDir = "samples"; int samplesDirArgLocation = 2; int retVal = OK; char origDir[2 * MAXLINE + 1]; // Skip optional -q quiet mode command-line parameter, if present if (argc > samplesDirArgLocation && strcmp(argv[samplesDirArgLocation], "-q") == 0) samplesDirArgLocation++; // Accept overriding sample directory command-line parameter, if present if (argc > samplesDirArgLocation) samplesDir = argv[samplesDirArgLocation]; memset(origDir, '\0', (2 * MAXLINE + 1)); if (!getcwd(origDir, 2 * MAXLINE)) return NOTOK; // Preserve original behavior before the samplesDir command-line parameter was available if (strcmp(samplesDir, "samples") == 0) { if (chdir(samplesDir) != 0) { if (chdir("..") != 0 || chdir(samplesDir) != 0) { // Give success result, but Warn if no samples (except no warning if in quiet mode) gp_Message("WARNING: Unable to change to samples directory to " "run tests on samples."); chdir(origDir); return OK; } } } else { // New behavior if samplesDir command-line parameter was specified if (chdir(samplesDir) != 0) { gp_Message("WARNING: Unable to change to samples directory to run " "tests on samples."); return OK; } } if (runSpecificGraphTests() != OK) retVal = NOTOK; else if (runGraphTransformationTests() != OK) retVal = NOTOK; else if (runTestAllGraphsTests() != OK) retVal = NOTOK; // All done. if (retVal == OK) gp_Message("============\n\nAll tests have succeeded."); else gp_Message("============\n\nOne or more tests FAILED."); chdir(origDir); FlushConsole(stdout); return retVal; } int runSpecificGraphTests(void) { int retVal = OK; #ifdef USE_1BASEDARRAYS gp_Message("\n\tStarting 1-based Array Index Tests\n"); if (runSpecificGraphTest("-p", "maxPlanar5.txt", TRUE) != OK) { gp_ErrorMessage("Planarity test on maxPlanar5.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-d", "maxPlanar5.txt", FALSE) != OK) { gp_ErrorMessage("Graph drawing test maxPlanar5.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-d", "drawExample.txt", TRUE) != OK) { gp_ErrorMessage("Graph drawing on drawExample.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-p", "Petersen.txt", FALSE) != OK) { gp_ErrorMessage("Planarity test on Petersen.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-o", "Petersen.txt", TRUE) != OK) { gp_ErrorMessage("Outerplanarity test on Petersen.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-2", "Petersen.txt", FALSE) != OK) { gp_ErrorMessage("K_{2,3} search on Petersen.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-3", "Petersen.txt", TRUE) != OK) { gp_ErrorMessage("K_{3,3} search on Petersen.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-4", "Petersen.txt", FALSE) != OK) { gp_ErrorMessage("K_4 search on Petersen.txt failed."); retVal = NOTOK; } gp_Message("\tFinished 1-based Array Index Tests.\n"); #endif if (runSpecificGraphTest("-p", "maxPlanar5.0-based.txt", FALSE) != OK) { gp_ErrorMessage("Planarity test on maxPlanar5.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-d", "maxPlanar5.0-based.txt", TRUE) != OK) { gp_ErrorMessage("Graph drawing test maxPlanar5.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-d", "drawExample.0-based.txt", FALSE) != OK) { gp_ErrorMessage("Graph drawing on drawExample.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-p", "Petersen.0-based.txt", TRUE) != OK) { gp_ErrorMessage("Planarity test on Petersen.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-o", "Petersen.0-based.txt", FALSE) != OK) { gp_ErrorMessage("Outerplanarity test on Petersen.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-2", "Petersen.0-based.txt", TRUE) != OK) { gp_ErrorMessage("K_{2,3} search on Petersen.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-3", "Petersen.0-based.txt", FALSE) != OK) { gp_ErrorMessage("K_{3,3} search on Petersen.0-based.txt failed."); retVal = NOTOK; } if (runSpecificGraphTest("-4", "Petersen.0-based.txt", TRUE) != OK) { gp_ErrorMessage("K_4 search on Petersen.0-based.txt failed."); retVal = NOTOK; } return retVal; } int runGraphTransformationTests(void) { int retVal = OK; /* GRAPH TRANSFORMATION TESTS */ // TRANSFORM TO ADJACENCY LIST // runGraphTransformationTest by reading file contents into string if (runGraphTransformationTest("-a", "nauty_example.g6", TRUE) != OK) { gp_ErrorMessage("Transforming nauty_example.g6 file contents as string " "to adjacency list failed."); retVal = NOTOK; } // runGraphTransformationTest by reading from file if (runGraphTransformationTest("-a", "nauty_example.g6", FALSE) != OK) { gp_ErrorMessage("Transforming nauty_example.g6 using file pointer to " "adjacency list failed."); retVal = NOTOK; } // runGraphTransformationTest by reading first graph from file into string if (runGraphTransformationTest("-a", "N5-all.g6", TRUE) != OK) { gp_ErrorMessage("Transforming first graph in N5-all.g6 (read as " "string) to adjacency list failed."); retVal = NOTOK; } // runGraphTransformationTest by reading first graph from file pointer if (runGraphTransformationTest("-a", "N5-all.g6", FALSE) != OK) { gp_ErrorMessage("Transforming first graph in N5-all.g6 (read from file " "pointer) to adjacency list failed."); retVal = NOTOK; } // runGraphTransformationTest by reading file contents corresponding to dense graph into string if (runGraphTransformationTest("-a", "K10.g6", TRUE) != OK) { gp_ErrorMessage("Transforming K10.g6 file contents as string to " "adjacency list failed."); retVal = NOTOK; } // runGraphTransformationTest by reading dense graph from file if (runGraphTransformationTest("-a", "K10.g6", FALSE) != OK) { gp_ErrorMessage("Transforming K10.g6 using file pointer to adjacency " "list failed."); retVal = NOTOK; } // TRANSFORM TO ADJACENCY MATRIX // runGraphTransformationTest by reading file contents into string if (runGraphTransformationTest("-m", "nauty_example.g6", TRUE) != OK) { gp_ErrorMessage("Transforming nauty_example.g6 file contents as string " "to adjacency matrix failed."); retVal = NOTOK; } // runGraphTransformationTest by reading from file if (runGraphTransformationTest("-m", "nauty_example.g6", FALSE) != OK) { gp_ErrorMessage("Transforming nauty_example.g6 using file pointer to " "adjacency matrix failed."); retVal = NOTOK; } // runGraphTransformationTest by reading first graph from file into string if (runGraphTransformationTest("-m", "N5-all.g6", TRUE) != OK) { gp_ErrorMessage("Transforming first graph in N5-all.g6 (read as " "string) to adjacency matrix failed."); retVal = NOTOK; } // runGraphTransformationTest by reading first graph from file pointer if (runGraphTransformationTest("-m", "N5-all.g6", FALSE) != OK) { gp_ErrorMessage("Transforming first graph in N5-all.g6 (read from file " "pointer) to adjacency matrix failed."); retVal = NOTOK; } // runGraphTransformationTest by reading file contents corresponding to dense graph into string if (runGraphTransformationTest("-m", "K10.g6", TRUE) != OK) { gp_ErrorMessage("Transforming K10.g6 file contents as string to " "adjacency matrix failed."); retVal = NOTOK; } // runGraphTransformationTest by reading dense graph from file if (runGraphTransformationTest("-m", "K10.g6", FALSE) != OK) { gp_ErrorMessage("Transforming K10.g6 using file pointer to adjacency " "matrix failed."); retVal = NOTOK; } // TRANSFORM TO .G6 // runGraphTransformationTest by reading from file if (runGraphTransformationTest("-g", "nauty_example.g6.0-based.AdjList.out.txt", TRUE) != OK) { gp_ErrorMessage("Transforming nauty_example.g6.0-based.AdjList.out.txt " "using file pointer to .g6 failed."); retVal = NOTOK; } // runGraphTransformationTest by reading from file if (runGraphTransformationTest("-g", "K10.g6.0-based.AdjList.out.txt", TRUE) != OK) { gp_ErrorMessage("Transforming K10.g6.0-based.AdjList.out.txt using " "file pointer to .g6 failed."); retVal = NOTOK; } return retVal; } int runTestAllGraphsTests(void) { int retVal = OK; // Run TestAllGraphs Tests if (runTestAllGraphsTest("-p", "n8.mALL.g6") != OK) { gp_ErrorMessage("Planarity test on all graphs failed."); retVal = NOTOK; } if (runTestAllGraphsTest("-d", "n8.mALL.g6") != OK) { gp_ErrorMessage("Planar graph drawing test on all graphs failed."); retVal = NOTOK; } if (runTestAllGraphsTest("-o", "n8.mALL.g6") != OK) { gp_ErrorMessage("Outerplanarity test on all graphs failed."); retVal = NOTOK; } if (runTestAllGraphsTest("-2", "n8.mALL.g6") != OK) { gp_ErrorMessage("K2,3 homeomorph search test on all graphs failed."); retVal = NOTOK; } if (runTestAllGraphsTest("-3", "n8.mALL.g6") != OK) { gp_ErrorMessage("K3,3 homeomorph search test on all graphs failed."); retVal = NOTOK; } if (runTestAllGraphsTest("-4", "n8.mALL.g6") != OK) { gp_ErrorMessage("K4 homeomorph search test on all graphs failed."); retVal = NOTOK; } return retVal; } int runTestAllGraphsTest(char const *commandString, char const *infileName) { char *outputStr = NULL; int Result = OK; char command = '\0', modifier = '\0'; if (GetCommandAndOptionalModifier(commandString, &command, &modifier) != OK) { gp_ErrorMessage("Unable to extract command (or optional modifier) from " "command string."); return NOTOK; } Result = TestAllGraphs(commandString, infileName, NULL, &outputStr); if (Result == OK) { const char *planarityValidationStr = "-p 12346 6966 5380 SUCCESS"; const char *drawPlanarValidationStr = "-d 12346 6966 5380 SUCCESS"; const char *outerplanarityValidationStr = "-o 12346 1150 11196 SUCCESS"; const char *K23SearchValidationStr = "-2 12346 1251 11095 SUCCESS"; const char *K33SearchValidationStr = "-3 12346 7200 5146 SUCCESS"; const char *K4SearchValidationStr = "-4 12346 1715 10631 SUCCESS"; const char *theValidationStr = NULL; switch (command) { case 'p': theValidationStr = planarityValidationStr; break; case 'd': theValidationStr = drawPlanarValidationStr; break; case 'o': theValidationStr = outerplanarityValidationStr; break; case '2': theValidationStr = K23SearchValidationStr; break; case '3': theValidationStr = K33SearchValidationStr; break; case '4': theValidationStr = K4SearchValidationStr; break; default: Result = NOTOK; break; } if (theValidationStr != NULL) Result = strstr(outputStr, theValidationStr) ? OK : NOTOK; } gp_Message(" "); if (outputStr != NULL) { free(outputStr); outputStr = NULL; } return Result == OK ? OK : NOTOK; } int runSpecificGraphTest(char const *commandString, char const *infileName, int inputInMemFlag) { int Result = OK; char *inputString = NULL, *actualOutput = NULL, *actualOutput2 = NULL; char const *expectedPrimaryResultFileName = ""; char command = '\0', modifier = '\0'; if (GetCommandAndOptionalModifier(commandString, &command, &modifier) != OK) { gp_ErrorMessage("Unable to extract command (or optional modifier) from " "command string."); return NOTOK; } // The algorithm, indicated by algorithmCode, operating on 'infileName' is expected to produce // an output that is stored in the file named 'expectedResultFileName' (return string not owned) expectedPrimaryResultFileName = ConstructPrimaryOutputFileName(infileName, NULL, command); // SpecificGraph() can invoke gp_Read() if the graph is to be read from a file, or it can invoke // gp_ReadFromString() if the inputInMemFlag is set. if (inputInMemFlag) { inputString = ReadTextFileIntoString(infileName); if (inputString == NULL) { gp_ErrorMessage("Failed to read input file into string."); Result = NOTOK; } } if (Result == OK) { // Perform the indicated algorithm on the graph in the input file or string. Result = SpecificGraph(commandString, infileName, NULL, NULL, inputString, &actualOutput, &actualOutput2); } if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("Test failed (graph processor returned failure " "result)."); Result = NOTOK; } else { // Test that the primary actual output matches the primary expected output if (TextFileMatchesString(expectedPrimaryResultFileName, actualOutput) == TRUE) gp_Message("Test succeeded (result equal to exemplar)."); else { gp_ErrorMessage("Test failed (result not equal to exemplar)."); Result = NOTOK; } } // Test that the secondary actual output matches the secondary expected output if (command == 'd' && (Result == OK || Result == NONEMBEDDABLE)) { char *expectedSecondaryResultFileName = (char *)malloc(strlen(expectedPrimaryResultFileName) + strlen(".render.txt") + 1); if (expectedSecondaryResultFileName == NULL) { gp_ErrorMessage("Unable to allocate memory for expected secondary " "output file name."); Result = NOTOK; } else { sprintf(expectedSecondaryResultFileName, "%s%s", expectedPrimaryResultFileName, ".render.txt"); if (TextFileMatchesString(expectedSecondaryResultFileName, actualOutput2) == TRUE) gp_Message("Test succeeded (secondary result equal to " "exemplar)."); else { gp_ErrorMessage("Test failed (secondary result not equal to " "exemplar)."); Result = NOTOK; } if (expectedSecondaryResultFileName != NULL) { free(expectedSecondaryResultFileName); expectedSecondaryResultFileName = NULL; } } } gp_Message(" "); if (inputString != NULL) { free(inputString); inputString = NULL; } if (actualOutput != NULL) { free(actualOutput); actualOutput = NULL; } if (actualOutput2 != NULL) { free(actualOutput2); actualOutput2 = NULL; } // NOTE: Test run successfully if OK or NONEMBEDDABLE result; Result is only // NOTOK when an error occurs during one of the subordinate function calls, // or if the output does not match what is expected. return (Result == OK || Result == NONEMBEDDABLE) ? OK : Result; } int runGraphTransformationTest(char const *command, char const *infileName, int inputInMemFlag) { int Result = OK; char *inputString = NULL; char transformationCode = '\0'; // runGraphTransformationTest will not test performing an algorithm on a given // input graph; it will only support "-(gam)" if (command == NULL || strlen(command) < 2) { gp_ErrorMessage("runGraphTransformationTest only supports -(gam)."); return NOTOK; } else if (strlen(command) == 2) transformationCode = command[1]; // SpecificGraph() can invoke gp_Read() if the graph is to be read from a file, or it can invoke // gp_ReadFromString() if the inputInMemFlag is set. if (inputInMemFlag) { inputString = ReadTextFileIntoString(infileName); if (inputString == NULL) { gp_ErrorMessage("Failed to read input file into string."); Result = NOTOK; } } if (Result == OK) { // We need to capture whether output is 0- or 1-based to construct the name of the file to compare actualOutput with int zeroBasedOutputFlag = 0; char *actualOutput = NULL; // We want to handle the test being run when we read from an input file or read from a string, // so pass both infileName and inputString. // We want to output to string, so we pass in the address of the actualOutput string. Result = TransformGraph(command, infileName, inputString, &zeroBasedOutputFlag, NULL, &actualOutput); if (Result != OK || actualOutput == NULL) { gp_ErrorMessage("Failed to perform transformation."); Result = NOTOK; } else { char *expectedOutfileName = NULL; // Final arg is baseFlag, which is dependent on whether the FLAGS_ZEROBASEDIO is set in a graph's graphFlags Result = ConstructTransformationExpectedResultFileName(infileName, &expectedOutfileName, transformationCode, zeroBasedOutputFlag ? 0 : 1); if (Result != OK || expectedOutfileName == NULL) { gp_ErrorMessage("Unable to construct output file name for " "expected transformation output."); Result = NOTOK; } else { Result = TextFileMatchesString(expectedOutfileName, actualOutput); if (Result == TRUE) { gp_Message("For the transformation %s on file \"%.*s\", " "actual output matched expected output file.", command, FILENAME_MAX, infileName); Result = OK; } else { gp_ErrorMessage("For the transformation %s on file \"%.*s\", " "actual output did not match expected " "output file.", command, FILENAME_MAX, infileName); Result = NOTOK; } if (expectedOutfileName != NULL) { free(expectedOutfileName); expectedOutfileName = NULL; } if (actualOutput != NULL) { free(actualOutput); actualOutput = NULL; } } } } gp_Message(" "); if (inputString != NULL) { free(inputString); inputString = NULL; } return Result; } /**************************************************************************** callRandomGraphs() ****************************************************************************/ // 'planarity -r [-q] C K N [O]': Random graphs int callRandomGraphs(int argc, char *argv[]) { int offset = 0, NumGraphs = 0, SizeOfGraphs = 0; char *commandString = NULL, *outfileName = NULL; if (argc < 5 || argc > 7) return NOTOK; if (strncmp(argv[2], "-q", 2) == 0) { if (argc < 6) return NOTOK; offset = 1; } if (argc > (6 + offset)) return NOTOK; commandString = argv[2 + offset]; NumGraphs = atoi(argv[3 + offset]); SizeOfGraphs = atoi(argv[4 + offset]); if (argc == (6 + offset)) outfileName = argv[5 + offset]; return RandomGraphs(commandString, NumGraphs, SizeOfGraphs, outfileName); } /**************************************************************************** callSpecificGraph() ****************************************************************************/ // 'planarity -s [-q] C I O [O2]': Specific graph int callSpecificGraph(int argc, char *argv[]) { int offset = 0; char *commandString = NULL; char *infileName = NULL, *outfileName = NULL, *outfile2Name = NULL; if (argc < 5) return NOTOK; if (strncmp(argv[2], "-q", 2) == 0) { if (argc < 6) return NOTOK; offset = 1; } if (argc > (6 + offset)) return NOTOK; commandString = argv[2 + offset]; infileName = argv[3 + offset]; outfileName = argv[4 + offset]; if (argc == 6 + offset) outfile2Name = argv[5 + offset]; return SpecificGraph(commandString, infileName, outfileName, outfile2Name, NULL, NULL, NULL); } /**************************************************************************** callRandomMaxPlanarGraph() ****************************************************************************/ // 'planarity -rm [-q] N O [O2]': Maximal planar random graph int callRandomMaxPlanarGraph(int argc, char *argv[]) { int offset = 0, numVertices = 0; char *outfileName = NULL, *outfile2Name = NULL; if (argc < 4) return NOTOK; if (strncmp(argv[2], "-q", 2) == 0) { if (argc < 5) return NOTOK; offset = 1; } if (argc > (5 + offset)) return NOTOK; numVertices = atoi(argv[2 + offset]); outfileName = argv[3 + offset]; if (argc == 5 + offset) outfile2Name = argv[4 + offset]; return RandomGraph("-p", 0, numVertices, outfileName, outfile2Name); } /**************************************************************************** callRandomNonplanarGraph() ****************************************************************************/ // 'planarity -rn [-q] N O [O2]': Non-planar random graph (maximal planar plus edge) int callRandomNonplanarGraph(int argc, char *argv[]) { int offset = 0, numVertices = 0; char *outfileName = NULL, *outfile2Name = NULL; if (argc < 4) return NOTOK; if (strncmp(argv[2], "-q", 2) == 0) { if (argc < 5) return NOTOK; offset = 1; } if (argc > (5 + offset)) return NOTOK; numVertices = atoi(argv[2 + offset]); outfileName = argv[3 + offset]; if (argc == 5 + offset) outfile2Name = argv[4 + offset]; return RandomGraph("-p", 1, numVertices, outfileName, outfile2Name); } /**************************************************************************** callTransformGraph() ****************************************************************************/ // 'planarity -x [-q] -(gam) I O': Input file I is transformed from its given // format to the format given by the g (g6), a (adjacency list) or m (matrix), // and written to output file O. int callTransformGraph(int argc, char *argv[]) { int offset = 0; char *commandString = NULL; char *infileName = NULL, *outfileName = NULL; if (argc < 5) return NOTOK; if (argv[2][0] == '-' && argv[2][1] == 'q') { if (argc < 6) return NOTOK; offset = 1; } if (argc > (5 + offset)) return NOTOK; commandString = argv[2 + offset]; infileName = argv[3 + offset]; outfileName = argv[4 + offset]; // We don't want to read from string, so inputStr is NULL // We don't want to write to string, so pOutputStr is NULL // We don't need to capture whether output is 0- or 1-based, so zeroBasedOutputFlag arg is NULL return TransformGraph(commandString, infileName, NULL, NULL, outfileName, NULL); } /**************************************************************************** callTestAllGraphs() ****************************************************************************/ // 'planarity -t [-q] C I O': If the command line argument after -t [-q] is a // recognized algorithm command C, then the input file I must be in ".g6" format // (report an error otherwise), and the algorithm(s) indicated by C are executed // on the graph(s) in the input file, with the results of the execution stored // in output file O. int callTestAllGraphs(int argc, char *argv[]) { int offset = 0; char *commandString = NULL; char *infileName = NULL, *outfileName = NULL; if (argc < 5) return NOTOK; if (argv[2][0] == '-' && argv[2][1] == 'q') { if (argc < 6) return NOTOK; offset = 1; } if (argc > (5 + offset)) return NOTOK; commandString = argv[2 + offset]; infileName = argv[3 + offset]; outfileName = argv[4 + offset]; // NOTE: We don't want to write to string, so pOutputStr is NULL return TestAllGraphs(commandString, infileName, outfileName, NULL); } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityHelp.c000066400000000000000000000127171521450711600270640ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" char const *GetProjectTitle(void) { static char projectTitle[MAXLINE + 1]; sprintf(projectTitle, "\n===================================================" "\nThe Edge Addition Planarity Suite version %s" "\nbased on libPlanarity graph library version %s" "\nCopyright (c) 1997-2026 by John M. Boyer" "\nAll rights reserved." "\nSee the LICENSE.TXT file for licensing information." "\nContact info: jboyer at acm.org" "\n===================================================\n", gp_GetProjectVersionFull(), gp_GetLibPlanarityVersionFull()); return projectTitle; } /**************************************************************************** helpMessage() ****************************************************************************/ int helpMessage(char *param) { gp_Message("%s", GetProjectTitle()); if (param == NULL) { gp_Message( "'planarity': if no command-line, then menu-driven\n" "'planarity (-h|-help)': this message\n" "'planarity (-h|-help) -menu': more help with menu-based command line\n" "'planarity (-i|-info): copyright and license information\n" "'planarity -test [-q] [samples dir]': runs tests (optional quiet mode)\n"); gp_Message( "Common usages\n" "-------------\n" "planarity -s -q -p infile.txt embedding.out [obstruction.out]\n" "Process infile.txt in quiet mode (-q), putting planar embedding in \n" "embedding.out or (optionally) a Kuratowski subgraph in Obstruction.out\n" "Process returns 0=planar, 1=nonplanar, -1=error\n" "\n" "planarity -s -q -d infile.txt embedding.out [drawing.out]\n" "If graph in infile.txt is planar, then put embedding in embedding.out \n" "and (optionally) an ASCII art drawing in drawing.out\n" "Process returns 0=planar, 1=nonplanar, -1=error"); } else if (strcmp(param, "-i") == 0 || strcmp(param, "-info") == 0) { gp_Message( "Includes a reference implementation of the following:\n" "\n" "* John M. Boyer. \"Subgraph Homeomorphism via the Edge Addition Planarity \n" " Algorithm\". Journal of Graph Algorithms and Applications, Vol. 16, \n" " no. 2, pp. 381-410, 2012. http://dx.doi.org/10.7155/jgaa.00268\n" "\n" "* John M. Boyer. \"A New Method for Efficiently Generating Planar Graph\n" " Visibility Representations\". In P. Eades and P. Healy, editors,\n" " Proceedings of the 13th International Conference on Graph Drawing 2005,\n" " Lecture Notes Comput. Sci., Volume 3843, pp. 508-511, Springer-Verlag, 2006.\n" " http://dx.doi.org/10.1007/11618058_47\n" "\n" "* John M. Boyer and Wendy J. Myrvold. \"On the Cutting Edge: Simplified O(n)\n" " Planarity by Edge Addition\". Journal of Graph Algorithms and Applications,\n" " Vol. 8, No. 3, pp. 241-273, 2004. http://dx.doi.org/10.7155/jgaa.00091\n" "\n" "* John M. Boyer. \"Simplified O(n) Algorithms for Planar Graph Embedding,\n" " Kuratowski Subgraph Isolation, and Related Problems\". Ph.D. Dissertation,\n" " University of Victoria, 2001. https://dspace.library.uvic.ca/handle/1828/9918\n"); } else if (strcmp(param, "-menu") == 0) { gp_Message( "'planarity -r [-q] C K N [O]': Random graphs\n" "'planarity -s [-q] C I O [O2]': Specific graph\n" "'planarity -rm [-q] N O [O2]': Random maximal planar graph\n" "'planarity -rn [-q] N O [O2]': Random nonplanar graph (maximal planar + edge)\n" "'planarity -t [-q] C I O': Test algorithm on graph(s) in .g6 file\n" "'planarity -x [-q] -(gam) I O': Transform graph to .g6 (g), Adjacency List (a), or Adjacency Matrix (m)\n" "'planarity I O [-n O2]': Legacy command-line (default -s -p)\n"); gp_Message("-q is for quiet mode (no messages to stdout and stderr)\n"); gp_Message("%s", GetAlgorithmFlags()); gp_Message( "K = # of graphs to randomly generate\n" "N = # of vertices in each randomly generated graph\n" "I = Input file (for work on a specific graph)\n" "O = Primary output file\n" " For example, if C=-p then O receives the planar embedding\n" " If C=-3, then O receives a subgraph containing a K_{3,3}\n" "O2= Secondary output file\n" " For -s, if C=-p or -o, then O2 receives the embedding obstruction\n" " For -s, if C=-d, then O2 receives a drawing of the planar graph\n" " For -rm and -rn, O2 contains the original randomly generated graph"); gp_Message( "planarity process results: 0=OK, -1=NOTOK, 1=NONEMBEDDABLE\n" " 1 result only produced by specific graph mode (-s)\n" " with command -2,-3,-4: found K_{2,3}, K_{3,3} or K_4\n" " with command -p,-d: found planarity obstruction\n" " with command -o: found outerplanarity obstruction"); } FlushConsole(stdout); return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityMenu.c000066400000000000000000000374461521450711600271060ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" /**************************************************************************** MENU-DRIVEN PROGRAM ****************************************************************************/ int TransformGraphMenu(void); int TestAllGraphsMenu(void); int menu(void) { int Result = OK; char lineBuff[MAXLINE + 1]; int numCharsToReprCOMMANDSTRINGMAXLENGTH = 0; char const *choiceStringFormatFormat = " %%%ds"; char *choiceStringFormat = NULL; char choiceString[COMMANDSTRINGMAXLENGTH + 1]; char *secondOutfile = NULL; char command = '\0'; memset(lineBuff, '\0', (MAXLINE + 1)); memset(choiceString, '\0', (COMMANDSTRINGMAXLENGTH + 1)); if (GetNumCharsToReprInt(COMMANDSTRINGMAXLENGTH, &numCharsToReprCOMMANDSTRINGMAXLENGTH) != OK) { gp_ErrorMessage("Unable to determine number of characters required to " "represent COMMANDSTRINGMAXLENGTH."); Result = NOTOK; } else { choiceStringFormat = (char *)malloc((strlen(choiceStringFormatFormat) + numCharsToReprCOMMANDSTRINGMAXLENGTH + 1) * sizeof(char)); if (choiceStringFormat == NULL) { gp_ErrorMessage("Unable to allocate memory for choice string " "format string."); Result = NOTOK; } } if (Result == OK) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(choiceStringFormat, choiceStringFormatFormat, COMMANDSTRINGMAXLENGTH); #pragma GCC diagnostic pop while (1) { gp_Message("%s", GetProjectTitle()); gp_Message("%s", GetAlgorithmSpecifiers()); gp_Message( "X. Transform single graph in supported file to .g6, adjacency list, or adjacency matrix\n" "T. Perform an algorithm test on all graphs in .g6 input file\n" "H. Help message for command line version\n" "R. Reconfigure options\n" "Q. Quit\n"); gp_MessagePrompt("Enter Choice:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch menu choice from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > 2 || sscanf(lineBuff, choiceStringFormat, choiceString) != 1) { gp_Message("Invalid input; please retry."); continue; } #pragma GCC diagnostic pop choiceString[0] = (char)tolower(choiceString[0]); choiceString[1] = (char)tolower(choiceString[1]); if (strcmp(choiceString, "h") == 0) helpMessage(NULL); else if (strcmp(choiceString, "r") == 0) { if (Reconfigure() != OK) { gp_ErrorMessage("Encountered unrecoverable error when " "reconfiguring; exiting."); Result = NOTOK; break; } continue; } else if (strcmp(choiceString, "x") == 0) { if ((Result = TransformGraphMenu()) != OK) gp_ErrorMessage("Transform Graph Menu emitted an error."); } else if (strcmp(choiceString, "t") == 0) { if ((Result = TestAllGraphsMenu()) != OK) gp_ErrorMessage("Test All Graphs Menu emitted an error."); } else if (strcmp(choiceString, "q") == 0) break; else { char *commandString = choiceString; if (GetCommandAndOptionalModifier(commandString, &command, NULL) != OK) { gp_Message("Unable to extract command from choice; " "please retry."); commandString = NULL; continue; } if (command == 'p' || command == 'd' || command == 'o') secondOutfile = (char *)""; if (!strchr(GetAlgorithmChoices(), command)) gp_Message("Invalid algorithm command choice; please " "retry."); else { switch (tolower(Mode)) { case 's': Result = SpecificGraph(commandString, NULL, NULL, secondOutfile, NULL, NULL, NULL); break; case 'r': Result = RandomGraphs(commandString, 0, 0, NULL); break; case 'm': Result = RandomGraph(commandString, 0, 0, NULL, NULL); break; case 'n': Result = RandomGraph(commandString, 1, 0, NULL, NULL); break; default: break; } } } gp_Message("\nPress return key to continue..."); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch from stdin; exiting."); Result = NOTOK; break; } gp_Message("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); FlushConsole(stdout); } } // Certain debuggers don't terminate correctly with pending output content FlushConsole(stdout); FlushConsole(stderr); if (choiceStringFormat != NULL) { free(choiceStringFormat); choiceStringFormat = NULL; } // NOTE: Translates internal planarity codes to appropriate exit codes return Result == OK ? 0 : (Result == NONEMBEDDABLE ? 1 : -1); } int TransformGraphMenu(void) { int Result = OK; int numCharsToReprFILENAMEMAXLENGTH = 0; char const *fileNameFormatFormat = " %%%d[^\r\n]"; char *fileNameFormat = NULL; char lineBuff[MAXLINE + 1]; char infileName[FILENAMEMAXLENGTH + 1]; char outfileName[FILENAMEMAXLENGTH + 1]; char commandStr[COMMANDSTRINGMAXLENGTH + 1]; char outputFormat = '\0'; memset(lineBuff, '\0', (MAXLINE + 1)); memset(infileName, '\0', (FILENAMEMAXLENGTH + 1)); memset(outfileName, '\0', (FILENAMEMAXLENGTH + 1)); memset(commandStr, '\0', (COMMANDSTRINGMAXLENGTH + 1)); if (GetNumCharsToReprInt(FILENAMEMAXLENGTH, &numCharsToReprFILENAMEMAXLENGTH) != OK) { gp_ErrorMessage("Unable to determine number of characters required to " "represent FILENAMEMAXLENGTH."); return NOTOK; } fileNameFormat = (char *)malloc((strlen(fileNameFormatFormat) + numCharsToReprFILENAMEMAXLENGTH + 1) * sizeof(char)); if (fileNameFormat == NULL) { gp_ErrorMessage("Unable to allocate memory for file name format " "string."); return NOTOK; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(fileNameFormat, fileNameFormatFormat, FILENAMEMAXLENGTH); #pragma GCC diagnostic pop while (1) { gp_MessagePrompt("Enter input file name:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read input file name from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH || sscanf(lineBuff, fileNameFormat, infileName) != 1 || strlen(infileName) == 0) gp_Message("Invalid input file name; please retry."); else if (strncmp(infileName, "stdin", strlen("stdin")) == 0) { gp_Message("Please retry with an input file path: stdin not " "supported from menu."); memset(infileName, '\0', (FILENAMEMAXLENGTH + 1)); } else break; #pragma GCC diagnostic pop } if (Result == OK) { while (1) { gp_MessagePrompt("Enter output file name, or type \"stdout\" to " "output to console:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read output file name from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH || sscanf(lineBuff, fileNameFormat, outfileName) != 1 || strlen(outfileName) == 0) gp_Message("Invalid output file name; please retry."); else break; #pragma GCC diagnostic pop } } if (Result == OK) { while (1) { gp_Message("%s", GetSupportedOutputChoices()); gp_MessagePrompt("Enter output format:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read output format from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &outputFormat) != 1 || !strchr(GetSupportedOutputFormats(), tolower(outputFormat))) gp_Message("Invalid choice for output format; please " "retry."); else { if (sprintf(commandStr, "-%c", (char)tolower(outputFormat)) < 1) { gp_ErrorMessage("Unable to construct commandStr."); Result = NOTOK; } break; } } } if (Result == OK) Result = TransformGraph(commandStr, infileName, NULL, NULL, outfileName, NULL); if (fileNameFormat != NULL) { free(fileNameFormat); fileNameFormat = NULL; } return Result; } int TestAllGraphsMenu(void) { int Result = OK; char lineBuff[MAXLINE + 1]; int numCharsToReprFILENAMEMAXLENGTH = 0; char const *fileNameFormatFormat = " %%%d[^\r\n]"; char *fileNameFormat = NULL; char infileName[FILENAMEMAXLENGTH + 1]; char outfileName[FILENAMEMAXLENGTH + 1]; int numCharsToReprCOMMANDSTRINGMAXLENGTH = 0; char const *commandStringFormatFormat = " %%%ds"; char *commandStringFormat = NULL; char commandString[COMMANDSTRINGMAXLENGTH + 1]; memset(lineBuff, '\0', (MAXLINE + 1)); memset(infileName, '\0', (FILENAMEMAXLENGTH + 1)); memset(outfileName, '\0', (FILENAMEMAXLENGTH + 1)); memset(commandString, '\0', (COMMANDSTRINGMAXLENGTH + 1)); if (GetNumCharsToReprInt(COMMANDSTRINGMAXLENGTH, &numCharsToReprCOMMANDSTRINGMAXLENGTH) != OK) { gp_ErrorMessage("Unable to determine number of characters required to " "represent COMMANDSTRINGMAXLENGTH."); return NOTOK; } commandStringFormat = (char *)malloc((strlen(commandStringFormatFormat) + numCharsToReprCOMMANDSTRINGMAXLENGTH + 1) * sizeof(char)); if (commandStringFormat == NULL) { gp_ErrorMessage("Unable to allocate memory for command string format " "string."); return NOTOK; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(commandStringFormat, commandStringFormatFormat, COMMANDSTRINGMAXLENGTH); #pragma GCC diagnostic pop if (GetNumCharsToReprInt(FILENAMEMAXLENGTH, &numCharsToReprFILENAMEMAXLENGTH) != OK) { gp_ErrorMessage("Unable to determine number of characters required to " "represent FILENAMEMAXLENGTH."); if (commandStringFormat != NULL) { free(commandStringFormat); commandStringFormat = NULL; } return NOTOK; } fileNameFormat = (char *)malloc((strlen(fileNameFormatFormat) + numCharsToReprFILENAMEMAXLENGTH + 1) * sizeof(char)); if (fileNameFormat == NULL) { gp_ErrorMessage("Unable to allocate memory for file name format " "string."); if (commandStringFormat != NULL) { free(commandStringFormat); commandStringFormat = NULL; } return NOTOK; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(fileNameFormat, fileNameFormatFormat, FILENAMEMAXLENGTH); #pragma GCC diagnostic pop while (1) { gp_MessagePrompt("Enter input file name:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read input file name from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH || sscanf(lineBuff, fileNameFormat, infileName) != 1 || strlen(infileName) == 0) gp_Message("Invalid input file name; please retry."); else if (strncmp(infileName, "stdin", strlen("stdin")) == 0) { gp_Message("Please retry with an input file path: stdin not " "supported from menu."); memset(infileName, '\0', (FILENAMEMAXLENGTH + 1)); } else break; #pragma GCC diagnostic pop } if (Result == OK) { while (1) { gp_MessagePrompt("Enter output file name, or type \"stdout\" to " "output to console:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read output file name from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH || sscanf(lineBuff, fileNameFormat, outfileName) != 1 || strlen(outfileName) == 0) gp_Message("Invalid output file name; please retry."); else break; #pragma GCC diagnostic pop } } if (Result == OK) { while (1) { gp_Message("%s", GetAlgorithmSpecifiers()); gp_MessagePrompt("Enter algorithm specifier (with optional " "modifier):"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read command and optional modifier " "from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > 2 || sscanf(lineBuff, commandStringFormat, commandString) != 1 || strlen(commandString) == 0) gp_ErrorMessage("Invalid command and optional modifier."); else break; #pragma GCC diagnostic pop } } if (Result == OK) Result = TestAllGraphs(commandString, infileName, outfileName, NULL); if (commandStringFormat != NULL) { free(commandStringFormat); commandStringFormat = NULL; } if (fileNameFormat != NULL) { free(fileNameFormat); fileNameFormat = NULL; } return (Result == OK) ? OK : NOTOK; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityRandomGraphs.c000066400000000000000000000633331521450711600305610ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" int GetNumberIfZero(int *pNum, char const *prompt, int min, int max); void ResetGraphStorage(graphP *pGraph, int ReuseGraphs, char command); graphP MakeGraph(int Size, char command); int PromptSaveGraph(graphP theGraph, graphP origGraph, int extraEdges, int saveMode); /**************************************************************************** * RandomGraphs() * * Top-level method to randomly generate graphs to test the algorithm given by * the command parameter. * The number of graphs to generate, and the number of vertices for each graph, * can be sent as the second and third params. For each that is sent as zero, * this method will prompt the user for a value. ****************************************************************************/ #define NUM_MINORS 9 int RandomGraphs(char const *const commandString, int NumGraphs, int SizeOfGraphs, char *outfileName) { int Result = OK; int writeResult = OK; int K = 0, countUpdateFreq = 0, embedFlags = 0, MainStatistic = 0; int ReuseGraphs = TRUE; char command = '\0', modifier = '\0'; int ObstructionMinorFreqs[NUM_MINORS]; graphP theGraph = NULL, origGraph = NULL; platform_time start, end; G6WriteIteratorP theG6WriteIterator = NULL; char const g6WriterInitializationgp_ErrorMessage[] = "Unable to write random graphs to G6 outfile \"%.*s\" due to failure initializing G6WriteIterator."; char const writegp_ErrorMessage[] = "Failed to write graph \"%.*s\".\nMake the directory if not present."; char theFileName[FILENAMEMAXLENGTH + 1]; memset(ObstructionMinorFreqs, 0, NUM_MINORS * sizeof(int)); memset(theFileName, '\0', (FILENAMEMAXLENGTH + 1)); if ((Result = GetCommandAndOptionalModifier(commandString, &command, &modifier)) != OK) { gp_ErrorMessage("Unable to extract command and optional modifier " "character from commandString.\n"); return Result; } if ((Result = GetEmbedFlags(command, modifier, &embedFlags)) != OK) { gp_ErrorMessage("Unable to derive embedFlags from command and optional " "modifier character.\n"); return Result; } if ((Result = GetNumberIfZero(&NumGraphs, "Enter number of graphs to generate:", 1, 1000000000)) != OK) { gp_ErrorMessage("Encountered unrecoverable error when prompting for " "NumGraphs.\n"); return Result; } if ((Result = GetNumberIfZero(&SizeOfGraphs, "Enter size of graphs:", 1, 10000000)) != OK) { gp_ErrorMessage("Encountered unrecoverable error when prompting for " "SizeOfGraphs.\n"); return Result; } theGraph = MakeGraph(SizeOfGraphs, command); // The origGraph no longer needs extensions, so we just use a null terminator for the command origGraph = MakeGraph(SizeOfGraphs, '\0'); if (theGraph == NULL || origGraph == NULL) { gp_ErrorMessage("Unable to allocate and initialize graph datastructures " "to contain randomly generated graphs.\n"); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } if (outfileName != NULL || (tolower(OrigOut) == 'y' && tolower(OrigOutFormat) == 'g')) { if (g6_NewWriter((&theG6WriteIterator), theGraph) != OK) { gp_ErrorMessage("Unable to allocate G6WriteIterator."); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } } if (outfileName != NULL) { if (g6_InitWriterWithFileName(theG6WriteIterator, outfileName) != OK) { gp_ErrorMessage(g6WriterInitializationgp_ErrorMessage, FILENAME_MAX, outfileName); g6_FreeWriter((&theG6WriteIterator)); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } } else if (tolower(OrigOut) == 'y' && tolower(OrigOutFormat) == 'g') { // If outfileName is NULL, then the only case in which we would want to // output the generated random graphs to .g6 is if we Reconfigure() and // choose these options; in that case, need to set a default output file name. sprintf(theFileName, "random%cn%d.k%d.g6", FILE_DELIMITER, SizeOfGraphs, NumGraphs); if (g6_InitWriterWithFileName(theG6WriteIterator, theFileName) != OK) { gp_ErrorMessage(g6WriterInitializationgp_ErrorMessage, FILENAME_MAX, theFileName); g6_FreeWriter((&theG6WriteIterator)); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } } // Seed the random number generator with "now". Do it after any prompting // to tie randomness to human process of answering the prompt. // Acceptable downcast of time_t to unsigned int (seeding benefits from the lower bits of now) srand((unsigned int)time(NULL)); // Select a counter update frequency that updates more frequently with larger graphs // and which is relatively prime with 10 so that all digits of the count will change // even though we aren't showing the count value on every iteration countUpdateFreq = 3579 / SizeOfGraphs; countUpdateFreq = countUpdateFreq < 1 ? 1 : countUpdateFreq; countUpdateFreq = countUpdateFreq % 2 == 0 ? countUpdateFreq + 1 : countUpdateFreq; countUpdateFreq = countUpdateFreq % 5 == 0 ? countUpdateFreq + 2 : countUpdateFreq; // Start the count fprintf(stdout, "0\r"); fflush(stdout); // Start the timer platform_GetTime(start); // Generate and process the number of graphs requested for (K = 0; K < NumGraphs; K++) { if ((Result = gp_CreateRandomGraph(theGraph)) == OK) { if (theG6WriteIterator != NULL) { if ((writeResult = g6_WriteGraph(theG6WriteIterator)) != OK) { gp_ErrorMessage("Unable to write graph number %d using " "G6WriteIterator.", K); Result = writeResult; break; } } else if (tolower(OrigOut) == 'y' && tolower(OrigOutFormat) == 'a') { sprintf(theFileName, "random%c%d.txt", FILE_DELIMITER, K % 10); if ((writeResult = gp_Write(theGraph, theFileName, WRITE_ADJLIST)) != OK) { gp_ErrorMessage(writegp_ErrorMessage, FILENAME_MAX, theFileName); Result = writeResult; break; } } if ((Result = gp_CopyGraph(origGraph, theGraph)) != OK) { gp_ErrorMessage("Unable to make a copy of graph number %d " "before embedding.", K); gp_Free(&theGraph); gp_Free(&origGraph); g6_FreeWriter((&theG6WriteIterator)); return Result; } Result = gp_Embed(theGraph, embedFlags); if (gp_TestEmbedResultIntegrity(theGraph, origGraph, Result) != Result) Result = NOTOK; if (Result == OK) { MainStatistic++; if (tolower(EmbeddableOut) == 'y') { sprintf(theFileName, "embedded%c%d.txt", FILE_DELIMITER, K % 10); if ((writeResult = gp_Write(theGraph, theFileName, WRITE_ADJMATRIX)) != OK) { gp_ErrorMessage(writegp_ErrorMessage, FILENAME_MAX, theFileName); Result = writeResult; } } if (tolower(AdjListsForEmbeddingsOut) == 'y') { sprintf(theFileName, "adjlist%c%d.txt", FILE_DELIMITER, K % 10); if ((writeResult = gp_Write(theGraph, theFileName, WRITE_ADJLIST)) != OK) { gp_ErrorMessage(writegp_ErrorMessage, FILENAME_MAX, theFileName); Result = writeResult; } } } else if (Result == NONEMBEDDABLE) { if (embedFlags == EMBEDFLAGS_PLANAR || embedFlags == EMBEDFLAGS_OUTERPLANAR) { if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_A) ObstructionMinorFreqs[0]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_B) ObstructionMinorFreqs[1]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_C) ObstructionMinorFreqs[2]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_D) ObstructionMinorFreqs[3]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_E) ObstructionMinorFreqs[4]++; if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_E1) ObstructionMinorFreqs[5]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_E2) ObstructionMinorFreqs[6]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_E3) ObstructionMinorFreqs[7]++; else if (gp_GetObstructionMinorType(theGraph) & MINORTYPE_E4) ObstructionMinorFreqs[8]++; if (tolower(ObstructedOut) == 'y') { sprintf(theFileName, "obstructed%c%d.txt", FILE_DELIMITER, K % 10); if ((writeResult = gp_Write(theGraph, theFileName, WRITE_ADJMATRIX)) != OK) { gp_ErrorMessage(writegp_ErrorMessage, FILENAME_MAX, theFileName); Result = writeResult; } } } } // If there is an error in processing, then write the file for debugging if (Result != OK && Result != NONEMBEDDABLE) { sprintf(theFileName, "error%c%d.txt", FILE_DELIMITER, K % 10); if ((writeResult = gp_Write(origGraph, theFileName, WRITE_ADJLIST)) != OK) { gp_ErrorMessage(writegp_ErrorMessage, FILENAME_MAX, theFileName); Result = writeResult; } } } // Terminate loop on error if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("\nError found"); Result = NOTOK; break; } // Reset (or recreate) graph for next iteration ResetGraphStorage(&theGraph, ReuseGraphs, command); // Show progress, but not so often that it bogs down progress if (!(gp_GetQuietMode() & QUIETMODE_MESSAGES)) { if ((K + 1) % countUpdateFreq == 0) { fprintf(stdout, "%d\r", K + 1); fflush(stdout); } } } // Stop the timer platform_GetTime(end); // Finish the count fprintf(stdout, "%d\n", NumGraphs); fflush(stdout); gp_Message("Done (%.3lf seconds).", platform_GetDuration(start, end)); // Print some demographic results if (Result == OK || Result == NONEMBEDDABLE) { gp_Message("No Errors Found."); // Report statistics for planar or outerplanar embedding if (embedFlags == EMBEDFLAGS_PLANAR || embedFlags == EMBEDFLAGS_OUTERPLANAR) { gp_Message("Num Embedded=%d.", MainStatistic); for (K = 0; K < 5; K++) { // Outerplanarity does not produces minors C and D if (embedFlags == EMBEDFLAGS_OUTERPLANAR && (K == 2 || K == 3)) continue; gp_Message("Minor %c = %d", K + 'A', ObstructionMinorFreqs[K]); } if (!(embedFlags & ~EMBEDFLAGS_PLANAR)) { gp_Message("\nNote: E1 are added to C, E2 are added to A, and " "E=E3+E4+K5 homeomorphs.\n"); for (K = 5; K < NUM_MINORS; K++) { gp_Message("Minor E%d = %d", K - 4, ObstructionMinorFreqs[K]); } } } // Report statistics for graph drawing else if (embedFlags == EMBEDFLAGS_DRAWPLANAR) { gp_Message("Num Graphs Embedded and Drawn=%d.", MainStatistic); } // Report statistics for subgraph homeomorphism algorithms else if (embedFlags == EMBEDFLAGS_SEARCHFORK23) { gp_Message("Of the generated graphs, %d did not contain a K_{2,3} " "homeomorph as a subgraph.", MainStatistic); } else if (embedFlags == EMBEDFLAGS_SEARCHFORK33) { gp_Message("Of the generated graphs, %d did not contain a K_{3,3} " "homeomorph as a subgraph.", MainStatistic); } else if (embedFlags == EMBEDFLAGS_SEARCHFORK4) { gp_Message("Of the generated graphs, %d did not contain a K_4 " "homeomorph as a subgraph.", MainStatistic); } } FlushConsole(stdout); g6_FreeWriter((&theG6WriteIterator)); // Free the graph structures created before the loop gp_Free(&theGraph); gp_Free(&origGraph); return Result == OK || Result == NONEMBEDDABLE ? OK : NOTOK; } /**************************************************************************** GetNumberIfZero() Internal function that gets a number if the given *pNum is zero. The prompt is displayed if the number must be obtained from the user. Whether the given number is used or obtained from the user, the function ensures it is in the range [min, max] and assigns the midpoint value if it is not. ****************************************************************************/ int GetNumberIfZero(int *pNum, char const *prompt, int min, int max) { char lineBuff[MAXLINE + 1]; memset(lineBuff, '\0', (MAXLINE + 1)); if (pNum == NULL) { gp_ErrorMessage("Unable to get number, as pointer to int is NULL."); return NOTOK; } if (prompt == NULL || strlen(prompt) == 0) { gp_ErrorMessage("Invalid prompt supplied."); return NOTOK; } while (*pNum == 0) { gp_MessagePrompt("%s", prompt); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read integer choice from stdin."); return NOTOK; } if (strlen(lineBuff) == 0 || sscanf(lineBuff, " %d", pNum) != 1) { gp_ErrorMessage("Invalid integer choice."); (*pNum) = 0; } } if (min < 1) min = 1; if (max < min) max = min; if (*pNum < min || *pNum > max) { *pNum = (max + min) / 2; gp_Message("Number out of range [%d, %d]; changed to %d.", min, max, *pNum); } return OK; } /**************************************************************************** MakeGraph() Internal function that makes a new graph, initializes it, and attaches an algorithm to it based on the command. ****************************************************************************/ graphP MakeGraph(int Size, char command) { graphP theGraph = NULL; if ((theGraph = gp_New()) == NULL || gp_EnsureVertexCapacity(theGraph, Size) != OK) { gp_ErrorMessage("Error creating space for a graph of the given size."); gp_Free(&theGraph); return NULL; } if (command != '\0') { if (ExtendGraph(theGraph, command) != OK) { gp_ErrorMessage("Unable to extend graph based on command '%c'", command); gp_Free(&theGraph); } } return theGraph; } /**************************************************************************** ResetGraphStorage() Internal function that will either reset the given graph or free it and make a new one just like it. ****************************************************************************/ void ResetGraphStorage(graphP *pGraph, int ReuseGraphs, char command) { if (ReuseGraphs) gp_ResetGraphStorage(*pGraph); else { graphP newGraph = MakeGraph(gp_GetN(*pGraph), command); gp_Free(pGraph); *pGraph = newGraph; } } /**************************************************************************** Creates a random maximal planar graph, then adds 'extraEdges' edges to it. ****************************************************************************/ int RandomGraph(char const *const commandString, int extraEdges, int numVertices, char *outfileName, char *outfile2Name) { int Result = OK; platform_time start, end; graphP theGraph = NULL, origGraph = NULL; int embedFlags = 0; char command = '\0', modifier = '\0'; if ((Result = GetCommandAndOptionalModifier(commandString, &command, &modifier)) != OK) { gp_ErrorMessage("Unable to extract command and optional modifier " "character from commandString."); return Result; } if ((Result = GetEmbedFlags(command, modifier, &embedFlags)) != OK) { gp_ErrorMessage("Unable to derive embedFlags from command and optional " "modifier character."); return Result; } if ((Result = GetNumberIfZero(&numVertices, "Enter number of vertices:", 1, 10000000) != OK)) { gp_ErrorMessage("Encountered unrecoverable error when prompting for " "numVertices."); return Result; } if ((theGraph = MakeGraph(numVertices, command)) == NULL) return NOTOK; // Acceptable downcast of time_t to unsigned int (seeding benefits from the lower bits of now) srand((unsigned int)time(NULL)); gp_Message("Creating the random graph..."); platform_GetTime(start); if (gp_CreateRandomGraphEx(theGraph, 3 * numVertices - 6 + extraEdges) != OK) { gp_ErrorMessage("gp_CreateRandomGraphEx() failed"); gp_Free(&theGraph); return NOTOK; } platform_GetTime(end); gp_Message("Created random graph with %d edges in %.3lf seconds.", gp_GetM(theGraph), platform_GetDuration(start, end)); // The user may have requested a copy of the random graph before processing if (outfile2Name != NULL) { if (gp_Write(theGraph, outfile2Name, WRITE_ADJLIST) != OK) { gp_ErrorMessage("Unable to write generated random graph before " "embedding."); gp_Free(&theGraph); return NOTOK; } } if ((origGraph = gp_DupGraph(theGraph)) == NULL) { gp_ErrorMessage("Unable to create copy of generated random graph before " "embedding."); gp_Free(&theGraph); return NOTOK; } // Do the requested algorithm on the randomly generated graph gp_Message("Now processing..."); FlushConsole(stdout); platform_GetTime(start); Result = gp_Embed(theGraph, embedFlags); platform_GetTime(end); if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("Failed to embed or find embedding obstruction in " "randomly generated graph."); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } if (gp_SortVertices(theGraph) != OK) { gp_ErrorMessage("Unable to sort vertices of graph after processing"); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } if (gp_TestEmbedResultIntegrity(theGraph, origGraph, Result) != Result) Result = NOTOK; // Write what the algorithm determined and how long it took WriteAlgorithmResults(theGraph, Result, command, start, end, NULL); // On successful algorithm result, write the output file and see if the // user wants the edge list formatted file. if (Result == OK || Result == NONEMBEDDABLE) { // If an outfileName was given on the command-line, then we write only to it. if (outfileName != NULL) { if (gp_Write(theGraph, outfileName, WRITE_ADJLIST) != OK) { gp_ErrorMessage("Unable to write embedded graph as adjacency " "list."); Result = NOTOK; } } // If no outfileName was given and not quiet mode (i.e., if in menu mode), // then we ask the user if they want to save in various formats. else if (!(gp_GetQuietMode() & QUIETMODE_MESSAGES)) { if (PromptSaveGraph(theGraph, origGraph, extraEdges, 0) != OK) { gp_ErrorMessage("Error saving graph in edge list format."); Result = NOTOK; } if (PromptSaveGraph(theGraph, origGraph, extraEdges, WRITE_ADJLIST) != OK) { gp_ErrorMessage("Error saving graph in adjacency list format."); Result = NOTOK; } if (PromptSaveGraph(theGraph, origGraph, extraEdges, WRITE_G6) != OK) { gp_ErrorMessage("Error saving graph in G6 format."); Result = NOTOK; } } } else gp_ErrorMessage("Failure occurred."); gp_Free(&theGraph); gp_Free(&origGraph); FlushConsole(stdout); return Result; } int PromptSaveGraph(graphP theGraph, graphP origGraph, int extraEdges, int saveMode) { char saveGraph = '\0'; char theFileName[MAXLINE + 1]; char lineBuff[MAXLINE + 1]; memset(theFileName, '\0', (MAXLINE + 1)); memset(lineBuff, '\0', (MAXLINE + 1)); // Select the prompt string switch (saveMode) { case WRITE_ADJLIST: gp_MessagePrompt("Do you want to save the generated graph in adjacency " "list format (y/n)?"); break; case WRITE_ADJMATRIX: gp_MessagePrompt("Do you want to save the generated graph in adjacency " "matrix format (y/n)?"); break; case WRITE_G6: gp_MessagePrompt("Do you want to save the generated graph in G6 format " "(y/n)?"); break; default: gp_MessagePrompt("Do you want to save the generated graph in edge list " "format (y/n)?"); break; } // Prompt the user while (1) { if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read user input."); return NOTOK; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &saveGraph) != 1 || !strchr(YESNOCHOICECHARS, saveGraph)) gp_Message("Invalid choice, please try again (enter y/n)."); else { saveGraph = (char)tolower(lineBuff[0]); // If no, then bail out, reporting function success if (saveGraph == 'n') return OK; // Otherwise proceed to the file save logic break; } } // Construct theFileName for saving if (extraEdges > 0) strcpy(theFileName, "nonPlanar"); else strcpy(theFileName, "maxPlanar"); switch (saveMode) { case WRITE_ADJLIST: strcat(theFileName, "AdjList.txt"); break; case WRITE_ADJMATRIX: strcat(theFileName, "AdjMatrix.txt"); break; case WRITE_G6: strcat(theFileName, ".g6"); break; default: strcat(theFileName, "EdgeList.txt"); break; } gp_Message("Saving edge list format of original graph to \"%.*s\"", FILENAME_MAX, theFileName); SaveAsciiGraph(origGraph, theFileName); strcat(theFileName, ".out.txt"); gp_Message("Saving edge list format of result to \"%.*s\"", FILENAME_MAX, theFileName); SaveAsciiGraph(theGraph, theFileName); // Save the original graph gp_Message("Saving original graph to \"%.*s\"", FILENAME_MAX, theFileName); if (saveMode) { if (gp_Write(origGraph, theFileName, saveMode) != OK) { gp_ErrorMessage("Failed to save original graph."); return NOTOK; } if (saveMode == WRITE_ADJLIST) { char zeroBasedFileName[MAXLINE + 1]; strcpy(zeroBasedFileName, theFileName); strcat(zeroBasedFileName, ".0-based.txt"); origGraph->graphFlags |= GRAPHFLAGS_ZEROBASEDIO; gp_Message("Also saving original graph in 0-based adjacency " "list format."); gp_Write(origGraph, zeroBasedFileName, saveMode); origGraph->graphFlags &= ~GRAPHFLAGS_ZEROBASEDIO; } } else SaveAsciiGraph(origGraph, theFileName); // Save the result graph if (saveMode == WRITE_G6) strcat(theFileName, ".out.g6"); else strcat(theFileName, ".out.txt"); gp_Message("Saving result graph to \"%.*s\"", FILENAME_MAX, theFileName); if (saveMode) { if (gp_Write(theGraph, theFileName, saveMode) != OK) { gp_ErrorMessage("Failed to save result graph."); return NOTOK; } } else SaveAsciiGraph(theGraph, theFileName); return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planaritySpecificGraph.c000066400000000000000000000241541521450711600307010ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" /**************************************************************************** SpecificGraph() commandString - a string (e.g. p,d,o,2,3,3e,4) indicating the algorithm to run on the specific graph infileName - name of file to read, or NULL to cause the program to prompt the user for a file name outfileName - name of primary output file, or NULL to construct an output file name based on the input outfile2Name - name of a secondary output file, or NULL to suppress secondary output, or empty string to construct the secondary output file name based on the output file name. For p=planarity and o=outerplanarity, empty string means that the planarity or outerplanarity obstruction will be written to outfileName, rather than only an embedding For d=drawing a planar graph, empty string means the visibility representation will be written to outfileName+".render.txt" inputStr - if non-NULL, overrides infileName and provides the input graph within a string pOutputStr - if non-NULL, overrides outfileName and provides a pointer pointer where a string containing the primary output should go. For p=planarity, o=outerplanarity, and d=drawing, the primary output is the graph embedding For p=planarity and o=outerplanarity, if the graph is not embeddable, then the primary output will contain the planarity or outerplanarity obstruction subgraph For 2,3,4=subgraph homeomorphism, the primary output is the homeomorphic subgraph, if found pOutput2Str - if non-NULL, overrides outfile2Name and provides a pointer pointer where a string containing the secondary output should go. For d=drawing a planar graph, the visibility representation will be written to this secondary output ****************************************************************************/ int SpecificGraph( char const *const commandString, char const *infileName, char *outfileName, char *outfile2Name, char *inputStr, char **pOutputStr, char **pOutput2Str) { int Result = OK; graphP theGraph = NULL, origGraph = NULL; platform_time start, end; char command = '\0', modifier = '\0'; int embedFlags = 0; if (GetCommandAndOptionalModifier(commandString, &command, &modifier) != OK) { gp_ErrorMessage("Unable to derive command and modifier from " "commandString."); return NOTOK; } if (GetEmbedFlags(command, modifier, &embedFlags) != OK) { gp_ErrorMessage("Unable to derive embedFlags from command and optional " "modifier character."); return NOTOK; } // Get the file name of the graph to test if (inputStr == NULL) { if (infileName != NULL) { if ((infileName = ConstructInputFileName(infileName)) == NULL) { gp_ErrorMessage("Error constructing input file name."); Result = NOTOK; } } else { while (1) { infileName = ConstructInputFileName(infileName); if (infileName == NULL || strlen(infileName) == 0) { gp_ErrorMessage("Error constructing input file name."); Result = NOTOK; break; } else if (strncmp(infileName, "stdin", strlen("stdin")) == 0) { // NOTE: When run from command-line or test, it is not // possible to have infileName being NULL and therefore // prompting the user for the input file name, so there's no // way you could have them enter stdin and reach this error // from command-line gp_Message("Please retry with an input file " "path: stdin not supported from menu."); infileName = NULL; } else break; } } } if (Result == OK) { // Create the graph and, if needed, attach the correct algorithm to it if ((theGraph = gp_New()) == NULL) { gp_ErrorMessage("Unable to allocate graph."); return NOTOK; } // Read the graph into memory if (inputStr == NULL) Result = gp_Read(theGraph, infileName); else Result = gp_ReadFromString(theGraph, inputStr); } // If there was an unrecoverable error, report it and exit early. if (Result != OK) { gp_ErrorMessage("Failed to read graph."); gp_Free(&theGraph); return NOTOK; } // Copy the graph for integrity checking origGraph = gp_DupGraph(theGraph); if (origGraph == NULL) { gp_ErrorMessage("Unable to duplicate original graph."); gp_Free(&theGraph); return NOTOK; } // Extend theGraph so that it is equivalent to having created a new instance // of a graph subclass that supports the desired algorithm if (ExtendGraph(theGraph, command) == OK) { // Run the algorithm platform_GetTime(start); // gp_DepthFirstSearch(theGraph); // gp_SortVertices(theGraph); // gp_Write(theGraph, "debug.before.txt", WRITE_DEBUGINFO); // gp_SortVertices(theGraph); Result = gp_Embed(theGraph, embedFlags); platform_GetTime(end); if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("Failed to embed graph."); gp_Free(&theGraph); gp_Free(&origGraph); return NOTOK; } Result = gp_TestEmbedResultIntegrity(theGraph, origGraph, Result); } else { platform_GetTime(start); Result = NOTOK; platform_GetTime(end); } // Write what the algorithm determined and how long it took WriteAlgorithmResults(theGraph, Result, command, start, end, infileName); // Free the graph obtained for integrity checking. gp_Free(&origGraph); // Report an error, if there was one, free the graph, and return if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("AN ERROR HAS BEEN DETECTED"); Result = NOTOK; // gp_Write(theGraph, "debug.after.txt", WRITE_DEBUGINFO); } // Provide the output file(s) else { // Restore the vertex ordering of the original graph (undo DFS numbering) if (gp_SortVertices(theGraph) != OK) { gp_ErrorMessage("Unable to restore original vertex ordering."); gp_Free(&theGraph); return NOTOK; } // Determine the name of the primary output file outfileName = ConstructPrimaryOutputFileName(infileName, outfileName, command); // For some algorithms, the primary output file is not always written if ((strchr("pdo", command) && Result == NONEMBEDDABLE) || (strchr("234", command) && Result == OK)) { // Do not write the file } // Write the primary output file, if appropriate to do so else { int writeResult = OK; if (pOutputStr == NULL) writeResult = gp_Write(theGraph, outfileName, WRITE_ADJLIST); else writeResult = gp_WriteToString(theGraph, pOutputStr, WRITE_ADJLIST); if (writeResult != OK) { gp_ErrorMessage("Failed to write graph to primary output file."); Result = NOTOK; } } // NOW WE WANT TO WRITE THE SECONDARY OUTPUT to a FILE or STRING // When called from the menu system, we want to write the planar or outerplanar // obstruction, if one exists. For planar graph drawing, we want the character // art rendition. if (outfile2Name != NULL || pOutput2Str != NULL) { int writeResult = OK; if (pOutput2Str != NULL) { // A non-embeddable obstruction subgraph also goes into the primary output, not the secondary if ((command == 'p' || command == 'o') && Result == NONEMBEDDABLE) writeResult = gp_WriteToString(theGraph, pOutputStr, WRITE_ADJLIST); // Only the planar visibility representation goes into the secondary output else if (command == 'd' && Result == OK) writeResult = gp_DrawPlanar_RenderToString(theGraph, pOutput2Str); } else if (outfile2Name != NULL) { if ((command == 'p' || command == 'o') && Result == NONEMBEDDABLE) { // By default, use the same name as the primary output file name if (strlen(outfile2Name) == 0) outfile2Name = outfileName; writeResult = gp_Write(theGraph, outfile2Name, WRITE_ADJLIST); } else if (command == 'd' && Result == OK) { // An empty but non-NULL string is passed to indicate the necessity // of selecting a default name for the second output file. // By default, add ".render.txt" to the primary output file name if (strlen(outfile2Name) == 0) strcat((outfile2Name = outfileName), ".render.txt"); writeResult = gp_DrawPlanar_RenderToFile(theGraph, outfile2Name); } } if (writeResult != OK) { gp_ErrorMessage("Failed to write secondary output file."); Result = NOTOK; } } } // Free the graph gp_Free(&theGraph); // Flush any remaining message content to the user, and return the result FlushConsole(stdout); return Result; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityTestAllGraphs.c000066400000000000000000000416531521450711600307120ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" typedef struct { double duration; int numGraphsTested; int numOK; int numNONEMBEDDABLE; int errorFlag; } testAllStats; typedef testAllStats *testAllStatsP; int testAllGraphs(char command, char modifier, char const *const infileName, testAllStatsP stats); int outputTestAllGraphsResults(char command, char modifier, testAllStatsP stats, char const *const infileName, char *outfileName, char **pOutputStr); // #define TESTALLGRAPHS_MEMORY_TIMING_TEST #ifdef TESTALLGRAPHS_MEMORY_TIMING_TEST int testAllGraphsN8(char command, char modifier, char const *const infileName, testAllStatsP stats); #endif /**************************************************************************** TestAllGraphs() commandString - command to run; e.g.`-(pdo234)` (plus optional modifier character) to perform the corresponding algorithm on each graph in .g6 file infileName - non-NULL and nonempty string containing name of .g6 input file outfileName - name of primary output file, or NULL pOutputStr - pointer to string which we wish to use to store the result of applying the chosen graph algorithm extension to all graphs in the .g6 file ****************************************************************************/ int TestAllGraphs(char const *const commandString, char const *const infileName, char *outfileName, char **pOutputStr) { int Result = OK; char command = '\0', modifier = '\0'; platform_time start, end; testAllStats stats; memset(&stats, 0, sizeof(testAllStats)); if (GetCommandAndOptionalModifier(commandString, &command, &modifier) != OK) { gp_ErrorMessage("Unable to determine command (and optional modifier) from " "command string.\n"); return NOTOK; } if (infileName == NULL) { gp_ErrorMessage("No input file provided."); return NOTOK; } gp_Message("Starting to test all graphs in \"%.*s\" for command=\"%s\".", FILENAME_MAX, infileName, commandString); // Start the timer platform_GetTime(start); #ifndef TESTALLGRAPHS_MEMORY_TIMING_TEST Result = testAllGraphs(command, modifier, infileName, &stats); #else Result = testAllGraphsN8(command, modifier, infileName, &stats); #endif // Stop the timer platform_GetTime(end); stats.duration = platform_GetDuration(start, end); if (Result != OK) { gp_ErrorMessage("Encountered error while running command '%c' on all " "graphs in \"%.*s\".", command, FILENAME_MAX, infileName); Result = NOTOK; } else { gp_Message("Done testing all graphs (%.3lf seconds).", stats.duration); } if (outputTestAllGraphsResults(command, modifier, &stats, infileName, outfileName, pOutputStr) != OK) { gp_ErrorMessage("Error outputting results running command '%c' on all " "graphs in \"%.*s\".\n", command, FILENAME_MAX, infileName); Result = NOTOK; } return Result; } int testAllGraphs(char command, char modifier, char const *const infileName, testAllStatsP stats) { int Result = OK; graphP origGraphRead = NULL; graphP graphForEmbedding = NULL; int embedFlags = 0, numOK = 0, numNONEMBEDDABLE = 0; int order = 0; int lineNum = 0; G6ReadIteratorP theG6ReadIterator = NULL; if (GetEmbedFlags(command, modifier, &embedFlags) != OK) { gp_ErrorMessage("Invalid command or modifier."); stats->errorFlag = TRUE; return NOTOK; } if ((origGraphRead = gp_New()) == NULL) { gp_ErrorMessage("Unable to allocate graph for reading."); stats->errorFlag = TRUE; return NOTOK; } if (g6_NewReader((&theG6ReadIterator), origGraphRead) != OK || g6_InitReaderWithFileName(theG6ReadIterator, infileName) != OK) { gp_ErrorMessage("Unable to allocate or initialize G6 read iterator."); gp_Free(&origGraphRead); g6_FreeReader((&theG6ReadIterator)); stats->errorFlag = TRUE; return NOTOK; } // The order of the graphs in the G6 source file or string was determined by // g6_InitReaderWithFileName() and we obtain it to initialize the graph for // embedding order = gp_GetN(origGraphRead); if ((graphForEmbedding = gp_New()) == NULL || gp_EnsureVertexCapacity(graphForEmbedding, order) != OK) { gp_ErrorMessage("Unable allocate graph for embedding."); g6_FreeReader((&theG6ReadIterator)); gp_Free(&origGraphRead); gp_Free(&graphForEmbedding); stats->errorFlag = TRUE; return NOTOK; } if (ExtendGraph(graphForEmbedding, command) != OK) { gp_ErrorMessage("Unable to extend graph for embedding operation."); g6_FreeReader(&theG6ReadIterator); gp_Free(&origGraphRead); gp_Free(&graphForEmbedding); stats->errorFlag = TRUE; return NOTOK; } while (TRUE) { lineNum++; if (g6_ReadGraph(theG6ReadIterator) != OK) { gp_ErrorMessage("Unable to read graph on line %d.", lineNum); Result = NOTOK; break; } if (g6_EndReached(theG6ReadIterator)) break; if (gp_CopyGraph(graphForEmbedding, origGraphRead) != OK) { gp_ErrorMessage("Unable to copy graph."); Result = NOTOK; break; } Result = gp_Embed(graphForEmbedding, embedFlags); if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("Failed to embed graph on line %d for command '%c'.", lineNum, command); Result = NOTOK; } if (gp_TestEmbedResultIntegrity(graphForEmbedding, origGraphRead, Result) != Result) { gp_ErrorMessage("Embed integrity check failed for graph on line %d " "for command '%c'.\n", lineNum, command); Result = NOTOK; } if (Result == OK) numOK++; else if (Result == NONEMBEDDABLE) { numNONEMBEDDABLE++; // Now that we've processed the NONEMBEDDABLE result, we set the // Result to OK so that we exit the loop with an OK or NOTOK only Result = OK; } else { if (modifier == '\0') { gp_ErrorMessage("Command '%c' error on graph on line %d.", command, lineNum); } else { gp_ErrorMessage("Command '%c%c' error on graph on line %d.", command, modifier, lineNum); } Result = NOTOK; break; } } // Since we increment lineNum at the beginning of the loop, if an error // occurs during processing a graph on the current lineNum, or if we reach // the end of the input, then the number of graphs successfully tested is // lineNum - 1. stats->numGraphsTested = lineNum - 1; stats->numOK = numOK; stats->numNONEMBEDDABLE = numNONEMBEDDABLE; stats->errorFlag = (Result == OK) ? FALSE : TRUE; g6_FreeReader((&theG6ReadIterator)); gp_Free(&origGraphRead); gp_Free(&graphForEmbedding); return Result; } int outputTestAllGraphsResults(char command, char modifier, testAllStatsP stats, char const *const infileName, char *outfileName, char **pOutputStr) { int Result = OK; char *finalSlash = strrchr(infileName, FILE_DELIMITER); char const *infileBasename = finalSlash ? (finalSlash + 1) : infileName; char const *headerFormat = "FILENAME=\"%s\" DURATION=\"%.3lf\"\n"; int numCharsToReprNumGraphsTested = 0, numCharsToReprNumOK = 0, numCharsToReprNumNONEMBEDDABLE = 0; char *theOutputStr = NULL; int headerStrLen = 0, resultStrLen = 0; char *resultsStr = NULL; if (outfileName == NULL && (pOutputStr == NULL || *pOutputStr != NULL)) { gp_ErrorMessage("Invalid parameters: Must be able to output to file or " "memory."); return NOTOK; } headerStrLen = strlen(headerFormat) + strlen(infileBasename) + strlen("-1.7976931348623158e+308") + // -DBL_MAX from float.h 3; if (GetNumCharsToReprInt(stats->numGraphsTested, &numCharsToReprNumGraphsTested) != OK || GetNumCharsToReprInt(stats->numOK, &numCharsToReprNumOK) != OK || GetNumCharsToReprInt(stats->numNONEMBEDDABLE, &numCharsToReprNumNONEMBEDDABLE) != OK) { gp_ErrorMessage("Unable to determine the number of characters required " "to represent testAllGraphs stat values."); return NOTOK; } resultStrLen = 1 + // - char 1 + // command char 1 + // optional modifier char 1 + // space char numCharsToReprNumGraphsTested + 1 + // space char numCharsToReprNumOK + 1 + // space char numCharsToReprNumNONEMBEDDABLE + 1 + // space char 7 + // either ERROR or SUCCESS, so the longer of which is 7 chars 3 // (carriage return,) newline and null terminator; ; theOutputStr = (char *)malloc((headerStrLen + resultStrLen + 1) * sizeof(char)); if (theOutputStr == NULL) { gp_ErrorMessage("Unable allocate memory for the output."); return NOTOK; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(theOutputStr, headerFormat, infileBasename, stats->duration); #pragma GCC diagnostic pop resultsStr = theOutputStr + strlen(theOutputStr); if (modifier == '\0') sprintf(resultsStr, "-%c %d %d %d %s\n", command, stats->numGraphsTested, stats->numOK, stats->numNONEMBEDDABLE, stats->errorFlag ? "ERROR" : "SUCCESS"); else sprintf(resultsStr, "-%c%c %d %d %d %s\n", command, modifier, stats->numGraphsTested, stats->numOK, stats->numNONEMBEDDABLE, stats->errorFlag ? "ERROR" : "SUCCESS"); if (outfileName != NULL) { FILE *outfile = strcmp(outfileName, "stdout") == 0 ? stdout : fopen(outfileName, WRITETEXT); if (outfile != NULL) { fprintf(outfile, "%s", theOutputStr); if (strcmp(outfileName, "stdout") != 0) fclose(outfile); outfile = NULL; Result = OK; } else Result = NOTOK; free(theOutputStr); theOutputStr = NULL; } else { *pOutputStr = theOutputStr; theOutputStr = NULL; Result = OK; } return Result; } #ifdef TESTALLGRAPHS_MEMORY_TIMING_TEST /******************************************************************************* * An alternative version of testAllGraphs() that can be used for timing tests *******************************************************************************/ #define NUM8VERTEXTGRAPHS 12346 int testAllGraphsN8(char command, char modifier, char const *const infileName, testAllStatsP stats) { int Result = OK; graphP origGraphRead[NUM8VERTEXTGRAPHS + 1]; graphP graphForEmbedding = NULL; int embedFlags = 0, numOK = 0, numNONEMBEDDABLE = 0; int order = 0; int lineNum = 0; platform_time start, end; G6ReadIteratorP theG6ReadIterator = NULL; if (infileName == NULL || !strstr(infileName, "n8.mALL.g6")) { gp_ErrorMessage("testAllGraphsN8() is coded just for processing all 8-vertex graphs"); return NOTOK; } if (GetEmbedFlags(command, modifier, &embedFlags) != OK) { gp_ErrorMessage("Invalid command or modifier.\n"); stats->errorFlag = TRUE; return NOTOK; } // Initialize all pointer and allocate a graph structure that is // needed to read all graphs from the file into memory for (lineNum = 0; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) origGraphRead[lineNum] = NULL; if ((origGraphRead[0] = gp_New()) == NULL) { gp_ErrorMessage("Unable to allocate graph for reading.\n"); stats->errorFlag = TRUE; return NOTOK; } // The 0th origGraph will be used by the read iterator, and then the // graphs will be copied into locations 1 through NUM8VERTEXTGRAPHS if (g6_NewReader((&theG6ReadIterator), origGraphRead[0]) != OK || g6_InitReaderWithFileName(theG6ReadIterator, infileName) != OK) { gp_ErrorMessage("Unable to allocate or initialize G6 read iterator.\n"); gp_Free(&origGraphRead[0]); g6_FreeReader((&theG6ReadIterator)); stats->errorFlag = TRUE; return NOTOK; } // The order of the graphs in the G6 source file or string was determined by // g6_InitReaderWithFileName(), and we obtain it to ensure vertex capacity in // the graphs that will receive copies origGraphRead[0] from each line of the // g6 file and also to help initialize the graph for embedding order = gp_GetN(origGraphRead[0]); for (lineNum = 1; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) { if ((origGraphRead[lineNum] = gp_New()) == NULL || gp_EnsureVertexCapacity(origGraphRead[lineNum], order) != OK) { gp_ErrorMessage("Unable allocate graphs for reading.\n"); g6_FreeReader((&theG6ReadIterator)); for (lineNum = 0; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) gp_Free(&origGraphRead[lineNum]); stats->errorFlag = TRUE; return NOTOK; } } if ((graphForEmbedding = gp_New()) == NULL || gp_EnsureVertexCapacity(graphForEmbedding, order) != OK || ExtendGraph(graphForEmbedding, command) != OK) { gp_ErrorMessage("Unable allocate graph for embedding.\n"); g6_FreeReader((&theG6ReadIterator)); for (lineNum = 0; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) gp_Free(&origGraphRead[lineNum]); gp_Free(&graphForEmbedding); stats->errorFlag = TRUE; return NOTOK; } // Read the graphs into memory lineNum = 0; while (lineNum <= NUM8VERTEXTGRAPHS) { lineNum++; if (g6_ReadGraph(theG6ReadIterator) != OK) { gp_ErrorMessage("Unable to read graph on line %d.\n", lineNum); Result = NOTOK; break; } if (g6_EndReached(theG6ReadIterator)) break; if (gp_CopyGraph(origGraphRead[lineNum], origGraphRead[0]) != OK) { gp_ErrorMessage("Unable to copy graph.\n"); Result = NOTOK; break; } } g6_FreeReader((&theG6ReadIterator)); if (Result != OK) { for (lineNum = 0; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) gp_Free(&origGraphRead[lineNum]); gp_Free(&graphForEmbedding); return NOTOK; } // Since we increment lineNum at the beginning of the loop, if an error // occurs during processing a graph on the current lineNum, or if we reach // the end of the input, then the number of graphs successfully read is // lineNum - 1. stats->numGraphsTested = lineNum - 1; // Start a timer, and we will iterate the work many times to // get a more reliable reading. platform_GetTime(start); for (int i = 0; i < 1000; ++i) { numOK = numNONEMBEDDABLE = 0; for (lineNum = 1; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) { if (gp_CopyGraph(graphForEmbedding, origGraphRead[lineNum]) != OK) { gp_ErrorMessage("Unable to copy graph.\n"); Result = NOTOK; break; } Result = gp_Embed(graphForEmbedding, embedFlags); if (Result != OK && Result != NONEMBEDDABLE) { gp_ErrorMessage("Failed to embed graph on line %d", lineNum); Result = NOTOK; break; } if (gp_TestEmbedResultIntegrity(graphForEmbedding, origGraphRead[lineNum], Result) != Result) { gp_ErrorMessage("Embed integrity check failed for graph on line %d", lineNum); Result = NOTOK; break; } if (Result == OK) numOK++; else if (Result == NONEMBEDDABLE) { numNONEMBEDDABLE++; Result = OK; } } if (Result != OK) break; } platform_GetTime(end); gp_Message("In-memory graph processing test executed in %.3lf seconds.\n", platform_GetDuration(start, end)); stats->numOK = numOK; stats->numNONEMBEDDABLE = numNONEMBEDDABLE; stats->errorFlag = (Result == OK) ? FALSE : TRUE; for (lineNum = 0; lineNum <= NUM8VERTEXTGRAPHS; lineNum++) gp_Free(&origGraphRead[lineNum]); gp_Free(&graphForEmbedding); return Result == OK ? OK : NOTOK; } #endif edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityTransformGraph.c000066400000000000000000000070341521450711600311250ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" int transformFile(graphP theGraph, char const *infileName); int transformString(graphP theGraph, char *inputStr); /**************************************************************************** TransformGraph() commandString - command to run; i.e. `-(gam)` to transform graph to .g6, adjacency list, or adjacency matrix format infileName - name of file to read, or NULL to cause the program to prompt the user for a file name inputStr - string containing input graph, or NULL to cause the program to fall back on reading from file outputBase - pointer to the flag set for whether output is 0- or 1-based outputFormat - output format outfileName - name of primary output file, or NULL to construct an output file name based on the input pOutputStr - pointer to string which we wish to use to store the transformation output ****************************************************************************/ int TransformGraph(char const *const commandString, char const *const infileName, char *inputStr, int *outputBase, char const *outfileName, char **pOutputStr) { int Result = OK; int outputFormat = -1; graphP theGraph = NULL; theGraph = gp_New(); if (theGraph == NULL) { gp_ErrorMessage("Unable to allocate graphP for input graph " "transformation target."); return NOTOK; } if (commandString[0] == '-') { if (commandString[1] == 'g') outputFormat = WRITE_G6; else if (commandString[1] == 'a') outputFormat = WRITE_ADJLIST; else if (commandString[1] == 'm') outputFormat = WRITE_ADJMATRIX; else { gp_ErrorMessage("Invalid argument; only -(gam) is allowed."); gp_Free(&theGraph); return NOTOK; } if (inputStr) Result = transformString(theGraph, inputStr); else Result = transformFile(theGraph, infileName); if (Result != OK) { gp_ErrorMessage("Unable to transform input graph."); } else { // Want to know whether the output is 0- or 1-based; will always be // 0-based for transformations of .g6 input if (outputBase != NULL) (*outputBase) = (gp_GetGraphFlags(theGraph) & GRAPHFLAGS_ZEROBASEDIO) ? 1 : 0; if (pOutputStr != NULL) Result = gp_WriteToString(theGraph, pOutputStr, outputFormat); else Result = gp_Write(theGraph, outfileName, outputFormat); if (Result != OK) gp_ErrorMessage("Unable to write graph."); } } else { gp_ErrorMessage("Invalid argument; must start with '-'."); Result = NOTOK; } gp_Free(&theGraph); return Result; } int transformFile(graphP theGraph, char const *infileName) { if (infileName == NULL) { if ((infileName = ConstructInputFileName(infileName)) == NULL) { gp_ErrorMessage("Unable to construct input file name for graph to " "transform."); return NOTOK; } } return gp_Read(theGraph, infileName); } int transformString(graphP theGraph, char *inputStr) { if (inputStr == NULL || strlen(inputStr) == 0) { gp_ErrorMessage("Input string is null or empty."); return NOTOK; } return gp_ReadFromString(theGraph, inputStr); } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarityUtils.c000066400000000000000000001041521521450711600272670ustar00rootroot00000000000000/* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #include "planarity.h" #include #include /**************************************************************************** Configuration ****************************************************************************/ char Mode = 'r', OrigOut = 'n', OrigOutFormat = 'a', EmbeddableOut = 'n', ObstructedOut = 'n', AdjListsForEmbeddingsOut = 'n'; int Reconfigure(void) { int Result = OK; char lineBuff[MAXLINE + 1]; memset(lineBuff, '\0', (MAXLINE + 1)); while (1) { gp_MessagePrompt("Do you want to \n" " Randomly generate graphs (r),\n" " Specify a graph (s),\n" " Randomly generate a maximal planar graph (m), or\n" " Randomly generate a non-planar graph (n)?"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch reconfigure choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &Mode) != 1 || !strchr("rsmn", tolower(Mode))) gp_Message("Invalid choice for Mode; please retry."); else { Mode = (char)tolower(Mode); break; } } if (Result == OK && Mode == 'r') { gp_Message("NOTE: The directories for the graphs you want must exist."); while (1) { gp_MessagePrompt("Do you want original graphs in directory " "'random'? (y/n)"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &OrigOut) != 1 || !strchr(YESNOCHOICECHARS, OrigOut)) gp_Message("Invalid choice; please retry."); else { OrigOut = (char)tolower(OrigOut); break; } } if (Result == OK && OrigOut == 'y') { while (1) { gp_MessagePrompt("Do you want to output generated graphs to " "Adjacency List (last 10 only) or to G6 " "(all)? (a/g)"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &OrigOutFormat) != 1 || !strchr("aAgG", OrigOutFormat)) gp_Message("Invalid choice; please retry."); else { OrigOutFormat = (char)tolower(OrigOutFormat); break; } } } if (Result == OK) { while (1) { gp_MessagePrompt("Do you want adj. matrix of embeddable graphs " "in directory 'embedded' (last 10 max))? " "(y/n)"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &EmbeddableOut) != 1 || !strchr(YESNOCHOICECHARS, EmbeddableOut)) gp_Message("Invalid choice; please retry."); else { EmbeddableOut = (char)tolower(EmbeddableOut); break; } } } if (Result == OK) { while (1) { gp_MessagePrompt("Do you want adj. matrix of obstructed graphs " "in directory 'obstructed' (last 10 max)? " "(y/n)"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &ObstructedOut) != 1 || !strchr(YESNOCHOICECHARS, ObstructedOut)) gp_Message("Invalid choice; please retry."); else { ObstructedOut = (char)tolower(ObstructedOut); break; } } } if (Result == OK) { while (1) { gp_MessagePrompt("Do you want adjacency list format of " "embeddings in directory 'adjlist' " "(last 10 max)? (y/n)"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to fetch choice from stdin."); Result = NOTOK; break; } if (strlen(lineBuff) != 1 || sscanf(lineBuff, " %c", &AdjListsForEmbeddingsOut) != 1 || !strchr(YESNOCHOICECHARS, AdjListsForEmbeddingsOut)) gp_Message("Invalid choice; please retry."); else { AdjListsForEmbeddingsOut = (char)tolower(AdjListsForEmbeddingsOut); break; } } } } FlushConsole(stdout); return Result; } int GetLineFromStdin(char *lineBuff, int lineBuffSize) { if (lineBuff == NULL) { gp_ErrorMessage("Line buffer to populate is NULL."); return NOTOK; } memset(lineBuff, '\0', lineBuffSize); if (fgets(lineBuff, lineBuffSize, stdin) == NULL && ferror(stdin)) { gp_ErrorMessage("Call to fgets() from stdin failed."); return NOTOK; } // From https://stackoverflow.com/a/28462221, strcspn finds the index of the // first char in charset; this way, I replace the char at that index with // the null-terminator // N.B. See https://cplusplus.com/reference/cstring/strcspn/ : if no chars // in str2 appear in str1, will return strlen(str1), so this line is a no-op lineBuff[strcspn(lineBuff, "\n\r")] = '\0'; // works for LF, CR, CRLF, LFCR, ... return OK; } void FlushConsole(FILE *f) { // N.B. fflush(stdin) constitutes undefined behaviour; see: // https://c-faq.com/stdio/stdinflush.html if (f == stdin) return; fflush(f); } /**************************************************************************** ****************************************************************************/ void SaveAsciiGraph(graphP theGraph, char *fileName) { int vertexLabelFix, e; FILE *outfile = fopen(fileName, WRITETEXT); // The fileName may specify a directory that doesn't exist if (outfile == NULL) { gp_ErrorMessage("Failed to write to \"%.*s\"\nMake the directory if " "not present.", FILENAME_MAX, fileName); return; } // If fileName includes path elements, remove them before writing the file's name to the file if (strrchr(fileName, FILE_DELIMITER)) fileName = strrchr(fileName, FILE_DELIMITER) + 1; fprintf(outfile, "%s\n", fileName); // This edge list file format uses 1-based vertex numbering, and the current code // internally uses 1-based indexing by default, so this vertex label 'fix' adds zero // But earlier code used 0-based indexing and added one on output, so we replicate // that behavior in case the current code has been compiled with zero-based indexing. vertexLabelFix = 1 - gp_LowerBoundVertexStorage(theGraph); // Iterate over the edges of the graph for (e = gp_LowerBoundEdges(theGraph); e < gp_UpperBoundEdges(theGraph); e += 2) { // Only output edges that haven't been deleted (i.e. skip the edge holes) if (gp_EdgeInUse(theGraph, e)) { fprintf(outfile, "%d ", gp_GetNeighbor(theGraph, e) + vertexLabelFix); fprintf(outfile, "%d\n", gp_GetNeighbor(theGraph, e + 1) + vertexLabelFix); } } // Since vertex numbers are at least 1, this indicates the end of the edge list fprintf(outfile, "0 0\n"); fclose(outfile); } /**************************************************************************** ReadTextFileIntoString() Reads the file content indicated by infileName using a single fread(), and returns the result in an allocated string. The caller needs to free() the returned string when done with it. Returns NULL on error, or an allocated string containing the file content. ****************************************************************************/ char *ReadTextFileIntoString(char const *infileName) { FILE *infile = NULL; char *inputString = NULL; if (infileName == NULL || strlen(infileName) == 0) gp_ErrorMessage("Unable to fopen() with NULL or empty infileName."); else if ((infile = fopen(infileName, "r")) == NULL) gp_ErrorMessage("fopen() failed."); else { long filePos = ftell(infile); long fileSize; fseek(infile, 0, SEEK_END); fileSize = ftell(infile); fseek(infile, filePos, SEEK_SET); if ((inputString = (char *)malloc((fileSize + 1) * sizeof(char))) != NULL) { long bytesRead = fread((void *)inputString, 1, fileSize, infile); inputString[bytesRead] = '\0'; } fclose(infile); } return inputString; } /**************************************************************************** * TextFileMatchesString() * * Compares the text file content from the file named 'theFileName' with * the content of 'theString'. * * Textual equality is measured as content equality except for suppressing * differences between CRLF and LF-only line delimiters. * * Returns TRUE if the contents are textually equal, FALSE otherwise ****************************************************************************/ int TextFileMatchesString(char const *theFileName, char const *theString) { int Result = TRUE; FILE *infile = NULL; if (theFileName != NULL) infile = fopen(theFileName, "r"); if (infile == NULL || theString == NULL) Result = FALSE; else { int c1 = 0, c2 = 0, stringIndex = 0; // Read the input file to the end while ((c1 = fgetc(infile)) != EOF) { // Want to suppress distinction between lines ending with CRLF versus LF // by looking at only LF characters in the file if (c1 == '\r') continue; // Since c1 now has a non-CR, non-EOF from the input file, we now also // get a character from the string, except ignoring CRs again while ((c2 = (int)theString[stringIndex++]) == '\r') ; // If c1 doesn't equal c2 (whether c2 is a null terminator or a different character) // then the file content doesn't match the string if (c1 != c2) { Result = FALSE; break; } } // If the outer while loop got to the end of the file if (c1 == EOF) { // Then get another character from the string, once again suppressing CRs, and then... while ((c2 = (int)theString[stringIndex++]) == '\r') ; // Test whether or not the second file also ends, same as the first. if (c2 != '\0') Result = FALSE; } } if (infile != NULL) fclose(infile); return Result; } /**************************************************************************** ****************************************************************************/ int TextFilesEqual(char *file1Name, char *file2Name) { int Result = TRUE; FILE *infile1 = NULL, *infile2 = NULL; infile1 = fopen(file1Name, "r"); infile2 = fopen(file2Name, "r"); if (infile1 == NULL || infile2 == NULL) Result = FALSE; else { int c1 = 0, c2 = 0; // Read the first file to the end while ((c1 = fgetc(infile1)) != EOF) { // Want to suppress distinction between lines ending with CRLF versus LF if (c1 == '\r') continue; // Get a char from the second file, except suppress CR again while ((c2 = fgetc(infile2)) == '\r') ; // If we got a char from the first file, but not from the second // then the second file is shorter, so files are not equal if (c2 == EOF) { Result = FALSE; break; } // If we got a char from second file, but not equal to char from // first file, then files are not equal if (c1 != c2) { Result = FALSE; break; } } // If we got to the end of the first file without breaking the loop... if (c1 == EOF) { // Then, once again, suppress CRs first, and then... while ((c2 = fgetc(infile2)) == '\r') ; // Test whether or not the second file also ends, same as the first. if (fgetc(infile2) != EOF) Result = FALSE; } } if (infile1 != NULL) fclose(infile1); if (infile2 != NULL) fclose(infile2); return Result; } /**************************************************************************** ****************************************************************************/ int BinaryFilesEqual(char *file1Name, char *file2Name) { int Result = TRUE; FILE *infile1 = NULL, *infile2 = NULL; infile1 = fopen(file1Name, "r"); infile2 = fopen(file2Name, "r"); if (infile1 == NULL || infile2 == NULL) Result = FALSE; else { int c1 = 0, c2 = 0; // Read the first file to the end while ((c1 = fgetc(infile1)) != EOF) { // If we got a char from the first file, but not from the second // then the second file is shorter, so files are not equal if ((c2 = fgetc(infile2)) == EOF) { Result = FALSE; break; } // If we got a char from second file, but not equal to char from // first file, then files are not equal if (c1 != c2) { Result = FALSE; break; } } // If we got to the end of the first file without breaking the loop... if (c1 == EOF) { // Then attempt to read from the second file to ensure it also ends. if (fgetc(infile2) != EOF) Result = FALSE; } } if (infile1 != NULL) fclose(infile1); if (infile2 != NULL) fclose(infile2); return Result; } /**************************************************************************** ALGORITHM FLAGS/SPECIFIERS ****************************************************************************/ char const *GetAlgorithmFlags(void) { return "C = command (algorithm implementation to run)\n" " -p = Planar embedding and Kuratowski subgraph isolation\n" " -d = Planar graph drawing by visibility representation\n" " -o = Outerplanar embedding and obstruction isolation\n" " -2 = Search for subgraph homeomorphic to K_{2,3}\n" " -3 = Search for subgraph homeomorphic to K_{3,3}\n" " -4 = Search for subgraph homeomorphic to K_4\n"; } char const *GetAlgorithmSpecifiers(void) { return "P. Planar embedding and Kuratowski subgraph isolation\n" "D. Planar graph drawing by visibility representation\n" "O. Outerplanar embedding and obstruction isolation\n" "2. Search for subgraph homeomorphic to K_{2,3}\n" "3. Search for subgraph homeomorphic to K_{3,3}\n" "4. Search for subgraph homeomorphic to K_4\n"; } char const *GetAlgorithmChoices(void) { return "pdo234"; } /**************************************************************************** * GetCommandAndOptionalModifier() * * When provided with a valid commandString, determine the command character * (and optional modifier) and return to the caller via the pointers to chars. * * Note that characters are processed with tolower(), since comparisons are * typically performed only against lower-case letters; when applied to a digit * (or to any other character where such conversion is not possible), tolower() * returns the same character. * * Returns OK when command (and optional modifier) are successfully extracted * from the commandString; no validation of the characters supplied is performed * at this point. * * Returns NOTOK if the commandString is invalid, or if * the pointer to the character to which you wish to assign the extracted * character is NULL. ****************************************************************************/ int GetCommandAndOptionalModifier(const char *commandString, char *command, char *modifier) { if (commandString != NULL && commandString[0] == '-') commandString++; if (commandString == NULL || strlen(commandString) == 0) { gp_ErrorMessage("Cannot get embed flags for empty command string."); return NOTOK; } if (command == NULL) { gp_ErrorMessage("Pointer to character to which to write command is " "NULL."); return NOTOK; } (*command) = '\0'; if (modifier != NULL) (*modifier) = '\0'; if (strlen(commandString) == 1) { (*command) = (char)tolower(commandString[0]); } else if (strlen(commandString) == 2) { (*command) = (char)tolower(commandString[0]); if (modifier != NULL) (*modifier) = (char)tolower(commandString[1]); } return OK; } /**************************************************************************** * GetEmbedFlags() * * Derives the embedFlags when provided with a valid command character (and * optionally a modifier) * * Returns OK if embedFlags successfully derived, NOTOK if command and/or * modifier character is invalid, or if the pointer to the integer to which you * wish to assign the derived embedFlags is NULL. ****************************************************************************/ int GetEmbedFlags(char command, char modifier, int *embedFlagsP) { if (embedFlagsP == NULL) { gp_ErrorMessage("Pointer to embedFlags int is NULL."); return NOTOK; } switch (command) { case 'p': (*embedFlagsP) = EMBEDFLAGS_PLANAR; break; case 'd': (*embedFlagsP) = EMBEDFLAGS_DRAWPLANAR; break; case 'o': (*embedFlagsP) = EMBEDFLAGS_OUTERPLANAR; break; case '2': (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK23; break; case '3': (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK33; // Future TODO: Add logic: if command == '3' and modifier == 'e', // then embedFlagsP |= EMBEDFLAGS_SEARCHEMBED (or similar) // Future TODO: Error out if modifier char is anything other than 'e' break; case '4': (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK4; break; default: gp_ErrorMessage("Unrecognized algorithm command specifier."); return NOTOK; } // Future TODO: Currently, there are no graph algorithm extensions for which // we wish to specify a modifier; if an additional character after the // command specifier is given, then error out. In the future, once there are // graph algorithm extensions whose behaviour may be conditioned on a user- // specified modifier character, this check should be removed. if (modifier != '\0') { gp_ErrorMessage("Algorithm modifiers currently not supported."); return NOTOK; } return OK; } /**************************************************************************** * GetAlgorithmName() * * When provided with a valid command char, derives the corresponding algorithm * name returned via char const * to the caller. This memory should not be * freed by the caller. * ****************************************************************************/ char const *GetAlgorithmName(char command) { char const *algorithmName = "UnsupportedAlgorithm"; switch (command) { case 'p': algorithmName = PLANARITY_NAME; break; case 'd': algorithmName = DRAWPLANAR_NAME; break; case 'o': algorithmName = OUTERPLANARITY_NAME; break; case '2': algorithmName = K23SEARCH_NAME; break; case '3': algorithmName = K33SEARCH_NAME; break; case '4': algorithmName = K4SEARCH_NAME; break; default: break; } return algorithmName; } /**************************************************************************** ****************************************************************************/ char const *GetTransformationName(char command) { char const *transformationName = "UnsupportedTransformation"; switch (command) { case 'g': transformationName = "G6"; break; case 'a': transformationName = "AdjList"; break; case 'm': transformationName = "AdjMat"; break; default: break; } return transformationName; } char const *GetSupportedOutputChoices(void) { return "G. G6 format\n" "A. Adjacency List format\n" "M. Adjacency Matrix format\n"; } char const *GetSupportedOutputFormats(void) { return "gam"; } /**************************************************************************** ****************************************************************************/ char const *GetBaseName(int baseFlag) { char const *transformationName = baseFlag ? "1-based" : "0-based"; return transformationName; } /**************************************************************************** * ExtendGraph() * * The graphLib provides a run-time inheritance method so that the base * graph structure and methods can be extended with new and modified * capabilities. This method subclasses the graph with an extension selected * based on the command-line received by the planarity wrapper application * for the graphLib. * * The command-line may include an extension modifier, but it is not passed * as a parameter here because it is not needed to determine which extension * the graph is to inherit * * Returns OK if the graph was successfully extended * Returns NOTOK if theGraph is not properly initialized, if the operation to * extend the graph failed, or if an invalid command char was supplied. ****************************************************************************/ int ExtendGraph(graphP theGraph, char command) { if (theGraph == NULL || gp_GetN(theGraph) <= 0) { gp_ErrorMessage("Unable to extend graph with algorithm extension due " "to NULL or uninitialized graph."); return NOTOK; } switch (command) { case 'p': return gp_ExtendWith_Planarity(theGraph); case 'd': return gp_ExtendWith_DrawPlanar(theGraph); case 'o': return gp_ExtendWith_Outerplanarity(theGraph); case '2': return gp_ExtendWith_K23Search(theGraph); case '3': return gp_ExtendWith_K33Search(theGraph); case '4': return gp_ExtendWith_K4Search(theGraph); default: break; } return NOTOK; } /**************************************************************************** A string used to construct input and output file names. The SUFFIXMAXLENGTH is 32 to accommodate ".out.txt" + ".render.txt" + ".test.txt" ****************************************************************************/ char theFileName[FILENAMEMAXLENGTH + 1 + ALGORITHMNAMEMAXLENGTH + 1 + SUFFIXMAXLENGTH + 1]; /**************************************************************************** ConstructInputFileName() Returns a string not owned by the caller (do not free string). String contains infileName content if infileName is non-NULL. If infileName is NULL, then the user is asked to supply a name. Returns NULL on error, or a non-NULL string on success. ****************************************************************************/ char *ConstructInputFileName(char const *infileName) { int Result = OK; int numCharsToReprFILENAMEMAXLENGTH = 0; char const *fileNameFormatFormat = " %%%d[^\r\n]"; char *fileNameFormat = NULL; char lineBuff[MAXLINE + 1]; memset(lineBuff, '\0', (MAXLINE + 1)); if (GetNumCharsToReprInt(FILENAMEMAXLENGTH, &numCharsToReprFILENAMEMAXLENGTH) != OK) { gp_ErrorMessage("Unable to determine number of characters required to " "represent FILENAMEMAXLENGTH."); return NULL; } fileNameFormat = (char *)malloc((strlen(fileNameFormatFormat) + numCharsToReprFILENAMEMAXLENGTH + 1) * sizeof(char)); if (fileNameFormat == NULL) { gp_ErrorMessage("Unable to allocate memory for file name format " "string."); return NULL; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" sprintf(fileNameFormat, fileNameFormatFormat, FILENAMEMAXLENGTH); #pragma GCC diagnostic pop if (infileName == NULL) { while (1) { gp_MessagePrompt("Enter graph file name:"); if (GetLineFromStdin(lineBuff, MAXLINE) != OK) { gp_ErrorMessage("Unable to read graph file name from stdin."); Result = NOTOK; break; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH || sscanf(lineBuff, fileNameFormat, theFileName) != 1) gp_Message("Invalid input file name; please retry."); else { if (strncmp(theFileName, "stdin", strlen("stdin")) != 0 && !strchr(theFileName, '.')) { gp_Message("Graph file name does not have extension; " "automatically appending \".txt\"."); strcat(theFileName, ".txt"); } break; } #pragma GCC diagnostic pop } } else { if (strlen(infileName) > FILENAMEMAXLENGTH) { gp_ErrorMessage("File name is too long."); Result = NOTOK; } else if (strlen(infileName) == 0) { gp_ErrorMessage("File name is empty."); Result = NOTOK; } if (Result == OK) { strcpy(theFileName, infileName); } } if (fileNameFormat != NULL) { free(fileNameFormat); fileNameFormat = NULL; } return Result != OK ? NULL : theFileName; } /**************************************************************************** * ConstructPrimaryOutputFileName() * * Returns a string not owned by the caller (do not free string). * Reuses the same memory space as ConstructInputFileName(). * If outfileName is non-NULL, then the result string contains its content. * If outfileName is NULL, then the infileName and the command's algorithm name * are used to construct a string. * * Returns non-NULL string ****************************************************************************/ char *ConstructPrimaryOutputFileName(char const *infileName, char const *outfileName, char command) { char const *algorithmName = GetAlgorithmName(command); if (outfileName == NULL) { // The output file name is based on the input file name if (theFileName != infileName) strcpy(theFileName, infileName); // If the primary output file name has not been given, then we use // the input file name + the algorithm name + a simple suffix if (strlen(algorithmName) <= ALGORITHMNAMEMAXLENGTH) { strcat(theFileName, "."); strcat(theFileName, algorithmName); } else gp_Message("Algorithm Name is too long, so it will not be used in " "output file name."); strcat(theFileName, ".out.txt"); } else { if (strlen(outfileName) > FILENAMEMAXLENGTH) { // The output file name is based on the input file name if (theFileName != infileName) strcpy(theFileName, infileName); if (strlen(algorithmName) <= ALGORITHMNAMEMAXLENGTH) { strcat(theFileName, "."); strcat(theFileName, algorithmName); } strcat(theFileName, ".out.txt"); gp_Message("Outfile file name is too long. Result placed in " "\"%.*s\"", FILENAME_MAX, theFileName); } else { if (theFileName != outfileName) strcpy(theFileName, outfileName); } } return theFileName; } /**************************************************************************** * ConstructTransformationExpectedResultFileName() * * Returns a string whose ownership will be transferred to the caller (must free * string). * If outfileName is non-NULL, then the result string contains its content * If outfileName is NULL, then the infileName, the command's algorithm name, * and whether or not this output file correspond to the actual (0) or expected * (1) output file from testing are used to construct a string. * * Returns non-NULL string ****************************************************************************/ int ConstructTransformationExpectedResultFileName(char const *infileName, char **outfileName, char command, int baseFlag) { int Result = OK; char const *baseName = GetBaseName(baseFlag); char const *transformationName = GetTransformationName(command); int infileNameLen = -1; if (infileName == NULL || (infileNameLen = strlen(infileName)) < 1) { gp_ErrorMessage("Cannot construct transformation output file name for " "empty infileName."); return NOTOK; } if ((*outfileName) == NULL) { (*outfileName) = (char *)calloc( infileNameLen + 1 + strlen(baseName) + 1 + strlen(transformationName) + ((command == 'g') ? strlen(".out.g6") : strlen(".out.txt")) + 1, sizeof(char)); if ((*outfileName) == NULL) { gp_ErrorMessage("Unable to allocate memory for output file name."); return NOTOK; } strcpy((*outfileName), infileName); strcat((*outfileName), "."); strcat((*outfileName), baseName); strcat((*outfileName), "."); strcat((*outfileName), transformationName); strcat((*outfileName), command == 'g' ? ".out.g6" : ".out.txt"); } else { gp_ErrorMessage("outfileName already allocated."); Result = NOTOK; } return Result; } /**************************************************************************** * WriteAlgorithmResults() ****************************************************************************/ void WriteAlgorithmResults(graphP theGraph, int Result, char command, platform_time start, platform_time end, char const *infileName) { char algorithmResults[MAXLINE + 1]; char *target = algorithmResults; memset(algorithmResults, '\0', MAXLINE + 1); target += sprintf(target, "The graph "); if (infileName) { target += sprintf(target, "\"%.*s\" ", FILENAMEMAXLENGTH, infileName); } switch (command) { case 'p': target += sprintf(target, "is%s planar.", Result == OK ? "" : " not"); break; case 'd': target += sprintf(target, "is%s planar.", Result == OK ? "" : " not"); break; case 'o': target += sprintf(target, "is%s outerplanar.", Result == OK ? "" : " not"); break; case '2': target += sprintf(target, "has %s subgraph homeomorphic to K_{2,3}.", Result == OK ? "no" : "a"); break; case '3': target += sprintf(target, "has %s subgraph homeomorphic to K_{3,3}.", Result == OK ? "no" : "a"); break; case '4': target += sprintf(target, "has %s subgraph homeomorphic to K_4.", Result == OK ? "no" : "a"); break; default: target += sprintf(target, "has not been processed due to unrecognized command."); break; } gp_Message("%s", algorithmResults); gp_Message("Algorithm '%s' executed in %.3lf seconds.", GetAlgorithmName(command), platform_GetDuration(start, end)); } /**************************************************************************** * GetNumCharsToReprInt() ****************************************************************************/ int GetNumCharsToReprInt(int theNum, int *numCharsRequired) { int charCount = 0; if (numCharsRequired == NULL) return NOTOK; if (theNum < 0) { charCount++; // N.B. since 32-bit signed integers are represented using twos-complement, // the absolute value of INT_MIN is not defined; however, adding 1 to this // min value before taking the absolute value will still require the same // number of digits. if ((theNum == INT_MIN) || (theNum == INT8_MAX) || (theNum == INT16_MIN) || (theNum == INT32_MIN)) theNum++; theNum = abs(theNum); } while (theNum > 0) { theNum /= 10; charCount++; } (*numCharsRequired) = charCount; return OK; } edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/platformTime.h000066400000000000000000000027421521450711600267150ustar00rootroot00000000000000#ifndef PLATFORM_TIME #define PLATFORM_TIME /* Copyright (c) 1997-2026, John M. Boyer All rights reserved. See the LICENSE.TXT file for licensing information. */ #ifdef WINDOWS #include #include #include #define platform_time DWORD #define platform_GetTime(timeVar) (timeVar = GetTickCount()) #define platform_GetDuration(startTime, endTime) ((double)(endTime - startTime) / 1000.0) #else #include typedef struct { clock_t hiresTime; time_t lowresTime; } platform_time; #define platform_GetTime(timeVar) (timeVar.hiresTime = clock(), timeVar.lowresTime = time(NULL)) // Many flavors of Unix have CLOCKS_PER_SEC at 1 million, and clock_t as a 4 byte long integer // which means that the clock() construct has a resolution of only about 2000 seconds // If we're getting a duration longer than that, then we fall back to the coarser time() measure #define platform_GetDuration(startTime, endTime) ( \ ((double)(endTime.lowresTime - startTime.lowresTime)) > 2000 ? ((double)(endTime.lowresTime - startTime.lowresTime)) : ((double)(endTime.hiresTime - startTime.hiresTime)) / CLOCKS_PER_SEC) /* #define platform_time clock_t #define platform_GetTime() clock() #define platform_GetDuration(startTime, endTime) (((double) (endTime - startTime)) / CLOCKS_PER_SEC) */ /* #define platform_time time_t #define platform_GetTime() time((time_t *)NULL) #define platform_GetDuration(startTime, endTime) ((double) (endTime - startTime)) */ #endif #endif edge-addition-planarity-suite-Version_5.0.0.0/c/samples/000077500000000000000000000000001521450711600230745ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/samples/K10.g6000066400000000000000000000000241521450711600236610ustar00rootroot00000000000000>>graph6<>graph6<>graph6< 0: 0 1: 2 2: 0 3: 1 4: 2 5: 2 6: 1 7: 1 8: 0 9: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.0-based.txt.K23Search.out.txt000066400000000000000000000001411521450711600317400ustar00rootroot00000000000000N=10 0: -1 1: -1 2: -1 3: 4 8 -1 4: 3 9 -1 5: 7 8 -1 6: 8 9 -1 7: 9 5 -1 8: 5 6 3 -1 9: 4 6 7 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.0-based.txt.K33Search.out.txt000066400000000000000000000001611521450711600317430ustar00rootroot00000000000000N=10 0: -1 1: 2 6 -1 2: 1 3 7 -1 3: 2 4 8 -1 4: 3 9 -1 5: 7 8 -1 6: 8 9 1 -1 7: 9 5 2 -1 8: 5 6 3 -1 9: 4 6 7 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.0-based.txt.K4Search.out.txt000066400000000000000000000001511521450711600316600ustar00rootroot00000000000000N=10 0: -1 1: -1 2: 3 7 -1 3: 2 4 8 -1 4: 3 9 -1 5: 7 8 -1 6: 8 9 -1 7: 9 5 2 -1 8: 5 6 3 -1 9: 4 6 7 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.0-based.txt.Outerplanarity.out.txt000066400000000000000000000001411521450711600332750ustar00rootroot00000000000000N=10 0: -1 1: -1 2: -1 3: 4 8 -1 4: 3 9 -1 5: 7 8 -1 6: 8 9 -1 7: 9 5 -1 8: 5 6 3 -1 9: 4 6 7 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.0-based.txt.Planarity.out.txt000066400000000000000000000001611521450711600322200ustar00rootroot00000000000000N=10 0: -1 1: 2 6 -1 2: 1 3 7 -1 3: 2 4 8 -1 4: 3 9 -1 5: 7 8 -1 6: 8 9 1 -1 7: 9 5 2 -1 8: 5 6 3 -1 9: 4 6 7 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt000066400000000000000000000001671521450711600254260ustar00rootroot00000000000000N=10 1: 2 6 5 0 2: 3 7 1 0 3: 4 8 2 0 4: 5 9 3 0 5: 1 10 4 0 6: 1 8 9 0 7: 2 9 10 0 8: 3 10 6 0 9: 4 6 7 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.ColorVertices.out.txt000066400000000000000000000003131521450711600316250ustar00rootroot00000000000000N=10 1: 2 6 5 0 2: 3 7 1 0 3: 4 8 2 0 4: 5 9 3 0 5: 1 10 4 0 6: 1 8 9 0 7: 2 9 10 0 8: 3 10 6 0 9: 4 6 7 0 10: 5 7 8 0 1: 0 2: 2 3: 0 4: 1 5: 2 6: 2 7: 1 8: 1 9: 0 10: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.K23Search.out.txt000066400000000000000000000001331521450711600305270ustar00rootroot00000000000000N=10 1: 0 2: 0 3: 0 4: 5 9 0 5: 4 10 0 6: 8 9 0 7: 9 10 0 8: 10 6 0 9: 6 7 4 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.K33Search.out.txt000066400000000000000000000001531521450711600305320ustar00rootroot00000000000000N=10 1: 0 2: 3 7 0 3: 2 4 8 0 4: 3 5 9 0 5: 4 10 0 6: 8 9 0 7: 9 10 2 0 8: 10 6 3 0 9: 6 7 4 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.K4Search.out.txt000066400000000000000000000001431521450711600304470ustar00rootroot00000000000000N=10 1: 0 2: 0 3: 4 8 0 4: 3 5 9 0 5: 4 10 0 6: 8 9 0 7: 9 10 0 8: 10 6 3 0 9: 6 7 4 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.Outerplanarity.out.txt000066400000000000000000000001331521450711600320640ustar00rootroot00000000000000N=10 1: 0 2: 0 3: 0 4: 5 9 0 5: 4 10 0 6: 8 9 0 7: 9 10 0 8: 10 6 0 9: 6 7 4 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/Petersen.txt.Planarity.out.txt000066400000000000000000000001531521450711600310070ustar00rootroot00000000000000N=10 1: 0 2: 3 7 0 3: 2 4 8 0 4: 3 5 9 0 5: 4 10 0 6: 8 9 0 7: 9 10 2 0 8: 10 6 3 0 9: 6 7 4 0 10: 5 7 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/drawExample.0-based.txt000066400000000000000000000003121521450711600273140ustar00rootroot00000000000000N=15 0: 1 3 14 -4 1: 0 2 5 8 11 -4 2: 1 11 5 -4 3: 0 4 7 -4 4: 3 6 10 12 -4 5: 1 9 2 -4 6: 4 13 -4 7: 3 10 14 -4 8: 1 13 -4 9: 5 14 -4 10: 4 12 7 -4 11: 1 2 -4 12: 4 10 13 -4 13: 8 12 6 -4 14: 0 9 7 -4 drawExample.0-based.txt.ColorVertices.out.txt000066400000000000000000000004731521450711600334530ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/samplesN=15 0: 1 3 14 -1 1: 0 2 5 8 11 -1 2: 1 11 5 -1 3: 0 4 7 -1 4: 3 6 10 12 -1 5: 1 9 2 -1 6: 4 13 -1 7: 3 10 14 -1 8: 1 13 -1 9: 5 14 -1 10: 4 12 7 -1 11: 1 2 -1 12: 4 10 13 -1 13: 8 12 6 -1 14: 0 9 7 -1 0: 1 1: 0 2: 1 3: 0 4: 1 5: 2 6: 2 7: 1 8: 1 9: 1 10: 0 11: 2 12: 2 13: 0 14: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/drawExample.0-based.txt.DrawPlanar.out.txt000066400000000000000000000015531521450711600330020ustar00rootroot00000000000000N=15 0: 1 3 14 -1 1: 2 11 5 8 0 -1 2: 5 11 1 -1 3: 0 4 7 -1 4: 6 12 10 3 -1 5: 9 1 2 -1 6: 13 4 -1 7: 3 10 14 -1 8: 1 13 -1 9: 14 5 -1 10: 7 4 12 -1 11: 1 2 -1 12: 10 4 13 -1 13: 8 12 6 -1 14: 0 7 9 -1 0: 0 0 21 1: 14 4 21 2: 12 3 5 3: 1 8 11 4: 2 11 16 5: 11 2 7 6: 3 16 17 7: 8 9 13 8: 7 19 20 9: 10 1 2 10: 6 12 15 11: 13 5 6 12: 5 14 18 13: 4 17 19 14: 9 0 10 0: 21 0 14 1: 21 0 14 2: 8 0 1 3: 8 0 1 4: 0 0 9 5: 0 0 9 6: 4 12 14 7: 4 12 14 8: 7 11 14 9: 7 11 14 10: 20 7 14 11: 20 7 14 12: 6 13 14 13: 6 13 14 14: 5 12 13 15: 5 12 13 16: 3 11 12 17: 3 11 12 18: 11 1 2 19: 11 1 2 20: 9 1 8 21: 9 1 8 22: 16 2 3 23: 16 2 3 24: 12 2 6 25: 12 2 6 26: 14 2 5 27: 14 2 5 28: 2 10 11 29: 2 10 11 30: 17 3 4 31: 17 3 4 32: 13 6 8 33: 13 6 8 34: 10 8 9 35: 10 8 9 36: 19 4 7 37: 19 4 7 38: 1 9 10 39: 1 9 10 40: 15 5 6 41: 15 5 6 42: 18 4 5 43: 18 4 5 drawExample.0-based.txt.DrawPlanar.out.txt.render.txt000066400000000000000000000012621521450711600350140ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/samples----------0----------- | | | | -3-- | | | | | | | --4--- | | | | | | | | | | | 6- | | | | | | | | | | | -13 | | | | | || | | | | --12-| | | | | | | | | | -10- | | | | | | | | | | 8-| | | | || | --7-- || | | || -----14---- || | || 9- || | || --5--- || | | || -2- | || || | || |11| || | || || --------1--------- edge-addition-planarity-suite-Version_5.0.0.0/c/samples/drawExample.txt000066400000000000000000000002761521450711600261130ustar00rootroot00000000000000N=15 1: 2 4 15 0 2: 1 3 6 9 12 0 3: 2 12 6 0 4: 1 5 8 0 5: 4 7 11 13 0 6: 2 10 3 0 7: 5 14 0 8: 4 11 15 0 9: 2 14 0 10: 6 15 0 11: 5 13 8 0 12: 2 3 0 13: 5 11 14 0 14: 9 13 7 0 15: 1 10 8 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/drawExample.txt.ColorVertices.out.txt000066400000000000000000000004601521450711600323140ustar00rootroot00000000000000N=15 1: 2 4 15 0 2: 1 3 6 9 12 0 3: 2 12 6 0 4: 1 5 8 0 5: 4 7 11 13 0 6: 2 10 3 0 7: 5 14 0 8: 4 11 15 0 9: 2 14 0 10: 6 15 0 11: 5 13 8 0 12: 2 3 0 13: 5 11 14 0 14: 9 13 7 0 15: 1 10 8 0 1: 1 2: 0 3: 1 4: 0 5: 1 6: 2 7: 2 8: 1 9: 1 10: 1 11: 0 12: 2 13: 2 14: 0 15: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/drawExample.txt.DrawPlanar.out.txt000066400000000000000000000015421521450711600315660ustar00rootroot00000000000000N=15 1: 2 4 15 0 2: 3 12 6 9 1 0 3: 6 12 2 0 4: 1 5 8 0 5: 7 13 11 4 0 6: 10 2 3 0 7: 14 5 0 8: 4 11 15 0 9: 2 14 0 10: 15 6 0 11: 8 5 13 0 12: 2 3 0 13: 11 5 14 0 14: 9 13 7 0 15: 1 8 10 0 1: 0 0 21 2: 14 4 21 3: 12 3 5 4: 1 8 11 5: 2 11 16 6: 11 2 7 7: 3 16 17 8: 8 9 13 9: 7 19 20 10: 10 1 2 11: 6 12 15 12: 13 5 6 13: 5 14 18 14: 4 17 19 15: 9 0 10 2: 21 0 14 3: 21 0 14 4: 8 0 1 5: 8 0 1 6: 0 0 9 7: 0 0 9 8: 4 12 14 9: 4 12 14 10: 7 11 14 11: 7 11 14 12: 20 7 14 13: 20 7 14 14: 6 13 14 15: 6 13 14 16: 5 12 13 17: 5 12 13 18: 3 11 12 19: 3 11 12 20: 11 1 2 21: 11 1 2 22: 9 1 8 23: 9 1 8 24: 16 2 3 25: 16 2 3 26: 12 2 6 27: 12 2 6 28: 14 2 5 29: 14 2 5 30: 2 10 11 31: 2 10 11 32: 17 3 4 33: 17 3 4 34: 13 6 8 35: 13 6 8 36: 10 8 9 37: 10 8 9 38: 19 4 7 39: 19 4 7 40: 1 9 10 41: 1 9 10 42: 15 5 6 43: 15 5 6 44: 18 4 5 45: 18 4 5 drawExample.txt.DrawPlanar.out.txt.render.txt000066400000000000000000000012621521450711600336020ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/samples----------1----------- | | | | -4-- | | | | | | | --5--- | | | | | | | | | | | 7- | | | | | | | | | | | -14 | | | | | || | | | | --13-| | | | | | | | | | -11- | | | | | | | | | | 9-| | | | || | --8-- || | | || -----15---- || | || 10 || | || --6--- || | | || -3- | || || | || |12| || | || || --------2--------- edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.0-based.txt000066400000000000000000000001061521450711600270540ustar00rootroot00000000000000N=5 0: 2 1 4 3 -1 1: 2 4 0 3 -1 2: 0 1 4 -1 3: 4 0 1 -1 4: 1 0 3 2 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.0-based.txt.ColorVertices.out.txt000066400000000000000000000002001521450711600332550ustar00rootroot00000000000000N=5 0: 2 1 4 3 -1 1: 2 4 0 3 -1 2: 0 1 4 -1 3: 4 0 1 -1 4: 1 0 3 2 -1 0: 1 1: 2 2: 3 3: 3 4: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.0-based.txt.DrawPlanar.out.txt000066400000000000000000000004701521450711600325360ustar00rootroot00000000000000N=5 0: 2 4 3 1 -1 1: 0 3 4 2 -1 2: 1 4 0 -1 3: 0 4 1 -1 4: 0 2 1 3 -1 0: 0 0 8 1: 3 0 6 2: 4 1 8 3: 2 2 5 4: 1 4 7 0: 8 0 4 1: 8 0 4 2: 0 0 3 3: 0 0 3 4: 4 0 1 5: 4 0 1 6: 2 0 2 7: 2 0 2 8: 1 3 4 9: 1 3 4 10: 6 1 3 11: 6 1 3 12: 3 2 3 13: 3 2 3 14: 7 1 4 15: 7 1 4 16: 5 1 2 17: 5 1 2 maxPlanar5.0-based.txt.DrawPlanar.out.txt.render.txt000066400000000000000000000001441521450711600345510ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/c/samples----0---- | | | | | | -4--| | | |||| | -3--||| | | ||| ---1---|| | || ---2---- edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.0-based.txt.Planarity.out.txt000066400000000000000000000001061521450711600324420ustar00rootroot00000000000000N=5 0: 2 4 3 1 -1 1: 0 3 4 2 -1 2: 1 4 0 -1 3: 0 4 1 -1 4: 0 2 1 3 -1 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.txt000066400000000000000000000001011521450711600256350ustar00rootroot00000000000000N=5 1: 3 2 5 4 0 2: 3 5 1 4 0 3: 1 2 5 0 4: 5 1 2 0 5: 2 1 4 3 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.txt.ColorVertices.out.txt000066400000000000000000000001731521450711600320540ustar00rootroot00000000000000N=5 1: 3 2 5 4 0 2: 3 5 1 4 0 3: 1 2 5 0 4: 5 1 2 0 5: 2 1 4 3 0 1: 1 2: 2 3: 3 4: 3 5: 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.txt.DrawPlanar.out.txt000066400000000000000000000004651521450711600313300ustar00rootroot00000000000000N=5 1: 3 5 4 2 0 2: 1 4 5 3 0 3: 2 5 1 0 4: 1 5 2 0 5: 1 3 2 4 0 1: 0 0 8 2: 3 0 6 3: 4 1 8 4: 2 2 5 5: 1 4 7 2: 8 0 4 3: 8 0 4 4: 0 0 3 5: 0 0 3 6: 4 0 1 7: 4 0 1 8: 2 0 2 9: 2 0 2 10: 1 3 4 11: 1 3 4 12: 6 1 3 13: 6 1 3 14: 3 2 3 15: 3 2 3 16: 7 1 4 17: 7 1 4 18: 5 1 2 19: 5 1 2 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.txt.DrawPlanar.out.txt.render.txt000066400000000000000000000001441521450711600334160ustar00rootroot00000000000000----1---- | | | | | | -5--| | | |||| | -4--||| | | ||| ---2---|| | || ---3---- edge-addition-planarity-suite-Version_5.0.0.0/c/samples/maxPlanar5.txt.Planarity.out.txt000066400000000000000000000001011521450711600312230ustar00rootroot00000000000000N=5 1: 3 5 4 2 0 2: 1 4 5 3 0 3: 2 5 1 0 4: 1 5 2 0 5: 1 3 2 4 0 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/n8.mALL.g6000066400000000000000000002506261521450711600244560ustar00rootroot00000000000000G????? G???C? G???E? G???F? G???F_ G???Fo G???Fw G???F{ G??CA? G??CE? G??CB? G??CCC G??CF? G??CB_ G??CEC G??CF_ G??CBo G??CFC G??CFo G??CBw G??CFc G??CFw G??CFs G??CF{ G??EE? G??E@_ G??EF? G??ED_ G??E@o G??EEC G??EDC G??E@c G??EF_ G??EDo G??E@w G??EFC G??EDc G??E@s G??EFo G??EDw G??EFc G??EDs G??E@{ G??EFw G??EFs G??ED{ G??EF{ G??FF? G??FE_ G??FCo G??F?w G??FF_ G??FEo G??FCw G??FFC G??FEc G??FCs G??F?{ G??FFo G??FEw G??FFc G??FEs G??FC{ G??FFw G??FFs G??FE{ G??FF{ G??Ff_ G??FfO G??FeW G??Ffo G??FfW G??Ffc G??FfS G??Fe[ G??Ffw G??Ffs G??Ff[ G??Ff{ G??Fvo G??Fvg G??Fvw G??Fvs G??Fvk G??Fv{ G??F~w G??F~{ G?AA@? G?AAE? G?AAD? G?AA@_ G?AACG G?AAF? G?AAD_ G?AA@o G?AAEG G?AADG G?AAF_ G?AADo G?AAFG G?AADg G?AAEK G?AAFo G?AAFg G?AADw G?AAFK G?AAFw G?AAFk G?AAF{ G?AEAG G?AEF? G?AEB_ G?AE@o G?AEEG G?AEBG G?AEEC G?AEDC G?AEF_ G?AEDo G?AEBo G?AEFG G?AEDg G?AEBg G?AEFC G?AEDc G?AEEK G?AEFo G?AEFg G?AEDw G?AEBw G?AEFc G?AEDs G?AEFK G?AEFw G?AEFs G?AEFk G?AEF{ G?ABE? G?ABB? G?ABC_ G?AB?o G?ABCG G?ABF? G?ABE_ G?ABB_ G?ABCo G?ABAo G?ABEG G?ABCg G?ABEC G?ABBC G?ABCc G?ABAc G?AB?s G?ABCK G?ABF_ G?ABEo G?ABBo G?ABFG G?ABEg G?ABCw G?ABFC G?ABEc G?ABBc G?ABCs G?ABAs G?ABEK G?ABCk G?ABFo G?ABFg G?ABEw G?ABFc G?ABEs G?ABBs G?ABFK G?ABEk G?ABC{ G?ABFw G?ABFs G?ABFk G?ABE{ G?ABF{ G?ACJ_ G?ACKK G?ACN_ G?ACJo G?ACNG G?ACMK G?ACNo G?ACNg G?ACNK G?ACNw G?ACNk G?ACN{ G?AFBG G?AFAg G?AF?w G?AFF_ G?AFEo G?AFBo G?AFFG G?AFEg G?AFBg G?AFCw G?AFAw G?AFFC G?AFEc G?AFCs G?AFEK G?AFBK G?AFCk G?AFAk G?AF?{ G?AFFo G?AFFg G?AFEw G?AFBw G?AFFc G?AFEs G?AFBs G?AFFK G?AFEk G?AFBk G?AFC{ G?AFA{ G?AFFw G?AFFs G?AFFk G?AFE{ G?AFB{ G?AFF{ G?ABf? G?ABb_ G?ABeO G?ABbO G?ABeG G?ABcW G?ABf_ G?ABfO G?ABbo G?ABfG G?ABeW G?ABfC G?ABbc G?ABeS G?ABbS G?ABeK G?ABc[ G?ABfo G?ABfg G?ABfW G?ABfc G?ABfS G?ABbs G?ABfK G?ABe[ G?ABfw G?ABfs G?ABfk G?ABf[ G?ABf{ G?AEN_ G?AELo G?AEJo G?AENG G?AELg G?AEMK G?AELK G?AENo G?AENg G?AELw G?AENc G?AELs G?AEJs G?AENK G?AELk G?AENw G?AENs G?AENk G?AEL{ G?AEN{ G?AFf_ G?AFfO G?AFfG G?AFbg G?AFeW G?AFbW G?AFfo G?AFfg G?AFfW G?AFbw G?AFfc G?AFfS G?AFfK G?AFbk G?AFe[ G?AFb[ G?AFfw G?AFfs G?AFfk G?AFf[ G?AFb{ G?AFf{ G?ABv_ G?ABro G?ABvG G?ABvo G?ABvg G?ABvc G?ABrs G?ABvK G?ABvw G?ABvs G?ABvk G?ABv{ G?AFNG G?AFMg G?AFKw G?AFNo G?AFNg G?AFMw G?AFNK G?AFMk G?AFK{ G?AFNw G?AFNs G?AFNk G?AFM{ G?AFN{ G?AFvo G?AFvg G?AFrw G?AFvw G?AFvs G?AFvk G?AFr{ G?AFv{ G?AFng G?AFnW G?AFnw G?AFnk G?AFn[ G?AFn{ G?AF~w G?AF~{ G?BEF? G?BE@o G?BEEG G?BEF_ G?BEDo G?BEFG G?BEDg G?BEEK G?BEFo G?BEFg G?BEDw G?BEFK G?BEFw G?BEFk G?BEF{ G?BDB? G?BDF? G?BDB_ G?BDAo G?BDDG G?BDBG G?BD@g G?BDCK G?BDAK G?BD?k G?BDF_ G?BDEo G?BDBo G?BDFG G?BDEg G?BDDg G?BDBg G?BDCw G?BDAw G?BD@w G?BDEK G?BDBK G?BDCk G?BDAk G?BD?{ G?BDFo G?BDFg G?BDEw G?BDDw G?BDBw G?BDFK G?BDEk G?BDBk G?BDC{ G?BDA{ G?BDFw G?BDFk G?BDE{ G?BDB{ G?BDF{ G?B@e? G?B@f? G?B@eO G?B@dO G?B@eG G?B@dG G?B@`g G?B@cW G?B@`W G?B@cK G?B@_[ G?B@f_ G?B@fO G?B@fG G?B@dg G?B@eW G?B@dW G?B@`w G?B@eK G?B@dK G?B@c[ G?B@fo G?B@fg G?B@fW G?B@dw G?B@fK G?B@e[ G?B@d[ G?B@fw G?B@fk G?B@f[ G?B@f{ G?BFF? G?BFE_ G?BFEG G?BF@g G?BF?w G?BFF_ G?BFEo G?BFDo G?BFFG G?BFEg G?BFDg G?BFCw G?BF@w G?BFFC G?BFEc G?BFFo G?BFFg G?BFEw G?BFDw G?BFFc G?BFEs G?BFDs G?BFFK G?BFEk G?BFDk G?BFC{ G?BF@{ G?BFFw G?BFFs G?BFFk G?BFE{ G?BFD{ G?BFF{ G?BDb_ G?BDbO G?BDbG G?BDaW G?BD`W G?BDf_ G?BDfO G?BDdo G?BDbo G?BDfG G?BDdg G?BDbg G?BDeW G?BDdW G?BDbW G?BD`w G?BDfC G?BDdc G?BDbc G?BDeS G?BDdS G?BDbS G?BDeK G?BDfo G?BDfg G?BDfW G?BDdw G?BDbw G?BDfc G?BDfS G?BDds G?BDbs G?BDfK G?BDdk G?BDbk G?BDe[ G?BDd[ G?BDb[ G?BD`{ G?BDfw G?BDfs G?BDfk G?BDf[ G?BDd{ G?BDb{ G?BDf{ G?B@v? G?B@t_ G?B@po G?B@uG G?B@v_ G?B@to G?B@vG G?B@tg G?B@pw G?B@vC G?B@tc G?B@ps G?B@uK G?B@tK G?B@vo G?B@vg G?B@tw G?B@vc G?B@ts G?B@vK G?B@tk G?B@p{ G?B@vw G?B@vs G?B@vk G?B@t{ G?B@v{ G?BEN_ G?BELo G?BENG G?BEMK G?BEHk G?BENo G?BENg G?BELw G?BENK G?BELk G?BEH{ G?BENw G?BENk G?BEL{ G?BEN{ G?BDN_ G?BDMo G?BDJo G?BDNG G?BDMg G?BDLg G?BDJg G?BDKw G?BDHw G?BDMK G?BDJK G?BDKk G?BDIk G?BDG{ G?BDNo G?BDNg G?BDMw G?BDLw G?BDJw G?BDNK G?BDMk G?BDJk G?BDK{ G?BDI{ G?BDNw G?BDNk G?BDM{ G?BDJ{ G?BDN{ G?B@n_ G?B@nO G?B@nG G?B@lg G?B@mW G?B@lW G?B@hw G?B@no G?B@ng G?B@nW G?B@lw G?B@nK G?B@m[ G?B@l[ G?B@nw G?B@nk G?B@n[ G?B@n{ G?BFf_ G?BFfO G?BFfo G?BFfg G?BFfW G?BFdw G?BFfc G?BFfS G?BFfw G?BFfs G?BFfk G?BFf[ G?BFd{ G?BFf{ G?BDv_ G?BDto G?BDro G?BDvG G?BDvo G?BDvg G?BDtw G?BDrw G?BDvc G?BDts G?BDrs G?BDvK G?BDvw G?BDvs G?BDvk G?BDt{ G?BDr{ G?BDv{ G?BFN_ G?BFMo G?BFNo G?BFNg G?BFMw G?BFLw G?BFNc G?BFMs G?BFNK G?BFMk G?BFNw G?BFNs G?BFNk G?BFM{ G?BFL{ G?BFN{ G?BDn_ G?BDnO G?BDlo G?BDjo G?BDno G?BDng G?BDnW G?BDlw G?BDjw G?BDnc G?BDnS G?BDls G?BDjs G?BDnK G?BDlk G?BDjk G?BDm[ G?BDl[ G?BDj[ G?BDnw G?BDns G?BDnk G?BDn[ G?BDl{ G?BDj{ G?BDn{ G?B@~_ G?B@|o G?B@~o G?B@~g G?B@|w G?B@~c G?B@|s G?B@~K G?B@|k G?B@x{ G?B@~w G?B@~s G?B@~k G?B@|{ G?B@~{ G?BFvo G?BFvw G?BFvs G?BFv{ G?BFno G?BFng G?BFnW G?BFnw G?BFns G?BFnk G?BFn[ G?BFn{ G?BD~o G?BD~g G?BD|w G?BDzw G?BD~w G?BD~s G?BD~k G?BD|{ G?BDz{ G?BD~{ G?BF~w G?BF~{ G?BfF? G?BfF_ G?BfEo G?BfFG G?BfEK G?BfCk G?Bf?{ G?BfFo G?BfFg G?BfEw G?BfFK G?BfEk G?BfC{ G?BfFw G?BfFk G?BfE{ G?BfF{ G?Bef_ G?BefO G?Bedo G?BefG G?Beeg G?Bedg G?BeeW G?Becw G?BeeK G?BedK G?Be`k G?Bec[ G?Be`[ G?Befo G?Befg G?BefW G?Beew G?Bedw G?BefK G?Bedk G?Bee[ G?Bed[ G?Be`{ G?Befw G?Befk G?Bef[ G?Bed{ G?Bef{ G?Bcr_ G?Bcv_ G?Bcro G?BcvG G?Bcug G?Bcrg G?Bcsw G?Bcqw G?BcuK G?BcrK G?Bcqk G?Bcvo G?Bcvg G?Bcuw G?Bcrw G?BcvK G?Bcuk G?Bcrk G?Bcvw G?Bcvk G?Bcr{ G?Bcv{ G?Bff_ G?BffO G?BffG G?Bfeg G?Bffo G?Bffg G?BffW G?Bfew G?Bffc G?BffS G?Bffw G?Bffs G?Bffk G?Bff[ G?Bfe{ G?Bff{ G?Bev_ G?Beuo G?Beto G?BevG G?Beug G?Betg G?Besw G?Bepw G?Bevo G?Bevg G?Beuw G?Betw G?Bevc G?Beus G?Bets G?BevK G?Bevw G?Bevs G?Bevk G?Beu{ G?Bet{ G?Bev{ G?BfNo G?BfNg G?BfNK G?BfNw G?BfNk G?BfM{ G?BfN{ G?Beno G?Beng G?BenW G?Bemw G?Belw G?BenK G?Belk G?Bem[ G?Benw G?Benk G?Ben[ G?Bel{ G?Ben{ G?Bc~o G?Bc~g G?Bc}w G?Bczw G?Bc~K G?Bc}k G?Bczk G?Bc~w G?Bc~k G?Bcz{ G?Bc~{ G?Bfvo G?Bfvw G?Bfvs G?Bfv{ G?Bfno G?Bfnw G?Bfns G?Bfnk G?Bfn[ G?Bfn{ G?Be~o G?Be~w G?Be~s G?Be~k G?Be}{ G?Be|{ G?Be~{ G?Bf~w G?Bf~{ G?Bvf_ G?BvfO G?Bvfo G?Bvfg G?BvfK G?Bve[ G?Bvfw G?Bvfk G?Bvf[ G?Bvf{ G?BvUo G?BvVo G?BvVg G?BvVW G?BvUw G?BvVK G?BvUk G?BvS{ G?BvVw G?BvVk G?BvU{ G?BvV{ G?Bvvo G?Bvvg G?BvvW G?Bvvw G?Bvvs G?Bvv{ G?Bvnw G?Bvnk G?Bvn[ G?Bvn{ G?Bv^w G?Bv]{ G?Bv^{ G?Bv~{ G?B~vo G?B~vw G?B~vk G?B~v{ G?B~~{ G?`@?_ G?`@E? G?`@C_ G?`@CO G?`@F? G?`@E_ G?`@EO G?`@Co G?`@F_ G?`@FO G?`@Eo G?`@EW G?`@Fo G?`@FW G?`@Ew G?`@Fw G?`@F[ G?`@F{ G?`DB? G?`DA_ G?`D@O G?`DAG G?`DF? G?`DE_ G?`DD_ G?`DB_ G?`DEO G?`DDO G?`DBO G?`D@o G?`DEG G?`DBG G?`DAg G?`DEC G?`DDC G?`DCc G?`DF_ G?`DFO G?`DEo G?`DDo G?`DBo G?`DFG G?`DEg G?`DBg G?`DEW G?`DBW G?`DFC G?`DEc G?`DDc G?`DDS G?`DEK G?`DFo G?`DFg G?`DFW G?`DEw G?`DBw G?`DFc G?`DFS G?`DDs G?`DFK G?`DEk G?`DFw G?`DFs G?`DFk G?`DF[ G?`DF{ G?`@e? G?`@d? G?`@`_ G?`@cO G?`@f? G?`@d_ G?`@eO G?`@dO G?`@eC G?`@dC G?`@`c G?`@cS G?`@f_ G?`@fO G?`@do G?`@eW G?`@fC G?`@dc G?`@eS G?`@dS G?`@fo G?`@fW G?`@fc G?`@fS G?`@ds G?`@e[ G?`@fw G?`@fs G?`@f[ G?`@f{ G?`CQG G?`CR_ G?`CUO G?`CUG G?`CRG G?`CSS G?`CV_ G?`CVO G?`CTo G?`CVG G?`CRg G?`CUW G?`CUS G?`CTS G?`CVo G?`CVg G?`CVW G?`CVS G?`CTs G?`CU[ G?`CVw G?`CVs G?`CV[ G?`CV{ G?`FE_ G?`F@W G?`F?w G?`FF_ G?`FFO G?`FEo G?`FDo G?`FBo G?`FEW G?`FDW G?`FCw G?`F@w G?`FFC G?`FEc G?`FES G?`FDS G?`FBS G?`FCs G?`FAs G?`FC[ G?`F@[ G?`F?{ G?`FFo G?`FFW G?`FEw G?`FDw G?`FFc G?`FFS G?`FEs G?`FDs G?`FBs G?`FE[ G?`FD[ G?`FC{ G?`F@{ G?`FFw G?`FFs G?`FF[ G?`FE{ G?`FD{ G?`FF{ G?`Db_ G?`DbO G?`D`o G?`DbG G?`DaW G?`Df_ G?`DfO G?`Ddo G?`Dbo G?`DfG G?`Dbg G?`DeW G?`DbW G?`DfC G?`Ddc G?`Dbc G?`DeS G?`DdS G?`DbS G?`D`s G?`DeK G?`DbK G?`Da[ G?`Dfo G?`Dfg G?`DfW G?`Dbw G?`Dfc G?`DfS G?`Dds G?`Dbs G?`DfK G?`Dbk G?`De[ G?`Db[ G?`Dfw G?`Dfs G?`Dfk G?`Df[ G?`Db{ G?`Df{ G?`EV? G?`EUG G?`EV_ G?`EVO G?`ETo G?`EVG G?`ETg G?`ERg G?`EUW G?`ETW G?`EUS G?`EUK G?`ES[ G?`EVo G?`EVg G?`EVW G?`ETw G?`EVc G?`EVS G?`ETs G?`EVK G?`ETk G?`ERk G?`EU[ G?`ET[ G?`EVw G?`EVs G?`EVk G?`EV[ G?`ET{ G?`EV{ G?`DRG G?`DQg G?`DV_ G?`DVO G?`DUo G?`DTo G?`DVG G?`DUg G?`DRg G?`DUW G?`DUS G?`DTS G?`DSs G?`DUK G?`DRK G?`DQk G?`DVo G?`DVg G?`DVW G?`DUw G?`DVc G?`DVS G?`DUs G?`DTs G?`DVK G?`DUk G?`DRk G?`DU[ G?`DVw G?`DVs G?`DVk G?`DV[ G?`DU{ G?`DV{ G?`Ff_ G?`FfO G?`Fdo G?`Fbo G?`FeW G?`FdW G?`F`w G?`Ffo G?`FfW G?`Fdw G?`Ffc G?`FfS G?`Fds G?`Fbs G?`Fe[ G?`Fd[ G?`F`{ G?`Ffw G?`Ffs G?`Ff[ G?`Fd{ G?`Ff{ G?`FVO G?`FUo G?`FVG G?`FUg G?`FUW G?`FTW G?`FSw G?`FVo G?`FVg G?`FVW G?`FUw G?`FTw G?`FVS G?`FUs G?`FVK G?`FUk G?`FU[ G?`FT[ G?`FS{ G?`FVw G?`FVs G?`FVk G?`FV[ G?`FU{ G?`FT{ G?`FV{ G?`DvO G?`Dto G?`DvG G?`Drg G?`DuW G?`Dvo G?`Dvg G?`DvW G?`DvS G?`Dts G?`DvK G?`Drk G?`Du[ G?`Dvw G?`Dvs G?`Dvk G?`Dv[ G?`Dv{ G?`E]W G?`E^o G?`E^W G?`E][ G?`E^w G?`E^s G?`E^[ G?`E^{ G?`Fvo G?`Fvg G?`FvW G?`Ftw G?`Fvw G?`Fvs G?`Fvk G?`Fv[ G?`Ft{ G?`Fv{ G?`F^W G?`F]w G?`F^w G?`F^[ G?`F]{ G?`F^{ G?`F~w G?`F~{ G?bB@O G?bBF? G?bBE_ G?bBD_ G?bBEO G?bBDO G?bBBO G?bB@o G?bBEG G?bBDG G?bBBG G?bBEC G?bBDC G?bBCc G?bBF_ G?bBFO G?bBEo G?bBDo G?bBBo G?bBFG G?bBEg G?bBDg G?bBBg G?bBEW G?bBDW G?bBBW G?bBCw G?bBFC G?bBEc G?bBDc G?bBES G?bBDS G?bBEK G?bBFo G?bBFg G?bBFW G?bBEw G?bBDw G?bBBw G?bBFc G?bBFS G?bBEs G?bBDs G?bBFK G?bBEk G?bBFw G?bBFs G?bBFk G?bBF[ G?bBF{ G?b@`_ G?b@aO G?b@f? G?b@d_ G?b@b_ G?b@eO G?b@bO G?b@dG G?b@eC G?b@dC G?b@bC G?b@`c G?b@cS G?b@aS G?b@cK G?b@f_ G?b@fO G?b@do G?b@bo G?b@fG G?b@dg G?b@eW G?b@fC G?b@dc G?b@bc G?b@eS G?b@dS G?b@bS G?b@eK G?b@dK G?b@fo G?b@fg G?b@fW G?b@fc G?b@fS G?b@ds G?b@bs G?b@fK G?b@dk G?b@e[ G?b@fw G?b@fs G?b@fk G?b@f[ G?b@f{ G?bAV? G?bAUO G?bAUG G?bAV_ G?bAVO G?bATo G?bAVG G?bAUW G?bAVo G?bAVg G?bAVW G?bAU[ G?bAVw G?bAV[ G?bAV{ G?bFF_ G?bFFO G?bFBo G?bFFG G?bFEg G?bFDg G?bFBg G?bFEW G?bFDW G?bFBW G?bFFC G?bFEK G?bFFo G?bFFg G?bFFW G?bFEw G?bFDw G?bFBw G?bFFc G?bFFS G?bFEs G?bFDs G?bFFK G?bFEk G?bFFw G?bFFs G?bFFk G?bFF[ G?bFF{ G?bBf? G?bB`o G?bBdG G?bB`W G?bBf_ G?bBfO G?bBdo G?bBbo G?bBfG G?bBdg G?bBbg G?bBeW G?bBdW G?bBbW G?bB`w G?bBfC G?bBbc G?bBeS G?bBdS G?bBbS G?bB`s G?bBeK G?bBbK G?bBc[ G?bBa[ G?bB`[ G?bBfo G?bBfg G?bBfW G?bBdw G?bBbw G?bBfc G?bBfS G?bBds G?bBbs G?bBfK G?bBdk G?bBbk G?bBe[ G?bBd[ G?bBb[ G?bB`{ G?bBfw G?bBfs G?bBfk G?bBf[ G?bBd{ G?bBb{ G?bBf{ G?bEQW G?bERo G?bERg G?bEUW G?bERW G?bEUK G?bEVo G?bEVg G?bEVW G?bETw G?bERw G?bEVc G?bEVS G?bETs G?bEVK G?bEU[ G?bEVw G?bEVs G?bEVk G?bEV[ G?bEV{ G?bBV? G?bBUO G?bBTG G?bBV_ G?bBVO G?bBUo G?bBTo G?bBRo G?bBVG G?bBUg G?bBTg G?bBUW G?bBUS G?bBRS G?bBQs G?bBVo G?bBVg G?bBVW G?bBUw G?bBVc G?bBVS G?bBUs G?bBTs G?bBRs G?bBVK G?bBUk G?bBTk G?bBU[ G?bBVw G?bBVs G?bBVk G?bBV[ G?bBU{ G?bBV{ G?bEJo G?bEJW G?bEMK G?bELK G?bEK[ G?bEI[ G?bENo G?bENg G?bENW G?bELw G?bEJw G?bENK G?bELk G?bEJk G?bEM[ G?bEL[ G?bEJ[ G?bEH{ G?bENw G?bENk G?bEN[ G?bEL{ G?bEJ{ G?bEN{ G?bDN_ G?bDNO G?bDMo G?bDJo G?bDNG G?bDLg G?bDKk G?bDNo G?bDNg G?bDNW G?bDMw G?bDNK G?bDMk G?bDNw G?bDNk G?bDN[ G?bDN{ G?bFbo G?bFbg G?bFbW G?bF`w G?bFfo G?bFfg G?bFfW G?bFdw G?bFbw G?bFfc G?bFfS G?bFds G?bFbs G?bFfK G?bFbk G?bFe[ G?bFd[ G?bFb[ G?bF`{ G?bFfw G?bFfs G?bFfk G?bFf[ G?bFd{ G?bFb{ G?bFf{ G?bFRW G?bFQw G?bFVo G?bFVg G?bFVW G?bFUw G?bFTw G?bFRw G?bFVS G?bFUs G?bFU[ G?bFR[ G?bFQ{ G?bFVw G?bFVs G?bFVk G?bFV[ G?bFU{ G?bFT{ G?bFR{ G?bFV{ G?bDro G?bDrg G?bDrW G?bDvo G?bDvg G?bDvW G?bDtw G?bDrw G?bDvS G?bDts G?bDrs G?bDvK G?bDrk G?bDu[ G?bDt[ G?bDr[ G?bDvw G?bDvs G?bDvk G?bDv[ G?bDt{ G?bDr{ G?bDv{ G?bBvO G?bBro G?bBuW G?bBvo G?bBvg G?bBvW G?bBvS G?bBrs G?bBu[ G?bBvw G?bBvs G?bBvk G?bBv[ G?bBv{ G?bFJo G?bFNo G?bFNg G?bFNW G?bFMw G?bFLw G?bFJw G?bFNS G?bFMs G?bFJs G?bFNK G?bFMk G?bFM[ G?bFL[ G?bFJ[ G?bFK{ G?bFI{ G?bFNw G?bFNs G?bFNk G?bFN[ G?bFM{ G?bFL{ G?bFJ{ G?bFN{ G?bDjo G?bDno G?bDng G?bDnW G?bDnc G?bDnS G?bDls G?bDjs G?bDnK G?bDlk G?bDm[ G?bDnw G?bDns G?bDnk G?bDn[ G?bDn{ G?bE^o G?bE^g G?bE^W G?bE][ G?bE^w G?bE^s G?bE^k G?bE^[ G?bE^{ G?bFvo G?bFvW G?bFrw G?bFvw G?bFvs G?bFv[ G?bFr{ G?bFv{ G?bFno G?bFng G?bFnW G?bFlw G?bFjw G?bFnw G?bFns G?bFnk G?bFn[ G?bFl{ G?bFj{ G?bFn{ G?bF^W G?bF]w G?bF^w G?bF^[ G?bF]{ G?bF^{ G?bF~w G?bF~{ G?`bF? G?`bB_ G?`bEO G?`bCo G?`bBG G?`bF_ G?`bFO G?`bEo G?`bFG G?`bEg G?`bBg G?`bEW G?`bBK G?`bFo G?`bFg G?`bFW G?`bEw G?`bFK G?`bBk G?`bFw G?`bFk G?`bF[ G?`bF{ G?`ad? G?`a`_ G?`af? G?`ad_ G?`ab_ G?`aeO G?`adO G?`aeG G?`adG G?`acg G?`aag G?`a`g G?`acW G?`acK G?`aaK G?`a`K G?`af_ G?`afO G?`ado G?`afG G?`aeg G?`adg G?`abg G?`aeW G?`adW G?`acw G?`aeK G?`adK G?`abK G?`a`k G?`ac[ G?`afo G?`afg G?`afW G?`aew G?`adw G?`afK G?`adk G?`abk G?`ae[ G?`ad[ G?`afw G?`afk G?`af[ G?`ad{ G?`af{ G?`fF? G?`fBO G?`fBG G?`fCg G?`fCW G?`f?w G?`fF_ G?`fFO G?`fEo G?`fBo G?`fFG G?`fEg G?`fBg G?`fEW G?`fBW G?`fCw G?`fAw G?`fFC G?`fES G?`fBS G?`fFo G?`fFg G?`fFW G?`fEw G?`fBw G?`fFc G?`fFS G?`fEs G?`fBs G?`fFK G?`fEk G?`fBk G?`fE[ G?`fB[ G?`fC{ G?`fA{ G?`fFw G?`fFs G?`fFk G?`fF[ G?`fE{ G?`fB{ G?`fF{ G?`ef? G?`e`o G?`eeG G?`edG G?`ecg G?`e`g G?`ecW G?`e`W G?`e_w G?`ef_ G?`efO G?`eeo G?`edo G?`ebo G?`efG G?`eeg G?`edg G?`ebg G?`eeW G?`edW G?`ebW G?`ecw G?`eaw G?`e`w G?`efC G?`eec G?`edc G?`eeS G?`edS G?`ebS G?`ecs G?`eas G?`e`s G?`ec[ G?`efo G?`efg G?`efW G?`eew G?`edw G?`ebw G?`efc G?`efS G?`ees G?`eds G?`ebs G?`efK G?`eek G?`edk G?`ebk G?`ee[ G?`ed[ G?`eb[ G?`ec{ G?`ea{ G?`e`{ G?`efw G?`efs G?`efk G?`ef[ G?`ee{ G?`ed{ G?`eb{ G?`ef{ G?`bf? G?`bb_ G?`bco G?`bbG G?`bcg G?`bcW G?`bf_ G?`bfO G?`beo G?`bfG G?`beg G?`bbg G?`beW G?`bcw G?`bfC G?`bec G?`bbc G?`beS G?`bcs G?`beK G?`bc[ G?`bfo G?`bfg G?`bfW G?`bew G?`bfc G?`bfS G?`bes G?`bfK G?`bek G?`bbk G?`be[ G?`bc{ G?`bfw G?`bfs G?`bfk G?`bf[ G?`be{ G?`bf{ G?`eV? G?`eUG G?`eTG G?`ePg G?`eSW G?`eV_ G?`eVO G?`eUo G?`eTo G?`eVG G?`eUg G?`eTg G?`eRg G?`eUW G?`eTW G?`eSw G?`eUS G?`eTS G?`eVo G?`eVg G?`eVW G?`eUw G?`eTw G?`eVc G?`eVS G?`eUs G?`eTs G?`eVK G?`eUk G?`eTk G?`eRk G?`eU[ G?`eT[ G?`eS{ G?`eVw G?`eVs G?`eVk G?`eV[ G?`eU{ G?`eT{ G?`eV{ G?`cuG G?`crG G?`csg G?`csW G?`cv_ G?`cvO G?`cuo G?`cvG G?`cug G?`crg G?`cuW G?`csw G?`cvC G?`cuS G?`css G?`cvo G?`cvg G?`cvW G?`cuw G?`cvc G?`cvS G?`cus G?`cvK G?`cuk G?`crk G?`cu[ G?`cs{ G?`cvw G?`cvs G?`cvk G?`cv[ G?`cu{ G?`cv{ G?`eN_ G?`eNO G?`eMo G?`eLo G?`eJo G?`eMW G?`eLW G?`eJW G?`eKw G?`eIw G?`eNC G?`eMc G?`eLc G?`eMS G?`eLS G?`eJS G?`eKs G?`eIs G?`eHs G?`eMK G?`eLK G?`eK[ G?`eI[ G?`eH[ G?`eNo G?`eNg G?`eNW G?`eMw G?`eLw G?`eJw G?`eNc G?`eNS G?`eMs G?`eLs G?`eJs G?`eNK G?`eMk G?`eLk G?`eJk G?`eM[ G?`eL[ G?`eJ[ G?`eK{ G?`eI{ G?`eH{ G?`eNw G?`eNs G?`eNk G?`eN[ G?`eM{ G?`eL{ G?`eJ{ G?`eN{ G?`bN_ G?`bNO G?`bMo G?`bNG G?`bMg G?`bJg G?`bMW G?`bKw G?`bMK G?`bJK G?`bKk G?`bIk G?`bK[ G?`bNo G?`bNg G?`bNW G?`bMw G?`bNK G?`bMk G?`bJk G?`bM[ G?`bK{ G?`bNw G?`bNk G?`bN[ G?`bM{ G?`bN{ G?`cn_ G?`cnO G?`cmo G?`cjo G?`cnG G?`cmg G?`cjg G?`cmW G?`cjW G?`ckw G?`ciw G?`cnC G?`cmc G?`cjc G?`cmS G?`cjS G?`cks G?`cis G?`cmK G?`ckk G?`ck[ G?`ci[ G?`cg{ G?`cno G?`cng G?`cnW G?`cmw G?`cjw G?`cnc G?`cnS G?`cms G?`cjs G?`cnK G?`cmk G?`cjk G?`cm[ G?`cj[ G?`ck{ G?`ci{ G?`cnw G?`cns G?`cnk G?`cn[ G?`cm{ G?`cj{ G?`cn{ G?`an_ G?`anO G?`alo G?`anG G?`amg G?`alg G?`ajg G?`amW G?`alW G?`akw G?`amK G?`alK G?`ahk G?`ak[ G?`ano G?`ang G?`anW G?`amw G?`alw G?`anK G?`alk G?`ajk G?`am[ G?`al[ G?`anw G?`ank G?`an[ G?`al{ G?`an{ G?`c^_ G?`c^O G?`c]o G?`c^G G?`c]g G?`c]W G?`c[w G?`c^C G?`c]S G?`c[s G?`c[[ G?`c^o G?`c^g G?`c^W G?`c]w G?`c^c G?`c^S G?`c]s G?`c^K G?`c]k G?`cZk G?`c][ G?`c[{ G?`c^w G?`c^s G?`c^k G?`c^[ G?`c]{ G?`c^{ G?`ff_ G?`ffO G?`feo G?`fbo G?`feW G?`ffo G?`ffg G?`ffW G?`few G?`fbw G?`ffc G?`ffS G?`fes G?`fbs G?`fe[ G?`ffw G?`ffs G?`ffk G?`ff[ G?`fe{ G?`fb{ G?`ff{ G?`fVO G?`fVo G?`fVg G?`fVW G?`fUw G?`fVS G?`fVw G?`fVs G?`fVk G?`fV[ G?`fU{ G?`fV{ G?`evO G?`euo G?`eto G?`evo G?`evg G?`evW G?`euw G?`etw G?`evS G?`eus G?`ets G?`evw G?`evs G?`evk G?`ev[ G?`eu{ G?`et{ G?`ev{ G?`fN_ G?`fNO G?`fMo G?`fJo G?`fNo G?`fNg G?`fNW G?`fMw G?`fJw G?`fNc G?`fNS G?`fMs G?`fJs G?`fNK G?`fM[ G?`fJ[ G?`fNw G?`fNs G?`fNk G?`fN[ G?`fM{ G?`fJ{ G?`fN{ G?`en_ G?`enO G?`emo G?`elo G?`ejo G?`eno G?`eng G?`enW G?`emw G?`elw G?`ejw G?`enc G?`enS G?`ems G?`els G?`ejs G?`enK G?`emk G?`elk G?`em[ G?`el[ G?`ej[ G?`ek{ G?`ei{ G?`eh{ G?`enw G?`ens G?`enk G?`en[ G?`em{ G?`el{ G?`ej{ G?`en{ G?`bn_ G?`bnO G?`bmo G?`bno G?`bng G?`bnW G?`bmw G?`bnc G?`bnS G?`bms G?`bnK G?`bmk G?`bjk G?`bm[ G?`bk{ G?`bnw G?`bns G?`bnk G?`bn[ G?`bm{ G?`bn{ G?`e^O G?`e]o G?`e\o G?`e^o G?`e^g G?`e^W G?`e]w G?`e\w G?`e^S G?`e]s G?`e\s G?`e][ G?`e\[ G?`e^w G?`e^s G?`e^k G?`e^[ G?`e]{ G?`e\{ G?`e^{ G?`c~_ G?`c~O G?`c}o G?`c~o G?`c~g G?`c~W G?`c}w G?`c~c G?`c~S G?`c}s G?`c~K G?`c}[ G?`c{{ G?`c~w G?`c~s G?`c~k G?`c~[ G?`c}{ G?`c~{ G?`fvo G?`fvw G?`fvs G?`fv{ G?`fno G?`fng G?`fnW G?`fmw G?`fjw G?`fnw G?`fns G?`fnk G?`fn[ G?`fm{ G?`fj{ G?`fn{ G?`f^o G?`f^W G?`f^w G?`f^s G?`f^[ G?`f^{ G?`e~o G?`e~W G?`e}w G?`e|w G?`e~w G?`e~s G?`e~[ G?`e}{ G?`e|{ G?`e~{ G?`f~w G?`f~{ G?aJB? G?aJB_ G?aJEO G?aJCo G?aJCW G?aJEC G?aJBC G?aJCc G?aJAc G?aJCS G?aJF_ G?aJFO G?aJEo G?aJEW G?aJCw G?aJFC G?aJEc G?aJBc G?aJES G?aJCs G?aJC[ G?aJFo G?aJFW G?aJEw G?aJFc G?aJFS G?aJEs G?aJE[ G?aJC{ G?aJFw G?aJFs G?aJF[ G?aJE{ G?aJF{ G?aJb_ G?aJeO G?aJcW G?aJf_ G?aJfO G?aJeW G?aJfC G?aJbc G?aJeS G?aJc[ G?aJfo G?aJfW G?aJfc G?aJfS G?aJe[ G?aJfw G?aJfs G?aJf[ G?aJf{ G?aK[[ G?aK^o G?aK^W G?aK][ G?aK^w G?aK^[ G?aK^{ G?aNbo G?aNbW G?aNfo G?aNfW G?aNbw G?aNfc G?aNfS G?aNbs G?aNe[ G?aNb[ G?aNfw G?aNfs G?aNf[ G?aNb{ G?aNf{ G?aNVo G?aNVg G?aNVW G?aNUw G?aNVS G?aNUs G?aNVK G?aNUk G?aNU[ G?aNS{ G?aNVw G?aNVs G?aNVk G?aNV[ G?aNU{ G?aNV{ G?aM^o G?aM^W G?aM\w G?aM][ G?aM\[ G?aM^w G?aM^s G?aM^[ G?aM\{ G?aM^{ G?aNvo G?aNvg G?aNvW G?aNvw G?aNvs G?aNvk G?aNv[ G?aNv{ G?aN^W G?aN]w G?aN^w G?aN^[ G?aN]{ G?aN^{ G?aN~w G?aN~{ G?bfF_ G?bfFO G?bfBo G?bfFG G?bfBg G?bfBW G?bfAw G?bfEK G?bfBK G?bfCk G?bfAk G?bfC[ G?bfA[ G?bf?{ G?bfFo G?bfFg G?bfFW G?bfEw G?bfBw G?bfFK G?bfEk G?bfBk G?bfE[ G?bfB[ G?bfC{ G?bfA{ G?bfFw G?bfFk G?bfF[ G?bfE{ G?bfB{ G?bfF{ G?bef_ G?befO G?bedo G?bebo G?befG G?beeg G?bedg G?bebg G?beeW G?bebW G?becw G?beaw G?be`w G?bedK G?bebK G?be`k G?bec[ G?bea[ G?be`[ G?befo G?befg G?befW G?beew G?bedw G?bebw G?befK G?bedk G?bebk G?bee[ G?bed[ G?beb[ G?be`{ G?befw G?befk G?bef[ G?bed{ G?beb{ G?bef{ G?bbV? G?bbV_ G?bbVO G?bbUo G?bbVG G?bbUK G?bbRK G?bbSk G?bbQk G?bbS[ G?bbVo G?bbVg G?bbVW G?bbUw G?bbRw G?bbVK G?bbUk G?bbRk G?bbU[ G?bbS{ G?bbVw G?bbVk G?bbV[ G?bbU{ G?bbV{ G?bav_ G?bavO G?bato G?bavG G?baug G?batg G?barg G?bauK G?batK G?barK G?bask G?bapk G?bas[ G?bavo G?bavg G?bavW G?bauw G?batw G?barw G?bavK G?bauk G?batk G?bark G?bau[ G?bat[ G?bavw G?bavk G?bav[ G?bat{ G?bav{ G?bfbo G?bffo G?bffg G?bffW G?bfew G?bfbw G?bffc G?bffS G?bfbs G?bfbk G?bfb[ G?bfa{ G?bffw G?bffs G?bffk G?bff[ G?bfe{ G?bfb{ G?bff{ G?bfRo G?bfRW G?bfVo G?bfVg G?bfVW G?bfUw G?bfRw G?bfVS G?bfRs G?bfR[ G?bfVw G?bfVs G?bfVk G?bfV[ G?bfU{ G?bfR{ G?bfV{ G?bero G?berW G?beqw G?bepw G?bevo G?bevg G?bevW G?beuw G?betw G?berw G?bevc G?bevS G?beus G?bets G?bers G?bevK G?berk G?ber[ G?beq{ G?bep{ G?bevw G?bevs G?bevk G?bev[ G?beu{ G?bet{ G?ber{ G?bev{ G?bbro G?bbvG G?bbug G?bbuW G?bbrW G?bbsw G?bbqw G?bbvo G?bbvg G?bbvW G?bbuw G?bbrw G?bbrs G?bbvw G?bbvs G?bbvk G?bbv[ G?bbu{ G?bbr{ G?bbv{ G?bfNo G?bfNg G?bfNW G?bfJw G?bfNK G?bfMk G?bfM[ G?bfJ[ G?bfK{ G?bfI{ G?bfNw G?bfNk G?bfN[ G?bfM{ G?bfJ{ G?bfN{ G?beno G?beng G?benW G?bemw G?belw G?bejw G?belk G?bem[ G?bel[ G?beh{ G?benw G?benk G?ben[ G?bel{ G?bej{ G?ben{ G?be^o G?be^g G?be^W G?be]w G?be\w G?beZw G?be^c G?be^S G?be]s G?be\s G?beZs G?be][ G?be\[ G?be^w G?be^s G?be^k G?be^[ G?be]{ G?be\{ G?beZ{ G?be^{ G?bb^o G?bb^g G?bb]k G?bb][ G?bb[{ G?bb^w G?bb^k G?bb^[ G?bb]{ G?bb^{ G?bc~o G?bc~W G?bc~c G?bc~S G?bc}s G?bczs G?bc}[ G?bc{{ G?bc~w G?bc~s G?bc~k G?bc~[ G?bc}{ G?bcz{ G?bc~{ G?ba~o G?ba~g G?ba}w G?ba}[ G?ba|[ G?ba~w G?ba~k G?ba~[ G?ba|{ G?ba~{ G?bfvo G?bfrw G?bfvw G?bfvs G?bfr{ G?bfv{ G?bfno G?bfnw G?bfns G?bfnk G?bfn[ G?bfj{ G?bfn{ G?bf^o G?bf^w G?bf^s G?bf^[ G?bfZ{ G?bf^{ G?be~o G?be~w G?be~s G?be~k G?be~[ G?be}{ G?be|{ G?bez{ G?be~{ G?bb~w G?bbz{ G?bb~{ G?bf~w G?bf~{ G?`vF? G?`vF_ G?`vFO G?`vEo G?`vBo G?`vBg G?`vBW G?`vFC G?`vEc G?`vES G?`vBS G?`vCs G?`vAs G?`vEK G?`vBK G?`vCk G?`vAk G?`vC[ G?`vA[ G?`v?{ G?`vFo G?`vFg G?`vFW G?`vEw G?`vBw G?`vFc G?`vFS G?`vEs G?`vBs G?`vFK G?`vEk G?`vBk G?`vE[ G?`vB[ G?`vC{ G?`vA{ G?`vFw G?`vFs G?`vFk G?`vF[ G?`vE{ G?`vB{ G?`vF{ G?`rb_ G?`rf_ G?`rfO G?`rfG G?`rbg G?`reW G?`reK G?`rbK G?`rc[ G?`rfo G?`rfg G?`rfW G?`rfK G?`rbk G?`re[ G?`rfw G?`rfk G?`rf[ G?`rf{ G?`uV_ G?`uVO G?`uTo G?`uVG G?`uTg G?`uRg G?`uUW G?`uTW G?`uUS G?`uTS G?`uUK G?`uTK G?`uRK G?`uPk G?`uS[ G?`uVo G?`uVg G?`uVW G?`uTw G?`uVc G?`uVS G?`uTs G?`uVK G?`uTk G?`uRk G?`uU[ G?`uT[ G?`uVw G?`uVs G?`uVk G?`uV[ G?`uT{ G?`uV{ G?`vf_ G?`vfO G?`vbo G?`vfG G?`vbg G?`veW G?`vbW G?`vfo G?`vfg G?`vfW G?`vbw G?`vfc G?`vfS G?`vbs G?`vfw G?`vfs G?`vfk G?`vf[ G?`vb{ G?`vf{ G?`vVO G?`vUo G?`vVG G?`vUg G?`vRg G?`vUW G?`vSw G?`vVo G?`vVg G?`vVW G?`vUw G?`vVS G?`vUs G?`vVw G?`vVs G?`vVk G?`vV[ G?`vU{ G?`vV{ G?`vNo G?`vNW G?`vJw G?`vNc G?`vNS G?`vMs G?`vJs G?`vNK G?`vMk G?`vM[ G?`vJ[ G?`vK{ G?`vI{ G?`vNw G?`vNs G?`vNk G?`vN[ G?`vM{ G?`vJ{ G?`vN{ G?`rno G?`rng G?`rnW G?`rnK G?`rjk G?`rm[ G?`rnw G?`rnk G?`rn[ G?`rn{ G?`u^o G?`u^g G?`u^W G?`u\w G?`u^c G?`u^S G?`u\s G?`u][ G?`u\[ G?`u^w G?`u^s G?`u^k G?`u^[ G?`u\{ G?`u^{ G?`vvo G?`vvw G?`vvs G?`vv{ G?`vno G?`vnw G?`vns G?`vnk G?`vn[ G?`vj{ G?`vn{ G?`v^o G?`v^w G?`v^s G?`v^[ G?`v]{ G?`v^{ G?`v~w G?`v~{ G?bNFO G?bNDo G?bNBo G?bNFG G?bNBg G?bNEW G?bNDW G?bNBW G?bNAw G?bN@w G?bNFC G?bNEc G?bNES G?bNDS G?bNBS G?bNCs G?bNAs G?bN@s G?bNDK G?bNBK G?bNAk G?bN@k G?bNC[ G?bNA[ G?bN@[ G?bN?{ G?bNFo G?bNFg G?bNFW G?bNEw G?bNDw G?bNBw G?bNFc G?bNFS G?bNEs G?bNDs G?bNBs G?bNFK G?bNEk G?bNDk G?bNBk G?bNE[ G?bND[ G?bNB[ G?bNC{ G?bNA{ G?bN@{ G?bNFw G?bNFs G?bNFk G?bNF[ G?bNE{ G?bND{ G?bNB{ G?bNF{ G?bLbo G?bLfG G?bLdg G?bLbg G?bLeW G?bLdW G?bLbW G?bL`w G?bLfC G?bLeS G?bLdS G?bL`s G?bLeK G?bLdK G?bLbK G?bL`k G?bLc[ G?bLa[ G?bL`[ G?bLfo G?bLfg G?bLfW G?bLdw G?bLbw G?bLfc G?bLfS G?bLds G?bLbs G?bLfK G?bLdk G?bLbk G?bLe[ G?bLd[ G?bLb[ G?bL`{ G?bLfw G?bLfs G?bLfk G?bLf[ G?bLd{ G?bLb{ G?bLf{ G?bMUW G?bMUK G?bMRK G?bMPk G?bMS[ G?bMVo G?bMVg G?bMVW G?bMTw G?bMVK G?bMTk G?bMRk G?bMU[ G?bMT[ G?bMVw G?bMVk G?bMV[ G?bMT{ G?bMV{ G?bLV_ G?bLTW G?bLUK G?bLRK G?bLSk G?bLQk G?bLS[ G?bLVo G?bLVg G?bLVW G?bLUw G?bLTw G?bLVK G?bLUk G?bLRk G?bLU[ G?bLS{ G?bLVw G?bLVk G?bLV[ G?bLU{ G?bLV{ G?bNbo G?bNfo G?bNfg G?bNfW G?bNdw G?bNbw G?bNfc G?bNfS G?bNds G?bNbs G?bNdk G?bNe[ G?bNd[ G?bN`{ G?bNfw G?bNfs G?bNfk G?bNf[ G?bNd{ G?bNb{ G?bNf{ G?bNVo G?bNVg G?bNVW G?bNUw G?bNTw G?bNVS G?bNUs G?bNTs G?bNTk G?bNT[ G?bNS{ G?bNVw G?bNVs G?bNVk G?bNV[ G?bNU{ G?bNT{ G?bNV{ G?bLvo G?bLvg G?bLvW G?bLtw G?bLts G?bLvw G?bLvs G?bLvk G?bLv[ G?bLt{ G?bLv{ G?bM^o G?bM^W G?bM\w G?bM][ G?bM^w G?bM^k G?bM^[ G?bM\{ G?bM^{ G?bL^o G?bL]w G?bL][ G?bL[{ G?bL^w G?bL^k G?bL^[ G?bL]{ G?bL^{ G?bNvo G?bNtw G?bNvw G?bNvs G?bNt{ G?bNv{ G?bNno G?bNnw G?bNns G?bNnk G?bNn[ G?bNl{ G?bNj{ G?bNn{ G?bN^o G?bN^w G?bN^s G?bN^[ G?bN]{ G?bN\{ G?bN^{ G?bL~w G?bL|{ G?bL~{ G?bN~w G?bN~{ G?bvbo G?bvfo G?bvfg G?bvbw G?bvfK G?bvbk G?bve[ G?bvb[ G?bvfw G?bvfk G?bvf[ G?bvb{ G?bvf{ G?bvVo G?bvVg G?bvVW G?bvRw G?bvVc G?bvVS G?bvUs G?bvRs G?bvVK G?bvUk G?bvRk G?bvU[ G?bvR[ G?bvS{ G?bvQ{ G?bvVw G?bvVs G?bvVk G?bvV[ G?bvU{ G?bvR{ G?bvV{ G?brvO G?brvo G?brvg G?brvK G?brrk G?bru[ G?brvw G?brvk G?brv[ G?brv{ G?bvvo G?bvvg G?bvvW G?bvrw G?bvvw G?bvvs G?bvr{ G?bvv{ G?bvnw G?bvnk G?bvn[ G?bvj{ G?bvn{ G?bv^w G?bv^s G?bv^[ G?bv]{ G?bv^{ G?br~[ G?br~{ G?bv~{ G?bnVo G?bnVW G?bnVK G?bnUk G?bnRk G?bnU[ G?bnS{ G?bnVw G?bnVk G?bnV[ G?bnU{ G?bnV{ G?bmvo G?bmvg G?bmvW G?bmuw G?bmtw G?bmvK G?bmtk G?bmrk G?bmu[ G?bmt[ G?bmvw G?bmvk G?bmv[ G?bmt{ G?bmv{ G?bnvo G?bnvW G?bnuw G?bnvw G?bnvs G?bnv{ G?bn^w G?bn^[ G?bn]{ G?bn^{ G?bm~w G?bm|{ G?bm~{ G?bn~{ G?b~vo G?b~vw G?b~vk G?b~v[ G?b~v{ G?b~~{ G?r@`_ G?r@f? G?r@d_ G?r@eO G?r@eC G?r@dC G?r@`c G?r@cS G?r@f_ G?r@fO G?r@do G?r@eW G?r@fC G?r@dc G?r@eS G?r@dS G?r@fo G?r@fW G?r@fc G?r@fS G?r@ds G?r@e[ G?r@fw G?r@fs G?r@f[ G?r@f{ G?rFf_ G?rFeW G?rF`w G?rFfo G?rFfW G?rFdw G?rFfc G?rFfS G?rFds G?rFe[ G?rFd[ G?rF`{ G?rFfw G?rFfs G?rFf[ G?rFd{ G?rFf{ G?rFV_ G?rFVo G?rFVg G?rFVW G?rFUw G?rFTw G?rFVS G?rFUs G?rFVK G?rFUk G?rFU[ G?rFT[ G?rFS{ G?rFVw G?rFVs G?rFVk G?rFV[ G?rFU{ G?rFT{ G?rFV{ G?rDrg G?rDvo G?rDvg G?rDvW G?rDvS G?rDts G?rDrs G?rDvK G?rDrk G?rDu[ G?rDvw G?rDvs G?rDvk G?rDv[ G?rDv{ G?rE]W G?rE^o G?rE^W G?rE][ G?rE^w G?rE^s G?rE^[ G?rE^{ G?rFvo G?rFvg G?rFvW G?rFtw G?rFvw G?rFvs G?rFvk G?rFv[ G?rFt{ G?rFv{ G?rF^W G?rF]w G?rF^w G?rF^[ G?rF]{ G?rF^{ G?rF~w G?rF~{ G?qbF? G?qbE_ G?qbEO G?qbDO G?qb@o G?qbF_ G?qbFO G?qbEo G?qbDo G?qbEW G?qbBW G?qbFo G?qbFW G?qbEw G?qbBw G?qbFw G?qbF[ G?qbF{ G?qa`_ G?qaf? G?qad_ G?qaeO G?qadO G?qa`o G?qaeG G?qabG G?qacg G?qaag G?qa`g G?qacW G?qaaW G?qa`W G?qa_w G?qacK G?qa`K G?qa_[ G?qaf_ G?qafO G?qado G?qafG G?qaeg G?qadg G?qabg G?qaeW G?qadW G?qabW G?qacw G?qaaw G?qa`w G?qaeK G?qadK G?qabK G?qa`k G?qac[ G?qa`[ G?qafo G?qafg G?qafW G?qaew G?qadw G?qabw G?qafK G?qadk G?qae[ G?qad[ G?qab[ G?qa`{ G?qafw G?qafk G?qaf[ G?qad{ G?qaf{ G?qfBo G?qfEW G?qfBW G?qfCw G?qfFC G?qfES G?qfDS G?qfFo G?qfFW G?qfEw G?qfBw G?qfFc G?qfFS G?qfEs G?qfDs G?qfFw G?qfFs G?qfF[ G?qfF{ G?qbf? G?qbe_ G?qbdO G?qbbO G?qbcW G?qb_w G?qbf_ G?qbfO G?qbeo G?qbdo G?qbbo G?qbeW G?qbbW G?qbcw G?qbaw G?qbbc G?qbbS G?qbfo G?qbfW G?qbew G?qbbw G?qbfc G?qbfS G?qbes G?qbds G?qbbs G?qbe[ G?qbb[ G?qbc{ G?qba{ G?qbfw G?qbfs G?qbf[ G?qbe{ G?qbb{ G?qbf{ G?qdV_ G?qdVG G?qdUg G?qdTg G?qdRg G?qdRW G?qdPw G?qdTK G?qdVo G?qdVg G?qdVW G?qdUw G?qdTw G?qdRw G?qdVc G?qdVS G?qdUs G?qdTs G?qdVK G?qdTk G?qdT[ G?qdVw G?qdVs G?qdVk G?qdV[ G?qdT{ G?qdV{ G?qbV? G?qbUG G?qbV_ G?qbVG G?qbUg G?qbTg G?qbUW G?qbTS G?qbSs G?qbVo G?qbVg G?qbVW G?qbUw G?qbTw G?qbRw G?qbVS G?qbUs G?qbTs G?qbU[ G?qbT[ G?qbS{ G?qbQ{ G?qbVw G?qbVs G?qbV[ G?qbU{ G?qbT{ G?qbV{ G?q`v? G?q`qg G?q`v_ G?q`uo G?q`to G?q`vG G?q`ug G?q`uW G?q`sw G?q`qw G?q`tS G?q`vo G?q`vg G?q`vW G?q`uw G?q`tw G?q`rw G?q`vS G?q`us G?q`u[ G?q`t[ G?q`r[ G?q`q{ G?q`vw G?q`vs G?q`v[ G?q`u{ G?q`v{ G?qfbo G?qfbW G?qffo G?qffW G?qfew G?qfbw G?qffc G?qffS G?qfbs G?qfb[ G?qffw G?qffs G?qff[ G?qfe{ G?qfb{ G?qff{ G?qfRg G?qfVo G?qfVg G?qfVW G?qfUw G?qfTw G?qfRw G?qfVS G?qfRs G?qfVK G?qfRk G?qfR[ G?qfVw G?qfVs G?qfVk G?qfV[ G?qfU{ G?qfT{ G?qfR{ G?qfV{ G?qerg G?qevo G?qevg G?qevW G?qeuw G?qetw G?qerw G?qevc G?qevS G?qeus G?qers G?qevK G?qetk G?qerk G?qeu[ G?qet[ G?qer[ G?qeq{ G?qep{ G?qevw G?qevs G?qevk G?qev[ G?qeu{ G?qet{ G?qer{ G?qev{ G?qdrg G?qdvo G?qdvg G?qdvW G?qduw G?qdtw G?qdrw G?qdvc G?qdvS G?qdus G?qdts G?qdrs G?qdvK G?qduk G?qdtk G?qdrk G?qdu[ G?qdt[ G?qdr[ G?qdq{ G?qdp{ G?qdvw G?qdvs G?qdvk G?qdv[ G?qdu{ G?qdt{ G?qdr{ G?qdv{ G?qbrg G?qbvo G?qbvg G?qbvW G?qbuw G?qbtw G?qbrw G?qbrs G?qbrk G?qbr[ G?qbvw G?qbvs G?qbvk G?qbv[ G?qbu{ G?qbt{ G?qbr{ G?qbv{ G?qeXw G?qe^o G?qe^g G?qe^W G?qe]w G?qe\w G?qeZw G?qe^c G?qe^S G?qeZs G?qe^K G?qeZk G?qe][ G?qe\[ G?qeZ[ G?qeY{ G?qeX{ G?qe^w G?qe^s G?qe^k G?qe^[ G?qe]{ G?qe\{ G?qeZ{ G?qe^{ G?qb]W G?qbZW G?qb^o G?qb^W G?qb]w G?qbZw G?qbZ[ G?qb^w G?qb^s G?qb^[ G?qb]{ G?qbZ{ G?qb^{ G?qczW G?qcyw G?qc~o G?qc~W G?qc}w G?qczw G?qc~c G?qc~S G?qc}s G?qc|s G?qczs G?qc}[ G?qcz[ G?qc{{ G?qcy{ G?qc~w G?qc~s G?qc~[ G?qc}{ G?qcz{ G?qc~{ G?qa~_ G?qa}W G?qaxw G?qa~o G?qa~g G?qa~W G?qa}w G?qa|w G?qazw G?qa~c G?qa~S G?qazs G?qa~K G?qazk G?qaz[ G?qay{ G?qax{ G?qa~w G?qa~s G?qa~k G?qa~[ G?qa}{ G?qa|{ G?qaz{ G?qa~{ G?qfvo G?qfvg G?qfvW G?qfrw G?qfvw G?qfvs G?qfvk G?qfv[ G?qfr{ G?qfv{ G?qf^W G?qfZw G?qf^w G?qf^[ G?qfZ{ G?qf^{ G?qe~o G?qe~g G?qe~W G?qe}w G?qe|w G?qezw G?qe~w G?qe~s G?qe~k G?qe~[ G?qe}{ G?qe|{ G?qez{ G?qe~{ G?qbzw G?qb~w G?qbz{ G?qb~{ G?qf~w G?qf~{ G?ovF? G?ovE_ G?ovD_ G?ovEO G?ov@o G?ovEG G?ovDG G?ovCW G?ov@W G?ov?w G?ovF_ G?ovFO G?ovEo G?ovDo G?ovFG G?ovEg G?ovDg G?ovEW G?ovDW G?ovCw G?ov@w G?ovFC G?ovEc G?ovDc G?ovES G?ovDS G?ovCs G?ov@s G?ovEK G?ovDK G?ovCk G?ov@k G?ovC[ G?ov@[ G?ov?{ G?ovFo G?ovFg G?ovFW G?ovEw G?ovDw G?ovFc G?ovFS G?ovEs G?ovDs G?ovFK G?ovEk G?ovDk G?ovE[ G?ovD[ G?ovC{ G?ov@{ G?ovFw G?ovFs G?ovFk G?ovF[ G?ovE{ G?ovD{ G?ovF{ G?ouPg G?ouV_ G?ouVG G?ouTg G?ouUW G?ouPw G?ouUS G?ouTS G?ouPs G?ouP[ G?ouVo G?ouVg G?ouVW G?ouTw G?ouVS G?ouTs G?ouU[ G?ouT[ G?ouP{ G?ouVw G?ouVs G?ouV[ G?ouT{ G?ouV{ G?otTG G?otQg G?otV_ G?otUo G?otTo G?otVG G?otUg G?otTg G?otRg G?otSw G?otQw G?otTS G?otRS G?otSs G?otQs G?otPs G?otTK G?otRK G?otQk G?otPk G?otVo G?otVg G?otVW G?otUw G?otTw G?otRw G?otVc G?otVS G?otUs G?otTs G?otRs G?otVK G?otUk G?otTk G?otRk G?otU[ G?otT[ G?otR[ G?otS{ G?otQ{ G?otP{ G?otVw G?otVs G?otVk G?otV[ G?otU{ G?otT{ G?otR{ G?otV{ G?opuG G?oppg G?opv_ G?opvO G?opto G?opvG G?optg G?opuW G?optW G?oppw G?opps G?opuK G?optK G?opvo G?opvg G?opvW G?optw G?opvc G?opvS G?opts G?opvK G?optk G?opu[ G?opt[ G?opp{ G?opvw G?opvs G?opvk G?opv[ G?opt{ G?opv{ G?ovf_ G?ovfO G?ovdo G?oveW G?ovdW G?ovfo G?ovfW G?ovfc G?ovfS G?ovds G?ove[ G?ovd[ G?ovfw G?ovfs G?ovf[ G?ovf{ G?ovVG G?ovUg G?ovTg G?ovVo G?ovVg G?ovVW G?ovUw G?ovTw G?ovVS G?ovUs G?ovVK G?ovUk G?ovTk G?ovU[ G?ovT[ G?ovS{ G?ovP{ G?ovVw G?ovVs G?ovVk G?ovV[ G?ovU{ G?ovT{ G?ovV{ G?otvG G?otvo G?otvg G?otvW G?ottw G?otrw G?otvS G?otts G?otrs G?otvK G?otu[ G?ott[ G?otr[ G?otp{ G?otvw G?otvs G?otvk G?otv[ G?ott{ G?otr{ G?otv{ G?ouXw G?ou^o G?ou^g G?ou^W G?ou\w G?ou][ G?ou\[ G?ouX{ G?ou^w G?ou^s G?ou^k G?ou^[ G?ou\{ G?ou^{ G?ot\W G?otYw G?ot^o G?ot^W G?ot]w G?ot\[ G?otZ[ G?otY{ G?ot^w G?ot^s G?ot^[ G?ot]{ G?ot^{ G?ovvo G?ovvg G?ovvW G?ovtw G?ovvw G?ovvs G?ovvk G?ovv[ G?ovt{ G?ovv{ G?ov^W G?ov]w G?ov\w G?ov^w G?ov^[ G?ov]{ G?ov\{ G?ov^{ G?ov~w G?ov~{ G?rfF_ G?rfFO G?rfEo G?rfFG G?rfDW G?rfEK G?rfFo G?rfFg G?rfFW G?rfEw G?rfDw G?rfFK G?rfEk G?rfFw G?rfFk G?rfF[ G?rfF{ G?ref_ G?refO G?refG G?reeg G?recw G?reeK G?re`k G?rec[ G?re`[ G?refo G?refg G?refW G?reew G?redw G?refK G?redk G?ree[ G?red[ G?re`{ G?refw G?refk G?ref[ G?red{ G?ref{ G?rff_ G?rffG G?rfeg G?rffo G?rffg G?rffW G?rfew G?rfdw G?rffc G?rffS G?rfes G?rfek G?rfe[ G?rfc{ G?rffw G?rffs G?rffk G?rff[ G?rfe{ G?rfd{ G?rff{ G?rfV_ G?rfVG G?rfVo G?rfVg G?rfVW G?rfUw G?rfTw G?rfVS G?rfUs G?rfUk G?rfU[ G?rfS{ G?rfVw G?rfVs G?rfVk G?rfV[ G?rfU{ G?rfT{ G?rfV{ G?rev_ G?revG G?reug G?revo G?revg G?revW G?reuw G?retw G?reus G?revw G?revs G?revk G?rev[ G?reu{ G?ret{ G?rev{ G?rdrW G?rdpw G?rdvo G?rdvg G?rdvW G?rduw G?rdtw G?rdrw G?rdvc G?rdvS G?rdus G?rdts G?rdrs G?rdvK G?rduk G?rdu[ G?rds{ G?rdq{ G?rdvw G?rdvs G?rdvk G?rdv[ G?rdu{ G?rdt{ G?rdr{ G?rdv{ G?rfNo G?rfNg G?rfNW G?rfMw G?rfNK G?rfMk G?rfNw G?rfNk G?rfN[ G?rfM{ G?rfL{ G?rfN{ G?reno G?renW G?renw G?renk G?ren[ G?rel{ G?ren{ G?rfvo G?rfuw G?rfvw G?rfvs G?rfu{ G?rfv{ G?rfno G?rfnw G?rfns G?rfnk G?rfn[ G?rfm{ G?rfn{ G?rf^o G?rf^w G?rf^s G?rf^[ G?rf]{ G?rf^{ G?re~w G?re}{ G?re~{ G?rd~o G?rd~w G?rd~s G?rd~k G?rd~[ G?rd}{ G?rd|{ G?rdz{ G?rd~{ G?rf~w G?rf~{ G?qvF_ G?qvFO G?qvBo G?qvFG G?qvBg G?qvEW G?qvDW G?qvBW G?qvAw G?qvFC G?qvEc G?qvES G?qvDS G?qvBS G?qvCs G?qvAs G?qv@s G?qvEK G?qvBK G?qvAk G?qv@k G?qvC[ G?qvA[ G?qv@[ G?qv?{ G?qvFo G?qvFg G?qvFW G?qvEw G?qvDw G?qvBw G?qvFc G?qvFS G?qvEs G?qvDs G?qvBs G?qvFK G?qvEk G?qvDk G?qvBk G?qvE[ G?qvD[ G?qvB[ G?qvC{ G?qvA{ G?qv@{ G?qvFw G?qvFs G?qvFk G?qvF[ G?qvE{ G?qvD{ G?qvB{ G?qvF{ G?qtf_ G?qtdo G?qtbo G?qtfG G?qtdg G?qteW G?qtdW G?qt`w G?qteK G?qtbK G?qt`k G?qtc[ G?qta[ G?qt`[ G?qtfo G?qtfg G?qtfW G?qtdw G?qtbw G?qtfK G?qtdk G?qtbk G?qte[ G?qtd[ G?qtb[ G?qt`{ G?qtfw G?qtfk G?qtf[ G?qtd{ G?qtb{ G?qtf{ G?qrf? G?qrd_ G?qrf_ G?qrfO G?qrdo G?qrfG G?qrdg G?qrbg G?qreW G?qrdW G?qrbW G?qr`w G?qreK G?qrdK G?qr`k G?qrc[ G?qr`[ G?qrfo G?qrfg G?qrfW G?qrdw G?qrbw G?qrfK G?qrdk G?qre[ G?qrd[ G?qr`{ G?qrfw G?qrfk G?qrf[ G?qrd{ G?qrf{ G?qvbo G?qvbg G?qvfo G?qvfg G?qvfW G?qvdw G?qvbw G?qvfc G?qvfS G?qvds G?qvbs G?qvdk G?qve[ G?qvd[ G?qvb[ G?qv`{ G?qvfw G?qvfs G?qvfk G?qvf[ G?qvd{ G?qvb{ G?qvf{ G?qvRg G?qvPw G?qvVo G?qvVg G?qvVW G?qvUw G?qvTw G?qvRw G?qvVS G?qvUs G?qvTs G?qvT[ G?qvS{ G?qvVw G?qvVs G?qvVk G?qvV[ G?qvU{ G?qvT{ G?qvR{ G?qvV{ G?qtrW G?qtpw G?qtvo G?qtvg G?qtvW G?qttw G?qtrw G?qtts G?qtvw G?qtvs G?qtvk G?qtv[ G?qtt{ G?qtr{ G?qtv{ G?qrvG G?qrtg G?qrrg G?qruW G?qrtW G?qrrW G?qrpw G?qrvo G?qrvg G?qrvW G?qrtw G?qrrw G?qrvS G?qrts G?qrrs G?qrtk G?qru[ G?qrt[ G?qrp{ G?qrvw G?qrvs G?qrvk G?qrv[ G?qrt{ G?qrr{ G?qrv{ G?qvNo G?qvNg G?qvNW G?qvMw G?qvLw G?qvJw G?qvNc G?qvNS G?qvMs G?qvLs G?qvJs G?qvNK G?qvMk G?qvNw G?qvNs G?qvNk G?qvN[ G?qvM{ G?qvL{ G?qvJ{ G?qvN{ G?qtno G?qtnW G?qtlw G?qtnK G?qtlk G?qtjk G?qtm[ G?qtnw G?qtnk G?qtn[ G?qtl{ G?qtj{ G?qtn{ G?qrno G?qrng G?qrnW G?qrlw G?qrjw G?qrnK G?qrm[ G?qrnw G?qrnk G?qrn[ G?qrl{ G?qrn{ G?qu^o G?qu^g G?qu^W G?qu\w G?quZw G?qu^S G?qu\s G?qu^K G?qu][ G?qu^w G?qu^s G?qu^k G?qu^[ G?qu\{ G?quZ{ G?qu^{ G?qvvo G?qvtw G?qvvw G?qvvs G?qvt{ G?qvv{ G?qvno G?qvnw G?qvns G?qvnk G?qvn[ G?qvl{ G?qvj{ G?qvn{ G?qv^o G?qv^w G?qv^s G?qv^[ G?qv]{ G?qv\{ G?qv^{ G?qt~w G?qt|{ G?qt~{ G?qr~o G?qr~w G?qr~s G?qr~[ G?qr|{ G?qrz{ G?qr~{ G?qv~w G?qv~{ G?rNF_ G?rNFO G?rNEo G?rNEW G?rNDW G?rNFC G?rNEc G?rNES G?rN@s G?rNC[ G?rN@[ G?rN?{ G?rNFo G?rNFW G?rNEw G?rNDw G?rNFc G?rNFS G?rNEs G?rNDs G?rNE[ G?rND[ G?rNC{ G?rN@{ G?rNFw G?rNFs G?rNF[ G?rNE{ G?rND{ G?rNF{ G?rLeW G?rLdW G?rL`w G?rLbc G?rLeS G?rLbS G?rL`s G?rLc[ G?rLa[ G?rL`[ G?rLfo G?rLfW G?rLdw G?rLbw G?rLfc G?rLfS G?rLds G?rLbs G?rLe[ G?rLd[ G?rLb[ G?rL`{ G?rLfw G?rLfs G?rLf[ G?rLd{ G?rLb{ G?rLf{ G?rNfO G?rNeW G?rNfo G?rNfW G?rNdw G?rNfc G?rNfS G?rNe[ G?rNfw G?rNfs G?rNf[ G?rNd{ G?rNf{ G?rNVg G?rNUw G?rNVS G?rNUs G?rNVK G?rNUk G?rNVw G?rNVs G?rNVk G?rNV[ G?rNU{ G?rNT{ G?rNV{ G?rM^W G?rM][ G?rM^w G?rM^[ G?rM\{ G?rM^{ G?rNvg G?rNvw G?rNvs G?rNvk G?rNv[ G?rNv{ G?rN^w G?rN^[ G?rN]{ G?rN^{ G?rL~w G?rL~s G?rL~[ G?rL|{ G?rLz{ G?rL~{ G?rN~w G?rN~{ G?qnBW G?qnFC G?qnBc G?qnDS G?qnBS G?qnAs G?qn@s G?qnC[ G?qnA[ G?qn?{ G?qnFo G?qnFW G?qnEw G?qnBw G?qnFc G?qnFS G?qnEs G?qnDs G?qnBs G?qnE[ G?qnB[ G?qnC{ G?qnA{ G?qnFw G?qnFs G?qnF[ G?qnE{ G?qnB{ G?qnF{ G?qmeW G?qmcw G?qmaw G?qmfC G?qmec G?qmdc G?qmbc G?qmbS G?qmas G?qm`s G?qmeK G?qmbK G?qmak G?qm`k G?qmc[ G?qma[ G?qm`[ G?qm_{ G?qmfo G?qmfg G?qmfW G?qmew G?qmdw G?qmbw G?qmfc G?qmfS G?qmes G?qmds G?qmbs G?qmfK G?qmek G?qmdk G?qmbk G?qme[ G?qmd[ G?qmb[ G?qmc{ G?qma{ G?qm`{ G?qmfw G?qmfs G?qmfk G?qmf[ G?qme{ G?qmd{ G?qmb{ G?qmf{ G?qjf? G?qjf_ G?qjbo G?qjeW G?qjbW G?qjcw G?qjaw G?qjbc G?qjeS G?qjdS G?qjbS G?qjcs G?qjas G?qj`s G?qjc[ G?qja[ G?qj_{ G?qjfo G?qjfW G?qjew G?qjbw G?qjfc G?qjfS G?qjes G?qjds G?qjbs G?qje[ G?qjb[ G?qjc{ G?qja{ G?qjfw G?qjfs G?qjf[ G?qje{ G?qjb{ G?qjf{ G?qnbo G?qnbW G?qnfo G?qnfW G?qnew G?qnbw G?qnfc G?qnfS G?qnbs G?qnb[ G?qnfw G?qnfs G?qnf[ G?qne{ G?qnb{ G?qnf{ G?qnVo G?qnVg G?qnVW G?qnUw G?qnTw G?qnRw G?qnVS G?qnVK G?qnRk G?qnVw G?qnVs G?qnVk G?qnV[ G?qnU{ G?qnT{ G?qnR{ G?qnV{ G?qmvg G?qmuw G?qmvc G?qmvS G?qmus G?qmts G?qmvK G?qmtk G?qmrk G?qmt[ G?qmvw G?qmvs G?qmvk G?qmv[ G?qmu{ G?qmt{ G?qmr{ G?qmv{ G?qlvg G?qlvW G?qlvc G?qlvS G?qlts G?qlvK G?qluk G?qltk G?qlrk G?qlvw G?qlvs G?qlvk G?qlv[ G?qlu{ G?qlt{ G?qlr{ G?qlv{ G?qjvg G?qjvW G?qjuw G?qjtw G?qjrw G?qjvS G?qjus G?qjts G?qjrs G?qjrk G?qjt[ G?qjvw G?qjvs G?qjvk G?qjv[ G?qju{ G?qjt{ G?qjr{ G?qjv{ G?qm^o G?qm^g G?qm^W G?qm]w G?qm^S G?qm]s G?qm\s G?qmZs G?qm][ G?qm\[ G?qm[{ G?qmX{ G?qm^w G?qm^s G?qm^[ G?qm]{ G?qm\{ G?qmZ{ G?qm^{ G?qj^W G?qj]w G?qjZw G?qj][ G?qj^w G?qj^[ G?qj]{ G?qj^{ G?qk~o G?qk~W G?qk}w G?qkzw G?qkz[ G?qk~w G?qk~[ G?qkz{ G?qk~{ G?qnvg G?qnvw G?qnvs G?qnvk G?qnv[ G?qnr{ G?qnv{ G?qn^w G?qn^[ G?qnZ{ G?qn^{ G?qm~w G?qm~s G?qm~k G?qm~[ G?qm}{ G?qm|{ G?qmz{ G?qm~{ G?qj~w G?qjz{ G?qj~{ G?qn~w G?qn~{ G?o~F? G?o~F_ G?o~FG G?o~Eg G?o~Dg G?o~EW G?o~FC G?o~Ec G?o~Dc G?o~ES G?o~DS G?o~Cs G?o~@s G?o~EK G?o~DK G?o~Ck G?o~@k G?o~C[ G?o~@[ G?o~?{ G?o~Fo G?o~Fg G?o~FW G?o~Ew G?o~Dw G?o~Fc G?o~FS G?o~Es G?o~Ds G?o~FK G?o~Ek G?o~Dk G?o~E[ G?o~D[ G?o~C{ G?o~@{ G?o~Fw G?o~Fs G?o~Fk G?o~F[ G?o~E{ G?o~D{ G?o~F{ G?o~f_ G?o~fO G?o~do G?o~eW G?o~dW G?o~fo G?o~fW G?o~fc G?o~fS G?o~ds G?o~e[ G?o~d[ G?o~fw G?o~fs G?o~f[ G?o~f{ G?o~Vg G?o~VW G?o~Tw G?o~VS G?o~Us G?o~Ts G?o~VK G?o~Uk G?o~Tk G?o~P{ G?o~Vw G?o~Vs G?o~Vk G?o~V[ G?o~U{ G?o~T{ G?o~V{ G?o|vo G?o|vg G?o|vW G?o|tw G?o|rw G?o|ts G?o|rs G?o|vK G?o|vw G?o|vs G?o|vk G?o|v[ G?o|t{ G?o|r{ G?o|v{ G?o}^g G?o}^W G?o}^S G?o}\s G?o}][ G?o}X{ G?o}^w G?o}^s G?o}^[ G?o}\{ G?o}^{ G?o|^o G?o|^W G?o|]w G?o|^S G?o|]s G?o|\s G?o|Zs G?o|\[ G?o|Y{ G?o|^w G?o|^s G?o|^[ G?o|]{ G?o|^{ G?o~vg G?o~vw G?o~vs G?o~vk G?o~v[ G?o~t{ G?o~v{ G?o~^w G?o~^[ G?o~]{ G?o~\{ G?o~^{ G?o~~w G?o~~{ G?rvfo G?rvfg G?rvfW G?rvfK G?rvdk G?rve[ G?rvd[ G?rv`{ G?rvfw G?rvfk G?rvf[ G?rvd{ G?rvf{ G?rvvo G?rvvg G?rvvw G?rvvs G?rvv{ G?rvnw G?rvnk G?rvn{ G?rv~{ G?rnfo G?rnfW G?rnew G?rnfc G?rnes G?rnfK G?rnek G?rndk G?rne[ G?rnd[ G?rnc{ G?rn`{ G?rnfw G?rnfs G?rnfk G?rnf[ G?rne{ G?rnd{ G?rnf{ G?rnVo G?rnVg G?rnVW G?rnVK G?rnUk G?rnTk G?rnU[ G?rnT[ G?rnVw G?rnVk G?rnV[ G?rnU{ G?rnT{ G?rnV{ G?rmvW G?rmuw G?rmvK G?rmtk G?rmu[ G?rmvw G?rmvk G?rmv[ G?rmt{ G?rmv{ G?rlvo G?rlvg G?rlvW G?rluw G?rltw G?rlrw G?rlvc G?rlvS G?rlus G?rlts G?rlrs G?rlvK G?rluk G?rltk G?rlrk G?rlu[ G?rlt[ G?rlr[ G?rls{ G?rlp{ G?rlvw G?rlvs G?rlvk G?rlv[ G?rlu{ G?rlt{ G?rlr{ G?rlv{ G?rnvg G?rnvw G?rnvs G?rnvk G?rnu{ G?rnv{ G?rnnw G?rnnk G?rnn{ G?rn^w G?rn^k G?rn^[ G?rn]{ G?rn^{ G?rm~k G?rm~{ G?rn~{ G?q~fo G?q~fg G?q~fW G?q~dw G?q~bw G?q~fc G?q~bs G?q~fK G?q~dk G?q~bk G?q~e[ G?q~d[ G?q~b[ G?q~`{ G?q~fw G?q~fs G?q~fk G?q~f[ G?q~d{ G?q~b{ G?q~f{ G?q|vo G?q|vg G?q|tw G?q|vK G?q|tk G?q|rk G?q|u[ G?q|t[ G?q|r[ G?q|p{ G?q|vw G?q|vk G?q|v[ G?q|t{ G?q|r{ G?q|v{ G?qzvo G?qztw G?qzrw G?qzvK G?qztk G?qzu[ G?qzt[ G?qzp{ G?qzvw G?qzvk G?qzv[ G?qzt{ G?qzv{ G?q~vg G?q~vw G?q~vs G?q~vk G?q~r{ G?q~v{ G?q~nw G?q~nk G?q~n{ G?q~^w G?q~^s G?q~^k G?q~^[ G?q~]{ G?q~^{ G?q|~w G?q|~k G?q|~[ G?q||{ G?q|z{ G?q|~{ G?qz~k G?qz~[ G?qz~{ G?q~~{ G?r~vw G?r~vk G?r~v[ G?r~t{ G?r~v{ G?r~~{ G?zfF_ G?zfFO G?zfEo G?zfEW G?zfFo G?zfFW G?zfEw G?zfFw G?zfF[ G?zfF{ G?zef_ G?zefO G?zeeo G?zeeW G?zedW G?zecw G?ze`w G?zeec G?zeeS G?zedS G?zecs G?ze`s G?zec[ G?ze`[ G?ze_{ G?zefo G?zefW G?zeew G?zedw G?zefc G?zefS G?zees G?zeds G?zee[ G?zed[ G?zec{ G?ze`{ G?zefw G?zefs G?zef[ G?zee{ G?zed{ G?zef{ G?zff_ G?zffO G?zfeW G?zffo G?zffW G?zfew G?zffc G?zffS G?zffw G?zffs G?zff[ G?zfe{ G?zff{ G?zfVG G?zfVg G?zfVW G?zfVS G?zfVw G?zfVs G?zfV[ G?zfU{ G?zfV{ G?zfvg G?zfvw G?zfvs G?zfvk G?zfv[ G?zfv{ G?zf^W G?zf]w G?zf^w G?zf^[ G?zf^{ G?ze}w G?ze~w G?ze~s G?ze~[ G?ze}{ G?ze|{ G?ze~{ G?zf~w G?zf~{ G?zTb_ G?zTf_ G?zTbo G?zTeW G?zTdW G?zTbW G?zT`w G?zTc[ G?zTa[ G?zTfo G?zTfW G?zTdw G?zTbw G?zTe[ G?zTb[ G?zTfw G?zTf[ G?zTb{ G?zTf{ G?zVf_ G?zVeo G?zVeW G?zV`w G?zVfo G?zVfW G?zVdw G?zVfc G?zVfS G?zVes G?zVe[ G?zVfw G?zVfs G?zVf[ G?zVd{ G?zVf{ G?zVVg G?zVUw G?zVVS G?zVUs G?zVTs G?zVVw G?zVVs G?zVV[ G?zVU{ G?zVT{ G?zVV{ G?zUvo G?zUvg G?zUvW G?zUuw G?zUtw G?zUus G?zUvK G?zUuk G?zUvw G?zUvs G?zUvk G?zUv[ G?zUu{ G?zUt{ G?zUv{ G?zTrg G?zTvo G?zTvg G?zTvW G?zTuw G?zTtw G?zTrw G?zTus G?zTrs G?zTvw G?zTvs G?zTv[ G?zTu{ G?zTr{ G?zTv{ G?zVvg G?zVvw G?zVvs G?zVvk G?zVv[ G?zVu{ G?zVv{ G?zV]w G?zV^w G?zV^[ G?zV]{ G?zV^{ G?zT~W G?zT|w G?zTzw G?zT~w G?zT~s G?zT~[ G?zT|{ G?zTz{ G?zT~{ G?zV~w G?zV~{ G?zvf_ G?zvfo G?zvfg G?zvfK G?zvek G?zve[ G?zvc{ G?zvfw G?zvfk G?zvf[ G?zve{ G?zvf{ G?zvvg G?zvvW G?zvuw G?zvvw G?zvvs G?zvvk G?zvv{ G?zvnk G?zvn{ G?zv~{ G?znfo G?znfW G?znew G?znfc G?znfS G?znes G?zne[ G?znc{ G?znfw G?znfs G?znf[ G?zne{ G?znf{ G?znvw G?znvs G?znvk G?znv{ G?zn^w G?zn^[ G?zn]{ G?zn^{ G?zm~w G?zm~s G?zm}{ G?zm~{ G?zn~{ G?z^fo G?z^dw G?z^fc G?z^fS G?z^es G?z^ds G?z^e[ G?z^d[ G?z^`{ G?z^fw G?z^fs G?z^f[ G?z^d{ G?z^f{ G?z^vw G?z^vs G?z^vk G?z^v{ G?z\~w G?z\~[ G?z\z{ G?z\~{ G?z^~{ G?z~vw G?z~vk G?z~v[ G?z~u{ G?z~v{ G?z~~{ G?~vf_ G?~vfo G?~vfW G?~ve[ G?~vfw G?~vf[ G?~vf{ G?~vvg G?~vvw G?~vvs G?~vv{ G?~v~w G?~v~{ G?~~~{ GCOcb? GCOcaO GCOce_ GCOceO GCOcbO GCOccc GCOcf_ GCOcfO GCOceo GCOcbW GCOcec GCOcfo GCOcfW GCOcfc GCOces GCOcfw GCOcfs GCOcf{ GCOfF? GCOfB_ GCOf?w GCOfF_ GCOfEo GCOfDo GCOfCw GCOfFC GCOfBc GCOfCs GCOf@s GCOf?{ GCOfFo GCOfEw GCOfFc GCOfEs GCOfDs GCOfC{ GCOfFw GCOfFs GCOfE{ GCOfF{ GCOef? GCOeeO GCOe`W GCOef_ GCOefO GCOeeo GCOedo GCOefG GCOedg GCOedW GCOebW GCOefC GCOeec GCOedc GCOeeS GCOecs GCOedK GCOebK GCOe`[ GCOefo GCOefg GCOefW GCOedw GCOefc GCOefS GCOees GCOeds GCOefK GCOedk GCOed[ GCOeb[ GCOefw GCOefs GCOefk GCOef[ GCOed{ GCOef{ GCOff_ GCOffO GCOfeo GCOfdo GCOfeW GCOfbW GCOfcw GCOffo GCOffW GCOfew GCOffc GCOffS GCOfes GCOfds GCOfe[ GCOfb[ GCOfc{ GCOffw GCOffs GCOff[ GCOfe{ GCOff{ GCOeuo GCOetg GCOevo GCOevg GCOeus GCOetk GCOevw GCOevs GCOevk GCOev{ GCOfvo GCOfvg GCOfuw GCOfvw GCOfvs GCOfvk GCOfu{ GCOfv{ GCOf~w GCOf~{ GCQ`f? GCQ`e_ GCQ`d_ GCQ`dG GCQ`f_ GCQ`fO GCQ`eo GCQ`fG GCQ`dg GCQ`fo GCQ`fg GCQ`fW GCQ`dk GCQ`fw GCQ`fk GCQ`f{ GCQfAW GCQfFO GCQfEo GCQfDo GCQfBo GCQfFG GCQfEg GCQfDg GCQfBg GCQfEW GCQfDW GCQfBW GCQfCw GCQfAw GCQf@w GCQfFC GCQfES GCQfDS GCQfBK GCQfAk GCQf@k GCQf@[ GCQf?{ GCQfFo GCQfFg GCQfFW GCQfEw GCQfDw GCQfBw GCQfFc GCQfFS GCQfEs GCQfDs GCQfBs GCQfFK GCQfEk GCQfDk GCQfBk GCQfE[ GCQfD[ GCQfB[ GCQfC{ GCQfA{ GCQf@{ GCQfFw GCQfFs GCQfFk GCQfF[ GCQfE{ GCQfD{ GCQfB{ GCQfF{ GCQeag GCQefO GCQeeo GCQebo GCQebg GCQeeW GCQebW GCQefC GCQeec GCQedc GCQeeS GCQedS GCQeeK GCQefo GCQefg GCQefW GCQeew GCQedw GCQebw GCQefc GCQefS GCQees GCQeds GCQefK GCQeek GCQefw GCQefs GCQefk GCQef[ GCQef{ GCQd`g GCQdbo GCQddg GCQdbg GCQdbW GCQdfC GCQddc GCQdeS GCQddK GCQdfo GCQdfg GCQdfW GCQdew GCQdbw GCQdfc GCQdfS GCQdes GCQdfK GCQddk GCQdfw GCQdfs GCQdfk GCQdf[ GCQdf{ GCQbf? GCQbd_ GCQbb_ GCQbeO GCQbdO GCQb`o GCQbeG GCQbcW GCQbf_ GCQbfO GCQbeo GCQbdo GCQbbo GCQbfG GCQbdg GCQbeW GCQbdW GCQbfC GCQbec GCQbdc GCQbbc GCQbeS GCQbdS GCQbcs GCQb`s GCQbeK GCQbdK GCQbc[ GCQbfo GCQbfg GCQbfW GCQbdw GCQbfc GCQbfS GCQbes GCQbds GCQbbs GCQbfK GCQbdk GCQbe[ GCQbd[ GCQbfw GCQbfs GCQbfk GCQbf[ GCQbd{ GCQbf{ GCQeRG GCQeQW GCQeV_ GCQeVO GCQeRo GCQeVG GCQeUg GCQeTg GCQeRg GCQeUW GCQeRW GCQeQw GCQeUS GCQeRK GCQeQk GCQePk GCQeQ[ GCQeVo GCQeVg GCQeVW GCQeUw GCQeRw GCQeVc GCQeVS GCQeUs GCQeRs GCQeVK GCQeUk GCQeTk GCQeRk GCQeU[ GCQeR[ GCQeQ{ GCQeVw GCQeVs GCQeVk GCQeV[ GCQeU{ GCQeR{ GCQeV{ GCQbV? GCQbU_ GCQbR_ GCQbUO GCQbUG GCQbRG GCQbQg GCQbQW GCQbV_ GCQbVO GCQbUo GCQbRo GCQbVG GCQbUg GCQbTg GCQbRg GCQbUW GCQbRW GCQbQw GCQbVC GCQbUc GCQbTc GCQbRc GCQbUS GCQbRS GCQbQs GCQbUK GCQbTK GCQbRK GCQbSk GCQbQk GCQbPk GCQbQ[ GCQbVo GCQbVg GCQbVW GCQbUw GCQbRw GCQbVc GCQbVS GCQbUs GCQbRs GCQbVK GCQbUk GCQbTk GCQbRk GCQbU[ GCQbR[ GCQbQ{ GCQbVw GCQbVs GCQbVk GCQbV[ GCQbU{ GCQbR{ GCQbV{ GCQeN_ GCQeNO GCQeMo GCQeLo GCQeJo GCQeNC GCQeMS GCQeLS GCQeMK GCQeK[ GCQeNo GCQeNg GCQeNW GCQeLw GCQeNc GCQeNS GCQeMs GCQeLs GCQeJs GCQeNK GCQeLk GCQeM[ GCQeL[ GCQeNw GCQeNs GCQeNk GCQeN[ GCQeL{ GCQeN{ GCQfbg GCQf`w GCQffo GCQffg GCQffW GCQfew GCQfdw GCQfbw GCQffc GCQfds GCQffK GCQfek GCQfdk GCQfbk GCQfe[ GCQfd[ GCQfc{ GCQf`{ GCQffw GCQffs GCQffk GCQff[ GCQfe{ GCQfd{ GCQfb{ GCQff{ GCQfRg GCQfRW GCQfQw GCQfVo GCQfVg GCQfVW GCQfUw GCQfRw GCQfVc GCQfVS GCQfUs GCQfVK GCQfUk GCQfTk GCQfRk GCQfU[ GCQfR[ GCQfQ{ GCQfVw GCQfVs GCQfVk GCQfV[ GCQfU{ GCQfR{ GCQfV{ GCQerg GCQerW GCQevo GCQevg GCQevW GCQeuw GCQerw GCQevc GCQeus GCQevK GCQeuk GCQetk GCQerk GCQeu[ GCQer[ GCQevw GCQevs GCQevk GCQev[ GCQeu{ GCQer{ GCQev{ GCQbv_ GCQbvO GCQbuo GCQbro GCQbvG GCQbtg GCQbuW GCQbvo GCQbvg GCQbvW GCQbvc GCQbvS GCQbus GCQbrs GCQbvK GCQbtk GCQbu[ GCQbvw GCQbvs GCQbvk GCQbv[ GCQbv{ GCQfNo GCQfNg GCQfNW GCQfMw GCQfLw GCQfJw GCQfNK GCQfMk GCQfLk GCQfL[ GCQfK{ GCQfNw GCQfNs GCQfNk GCQfN[ GCQfM{ GCQfL{ GCQfJ{ GCQfN{ GCQdno GCQdng GCQdnW GCQdlk GCQdnw GCQdns GCQdnk GCQdn[ GCQdn{ GCQe^o GCQe^g GCQe^W GCQe^K GCQe\k GCQe][ GCQe^w GCQe^s GCQe^k GCQe^[ GCQe^{ GCQfvo GCQfvg GCQfvW GCQfuw GCQfrw GCQfvw GCQfvs GCQfvk GCQfv[ GCQfu{ GCQfr{ GCQfv{ GCQfng GCQflw GCQfnw GCQfnk GCQfl{ GCQfn{ GCQf^g GCQf^W GCQf]w GCQf^w GCQf^k GCQf^[ GCQf]{ GCQf^{ GCQf~w GCQf~{ GCQR@O GCQRF? GCQRE_ GCQRD_ GCQREO GCQRDO GCQRBO GCQRCg GCQREC GCQRF_ GCQRFO GCQREo GCQRDo GCQREg GCQRDg GCQRFC GCQREc GCQRBS GCQRFo GCQRFg GCQREw GCQRDw GCQRFc GCQRFS GCQREk GCQRFw GCQRFs GCQRFk GCQRF{ GCQQV? GCQQU_ GCQQT_ GCQQSg GCQQV_ GCQQUo GCQQUg GCQQTg GCQQVo GCQQVg GCQQUw GCQQVw GCQQU{ GCQQV{ GCQUfO GCQUdo GCQUfG GCQUeW GCQUdW GCQUbW GCQUcw GCQUec GCQUbS GCQUcs GCQUa[ GCQUfo GCQUfg GCQUfW GCQUew GCQUdw GCQUfc GCQUfS GCQUes GCQUds GCQUfK GCQUek GCQUdk GCQUe[ GCQUd[ GCQUb[ GCQUc{ GCQUfw GCQUfs GCQUfk GCQUf[ GCQUe{ GCQUd{ GCQUf{ GCQTf_ GCQTeo GCQTbW GCQTec GCQTdc GCQTeS GCQTbS GCQTeK GCQTbK GCQTa[ GCQTfo GCQTfg GCQTfW GCQTew GCQTfc GCQTfS GCQTes GCQTfK GCQTek GCQTdk GCQTe[ GCQTb[ GCQTfw GCQTfs GCQTfk GCQTf[ GCQTe{ GCQTf{ GCQUQo GCQURo GCQUUg GCQUQw GCQUUc GCQUUS GCQUVo GCQUVg GCQUUw GCQURw GCQUVc GCQUVS GCQUUs GCQUVw GCQUVs GCQUU{ GCQUV{ GCQRV? GCQRU_ GCQRT_ GCQRUO GCQRSg GCQRV_ GCQRVO GCQRUo GCQRUg GCQRTg GCQRUS GCQRRS GCQRVo GCQRVg GCQRUw GCQRVc GCQRVS GCQRUs GCQRUk GCQRTk GCQRVw GCQRVs GCQRVk GCQRU{ GCQRV{ GCQSnO GCQSmg GCQSlg GCQSkk GCQSno GCQSng GCQSmw GCQSmk GCQSlk GCQSnw GCQSnk GCQSm{ GCQSn{ GCQVbW GCQVfo GCQVfg GCQVfW GCQVew GCQVdw GCQVfc GCQVfS GCQVes GCQVds GCQVfK GCQVek GCQVe[ GCQVd[ GCQVb[ GCQVc{ GCQVfw GCQVfs GCQVfk GCQVf[ GCQVe{ GCQVd{ GCQVf{ GCQVRo GCQVQw GCQVVo GCQVVg GCQVUw GCQVRw GCQVVS GCQVUs GCQVRs GCQVQ{ GCQVVw GCQVVs GCQVVk GCQVU{ GCQVR{ GCQVV{ GCQUvo GCQUvg GCQUvW GCQUuw GCQUus GCQUu[ GCQUvw GCQUvs GCQUvk GCQUv[ GCQUu{ GCQUv{ GCQUno GCQUng GCQUmw GCQUlw GCQUnS GCQUms GCQUmk GCQUk{ GCQUnw GCQUns GCQUnk GCQUm{ GCQUl{ GCQUn{ GCQTno GCQTng GCQTmw GCQTnc GCQTnS GCQTms GCQTmk GCQTlk GCQTnw GCQTns GCQTnk GCQTm{ GCQTn{ GCQVvo GCQVvW GCQVuw GCQVvw GCQVvs GCQVv[ GCQVu{ GCQVv{ GCQVno GCQVng GCQVmw GCQVlw GCQVnw GCQVns GCQVnk GCQVm{ GCQVl{ GCQVn{ GCQU}w GCQU~w GCQU}{ GCQU~{ GCQV~w GCQV~{ GCRbf_ GCRbfO GCRbeo GCRbdo GCRbfG GCRbdg GCRbcw GCRb`w GCRbeK GCRbdK GCRbck GCRbc[ GCRba[ GCRb`[ GCRbfo GCRbfg GCRbfW GCRbew GCRbdw GCRbbw GCRbfK GCRbek GCRbdk GCRbe[ GCRbd[ GCRbb[ GCRbc{ GCRbfw GCRbfk GCRbf[ GCRbe{ GCRbd{ GCRbf{ GCRcqo GCRcvO GCRcuo GCRcro GCRcug GCRcrg GCRcuW GCRcpw GCRcvC GCRcuS GCRctS GCRcss GCRcqs GCRcuK GCRctK GCRcrK GCRcsk GCRcqk GCRcpk GCRcs[ GCRcq[ GCRcp[ GCRco{ GCRcvo GCRcvg GCRcvW GCRcuw GCRctw GCRcrw GCRcvc GCRcvS GCRcus GCRcts GCRcrs GCRcvK GCRcuk GCRctk GCRcrk GCRcu[ GCRct[ GCRcr[ GCRcs{ GCRcq{ GCRcp{ GCRcvw GCRcvs GCRcvk GCRcv[ GCRcu{ GCRct{ GCRcr{ GCRcv{ GCR`v? GCR`u_ GCR`r_ GCR`v_ GCR`uo GCR`vG GCR`ug GCR`tg GCR`rg GCR`sw GCR`pw GCR`uK GCR`tK GCR`sk GCR`qk GCR`vo GCR`vg GCR`uw GCR`tw GCR`vK GCR`uk GCR`tk GCR`rk GCR`vw GCR`vk GCR`u{ GCR`v{ GCRffO GCRffG GCRfeg GCRffo GCRffg GCRffW GCRfew GCRfdw GCRfbw GCRffc GCRffS GCRfes GCRffw GCRffs GCRffk GCRff[ GCRfe{ GCRfd{ GCRfb{ GCRff{ GCRev_ GCRevG GCReug GCRepw GCRevo GCRevg GCReuw GCRetw GCReus GCRevw GCRevs GCRevk GCReu{ GCRet{ GCRev{ GCRdro GCRdrg GCRdqw GCRdpw GCRdvo GCRdvg GCRdvW GCRduw GCRdtw GCRdrw GCRdvc GCRdvS GCRdus GCRdts GCRdrs GCRdvK GCRduk GCRdu[ GCRdvw GCRdvs GCRdvk GCRdv[ GCRdu{ GCRdt{ GCRdr{ GCRdv{ GCRfNo GCRfNg GCRfMw GCRfNK GCRfMk GCRfK{ GCRfNw GCRfNk GCRfM{ GCRfL{ GCRfN{ GCReno GCReng GCRenc GCRenS GCRems GCRemk GCRem[ GCRek{ GCRei{ GCRenw GCRens GCRenk GCRen[ GCRem{ GCRel{ GCRej{ GCRen{ GCRdno GCRdng GCRdnW GCRdmw GCRdlw GCRdjw GCRdnc GCRdnS GCRdms GCRdls GCRdjs GCRdnK GCRdmk GCRdlk GCRdm[ GCRdl[ GCRdk{ GCRdi{ GCRdnw GCRdns GCRdnk GCRdn[ GCRdm{ GCRdl{ GCRdj{ GCRdn{ GCRbno GCRbng GCRbnW GCRbmw GCRblw GCRbjw GCRbnK GCRbmk GCRblk GCRbm[ GCRbk{ GCRbnw GCRbnk GCRbn[ GCRbm{ GCRbl{ GCRbn{ GCRc~o GCRc~g GCRc|w GCRc~c GCRc~S GCRc}s GCRc|s GCRczs GCRc{{ GCRcy{ GCRc~w GCRc~s GCRc~k GCRc~[ GCRc}{ GCRc|{ GCRcz{ GCRc~{ GCR`~o GCR`~g GCR`}w GCR`|w GCR`~K GCR`}k GCR`|k GCR`zk GCR`~w GCR`~k GCR`}{ GCR`~{ GCRfvo GCRfvw GCRfvs GCRfv{ GCRfno GCRfnw GCRfns GCRfnk GCRfn[ GCRfm{ GCRfn{ GCRe~o GCRe~w GCRe~s GCRe}{ GCRe~{ GCRd~o GCRd~w GCRd~s GCRd~k GCRd~[ GCRd}{ GCRd|{ GCRdz{ GCRd~{ GCRf~w GCRf~{ GCRVF_ GCRVFO GCRVEo GCRVFG GCRVEg GCRVDg GCRVBg GCRVEW GCRVDW GCRVFC GCRVEc GCRVES GCRVEK GCRVFo GCRVFg GCRVFW GCRVEw GCRVDw GCRVBw GCRVFc GCRVFS GCRVEs GCRVBs GCRVFK GCRVEk GCRVFw GCRVFs GCRVFk GCRVF[ GCRVF{ GCRUV_ GCRUUo GCRUTo GCRUUg GCRUUW GCRUTW GCRUSw GCRUQw GCRUUK GCRUVo GCRUVg GCRUVW GCRUUw GCRUTw GCRURw GCRUVK GCRUUk GCRUVw GCRUVk GCRUU{ GCRUV{ GCRSug GCRSuK GCRStK GCRSrK GCRSsk GCRSq[ GCRSvo GCRSvg GCRSvW GCRSuw GCRStw GCRSvK GCRSuk GCRStk GCRSu[ GCRSr[ GCRSvw GCRSvk GCRSv[ GCRSu{ GCRSv{ GCRVeg GCRVfo GCRVfg GCRVfW GCRVew GCRVdw GCRVfc GCRVdk GCRVfw GCRVfs GCRVfk GCRVf[ GCRVe{ GCRVd{ GCRVf{ GCRVVG GCRVUg GCRVUW GCRVVo GCRVVg GCRVVW GCRVUw GCRVTw GCRVRw GCRVVc GCRVVS GCRVUs GCRVTs GCRVRs GCRVVK GCRVUk GCRVTk GCRVRk GCRVT[ GCRVS{ GCRVP{ GCRVVw GCRVVs GCRVVk GCRVV[ GCRVU{ GCRVT{ GCRVR{ GCRVV{ GCRUug GCRUuW GCRUvo GCRUvg GCRUvW GCRUuw GCRUtw GCRUvc GCRUus GCRUts GCRUvK GCRUuk GCRUtk GCRUt[ GCRUs{ GCRUvw GCRUvs GCRUvk GCRUv[ GCRUu{ GCRUt{ GCRUv{ GCRTvo GCRTvg GCRTvW GCRTuw GCRTtw GCRTvc GCRTts GCRTvK GCRTuk GCRTtk GCRTvw GCRTvs GCRTvk GCRTv[ GCRTu{ GCRTt{ GCRTv{ GCRUno GCRUng GCRUmk GCRUm[ GCRUk{ GCRUnw GCRUnk GCRUn[ GCRUm{ GCRUl{ GCRUn{ GCRU^o GCRU^g GCRU^W GCRU]w GCRU\w GCRUZw GCRU^w GCRU^k GCRU]{ GCRU\{ GCRU^{ GCRS~o GCRS~g GCRS|w GCRS}[ GCRS~w GCRS~k GCRS~[ GCRS}{ GCRS~{ GCRVvo GCRVvg GCRVtw GCRVvw GCRVvs GCRVvk GCRVt{ GCRVv{ GCRVnw GCRVnk GCRVn{ GCRV^o GCRV^w GCRV^s GCRV^k GCRV^[ GCRV]{ GCRV\{ GCRVZ{ GCRV^{ GCRU~o GCRU~w GCRU~s GCRU~k GCRU}{ GCRU|{ GCRU~{ GCRT~w GCRT~k GCRT|{ GCRT~{ GCRV~w GCRV~{ GCQvFO GCQvBo GCQvFG GCQvDg GCQvEW GCQvDW GCQvBW GCQvCw GCQvAw GCQvFC GCQvEc GCQvBc GCQvES GCQvDS GCQvCs GCQvAs GCQv@s GCQvEK GCQvDK GCQvBK GCQvCk GCQvAk GCQv@k GCQvC[ GCQvA[ GCQv@[ GCQv?{ GCQvFo GCQvFg GCQvFW GCQvEw GCQvDw GCQvBw GCQvFc GCQvFS GCQvEs GCQvDs GCQvBs GCQvFK GCQvEk GCQvDk GCQvBk GCQvE[ GCQvD[ GCQvB[ GCQvC{ GCQvA{ GCQv@{ GCQvFw GCQvFs GCQvFk GCQvF[ GCQvE{ GCQvD{ GCQvB{ GCQvF{ GCQudo GCQucw GCQuec GCQudc GCQueS GCQucs GCQueK GCQubK GCQuck GCQuc[ GCQua[ GCQu`[ GCQufo GCQufg GCQufW GCQuew GCQudw GCQufc GCQufS GCQues GCQuds GCQufK GCQuek GCQudk GCQue[ GCQud[ GCQub[ GCQuc{ GCQufw GCQufs GCQufk GCQuf[ GCQue{ GCQud{ GCQuf{ GCQteo GCQtfG GCQtdg GCQteW GCQteK GCQtdK GCQtbK GCQtck GCQta[ GCQtfo GCQtfg GCQtfW GCQtew GCQtfK GCQtek GCQtdk GCQte[ GCQtb[ GCQtfw GCQtfk GCQtf[ GCQte{ GCQtf{ GCQuQo GCQuVO GCQuUo GCQuRo GCQuVG GCQuUg GCQuRg GCQuUW GCQuRW GCQuQw GCQuUS GCQuQs GCQuUK GCQuTK GCQuRK GCQuSk GCQuQk GCQuPk GCQuQ[ GCQuVo GCQuVg GCQuVW GCQuUw GCQuRw GCQuVc GCQuVS GCQuUs GCQuRs GCQuVK GCQuUk GCQuTk GCQuRk GCQuU[ GCQuR[ GCQuQ{ GCQuVw GCQuVs GCQuVk GCQuV[ GCQuU{ GCQuR{ GCQuV{ GCQrV? GCQrU_ GCQrV_ GCQrUo GCQrVG GCQrUg GCQrTg GCQrUW GCQrRW GCQrUK GCQrTK GCQrSk GCQrVo GCQrVg GCQrVW GCQrUw GCQrVK GCQrUk GCQrTk GCQrVw GCQrVk GCQrU{ GCQrV{ GCQvfo GCQvfg GCQvfW GCQvew GCQvdw GCQvfc GCQves GCQvds GCQvfw GCQvfs GCQvfk GCQvf[ GCQve{ GCQvd{ GCQvf{ GCQvRo GCQvRg GCQvRW GCQvQw GCQvVo GCQvVg GCQvVW GCQvUw GCQvRw GCQvVc GCQvVS GCQvUs GCQvRs GCQvUk GCQvTk GCQvVw GCQvVs GCQvVk GCQvV[ GCQvU{ GCQvR{ GCQvV{ GCQurW GCQuvo GCQuvg GCQuvW GCQuuw GCQuus GCQuvw GCQuvs GCQuvk GCQuv[ GCQuu{ GCQuv{ GCQuno GCQunc GCQums GCQuls GCQumk GCQum[ GCQuk{ GCQunw GCQuns GCQunk GCQun[ GCQum{ GCQul{ GCQun{ GCQtno GCQtng GCQtmw GCQtmk GCQtlk GCQtm[ GCQtnw GCQtnk GCQtn[ GCQtm{ GCQtn{ GCQu^o GCQu^g GCQu^W GCQu]w GCQuZw GCQu^c GCQu^S GCQu]s GCQuZs GCQu][ GCQuY{ GCQu^w GCQu^s GCQu^k GCQu^[ GCQu]{ GCQuZ{ GCQu^{ GCQr^o GCQr^g GCQr^W GCQr]w GCQr^K GCQr]k GCQr\k GCQr^w GCQr^k GCQr]{ GCQr^{ GCQvvo GCQvvw GCQvvs GCQvv{ GCQvno GCQvnw GCQvns GCQvnk GCQvm{ GCQvl{ GCQvn{ GCQv^o GCQv^w GCQv^s GCQv^k GCQv^[ GCQv]{ GCQvZ{ GCQv^{ GCQu~o GCQu~w GCQu~s GCQu}{ GCQu~{ GCQv~w GCQv~{ GCRvfo GCRvfg GCRvfK GCRvek GCRvdk GCRve[ GCRvd[ GCRvb[ GCRvc{ GCRvfw GCRvfk GCRvf[ GCRve{ GCRvd{ GCRvf{ GCRvVo GCRvVg GCRvVW GCRvUw GCRvTw GCRvRw GCRvVK GCRvUk GCRvTk GCRvRk GCRvS{ GCRvP{ GCRvVw GCRvVk GCRvU{ GCRvT{ GCRvV{ GCRuvo GCRuvg GCRuvW GCRuuw GCRutw GCRuvc GCRuus GCRuvK GCRuuk GCRutk GCRuu[ GCRut[ GCRur[ GCRus{ GCRuvw GCRuvs GCRuvk GCRuv[ GCRuu{ GCRut{ GCRuv{ GCRtvo GCRtvg GCRtuw GCRttw GCRtvK GCRtuk GCRttk GCRtu[ GCRtr[ GCRtvw GCRtvk GCRtv[ GCRtu{ GCRtv{ GCRvvo GCRvvg GCRvvW GCRvuw GCRvtw GCRvvw GCRvvs GCRvvk GCRvv{ GCRvnk GCRvn[ GCRvm{ GCRvl{ GCRvn{ GCRv^w GCRv]{ GCRv^{ GCRu~w GCRu~s GCRu}{ GCRu~{ GCRt~w GCRt~[ GCRt}{ GCRt~{ GCRv~{ GCR]vo GCR]uw GCR]vK GCR]uk GCR]tk GCR]vw GCR]vk GCR]u{ GCR]v{ GCR^vo GCR^uw GCR^vw GCR^vs GCR^v{ GCR]~w GCR]}{ GCR]~{ GCR^~{ GCR~vo GCR~vw GCR~vk GCR~u{ GCR~v{ GCR~~{ GCp`f? GCp`e_ GCp`eO GCp`dO GCp`f_ GCp`fO GCp`eo GCp`eg GCp`fo GCp`fg GCp`fW GCp`fw GCp`f{ GCpdbO GCpdao GCpd`o GCpdag GCpdf_ GCpdfO GCpdeo GCpddo GCpdbo GCpdfG GCpdeg GCpddg GCpdbg GCpdeW GCpddW GCpdbW GCpdcw GCpdfC GCpdeS GCpddS GCpdfo GCpdfg GCpdfW GCpdew GCpddw GCpdbw GCpdfc GCpdfS GCpdes GCpdds GCpdfK GCpdek GCpdfw GCpdfs GCpdfk GCpdf[ GCpdf{ GCpbd_ GCpbdO GCpb`o GCpbf_ GCpbfO GCpbeo GCpbdo GCpbbo GCpbfG GCpbeg GCpbdg GCpbbg GCpbaw GCpb`w GCpbbK GCpbfo GCpbfg GCpbfW GCpbew GCpbdw GCpbbw GCpbfc GCpbfS GCpbes GCpbbs GCpbfK GCpbbk GCpbfw GCpbfs GCpbfk GCpbb{ GCpbf{ GCpdVG GCpdUg GCpdRg GCpdRW GCpdPw GCpdVo GCpdVg GCpdVW GCpdUw GCpdTw GCpdRw GCpdVS GCpdUs GCpdRs GCpdU[ GCpdR[ GCpdS{ GCpdQ{ GCpdVw GCpdVs GCpdV[ GCpdU{ GCpdR{ GCpdV{ GCpbUo GCpbTg GCpbUW GCpbTW GCpbQw GCpbPw GCpbRc GCpbQs GCpbRK GCpbVo GCpbVg GCpbVW GCpbUw GCpbTw GCpbRw GCpbVc GCpbVS GCpbUs GCpbRs GCpbVK GCpbRk GCpbR[ GCpbVw GCpbVs GCpbVk GCpbV[ GCpbR{ GCpbV{ GCpffG GCpf`w GCpffo GCpffg GCpffW GCpfew GCpfdw GCpfbw GCpffc GCpffS GCpfes GCpfds GCpfbs GCpffK GCpfek GCpfbk GCpfe[ GCpfd[ GCpfb[ GCpfc{ GCpfa{ GCpf`{ GCpffw GCpffs GCpffk GCpff[ GCpfe{ GCpfd{ GCpfb{ GCpff{ GCpfVG GCpfVo GCpfVg GCpfVW GCpfUw GCpfTw GCpfRw GCpfVS GCpfUs GCpfU[ GCpfR[ GCpfQ{ GCpfVw GCpfVs GCpfVk GCpfV[ GCpfU{ GCpfT{ GCpfR{ GCpfV{ GCpev_ GCpevG GCpeug GCpevo GCpevg GCpevW GCpeuw GCpetw GCperw GCpeus GCpeu[ GCper[ GCpevw GCpevs GCpevk GCpev[ GCpeu{ GCpet{ GCper{ GCpev{ GCpdrg GCpdvo GCpdvg GCpdvW GCpduw GCpdtw GCpdrw GCpdvS GCpdus GCpdts GCpdrs GCpdvK GCpduk GCpdrk GCpdu[ GCpdt[ GCpdr[ GCpds{ GCpdq{ GCpdvw GCpdvs GCpdvk GCpdv[ GCpdu{ GCpdt{ GCpdr{ GCpdv{ GCpbvo GCpbvg GCpbvW GCpbvS GCpbus GCpbrs GCpbvK GCpbuk GCpbu[ GCpbvw GCpbvs GCpbvk GCpbv[ GCpbv{ GCpfKw GCpfNo GCpfNg GCpfNW GCpfMw GCpfLw GCpfJw GCpfNS GCpfMs GCpfNK GCpfMk GCpfM[ GCpfL[ GCpfJ[ GCpfK{ GCpfI{ GCpfNw GCpfNs GCpfNk GCpfN[ GCpfM{ GCpfL{ GCpfJ{ GCpfN{ GCpelW GCpeno GCpeng GCpenW GCpemw GCpelw GCpejw GCpenS GCpems GCpemk GCpem[ GCpel[ GCpej[ GCpei{ GCpenw GCpens GCpenk GCpen[ GCpem{ GCpel{ GCpej{ GCpen{ GCpdjo GCpdnG GCpdmg GCpdmW GCpdno GCpdng GCpdnW GCpdnc GCpdnS GCpdms GCpdls GCpdjs GCpdnK GCpdmk GCpdlk GCpdm[ GCpdnw GCpdns GCpdnk GCpdn[ GCpdn{ GCpe]W GCpe^o GCpe^g GCpe^W GCpe][ GCpe^w GCpe^s GCpe^k GCpe^[ GCpe^{ GCpfvo GCpfvW GCpfuw GCpfrw GCpfvw GCpfvs GCpfv[ GCpfu{ GCpfr{ GCpfv{ GCpfno GCpfng GCpfnW GCpfmw GCpflw GCpfjw GCpfnw GCpfns GCpfnk GCpfn[ GCpfm{ GCpfl{ GCpfj{ GCpfn{ GCpf^W GCpf]w GCpf^w GCpf^[ GCpf]{ GCpf^{ GCpf~w GCpf~{ GCpVbo GCpVfo GCpVfW GCpVew GCpVdw GCpVfc GCpVfS GCpVes GCpVds GCpVbs GCpVe[ GCpVd[ GCpVc{ GCpVfw GCpVfs GCpVf[ GCpVe{ GCpVd{ GCpVf{ GCpVV_ GCpVUg GCpVVo GCpVVg GCpVUw GCpVTw GCpVVS GCpVUs GCpVTs GCpVUk GCpVTk GCpVS{ GCpVP{ GCpVVw GCpVVs GCpVVk GCpVU{ GCpVT{ GCpVV{ GCpUuo GCpUvG GCpUvo GCpUvg GCpUvW GCpUuw GCpUus GCpUuk GCpUu[ GCpUvw GCpUvs GCpUvk GCpUv[ GCpUu{ GCpUv{ GCpVvo GCpVvg GCpVvW GCpVuw GCpVvw GCpVvs GCpVvk GCpVv[ GCpVu{ GCpVv{ GCpU}w GCpU~w GCpU}{ GCpU~{ GCpV~w GCpV~{ GCrb`o GCrbf_ GCrbfO GCrbeo GCrbdo GCrbbo GCrbfG GCrbeg GCrbdg GCrbdW GCrbcw GCrbfC GCrbec GCrbdc GCrbeS GCrbdS GCrbeK GCrbfo GCrbfg GCrbfW GCrbew GCrbdw GCrbbw GCrbfc GCrbfS GCrbes GCrbds GCrbfK GCrbek GCrbfw GCrbfs GCrbfk GCrbf[ GCrbf{ GCrbV_ GCrbVG GCrbUW GCrbTW GCrbRW GCrbSw GCrbPw GCrbVC GCrbUc GCrbTc GCrbUS GCrbTS GCrbUK GCrbTK GCrbSk GCrbPk GCrbS[ GCrbQ[ GCrbP[ GCrbO{ GCrbVo GCrbVg GCrbVW GCrbUw GCrbTw GCrbRw GCrbVc GCrbVS GCrbUs GCrbTs GCrbRs GCrbVK GCrbUk GCrbTk GCrbRk GCrbU[ GCrbT[ GCrbR[ GCrbS{ GCrbQ{ GCrbP{ GCrbVw GCrbVs GCrbVk GCrbV[ GCrbU{ GCrbT{ GCrbR{ GCrbV{ GCrffo GCrffg GCrffW GCrfew GCrfdw GCrfbw GCrffS GCrfds GCrffK GCrffw GCrffs GCrffk GCrff[ GCrff{ GCrfVo GCrfVg GCrfVW GCrfUw GCrfTw GCrfRw GCrfVS GCrfT[ GCrfR[ GCrfQ{ GCrfVw GCrfVs GCrfVk GCrfV[ GCrfU{ GCrfT{ GCrfR{ GCrfV{ GCreuw GCrerw GCrevc GCrevS GCrets GCrevK GCretk GCreu[ GCret[ GCrer[ GCrevw GCrevs GCrevk GCrev[ GCreu{ GCret{ GCrer{ GCrev{ GCrdvo GCrdvg GCrdvW GCrduw GCrdtw GCrdrw GCrdvS GCrdts GCrdrs GCrdrk GCrdt[ GCrdr[ GCrdq{ GCrdvw GCrdvs GCrdvk GCrdv[ GCrdu{ GCrdt{ GCrdr{ GCrdv{ GCrbv_ GCrbuW GCrbvo GCrbvg GCrbvW GCrbvS GCrbus GCrbrs GCrbu[ GCrbvw GCrbvs GCrbvk GCrbv[ GCrbv{ GCre^S GCre\s GCre][ GCre^w GCre^s GCre^k GCre^[ GCre^{ GCrfrw GCrfvw GCrfvs GCrfv[ GCrfu{ GCrfr{ GCrfv{ GCrfnw GCrfns GCrfnk GCrfn[ GCrfm{ GCrfl{ GCrfj{ GCrfn{ GCrf^w GCrf^[ GCrf]{ GCrf^{ GCrf~w GCrf~{ GCrRV_ GCrRUo GCrRRo GCrRVG GCrRUg GCrRUW GCrRVC GCrRUc GCrRTc GCrRRc GCrRUS GCrRSs GCrRQs GCrRUK GCrRTK GCrRSk GCrRVo GCrRVg GCrRVW GCrRUw GCrRVc GCrRVS GCrRUs GCrRTs GCrRRs GCrRVK GCrRUk GCrRTk GCrRU[ GCrRVw GCrRVs GCrRVk GCrRV[ GCrRU{ GCrRV{ GCrQv_ GCrQuo GCrQug GCrQuW GCrQvo GCrQvg GCrQvW GCrQuw GCrQvw GCrQu{ GCrQv{ GCrUqw GCrUuw GCrUrw GCrUvw GCrUvs GCrUvk GCrUu{ GCrUv{ GCrRv_ GCrRug GCrRuW GCrRvo GCrRvg GCrRvW GCrRuw GCrRus GCrRrs GCrRvw GCrRvs GCrRvk GCrRv[ GCrRu{ GCrRv{ GCrVNg GCrVNW GCrVMw GCrVLw GCrVJw GCrVMs GCrVJs GCrVM[ GCrVL[ GCrVNw GCrVNs GCrVNk GCrVN[ GCrVM{ GCrVL{ GCrVJ{ GCrVN{ GCrU^o GCrU^W GCrU]w GCrUZs GCrU][ GCrU^w GCrU^s GCrU^k GCrU^[ GCrU]{ GCrU^{ GCrVrw GCrVvw GCrVvs GCrVu{ GCrVr{ GCrVv{ GCrVnw GCrVns GCrVnk GCrVn[ GCrVm{ GCrVl{ GCrVj{ GCrVn{ GCrV^w GCrV^s GCrV^[ GCrV]{ GCrV^{ GCrU~w GCrU}{ GCrU~{ GCrV~w GCrV~{ GCpvbo GCpveg GCpveW GCpvdW GCpvaw GCpvfo GCpvfg GCpvfW GCpvew GCpvdw GCpvbw GCpvfc GCpvfS GCpves GCpvds GCpvbs GCpve[ GCpvc{ GCpvfw GCpvfs GCpvfk GCpvf[ GCpve{ GCpvd{ GCpvb{ GCpvf{ GCpvV_ GCpvVG GCpvUg GCpvTW GCpvSw GCpvPw GCpvVo GCpvVg GCpvVW GCpvUw GCpvTw GCpvRw GCpvVS GCpvUs GCpvTs GCpvVw GCpvVs GCpvVk GCpvV[ GCpvU{ GCpvT{ GCpvR{ GCpvV{ GCpuuo GCpuvG GCputW GCpuvo GCpuvg GCpuvW GCpuuw GCpuus GCpuvw GCpuvs GCpuvk GCpuv[ GCpuu{ GCpuv{ GCpuno GCpunW GCpumw GCpulw GCpujw GCpunc GCpunS GCpums GCpuls GCpujs GCpumk GCpui{ GCpunw GCpuns GCpunk GCpun[ GCpum{ GCpul{ GCpuj{ GCpun{ GCprno GCprng GCprnW GCprmw GCprmk GCprjk GCprnw GCprnk GCprn[ GCprm{ GCprn{ GCpvvo GCpvvw GCpvvs GCpvv{ GCpvno GCpvnw GCpvns GCpvnk GCpvn[ GCpvm{ GCpvl{ GCpvj{ GCpvn{ GCpv^o GCpv^w GCpv^s GCpv^[ GCpv]{ GCpv\{ GCpv^{ GCpu~o GCpu~w GCpu~s GCpu}{ GCpu~{ GCpv~w GCpv~{ GCrJf_ GCrJfO GCrJeo GCrJdo GCrJeW GCrJdW GCrJcw GCrJaw GCrJfC GCrJec GCrJdc GCrJeS GCrJdS GCrJcs GCrJ`s GCrJc[ GCrJa[ GCrJ`[ GCrJ_{ GCrJfo GCrJfW GCrJew GCrJdw GCrJbw GCrJfc GCrJfS GCrJes GCrJds GCrJbs GCrJe[ GCrJd[ GCrJb[ GCrJc{ GCrJa{ GCrJ`{ GCrJfw GCrJfs GCrJf[ GCrJe{ GCrJd{ GCrJb{ GCrJf{ GCrJvg GCrJvW GCrJuw GCrJtw GCrJus GCrJrs GCrJvK GCrJtk GCrJrk GCrJq{ GCrJvw GCrJvs GCrJvk GCrJv[ GCrJu{ GCrJt{ GCrJr{ GCrJv{ GCrMZw GCrM][ GCrM\[ GCrMY{ GCrMX{ GCrM^w GCrM^[ GCrM]{ GCrM\{ GCrMZ{ GCrM^{ GCrL^o GCrL\w GCrLZw GCrL\[ GCrL^w GCrL^s GCrL^[ GCrL\{ GCrL^{ GCrK~o GCrK}[ GCrK~w GCrK~[ GCrK}{ GCrK~{ GCrI~o GCrI}w GCrI}s GCrI|[ GCrI{{ GCrIy{ GCrIx{ GCrI~w GCrI~s GCrI~[ GCrI}{ GCrI|{ GCrIz{ GCrI~{ GCrH~o GCrH~W GCrH}w GCrH|w GCrHzw GCrH}s GCrHzs GCrH{{ GCrHx{ GCrH~w GCrH~s GCrH~[ GCrH}{ GCrH|{ GCrHz{ GCrH~{ GCrNvw GCrNvs GCrNvk GCrNv[ GCrNu{ GCrNt{ GCrNr{ GCrNv{ GCrN^w GCrN^[ GCrN^{ GCrM~w GCrM~[ GCrM}{ GCrM|{ GCrMz{ GCrM~{ GCrL~w GCrL~[ GCrL|{ GCrL~{ GCrJ~w GCrJ~[ GCrJ|{ GCrJz{ GCrJ~{ GCrN~w GCrN~{ GCqjeo GCqjfC GCqjec GCqjbc GCqjeS GCqjdS GCqjc[ GCqjfo GCqjfW GCqjfc GCqjfS GCqjes GCqje[ GCqjfw GCqjfs GCqjf[ GCqjf{ GCqnfo GCqnfW GCqnew GCqnbw GCqnfc GCqnfS GCqnbs GCqnb[ GCqna{ GCqnfw GCqnfs GCqnf[ GCqne{ GCqnb{ GCqnf{ GCqnvw GCqnvs GCqnvk GCqnv[ GCqnu{ GCqnv{ GCqn^w GCqn^[ GCqn]{ GCqn^{ GCqn~w GCqn~{ GCrvfo GCrvdw GCrvbw GCrvbk GCrve[ GCrvd[ GCrvb[ GCrvc{ GCrva{ GCrv`{ GCrvfw GCrvfk GCrvf[ GCrve{ GCrvd{ GCrvb{ GCrvf{ GCrvVo GCrvUw GCrvTs GCrvRs GCrvVK GCrvUk GCrvRk GCrvU[ GCrvT[ GCrvR[ GCrvS{ GCrvQ{ GCrvP{ GCrvVw GCrvVs GCrvVk GCrvV[ GCrvU{ GCrvT{ GCrvR{ GCrvV{ GCruvo GCruvg GCruvW GCruuw GCrutw GCrurw GCruts GCrurs GCruvK GCruuk GCrurk GCruu[ GCrur[ GCrus{ GCruq{ GCrup{ GCruvw GCruvs GCruvk GCruv[ GCruu{ GCrut{ GCrur{ GCruv{ GCrtvg GCrtrw GCrtts GCrtrs GCrtvK GCrtuk GCrtrk GCrtu[ GCrtt[ GCrtr[ GCrts{ GCrtq{ GCrtp{ GCrtvw GCrtvs GCrtvk GCrtv[ GCrtu{ GCrtt{ GCrtr{ GCrtv{ GCrrvo GCrrvg GCrrvK GCrruk GCrrtk GCrrrk GCrru[ GCrrt[ GCrrvw GCrrvk GCrrv[ GCrru{ GCrrv{ GCrvrw GCrvvw GCrvvs GCrvr{ GCrvv{ GCrvnw GCrvnk GCrvm{ GCrvj{ GCrvn{ GCru~w GCru~s GCru}{ GCru~{ GCrr}{ GCrr~{ GCrv~{ GCrnVW GCrnRw GCrnUk GCrnRk GCrnU[ GCrnT[ GCrnQ{ GCrnP{ GCrnVw GCrnVk GCrnV[ GCrnU{ GCrnT{ GCrnR{ GCrnV{ GCrmvW GCrmuw GCrmtw GCrmvS GCrmus GCrmrs GCrmvK GCrmuk GCrmrk GCrmt[ GCrms{ GCrmvw GCrmvs GCrmvk GCrmv[ GCrmu{ GCrmt{ GCrmr{ GCrmv{ GCrlvg GCrlvW GCrluw GCrltw GCrlrk GCrlvw GCrlvk GCrlv[ GCrlu{ GCrlv{ GCrnvw GCrnvs GCrnvk GCrnu{ GCrnt{ GCrnr{ GCrnv{ GCrnnw GCrnnk GCrnm{ GCrnn{ GCrn^w GCrn^k GCrn^[ GCrn]{ GCrn\{ GCrnZ{ GCrn^{ GCrm~w GCrm}{ GCrm~{ GCrl~k GCrl}{ GCrl~{ GCrj~w GCrj~k GCrj}{ GCrjz{ GCrj~{ GCrn~{ GCr]uw GCr]vK GCr]uk GCr]rk GCr]vw GCr]vk GCr]v[ GCr]u{ GCr]v{ GCr^vw GCr^vs GCr^v{ GCr]~w GCr]}{ GCr]~{ GCr^~{ GCr~vw GCr~vk GCr~v[ GCr~u{ GCr~v{ GCr~~{ GCXed_ GCXeco GCXecW GCXe`W GCXef_ GCXefO GCXeeo GCXedo GCXeeW GCXedW GCXecw GCXeec GCXedc GCXedS GCXecs GCXec[ GCXe`[ GCXefo GCXefW GCXeew GCXedw GCXefc GCXefS GCXees GCXeds GCXee[ GCXed[ GCXeb[ GCXec{ GCXefw GCXefs GCXef[ GCXee{ GCXed{ GCXef{ GCXff_ GCXfcw GCXffo GCXffW GCXfew GCXffc GCXffS GCXfes GCXfe[ GCXfb[ GCXfc{ GCXffw GCXffs GCXff[ GCXfe{ GCXff{ GCXfV_ GCXfVG GCXfRg GCXfVo GCXfVg GCXfVW GCXfUw GCXfRw GCXfVS GCXfRs GCXfVK GCXfRk GCXfU[ GCXfR[ GCXfQ{ GCXfVw GCXfVs GCXfVk GCXfV[ GCXfU{ GCXfR{ GCXfV{ GCXetg GCXevo GCXevg GCXevW GCXevS GCXeus GCXets GCXevK GCXetk GCXer[ GCXevw GCXevs GCXevk GCXev[ GCXev{ GCXb^_ GCXbZW GCXb^o GCXb^W GCXbZ[ GCXb^w GCXb^s GCXb^[ GCXb^{ GCXfvo GCXfvg GCXfvW GCXfuw GCXfvw GCXfvs GCXfvk GCXfv[ GCXfu{ GCXfv{ GCXf^W GCXfZw GCXf^w GCXf^[ GCXfZ{ GCXf^{ GCXf~w GCXf~{ GCZbf_ GCZbfO GCZbeo GCZbfG GCZbeg GCZbaw GCZbeK GCZbbK GCZbck GCZbc[ GCZba[ GCZbfo GCZbfg GCZbfW GCZbew GCZbbw GCZbfK GCZbek GCZbe[ GCZbb[ GCZbc{ GCZbfw GCZbfk GCZbf[ GCZbe{ GCZbf{ GCZff_ GCZfbo GCZffG GCZfbg GCZffo GCZffg GCZffW GCZfew GCZfbw GCZffc GCZffS GCZffw GCZffs GCZffk GCZff[ GCZfe{ GCZfb{ GCZff{ GCZfV_ GCZfVG GCZfUW GCZfSw GCZfVo GCZfVg GCZfVW GCZfUw GCZfRw GCZfVS GCZfVw GCZfVs GCZfVk GCZfV[ GCZfU{ GCZfR{ GCZfV{ GCZevG GCZeuW GCZepw GCZevo GCZevg GCZevW GCZeuw GCZetw GCZerw GCZevc GCZevS GCZeus GCZets GCZers GCZevK GCZer[ GCZevw GCZevs GCZevk GCZev[ GCZeu{ GCZet{ GCZer{ GCZev{ GCZbvG GCZbrg GCZbsw GCZbvo GCZbvg GCZbvW GCZbuw GCZbrw GCZbvc GCZbvS GCZbrs GCZbvK GCZbvw GCZbvs GCZbvk GCZbv[ GCZbu{ GCZbr{ GCZbv{ GCZfNo GCZfNg GCZfNW GCZfNK GCZfMk GCZfJk GCZfNw GCZfNk GCZfN[ GCZfM{ GCZfJ{ GCZfN{ GCZeno GCZeng GCZenW GCZemw GCZelw GCZejw GCZenc GCZenS GCZems GCZels GCZejs GCZemk GCZelk GCZenw GCZens GCZenk GCZen[ GCZem{ GCZel{ GCZej{ GCZen{ GCZbno GCZbng GCZbnW GCZbmw GCZbjw GCZbmk GCZbnw GCZbnk GCZbn[ GCZbm{ GCZbn{ GCZfvo GCZfvw GCZfvs GCZfv{ GCZfno GCZfnw GCZfns GCZfnk GCZfn[ GCZfn{ GCZf^o GCZf^w GCZf^s GCZf^[ GCZf^{ GCZe~o GCZe~w GCZe~s GCZe~k GCZe~[ GCZe}{ GCZe|{ GCZez{ GCZe~{ GCZb~o GCZb~w GCZb~s GCZb~k GCZb~[ GCZbz{ GCZb~{ GCZf~w GCZf~{ GCZVF_ GCZVFO GCZVEo GCZVEg GCZVDg GCZVEW GCZVDW GCZVBW GCZVFC GCZVEc GCZVES GCZVEK GCZVFo GCZVFg GCZVFW GCZVEw GCZVDw GCZVBw GCZVFc GCZVFS GCZVEs GCZVBs GCZVFK GCZVEk GCZVFw GCZVFs GCZVFk GCZVF[ GCZVF{ GCZTf_ GCZTfO GCZTeo GCZTeg GCZTdg GCZTeW GCZTdW GCZTcw GCZTeK GCZTbK GCZTck GCZTa[ GCZTfo GCZTfg GCZTfW GCZTew GCZTdw GCZTfK GCZTek GCZTe[ GCZTb[ GCZTc{ GCZTfw GCZTfk GCZTf[ GCZTe{ GCZTf{ GCZVf_ GCZVfO GCZVeo GCZVfG GCZVeg GCZVeW GCZVfo GCZVfg GCZVfW GCZVew GCZVdw GCZVfc GCZVes GCZVfw GCZVfs GCZVfk GCZVf[ GCZVe{ GCZVd{ GCZVf{ GCZVVG GCZVUW GCZVSw GCZVPw GCZVVo GCZVVg GCZVVW GCZVUw GCZVTw GCZVRw GCZVVc GCZVVS GCZVUs GCZVTs GCZVRs GCZVVw GCZVVs GCZVVk GCZVV[ GCZVU{ GCZVT{ GCZVR{ GCZVV{ GCZUuo GCZUvG GCZUug GCZUuW GCZUtW GCZUvo GCZUvg GCZUvW GCZUuw GCZUtw GCZUus GCZUvw GCZUvs GCZUvk GCZUv[ GCZUu{ GCZUt{ GCZUv{ GCZTuW GCZTrW GCZTvo GCZTvg GCZTvW GCZTuw GCZTtw GCZTvc GCZTus GCZTts GCZTvw GCZTvs GCZTvk GCZTv[ GCZTu{ GCZTt{ GCZTv{ GCZUno GCZUng GCZUnW GCZUmw GCZUlw GCZUmk GCZUlk GCZUnw GCZUnk GCZUn[ GCZUm{ GCZUl{ GCZUn{ GCZTno GCZTng GCZTnW GCZTmw GCZTlw GCZTnw GCZTnk GCZTn[ GCZTm{ GCZTn{ GCZVvo GCZVvw GCZVvs GCZVv{ GCZVno GCZVnw GCZVns GCZVnk GCZVm{ GCZVn{ GCZV^o GCZV^w GCZV^s GCZV^k GCZV^[ GCZV]{ GCZV\{ GCZVZ{ GCZV^{ GCZU~o GCZU~w GCZU~s GCZU}{ GCZU~{ GCZT~o GCZT~w GCZT~s GCZT~k GCZT}{ GCZT|{ GCZT~{ GCZV~w GCZV~{ GCZLf_ GCZLfO GCZLeo GCZLdo GCZLeW GCZLdW GCZLcw GCZLaw GCZLeS GCZLdS GCZLbS GCZLcs GCZLas GCZLc[ GCZLfo GCZLfg GCZLfW GCZLew GCZLdw GCZLbw GCZLfS GCZLes GCZLds GCZLbs GCZLe[ GCZLd[ GCZLfw GCZLfs GCZLf[ GCZLe{ GCZLf{ GCZJd_ GCZJf_ GCZJfO GCZJeo GCZJdo GCZJfG GCZJeg GCZJdg GCZJbg GCZJaw GCZJdc GCZJdS GCZJcs GCZJas GCZJ`s GCZJeK GCZJdK GCZJbK GCZJck GCZJak GCZJ`k GCZJc[ GCZJa[ GCZJ`[ GCZJ_{ GCZJfo GCZJfg GCZJfW GCZJew GCZJdw GCZJbw GCZJfc GCZJfS GCZJes GCZJds GCZJbs GCZJfK GCZJek GCZJdk GCZJbk GCZJe[ GCZJd[ GCZJb[ GCZJc{ GCZJa{ GCZJ`{ GCZJfw GCZJfs GCZJfk GCZJf[ GCZJe{ GCZJd{ GCZJb{ GCZJf{ GCZNfO GCZNfG GCZNeW GCZN`w GCZNfo GCZNfg GCZNfW GCZNew GCZNdw GCZNbw GCZNfc GCZNfS GCZNfK GCZNe[ GCZNfw GCZNfs GCZNfk GCZNf[ GCZNe{ GCZNd{ GCZNb{ GCZNf{ GCZMvg GCZMvW GCZMuw GCZMrw GCZMvc GCZMus GCZMvK GCZMuk GCZMq{ GCZMvw GCZMvs GCZMvk GCZMv[ GCZMu{ GCZMt{ GCZMr{ GCZMv{ GCZLvg GCZLvW GCZLuw GCZLtw GCZLrw GCZLus GCZLrs GCZLu[ GCZLvw GCZLvs GCZLv[ GCZLu{ GCZLv{ GCZJvo GCZJvg GCZJvW GCZJuw GCZJtw GCZJrw GCZJvc GCZJus GCZJrs GCZJvK GCZJuk GCZJtk GCZJrk GCZJq{ GCZJvw GCZJvs GCZJvk GCZJv[ GCZJu{ GCZJt{ GCZJr{ GCZJv{ GCZNNo GCZNNg GCZNNW GCZNMw GCZNLw GCZNJw GCZNNK GCZNMk GCZNLk GCZNNw GCZNNs GCZNNk GCZNN[ GCZNM{ GCZNL{ GCZNJ{ GCZNN{ GCZMno GCZMng GCZMnW GCZMmw GCZMlw GCZMjw GCZMnc GCZMnS GCZMmk GCZMnw GCZMns GCZMnk GCZMn[ GCZMm{ GCZMl{ GCZMj{ GCZMn{ GCZLno GCZLng GCZLnW GCZLmw GCZLlw GCZLjw GCZLnc GCZLnS GCZLms GCZLmk GCZLlk GCZLnw GCZLns GCZLnk GCZLn[ GCZLm{ GCZLl{ GCZLj{ GCZLn{ GCZJno GCZJng GCZJnW GCZJmw GCZJlw GCZJjw GCZJnc GCZJnS GCZJms GCZJls GCZJnK GCZJmk GCZJlk GCZJjk GCZJnw GCZJns GCZJnk GCZJn[ GCZJm{ GCZJl{ GCZJj{ GCZJn{ GCZM]w GCZMZw GCZM^K GCZM]k GCZM\k GCZMZk GCZM][ GCZM\[ GCZM[{ GCZMY{ GCZMX{ GCZM^w GCZM^k GCZM^[ GCZM]{ GCZM\{ GCZMZ{ GCZM^{ GCZL^g GCZL]w GCZL\w GCZLZw GCZL^K GCZL]k GCZLZk GCZL[{ GCZL^w GCZL^k GCZL^[ GCZL]{ GCZLZ{ GCZL^{ GCZK~g GCZK}w GCZK|w GCZK~c GCZK}s GCZKzs GCZK~K GCZK}k GCZK|k GCZKzk GCZK{{ GCZK~w GCZK~s GCZK~k GCZK~[ GCZK}{ GCZK|{ GCZKz{ GCZK~{ GCZI~o GCZI~g GCZI~K GCZI}k GCZI|k GCZI|[ GCZI{{ GCZI~w GCZI~k GCZI~[ GCZI}{ GCZI|{ GCZI~{ GCZH~o GCZH~g GCZH~W GCZH|w GCZH~K GCZH}k GCZH{{ GCZH~w GCZH~k GCZH~[ GCZH}{ GCZH~{ GCZNvg GCZNvw GCZNvs GCZNvk GCZNv[ GCZNv{ GCZNnw GCZNnk GCZNn[ GCZNn{ GCZN^w GCZN^[ GCZN^{ GCZM~w GCZM~s GCZM~k GCZM~[ GCZM}{ GCZM~{ GCZL~w GCZL~s GCZL~k GCZL~[ GCZL}{ GCZL|{ GCZL~{ GCZJ~w GCZJ~s GCZJ~k GCZJ~[ GCZJ}{ GCZJ|{ GCZJz{ GCZJ~{ GCZN~w GCZN~{ GCXnF_ GCXnBo GCXnBW GCXnFC GCXnBc GCXnBS GCXnCs GCXnC[ GCXnA[ GCXn?{ GCXnFo GCXnFW GCXnEw GCXnBw GCXnFc GCXnFS GCXnEs GCXnBs GCXnE[ GCXnB[ GCXnC{ GCXnA{ GCXnFw GCXnFs GCXnF[ GCXnE{ GCXnB{ GCXnF{ GCXmd_ GCXmf_ GCXmfO GCXmeo GCXmdo GCXmeW GCXmdW GCXmcw GCXmdc GCXmeS GCXmdS GCXmbS GCXmcs GCXmc[ GCXma[ GCXm`[ GCXmfo GCXmfW GCXmew GCXmdw GCXmfc GCXmfS GCXmes GCXmds GCXme[ GCXmd[ GCXmb[ GCXmc{ GCXmfw GCXmfs GCXmf[ GCXme{ GCXmd{ GCXmf{ GCXnf_ GCXnfO GCXnbW GCXncw GCXnfo GCXnfW GCXnew GCXnfc GCXnfS GCXnb[ GCXnfw GCXnfs GCXnf[ GCXne{ GCXnf{ GCXnVo GCXnVg GCXnVW GCXnRw GCXnVS GCXnUs GCXnRs GCXnVK GCXnRk GCXnVw GCXnVs GCXnVk GCXnV[ GCXnU{ GCXnR{ GCXnV{ GCXmvo GCXmvg GCXmvW GCXmuw GCXmtw GCXmvc GCXmus GCXmts GCXmvK GCXmuk GCXmtk GCXmvw GCXmvs GCXmvk GCXmv[ GCXmu{ GCXmt{ GCXmv{ GCXm^o GCXm^W GCXm]w GCXm\w GCXmZw GCXm^c GCXm^S GCXm]s GCXm\s GCXmZs GCXm][ GCXm\[ GCXm[{ GCXmY{ GCXmX{ GCXm^w GCXm^s GCXm^[ GCXm]{ GCXm\{ GCXmZ{ GCXm^{ GCXj^W GCXj]w GCXj][ GCXjZ[ GCXj[{ GCXj^w GCXj^[ GCXj]{ GCXj^{ GCXk~o GCXk}w GCXk~c GCXk~S GCXk}s GCXk{{ GCXk~w GCXk~s GCXk~[ GCXk}{ GCXk~{ GCXnvg GCXnvw GCXnvs GCXnvk GCXnv[ GCXnv{ GCXn^w GCXn^[ GCXnZ{ GCXn^{ GCXm~w GCXm~s GCXm~[ GCXm}{ GCXm|{ GCXm~{ GCXn~w GCXn~{ GCY^B_ GCY^F_ GCY^FO GCY^Eo GCY^Bo GCY^FG GCY^EW GCY^BW GCY^Cw GCY^ES GCY^BS GCY^Cs GCY^As GCY^C[ GCY^Fo GCY^Fg GCY^FW GCY^Ew GCY^Bw GCY^FS GCY^Es GCY^Bs GCY^E[ GCY^C{ GCY^Fw GCY^Fs GCY^F[ GCY^E{ GCY^F{ GCY^eo GCY^cw GCY^fo GCY^fg GCY^fW GCY^ew GCY^fc GCY^fw GCY^fs GCY^fk GCY^f[ GCY^e{ GCY^f{ GCY^Vo GCY^Vg GCY^VW GCY^Uw GCY^Rw GCY^VS GCY^Us GCY^Rs GCY^U[ GCY^S{ GCY^Vw GCY^Vs GCY^V[ GCY^U{ GCY^V{ GCY]vo GCY]vg GCY]vW GCY]uw GCY]tw GCY]vc GCY]us GCY]ts GCY]uk GCY]tk GCY]s{ GCY]vw GCY]vs GCY]vk GCY]v[ GCY]u{ GCY]t{ GCY]v{ GCY^No GCY^Ng GCY^NW GCY^Mw GCY^Jw GCY^Nc GCY^Ms GCY^NK GCY^Mk GCY^Jk GCY^Nw GCY^Ns GCY^Nk GCY^N[ GCY^M{ GCY^J{ GCY^N{ GCY]no GCY]ng GCY]nW GCY]mw GCY]lw GCY]nc GCY]mk GCY]lk GCY]nw GCY]ns GCY]nk GCY]n[ GCY]m{ GCY]l{ GCY]n{ GCY[~o GCY[~g GCY[~K GCY[}k GCY[{{ GCY[~w GCY[~k GCY[~[ GCY[}{ GCY[~{ GCY^vg GCY^vw GCY^vs GCY^vk GCY^v{ GCY^nw GCY^nk GCY^n{ GCY^^w GCY^^s GCY^^k GCY^^[ GCY^]{ GCY^Z{ GCY^^{ GCY]~w GCY]~s GCY]~k GCY]}{ GCY]|{ GCY]~{ GCY^~w GCY^~{ GCZvf_ GCZvfO GCZvfo GCZvfg GCZvfK GCZvek GCZve[ GCZvb[ GCZvc{ GCZvfw GCZvfk GCZvf[ GCZve{ GCZvf{ GCZvvo GCZvvg GCZvvw GCZvvs GCZvv{ GCZvnw GCZvnk GCZvn{ GCZv~{ GCZnfO GCZnfo GCZnfg GCZnfW GCZnew GCZnbw GCZnfc GCZnfK GCZnek GCZnbk GCZne[ GCZnb[ GCZnc{ GCZna{ GCZnfw GCZnfs GCZnfk GCZnf[ GCZne{ GCZnb{ GCZnf{ GCZnVg GCZnRw GCZnVK GCZnUk GCZnRk GCZnU[ GCZnR[ GCZnS{ GCZnQ{ GCZnVw GCZnVk GCZnV[ GCZnU{ GCZnR{ GCZnV{ GCZmvo GCZmvg GCZmvW GCZmuw GCZmtw GCZmrw GCZmvc GCZmrs GCZmvK GCZmuk GCZmtk GCZmrk GCZmu[ GCZmt[ GCZmr[ GCZms{ GCZmq{ GCZmp{ GCZmvw GCZmvs GCZmvk GCZmv[ GCZmu{ GCZmt{ GCZmr{ GCZmv{ GCZjvg GCZjvW GCZjvK GCZjuk GCZju[ GCZjr[ GCZjs{ GCZjvw GCZjvk GCZjv[ GCZju{ GCZjv{ GCZnvg GCZnvw GCZnvs GCZnvk GCZnv{ GCZnnw GCZnnk GCZnn{ GCZn^w GCZn^k GCZn^[ GCZn]{ GCZnZ{ GCZn^{ GCZm~w GCZm~s GCZm~k GCZm}{ GCZm|{ GCZm~{ GCZj~w GCZj~k GCZj}{ GCZj~{ GCZn~{ GCZ^fo GCZ^fg GCZ^ew GCZ^dw GCZ^fc GCZ^fK GCZ^ek GCZ^dk GCZ^e[ GCZ^d[ GCZ^b[ GCZ^c{ GCZ^fw GCZ^fs GCZ^fk GCZ^f[ GCZ^e{ GCZ^d{ GCZ^f{ GCZ]vg GCZ]uw GCZ]tw GCZ]vK GCZ]uk GCZ]tk GCZ]u[ GCZ]t[ GCZ]r[ GCZ]s{ GCZ]vw GCZ]vk GCZ]v[ GCZ]u{ GCZ]t{ GCZ]v{ GCZ\vo GCZ\vg GCZ\uw GCZ\vK GCZ\uk GCZ\u[ GCZ\r[ GCZ\s{ GCZ\vw GCZ\vk GCZ\v[ GCZ\u{ GCZ\v{ GCZ^vg GCZ^vw GCZ^vs GCZ^vk GCZ^v{ GCZ^nw GCZ^nk GCZ^n{ GCZ]~w GCZ]~k GCZ]}{ GCZ]|{ GCZ]~{ GCZ\~w GCZ\~k GCZ\~{ GCZ^~{ GCZ~vw GCZ~vk GCZ~v[ GCZ~u{ GCZ~v{ GCZ~~{ GCdbF? GCdbE_ GCdbCo GCdbEG GCdbBG GCdbF_ GCdbEo GCdbFG GCdbEg GCdbBK GCdbFo GCdbFg GCdbEw GCdbFK GCdbFw GCdbFk GCdbF{ GCdef_ GCdedo GCdefG GCdedg GCdebW GCdeec GCdedc GCdefo GCdefg GCdefW GCdeew GCdedw GCdefc GCdefS GCdees GCdeds GCdefK GCdeek GCdedk GCdee[ GCded[ GCdeb[ GCdec{ GCdefw GCdefs GCdefk GCdef[ GCdee{ GCded{ GCdef{ GCdcuo GCdcss GCdcvo GCdcvg GCdcuw GCdcus GCdcs{ GCdcvw GCdcvs GCdcu{ GCdcv{ GCdbM_ GCdbKo GCdbN_ GCdbMo GCdbNG GCdbMg GCdbKw GCdbMK GCdbJK GCdbKk GCdbNo GCdbNg GCdbMw GCdbNK GCdbMk GCdbK{ GCdbNw GCdbNk GCdbM{ GCdbN{ GCdffo GCdffg GCdffW GCdfew GCdffc GCdffS GCdfes GCdfek GCdfe[ GCdfc{ GCdffw GCdffs GCdffk GCdff[ GCdfe{ GCdff{ GCdevo GCdevg GCdeuw GCdetw GCdeus GCdets GCdes{ GCdevw GCdevs GCdevk GCdeu{ GCdet{ GCdev{ GCdfJo GCdfKw GCdfNo GCdfNg GCdfMw GCdfJw GCdfNc GCdfMs GCdfJs GCdfNK GCdfMk GCdfJk GCdfK{ GCdfI{ GCdfNw GCdfNs GCdfNk GCdfM{ GCdfJ{ GCdfN{ GCdeno GCdeng GCdenW GCdemw GCdelw GCdems GCdels GCdemk GCdelk GCdem[ GCdel[ GCdek{ GCdenw GCdens GCdenk GCden[ GCdem{ GCdel{ GCden{ GCdc~o GCdc~g GCdc}w GCdc{{ GCdc~w GCdc~s GCdc~k GCdc}{ GCdc~{ GCdfvo GCdfuw GCdfvw GCdfvs GCdfu{ GCdfv{ GCdfno GCdfng GCdfnW GCdfmw GCdfnw GCdfns GCdfnk GCdfn[ GCdfm{ GCdfn{ GCde}w GCde|w GCde~w GCde}{ GCde|{ GCde~{ GCdf~w GCdf~{ GCe[{{ GCe[~w GCe[}{ GCe[~{ GCe^vw GCe^vs GCe^vk GCe^u{ GCe^v{ GCe]~w GCe]}{ GCe]|{ GCe]~{ GCe^~w GCe^~{ GCfvfg GCfvfW GCfvek GCfvb[ GCfvc{ GCfvfw GCfvfk GCfvf[ GCfve{ GCfvf{ GCfvVg GCfvRw GCfvRk GCfvS{ GCfvQ{ GCfvVw GCfvVk GCfvU{ GCfvR{ GCfvV{ GCfvvw GCfvvs GCfvvk GCfvv{ GCfvnk GCfvn[ GCfvm{ GCfvn{ GCfv^w GCfv]{ GCfvZ{ GCfv^{ GCfu~w GCfu~s GCfu}{ GCfu|{ GCfu~{ GCfv~{ GCf^fg GCf^fW GCf^ew GCf^dw GCf^fc GCf^es GCf^dk GCf^b[ GCf^c{ GCf^fw GCf^fs GCf^fk GCf^f[ GCf^e{ GCf^d{ GCf^f{ GCf]uw GCf]uk GCf]s{ GCf]vw GCf]vk GCf]u{ GCf]t{ GCf]v{ GCf\vg GCf\tw GCf\uk GCf\s{ GCf\vw GCf\vk GCf\u{ GCf\v{ GCf^vw GCf^vs GCf^t{ GCf^v{ GCf]~w GCf]}{ GCf]|{ GCf]~{ GCf\~{ GCf^~{ GCf~vw GCf~vk GCf~u{ GCf~v{ GCf~~{ GCzffW GCzfew GCzfbw GCzffc GCzffS GCzfes GCzffw GCzffs GCzff[ GCzff{ GCzfVg GCzfVW GCzfRw GCzfVS GCzfRs GCzfR[ GCzfQ{ GCzfVw GCzfVs GCzfV[ GCzfU{ GCzfR{ GCzfV{ GCzbvg GCzbvW GCzbrw GCzbrs GCzbrk GCzbvw GCzbvs GCzbvk GCzbv[ GCzbu{ GCzbr{ GCzbv{ GCzfvw GCzfvs GCzfvk GCzfv[ GCzfr{ GCzfv{ GCzfZw GCzf^w GCzf^[ GCzfZ{ GCzf^{ GCze~w GCze~s GCze~[ GCze}{ GCze|{ GCzez{ GCze~{ GCzb}w GCzbzw GCzb~w GCzbz{ GCzb~{ GCzf~w GCzf~{ GCxvF_ GCxvFO GCxvBo GCxvFG GCxvEg GCxvBg GCxvEW GCxvBW GCxvBS GCxvFo GCxvFg GCxvFW GCxvEw GCxvBw GCxvFS GCxvBs GCxvFw GCxvFs GCxvF[ GCxvF{ GCxvf_ GCxvfO GCxveo GCxvbo GCxveW GCxvbW GCxvcw GCxvfo GCxvfW GCxvew GCxvfc GCxvfS GCxves GCxvbs GCxve[ GCxvb[ GCxvc{ GCxvfw GCxvfs GCxvf[ GCxve{ GCxvf{ GCxvVG GCxvVg GCxvRw GCxvVS GCxvVw GCxvVs GCxvV[ GCxvU{ GCxvR{ GCxvV{ GCxvvg GCxvvw GCxvvs GCxvvk GCxvv[ GCxvu{ GCxvr{ GCxvv{ GCxv]w GCxvZw GCxv^w GCxv^[ GCxvZ{ GCxv^{ GCxu}w GCxu|w GCxu~w GCxu~[ GCxu}{ GCxu|{ GCxu~{ GCxv~w GCxv~{ GCzvbo GCzvfo GCzvfg GCzvfW GCzvfK GCzvek GCzvbk GCzve[ GCzvb[ GCzvc{ GCzva{ GCzvfw GCzvfk GCzvf[ GCzve{ GCzvb{ GCzvf{ GCzvrw GCzvvw GCzvvs GCzvv{ GCzvnw GCzvnk GCzvj{ GCzvn{ GCzr~w GCzr~s GCzrz{ GCzr~{ GCzv~{ GCznfW GCznbw GCznfc GCznfS GCznes GCznbs GCzne[ GCznb[ GCznc{ GCzna{ GCznfw GCznfs GCznf[ GCzne{ GCznb{ GCznf{ GCznvw GCznvs GCznvk GCznr{ GCznv{ GCzn^w GCzn^[ GCzn]{ GCznZ{ GCzn^{ GCzm~w GCzm~s GCzm}{ GCzm|{ GCzm~{ GCzj}{ GCzjz{ GCzj~{ GCzn~{ GCz^fo GCz^fg GCz^fW GCz^fS GCz^es GCz^ds GCz^bs GCz^fK GCz^ek GCz^dk GCz^bk GCz^d[ GCz^b[ GCz^c{ GCz^a{ GCz^`{ GCz^fw GCz^fs GCz^fk GCz^f[ GCz^e{ GCz^d{ GCz^b{ GCz^f{ GCz^vw GCz^vs GCz^vk GCz^t{ GCz^v{ GCz^nw GCz^nk GCz^j{ GCz^n{ GCz]~w GCz]~k GCz]}{ GCz]|{ GCz]~{ GCz\~k GCz\~{ GCz^~{ GCx~fo GCx~ew GCx~fc GCx~fS GCx~es GCx~bs GCx~e[ GCx~b[ GCx~c{ GCx~fw GCx~fs GCx~f[ GCx~e{ GCx~f{ GCx}~w GCx}}{ GCx}|{ GCx}~{ GCx~~{ GCz~vw GCz~vk GCz~v[ GCz~u{ GCz~r{ GCz~v{ GCz~~{ GCvfVg GCvfVW GCvfRw GCvfVS GCvfRs GCvfRk GCvfR[ GCvfQ{ GCvfP{ GCvfVw GCvfVs GCvfVk GCvfV[ GCvfU{ GCvfT{ GCvfR{ GCvfV{ GCverw GCvevS GCveus GCvets GCvers GCver[ GCveq{ GCvep{ GCvevw GCvevs GCvev[ GCveu{ GCvet{ GCver{ GCvev{ GCvdrw GCvdvS GCvdrs GCvdr[ GCvdq{ GCvdvw GCvdvs GCvdv[ GCvdu{ GCvdr{ GCvdv{ GCvbvg GCvbvW GCvbuw GCvbtw GCvbrw GCvbvc GCvbrs GCvbuk GCvbrk GCvbvw GCvbvs GCvbvk GCvbv[ GCvbu{ GCvbt{ GCvbr{ GCvbv{ GCvfNo GCvfJw GCvfNK GCvfMk GCvfLk GCvfJk GCvfH{ GCvfNw GCvfNk GCvfN[ GCvfM{ GCvfL{ GCvfJ{ GCvfN{ GCveno GCvemw GCvejw GCvemk GCveh{ GCvenw GCvens GCvenk GCven[ GCvem{ GCvel{ GCvej{ GCven{ GCvdjw GCvdjs GCvdmk GCvdlk GCvdnw GCvdns GCvdnk GCvdn[ GCvdm{ GCvdl{ GCvdj{ GCvdn{ GCvbno GCvbng GCvbmk GCvblk GCvbl[ GCvbk{ GCvbnw GCvbnk GCvbn[ GCvbm{ GCvbl{ GCvbn{ GCvfvw GCvfvs GCvfvk GCvfu{ GCvfv{ GCvfnw GCvfnk GCvfn{ GCvf^w GCvf^s GCvf^k GCvf^[ GCvf]{ GCvfZ{ GCvf^{ GCve~w GCve~k GCve}{ GCve~{ GCvd~w GCvd~s GCvd~k GCvd~[ GCvd}{ GCvd|{ GCvdz{ GCvd~{ GCvb}w GCvb|w GCvb~w GCvb~s GCvb~k GCvb}{ GCvbz{ GCvb~{ GCvf~w GCvf~{ GCvUvw GCvUvs GCvUu{ GCvUv{ GCvVvw GCvVvs GCvVvk GCvVv[ GCvVu{ GCvVt{ GCvVv{ GCvV^w GCvV^[ GCvV]{ GCvV\{ GCvVZ{ GCvV^{ GCvU~w GCvU}{ GCvU|{ GCvU~{ GCvT~w GCvT|{ GCvT~{ GCvV~w GCvV~{ GCutvc GCutts GCutvw GCutvs GCutvk GCutv[ GCutu{ GCutt{ GCutv{ GCuvvw GCuvvs GCuvvk GCuvu{ GCuvt{ GCuvv{ GCuv^w GCuv^s GCuv^[ GCuv]{ GCuvZ{ GCuv^{ GCuu~w GCuu}{ GCuu|{ GCuu~{ GCuv~w GCuv~{ GCvvfg GCvvfK GCvve[ GCvvb[ GCvvc{ GCvvfw GCvvfk GCvvf[ GCvve{ GCvvd{ GCvvf{ GCvvvw GCvvvs GCvvvk GCvvt{ GCvvv{ GCvvnk GCvvl{ GCvvn{ GCvt~w GCvt|{ GCvt~{ GCvv~{ GCv^fo GCv^fW GCv^ew GCv^dw GCv^fS GCv^es GCv^e[ GCv^b[ GCv^c{ GCv^fw GCv^fs GCv^f[ GCv^e{ GCv^d{ GCv^f{ GCv^vw GCv^vs GCv^vk GCv^t{ GCv^v{ GCv]~w GCv]}{ GCv]|{ GCv]~{ GCv\|{ GCv\~{ GCv^~{ GCu~fo GCu~fW GCu~fS GCu~ds GCu~e[ GCu~b[ GCu~c{ GCu~fw GCu~fs GCu~f[ GCu~e{ GCu~f{ GCu~~{ GCv~vw GCv~vk GCv~u{ GCv~t{ GCv~v{ GCv~~{ GC~vfo GC~vfW GC~vew GC~ve[ GC~vb[ GC~vc{ GC~vfw GC~vf[ GC~ve{ GC~vf{ GC~vvw GC~vvs GC~vv{ GC~v~w GC~v~{ GC~~~{ GErvfg GErvfK GErvek GErvfw GErvfk GErvf[ GErvf{ GErtrw GErtr[ GErtvw GErtvk GErtv[ GErtu{ GErtv{ GErvvW GErvvw GErvvs GErvvk GErvv[ GErvv{ GErvnk GErvn[ GErvm{ GErvl{ GErvn{ GErv]{ GErv\{ GErv^{ GEru~w GEru~s GEru}{ GEru~{ GErt~w GErt}{ GErt~{ GErv~{ GEr^fg GEr^fW GEr^fS GEr^es GEr^fK GEr^ek GEr^e[ GEr^fw GEr^fs GEr^fk GEr^f[ GEr^e{ GEr^d{ GEr^f{ GEr]vo GEr]uw GEr]vw GEr]u{ GEr]v{ GEr^vo GEr^uw GEr^vw GEr^vs GEr^v{ GEr]~w GEr]}{ GEr]~{ GEr^~{ GEr~vw GEr~vk GEr~u{ GEr~v{ GEr~~{ GEhffO GEhfeW GEhfdW GEhf`w GEhffo GEhffW GEhfew GEhfdw GEhffc GEhffS GEhfes GEhfe[ GEhfc{ GEhf`{ GEhffw GEhffs GEhff[ GEhfe{ GEhfd{ GEhff{ GEheuo GEherW GEhevo GEhevg GEhevW GEhevS GEheus GEhets GEhevK GEherk GEher[ GEhevw GEhevs GEhevk GEhev[ GEhev{ GEhbro GEhbtg GEhbvo GEhbvg GEhbvc GEhbus GEhbrs GEhbuk GEhbtk GEhbvw GEhbvs GEhbvk GEhbv{ GEhfvo GEhfvg GEhfuw GEhfrw GEhfvw GEhfvs GEhfvk GEhfu{ GEhfr{ GEhfv{ GEhf~w GEhf~{ GEjf`w GEjfbw GEjffc GEjffS GEjffK GEjfa{ GEjf`{ GEjffw GEjffs GEjffk GEjff[ GEjfe{ GEjfd{ GEjfb{ GEjff{ GEjerW GEjeqw GEjevW GEjeuw GEjerw GEjevc GEjevS GEjeus GEjets GEjevK GEjeuk GEjetk GEjerk GEjeu[ GEjet[ GEjer[ GEjeq{ GEjep{ GEjevw GEjevs GEjevk GEjev[ GEjeu{ GEjet{ GEjer{ GEjev{ GEjbuo GEjbro GEjbvG GEjbug GEjbtg GEjbvo GEjbvg GEjbvc GEjbus GEjbrs GEjbvK GEjbuk GEjbtk GEjbvw GEjbvs GEjbvk GEjbv{ GEjfNK GEjfJk GEjfK{ GEjfI{ GEjfNw GEjfNk GEjfM{ GEjfJ{ GEjfN{ GEjeno GEjenc GEjenS GEjemk GEjel[ GEjenw GEjens GEjenk GEjen[ GEjen{ GEjdno GEjdng GEjdnc GEjdnS GEjdlk GEjdm[ GEjdnw GEjdns GEjdnk GEjdn[ GEjdn{ GEjfrw GEjfvw GEjfvs GEjfvk GEjfu{ GEjfr{ GEjfv{ GEjfnw GEjfnk GEjfn[ GEjfm{ GEjfl{ GEjfn{ GEjf~w GEjf~{ GEirvg GEirvW GEirtw GEirvc GEirus GEirts GEirrs GEirvw GEirvs GEirt{ GEirv{ GEit^W GEit\w GEit^c GEit\s GEit\k GEit\[ GEit^w GEit^s GEit^k GEit^[ GEit\{ GEit^{ GEivvw GEivvs GEivv[ GEivt{ GEivr{ GEivv{ GEivnw GEivns GEivnk GEivn[ GEivm{ GEivl{ GEivj{ GEivn{ GEiv^w GEiv^[ GEiv^{ GEit~w GEit~[ GEit|{ GEit~{ GEiv~w GEiv~{ GEhvF_ GEhvFO GEhvEo GEhvDo GEhvBo GEhvEg GEhvDg GEhvCw GEhvAw GEhvFC GEhvEc GEhvDc GEhvBc GEhvCs GEhvAs GEhv@s GEhvCk GEhvAk GEhv?{ GEhvFo GEhvFg GEhvEw GEhvDw GEhvFc GEhvFS GEhvEs GEhvDs GEhvBs GEhvEk GEhvDk GEhvC{ GEhvA{ GEhvFw GEhvFs GEhvFk GEhvE{ GEhvD{ GEhvF{ GEhuV_ GEhuVO GEhuUo GEhuTo GEhuRo GEhuUg GEhuUW GEhuTW GEhuSw GEhuQw GEhuUc GEhuVo GEhuVg GEhuVW GEhuUw GEhuTw GEhuRw GEhuVc GEhuUs GEhuVw GEhuVs GEhuU{ GEhuV{ GEhvV_ GEhvUo GEhvRo GEhvUg GEhvTg GEhvSw GEhvQw GEhvVo GEhvVg GEhvUw GEhvTw GEhvVc GEhvVS GEhvUs GEhvTs GEhvRs GEhvUk GEhvTk GEhvS{ GEhvQ{ GEhvVw GEhvVs GEhvVk GEhvU{ GEhvT{ GEhvV{ GEhuuW GEhuvo GEhuvW GEhutw GEhurw GEhuts GEhurs GEhus{ GEhup{ GEhuvw GEhuvs GEhuu{ GEhut{ GEhuv{ GEhttW GEhtvW GEhtuw GEhttw GEhtts GEhttk GEhtrk GEhtt[ GEhts{ GEhtq{ GEhtp{ GEhtvw GEhtvs GEhtvk GEhtv[ GEhtu{ GEhtt{ GEhtr{ GEhtv{ GEhrvg GEhrvW GEhrts GEhrrs GEhrvK GEhrtk GEhrrk GEhru[ GEhrt[ GEhrr[ GEhrs{ GEhrq{ GEhrp{ GEhrvw GEhrvs GEhrvk GEhrv[ GEhru{ GEhrt{ GEhrr{ GEhrv{ GEhtno GEhtng GEhtlw GEhtlk GEhtnw GEhtns GEhtnk GEhtm{ GEhtl{ GEhtn{ GEhvvW GEhvvw GEhvvs GEhvvk GEhvv[ GEhvu{ GEhvt{ GEhvr{ GEhvv{ GEhvnw GEhvnk GEhvl{ GEhvn{ GEhu~w GEhu~k GEhu}{ GEhu|{ GEhuz{ GEhu~{ GEht~w GEht|{ GEht~{ GEhv~w GEhv~{ GEjvfK GEjvbk GEjve[ GEjvb[ GEjvc{ GEjvfw GEjvfk GEjvf[ GEjve{ GEjvd{ GEjvb{ GEjvf{ GEjvVo GEjvVg GEjvVW GEjvUw GEjvTw GEjvVK GEjvUk GEjvTk GEjvRk GEjvS{ GEjvQ{ GEjvVw GEjvVk GEjvU{ GEjvR{ GEjvV{ GEjurw GEjuvK GEjutk GEjuu[ GEjur[ GEjus{ GEjuvw GEjuvs GEjuvk GEjuv[ GEjuu{ GEjut{ GEjur{ GEjuv{ GEjtvW GEjttw GEjtvc GEjtvK GEjttk GEjtrk GEjtt[ GEjtr[ GEjts{ GEjtvw GEjtvs GEjtvk GEjtv[ GEjtu{ GEjtt{ GEjtr{ GEjtv{ GEjvvw GEjvvs GEjvv{ GEjvnw GEjvnk GEjvn[ GEjvm{ GEjvl{ GEjvj{ GEjvn{ GEjv^w GEjv]{ GEjvZ{ GEjv^{ GEju~s GEju}{ GEju|{ GEju~{ GEjt~s GEjt|{ GEjt~{ GEjr~w GEjr~s GEjr}{ GEjr|{ GEjrz{ GEjr~{ GEjv~{ GEj\s{ GEj\q{ GEj\vw GEj\u{ GEj\r{ GEj\v{ GEj^rw GEj^vw GEj^vs GEj^vk GEj^v[ GEj^t{ GEj^r{ GEj^v{ GEj]~w GEj]}{ GEj]|{ GEj]~{ GEj\~{ GEj^~{ GEh~fg GEh~fW GEh~ew GEh~dw GEh~bw GEh~fS GEh~es GEh~fK GEh~ek GEh~dk GEh~bk GEh~e[ GEh~d[ GEh~b[ GEh~fw GEh~fs GEh~fk GEh~f[ GEh~e{ GEh~d{ GEh~b{ GEh~f{ GEh}vo GEh}vW GEh}uw GEh}tw GEh}rw GEh}us GEh}ts GEh}rs GEh}s{ GEh}p{ GEh}vw GEh}vs GEh}u{ GEh}t{ GEh}v{ GEhzvo GEhzrw GEhzvK GEhzuk GEhztk GEhzs{ GEhzq{ GEhzvw GEhzvk GEhzu{ GEhzr{ GEhzv{ GEh~vo GEh~rw GEh~vw GEh~vs GEh~r{ GEh~v{ GEh}~s GEh}}{ GEh}|{ GEh}~{ GEhz}{ GEhzz{ GEhz~{ GEh~~{ GEj~vw GEj~vk GEj~u{ GEj~r{ GEj~v{ GEj~~{ GEzfvw GEzfvs GEzfvk GEzfv[ GEzfu{ GEzft{ GEzfv{ GEzf]w GEzf^w GEzf^[ GEzf]{ GEzf^{ GEzf~w GEzf~{ GEzTng GEzTmw GEzTlw GEzTnc GEzTms GEzTls GEzTlk GEzTnw GEzTns GEzTnk GEzTm{ GEzTl{ GEzTj{ GEzTn{ GEzVvw GEzVvs GEzVvk GEzVv[ GEzVu{ GEzVt{ GEzVv{ GEzVnw GEzVnk GEzVm{ GEzVn{ GEzU~o GEzU~w GEzU}{ GEzU~{ GEzT~w GEzT~k GEzT}{ GEzT|{ GEzTz{ GEzT~{ GEzV~w GEzV~{ GEzvew GEzvfK GEzvek GEzvfw GEzvfk GEzvf[ GEzvf{ GEzvvw GEzvvs GEzvv{ GEzvnw GEzvnk GEzvn{ GEzv~{ GEznfo GEznfW GEzndw GEznfc GEznfS GEznes GEzne[ GEznc{ GEzn`{ GEznfw GEznfs GEznf[ GEzne{ GEznd{ GEznf{ GEznvw GEznvs GEznvk GEznv{ GEzn^w GEzn^[ GEzn^{ GEzn~{ GEz~vk GEz~v[ GEz~v{ GEz~~{ GEvvvw GEvvvs GEvvv{ GEvvnw GEvvnk GEvvn[ GEvvn{ GEvv^w GEvv]{ GEvv^{ GEvv~{ GEv^vw GEv^vs GEv^v{ GEv]~w GEv]}{ GEv]|{ GEv]~{ GEv\~w GEv\~s GEv\|{ GEv\~{ GEv^~{ GEu~vw GEu~vs GEu~v{ GEu~nw GEu~nk GEu~n[ GEu~n{ GEu|~w GEu|z{ GEu|~{ GEu~~{ GEv~vw GEv~vk GEv~u{ GEv~t{ GEv~v{ GEv~~{ GEnfbW GEnffW GEnfbw GEnffS GEnffK GEnfdk GEnffw GEnffs GEnffk GEnff[ GEnff{ GEnbvG GEnbvo GEnbrw GEnbus GEnbvw GEnbvs GEnbu{ GEnbv{ GEnfvw GEnfvs GEnfu{ GEnfr{ GEnfv{ GEnfnw GEnfns GEnfnk GEnfn[ GEnfm{ GEnfl{ GEnfj{ GEnfn{ GEne~w GEne}{ GEne|{ GEne~{ GEnb~g GEnb~w GEnb}{ GEnbz{ GEnb~{ GEnf~w GEnf~{ GEnvVo GEnvVW GEnvUw GEnvTw GEnvRw GEnvTk GEnvRk GEnvS{ GEnvQ{ GEnvVw GEnvVk GEnvU{ GEnvR{ GEnvV{ GEnvvw GEnvvs GEnvv{ GEnvnw GEnvnk GEnvn[ GEnvn{ GEnv^w GEnv]{ GEnvZ{ GEnv^{ GEnv~{ GEl~vw GEl~vs GEl~v{ GEl}~w GEl}~s GEl}}{ GEl}~{ GEl~~{ GEn~vw GEn~vk GEn~r{ GEn~v{ GEn~~{ GE~vvs GE~vv[ GE~vv{ GE~v~{ GE~~~{ GFzfvo GFzfuw GFzfvw GFzfvs GFzfvk GFzfu{ GFzfv{ GFzf~w GFzf~{ GFzvVo GFzvVg GFzvUw GFzvS{ GFzvVw GFzvU{ GFzvV{ GFzvvW GFzvvw GFzvu{ GFzvv{ GFzvnw GFzvnk GFzvn{ GFzv~{ GFz~vw GFz~u{ GFz~v{ GFz~~{ GF~~~{ GQhTQg GQhTV_ GQhTVO GQhTUg GQhTTS GQhTVo GQhTVg GQhTVS GQhTVw GQhTVs GQhTV{ GQhVf_ GQhVeo GQhVfo GQhVfW GQhVfc GQhVes GQhVe[ GQhVfw GQhVfs GQhVf[ GQhVf{ GQhVV_ GQhVUg GQhVVo GQhVVg GQhVTw GQhVVc GQhVVS GQhVTs GQhVUk GQhVTk GQhVVw GQhVVs GQhVVk GQhVT{ GQhVV{ GQhVvo GQhVvg GQhVvW GQhVvw GQhVvs GQhVvk GQhVv[ GQhVv{ GQhV~w GQhV~{ GQjdvo GQjdvg GQjdvW GQjdts GQjdtk GQjdq{ GQjdvw GQjdvs GQjdvk GQjdv[ GQjdu{ GQjdt{ GQjdv{ GQjfNK GQjfLk GQjfK{ GQjfNw GQjfNk GQjfN[ GQjfM{ GQjfL{ GQjfN{ GQjdmk GQjdm[ GQjdnw GQjdnk GQjdn[ GQjdm{ GQjdn{ GQjfvw GQjfvs GQjfv[ GQjfv{ GQjfnw GQjfns GQjfnk GQjfn[ GQjfm{ GQjfn{ GQjf^w GQjf^[ GQjf^{ GQje~w GQje~s GQje~[ GQje}{ GQjez{ GQje~{ GQjd~w GQjd~s GQjd~k GQjd~[ GQjd}{ GQjd|{ GQjd~{ GQjf~w GQjf~{ GQjRf_ GQjRfO GQjReo GQjRfG GQjReg GQjRbg GQjReW GQjRdW GQjRbW GQjRaw GQjReK GQjRdK GQjRak GQjRc[ GQjRa[ GQjRfo GQjRfg GQjRfW GQjRew GQjRbw GQjRfK GQjRek GQjRe[ GQjRd[ GQjRa{ GQjRfw GQjRfk GQjRf[ GQjRe{ GQjRf{ GQjVfg GQjVfW GQjVbw GQjVfc GQjVes GQjVek GQjVb[ GQjVfw GQjVfs GQjVfk GQjVf[ GQjVe{ GQjVb{ GQjVf{ GQjVRg GQjVRW GQjVVo GQjVVg GQjVVW GQjVRw GQjVVc GQjVVS GQjVTs GQjVVK GQjVUk GQjVVw GQjVVs GQjVVk GQjVV[ GQjVV{ GQjRv_ GQjRro GQjRvG GQjRug GQjRvo GQjRvg GQjRvc GQjRvS GQjRrs GQjRvK GQjRuk GQjRvw GQjRvs GQjRvk GQjRv{ GQjVNo GQjVNg GQjVNc GQjVMs GQjVNK GQjVNw GQjVNs GQjVNk GQjVM{ GQjVN{ GQjUno GQjUmk GQjUl[ GQjUj[ GQjUnw GQjUnk GQjUn[ GQjUj{ GQjUn{ GQjVrw GQjVvw GQjVvs GQjVvk GQjVv[ GQjVr{ GQjVv{ GQjVnw GQjVnk GQjVn[ GQjVm{ GQjVn{ GQjV~w GQjV~{ GQinTw GQinVc GQinVS GQinTs GQinVw GQinVs GQinVk GQinV[ GQinT{ GQinV{ GQil^W GQil\[ GQil^w GQil^[ GQil^{ GQinvw GQinvs GQinvk GQinv[ GQinv{ GQin^w GQin^[ GQin\{ GQin^{ GQin~w GQin~{ GQjvfg GQjvfK GQjvek GQjve[ GQjvd[ GQjva{ GQjvfw GQjvfk GQjvf[ GQjve{ GQjvf{ GQjuvo GQjuvg GQjuuw GQjurw GQjuvK GQjuuk GQjurk GQjut[ GQjur[ GQjuvw GQjuvk GQjuv[ GQjur{ GQjuv{ GQjvvw GQjvvs GQjvvk GQjvv{ GQjvnk GQjvn[ GQjvm{ GQjvn{ GQjv^w GQjv^s GQjv^[ GQjv\{ GQjv^{ GQju~w GQju~[ GQjuz{ GQju~{ GQjv~{ GQjnfg GQjnfW GQjnew GQjndw GQjnfc GQjnfK GQjnek GQjndk GQjnd[ GQjna{ GQjnfw GQjnfs GQjnfk GQjnf[ GQjne{ GQjnd{ GQjnf{ GQjnVg GQjnTw GQjnVK GQjnTk GQjnT[ GQjnVw GQjnVk GQjnV[ GQjnT{ GQjnV{ GQjlvo GQjlvg GQjlvW GQjlvK GQjlt[ GQjlvw GQjlvk GQjlv[ GQjlv{ GQjnvw GQjnvs GQjnt{ GQjnv{ GQjn^w GQjn^[ GQjn\{ GQjn^{ GQjl~{ GQjn~{ GQj~vw GQj~vk GQj~v[ GQj~v{ GQj~~{ GQzTrg GQzTvg GQzTvW GQzTuw GQzTus GQzTvw GQzTvs GQzTu{ GQzTv{ GQzVvg GQzVvw GQzVvs GQzVvk GQzVv[ GQzVu{ GQzVt{ GQzVr{ GQzVv{ GQzV]w GQzV^w GQzV^[ GQzV]{ GQzV^{ GQzV~w GQzV~{ GQyvfo GQyvfW GQyvew GQyvfc GQyves GQyvfw GQyvfs GQyvf[ GQyve{ GQyvf{ GQyvVo GQyvTw GQyvVw GQyvVs GQyvV[ GQyvV{ GQyurg GQyuvg GQyurw GQyuuk GQyus{ GQyuvw GQyuvs GQyuvk GQyuu{ GQyuv{ GQyvvw GQyvvs GQyvvk GQyvv[ GQyvt{ GQyvv{ GQyv^w GQyv^[ GQyv\{ GQyv^{ GQyuzw GQyu~w GQyu~s GQyu~[ GQyu}{ GQyuz{ GQyu~{ GQyv~w GQyv~{ GQzvVo GQzvVg GQzvVW GQzvUw GQzvTw GQzvVS GQzvVw GQzvVs GQzvV[ GQzvV{ GQzvvg GQzvvw GQzvvs GQzvv{ GQzvnw GQzvnk GQzvn{ GQzv~{ GQznvw GQznvs GQznvk GQznv{ GQzn^w GQzn^[ GQzn]{ GQzn\{ GQzn^{ GQzm~w GQzm~s GQzm}{ GQzm~{ GQzl~w GQzl~s GQzl|{ GQzl~{ GQzn~{ GQz^fo GQz^fc GQz^fS GQz^es GQz^ds GQz^e[ GQz^d[ GQz^b[ GQz^`{ GQz^fw GQz^fs GQz^f[ GQz^d{ GQz^f{ GQz^vw GQz^vs GQz^vk GQz^v{ GQz^~{ GQy~fW GQy~ew GQy~fc GQy~fS GQy~es GQy~e[ GQy~d[ GQy~a{ GQy~fw GQy~fs GQy~f[ GQy~e{ GQy~f{ GQy~vw GQy~vs GQy~vk GQy~v{ GQy}~w GQy}~s GQy}~[ GQy}}{ GQy}z{ GQy}~{ GQy~~{ GQz~vw GQz~vk GQz~v[ GQz~u{ GQz~t{ GQz~v{ GQz~~{ GQ~vvg GQ~vvw GQ~vvs GQ~vv{ GQ~v~w GQ~v~{ GQ~~~{ GUZvfo GUZvfg GUZvfK GUZvek GUZve[ GUZvd[ GUZvb[ GUZvfw GUZvfk GUZvf[ GUZve{ GUZvf{ GUZuvo GUZuvW GUZurw GUZuvK GUZuuk GUZuvw GUZuvk GUZuv[ GUZuv{ GUZvvo GUZvvg GUZvvW GUZvvw GUZvvs GUZvv{ GUZvnw GUZvnk GUZvn[ GUZvm{ GUZvn{ GUZv^w GUZv\{ GUZv^{ GUZu~w GUZu~[ GUZu~{ GUZv~{ GUZ~vw GUZ~vk GUZ~v{ GUZ~~{ GUxvvo GUxvvg GUxvuw GUxvvw GUxvvs GUxvvk GUxvv[ GUxvu{ GUxvv{ GUxv~w GUxv~{ GUzrvo GUzrvg GUzrvW GUzrvw GUzrv{ GUzvrw GUzvvw GUzvvk GUzvv[ GUzvv{ GUzv^[ GUzv]{ GUzv^{ GUzv~{ GUz^vw GUz^vs GUz^u{ GUz^v{ GUz]}{ GUz]~{ GUz^~{ GUz~vw GUz~vk GUz~u{ GUz~v{ GUz~~{ GU~vvw GU~vvs GU~vv{ GU~v~{ GU~~~{ GTzvvw GTzvvs GTzvvk GTzvv[ GTzvv{ GTzvn[ GTzvn{ GTzv^[ GTzv^{ GTzv~{ GTzn~{ GTz~vw GTz~vk GTz~v[ GTz~v{ GTz~~{ GTnvvw GTnvvs GTnvvk GTnvv{ GTnvnk GTnvn{ GTnv~{ GTm~vw GTm~vs GTm~v{ GTm|~w GTm||{ GTm|~{ GTm~~{ GTn~~{ GT~~~{ GV~~~{ G]zn^w G]zn^[ G]zn\{ G]zn^{ G]zl|{ G]zl~{ G]zn~{ G]y~~{ G]z~~{ G]~vvw G]~vvs G]~vv[ G]~vv{ G]~v~w G]~v~{ G]~~~{ G^~~~{ G~~~~{ edge-addition-planarity-suite-Version_5.0.0.0/c/samples/nauty_example.LEDA000066400000000000000000000001711521450711600263750ustar00rootroot00000000000000LEDA.GRAPH void void -2 5 |{v1}| |{v2}| |{v3}| |{v4}| |{v5}| 4 1 3 0 |{}| 1 5 0 |{}| 2 4 0 |{}| 4 5 0 |{}| edge-addition-planarity-suite-Version_5.0.0.0/c/samples/nauty_example.g6000066400000000000000000000000161521450711600262020ustar00rootroot00000000000000>>graph6<>graph6<` // "-test", // "${workspaceFolder}\\c\\samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Set Disassembly Flavor to Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ], "preLaunchTask": "C/C++: (Windows) [Debug] gcc.exe build planarity project" }, { "name": "C/C++: (Windows) [Release] gcc.exe build and run planarity project", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}\\Release\\planarity.exe", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}\\c\\samples\\nauty_example.g6", // "${workspaceFolder}\\c\\samples\\nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}\\c\\samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "C:\\msys64\\ucrt64\\bin\\gdb.exe", "setupCommands": [], "preLaunchTask": "C/C++: (Windows) [Release] gcc.exe build planarity project" }, { "name": "C/C++: (Linux) [Debug] gcc build and debug planarity project", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Debug/planarity", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}/c/samples/nauty_example.g6", // "${workspaceFolder}/c/samples/nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}/c/samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Set Disassembly Flavor to Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ], "preLaunchTask": "C/C++: (Linux) [Debug] gcc build planarity project" }, { "name": "C/C++: (Linux) [Release] gcc build and run planarity project", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Release/planarity", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}/c/samples/nauty_example.g6", // "${workspaceFolder}/c/samples/nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}/c/samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "setupCommands": [], "preLaunchTask": "C/C++: (Linux) [Release] gcc build planarity project" }, { "name": "C/C++: (Windows) [Debug] cl.exe build and debug planarity project", "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}\\Debug\\planarity.exe", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}\\c\\samples\\nauty_example.g6", // "${workspaceFolder}\\c\\samples\\nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}\\c\\samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "console": "integratedTerminal", // "externalConsole": false, // externalConsole deprecated for MSVC "preLaunchTask": "C/C++: (Windows) [Debug] cl.exe build planarity project" }, { "name": "C/C++: (Windows) [Release] cl.exe build and run planarity project", "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}\\Release\\planarity.exe", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}\\c\\samples\\nauty_example.g6", // "${workspaceFolder}\\c\\samples\\nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}\\c\\samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "console": "integratedTerminal", // "externalConsole": false, // externalConsole deprecated for MSVC "preLaunchTask": "C/C++: (Windows) [Release] cl.exe build planarity project" }, { "name": "C/C++: (MacOS) [Debug] clang build and debug planarity project", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Debug/planarity", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}/c/samples/nauty_example.g6", // "${workspaceFolder}/c/samples/nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}/c/samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "lldb", "preLaunchTask": "C/C++: (MacOS) [Debug] clang build planarity project" }, { "name": "C/C++: (MacOS) [Release] clang build and run planarity project", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Release/planarity", "args": [ // Command: `planarity -x [-q] -a I O` // "-x", // "-a", // "${workspaceFolder}/c/samples/nauty_example.g6", // "${workspaceFolder}/c/samples/nauty_example.g6.AdjList.expected.out.txt" // Command: `planarity -test ` // "-test", // "${workspaceFolder}/c/samples" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "lldb", "preLaunchTask": "C/C++: (MacOS) [Release] clang build planarity project" } ], "version": "2.0.0" }edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/.vscode/settings.json000066400000000000000000000011711521450711600316650ustar00rootroot00000000000000{ "[python]": { "editor.rulers": [ 79, 99 ], "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true }, "editor.formatOnSave": true, "editor.renderWhitespace": "all", "files.autoSave": "afterDelay", "python.analysis.typeCheckingMode": "basic", "black-formatter.cwd": "${workspaceFolder}/TestSupport/planaritytesting", "black-formatter.args": [ "--line-length=79", "--target-version=py312" ], "pylint.args": [ "--rcfile=${workspaceFolder}/TestSupport/planaritytesting/.pylintrc" ] }edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/.vscode/tasks.json000066400000000000000000000551511521450711600311610ustar00rootroot00000000000000{ "tasks": [ { "type": "cppbuild", "label": "C/C++: (Windows) [Debug] gcc.exe build planarity project", // Please be sure to include the path to gcc in your environment variables, // or change this to "command": "C:\\msys64\\ucrt64\\bin\\gcc.exe" or similar, // based on the installation path you used for gcc "command": "gcc.exe", "args": [ "-fdiagnostics-color=always", "-DDEBUG", // Added so that _DEBUG is defined; see appconst.h //"-DUSE_0BASEDARRAYS", // Commented out to use faster 1-based array indexing "-Wall", "-fno-strict-aliasing", "-pedantic", "-Wall", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wduplicated-branches", "-Wduplicated-cond", "-Werror", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wjump-misses-init", "-Wlogical-op", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=suggest-attribute=format", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wrestrict", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdiscarded-qualifiers", "-Wunused-result", "-g", // Generate debug symbols "${workspaceFolder}\\c\\planarityApp\\**.c", "${workspaceFolder}\\c\\graphLib\\**.c", "${workspaceFolder}\\c\\graphLib\\extensionSystem\\**.c", "${workspaceFolder}\\c\\graphLib\\homeomorphSearch\\**.c", "${workspaceFolder}\\c\\graphLib\\io\\**.c", "${workspaceFolder}\\c\\graphLib\\lowLevelUtils\\**.c", "${workspaceFolder}\\c\\graphLib\\planarityRelated\\**.c", "-o", "${workspaceFolder}\\Debug\\planarity.exe" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "Task generated by Debugger." }, { "type": "cppbuild", "label": "C/C++: (Windows) [Release] gcc.exe build planarity project", // Please be sure to include the path to gcc in your environment variables, // or change this to "command": "C:\\msys64\\ucrt64\\bin\\gcc.exe" or similar, // based on the installation path you used for gcc "command": "gcc.exe", "args": [ "-O3", //"-DUSE_0BASEDARRAYS", // Commented out to use faster 1-based array indexing "-Wall", "-fno-strict-aliasing", "-pedantic", "-Wall", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wduplicated-branches", "-Wduplicated-cond", "-Werror", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wjump-misses-init", "-Wlogical-op", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=suggest-attribute=format", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wrestrict", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdiscarded-qualifiers", "-Wunused-result", // "-g", // Uncomment to generate debug symbols for Release build and debug with VSCode "${workspaceFolder}\\c\\planarityApp\\**.c", "${workspaceFolder}\\c\\graphLib\\**.c", "${workspaceFolder}\\c\\graphLib\\extensionSystem\\**.c", "${workspaceFolder}\\c\\graphLib\\homeomorphSearch\\**.c", "${workspaceFolder}\\c\\graphLib\\io\\**.c", "${workspaceFolder}\\c\\graphLib\\lowLevelUtils\\**.c", "${workspaceFolder}\\c\\graphLib\\planarityRelated\\**.c", "-o", "${workspaceFolder}\\Release\\planarity.exe" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "Task generated by Debugger." }, { "type": "cppbuild", "label": "C/C++: (Linux) [Debug] gcc build planarity project", "command": "/usr/bin/gcc", "args": [ "-fdiagnostics-color=always", "-fno-strict-aliasing", "-DDEBUG", // Added so that _DEBUG is defined; see appconst.h //"-DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "-pedantic", "-Wall", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wduplicated-branches", "-Wduplicated-cond", "-Werror", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wjump-misses-init", "-Wlogical-op", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wrestrict", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdiscarded-qualifiers", "-Wunused-result", "-g", // Generate debug symbols "${workspaceFolder}/c/planarityApp/**.c", "${workspaceFolder}/c/graphLib/**.c", "${workspaceFolder}/c/graphLib/extensionSystem/**.c", "${workspaceFolder}/c/graphLib/homeomorphSearch/**.c", "${workspaceFolder}/c/graphLib/io/**.c", "${workspaceFolder}/c/graphLib/lowLevelUtils/**.c", "${workspaceFolder}/c/graphLib/planarityRelated/**.c", "-o", "${workspaceFolder}/Debug/planarity" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "compiler: /usr/bin/gcc" }, { "type": "cppbuild", "label": "C/C++: (Linux) [Release] gcc build planarity project", "command": "/usr/bin/gcc", "args": [ "-O3", "-fno-strict-aliasing", //"-DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "-pedantic", "-Wall", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wduplicated-branches", "-Wduplicated-cond", "-Werror", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wjump-misses-init", "-Wlogical-op", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wrestrict", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdiscarded-qualifiers", "-Wunused-result", // "-g", // Uncomment to generate debug symbols for Release build and debug with VSCode "${workspaceFolder}/c/planarityApp/**.c", "${workspaceFolder}/c/graphLib/**.c", "${workspaceFolder}/c/graphLib/extensionSystem/**.c", "${workspaceFolder}/c/graphLib/homeomorphSearch/**.c", "${workspaceFolder}/c/graphLib/io/**.c", "${workspaceFolder}/c/graphLib/lowLevelUtils/**.c", "${workspaceFolder}/c/graphLib/planarityRelated/**.c", "-o", "${workspaceFolder}/Release/planarity" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "compiler: /usr/bin/gcc" }, { "type": "cppbuild", "label": "C/C++: (Windows) [Debug] cl.exe build planarity project", "command": "cl.exe", "args": [ "/Od", // Disables optimization, which speeds compilation and simplifies debugging "/Zi", // Generate debug symbols "/EHsc", "/nologo", "/DDEBUG", // Added so that _DEBUG is defined; see appconst.h //"/DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "/Wall", "/wd4464", // Ignore warnings about relative paths "/wd4710", // Ignore warnings about not inlining scanf, sprintf, printf, fprintf, etc. "/wd4711", // Ignore warnings about automatic inline expansion, as we are not concerned with code size "/wd4774", // Ignore warnings about format string not being a string literal; requires developer caution! "/wd4996", // Ignore safety warnings about sprintf, scanf, strcpy, strcat, fopen, etc. "/wd5045", // Ignore Spectre mitigation warning "/wd4068", // Ignore warnings about unrecognized pragmas (like the ones that suppress format-nonliteral warnings) "/wd4100", // Ignore warnings about unused parameters (because they may be used by future function overloads or may help with programming consistency) "/wd4820", // Ignore warnings from antediluvian compiler about padding, e.g., for normal use of bool type "/external:anglebrackets", "/external:W0", // Ignore warnings thrown by external headers "/Fe${workspaceFolder}\\Debug\\planarity.exe", "${workspaceFolder}\\c\\planarityApp\\**.c", "${workspaceFolder}\\c\\graphLib\\**.c", "${workspaceFolder}\\c\\graphLib\\extensionSystem\\**.c", "${workspaceFolder}\\c\\graphLib\\homeomorphSearch\\**.c", "${workspaceFolder}\\c\\graphLib\\io\\**.c", "${workspaceFolder}\\c\\graphLib\\lowLevelUtils\\**.c", "${workspaceFolder}\\c\\graphLib\\planarityRelated\\**.c" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$msCompile" ], "group": { "kind": "build" }, "detail": "Task generated by Debugger." }, { "type": "cppbuild", "label": "C/C++: (Windows) [Release] cl.exe build planarity project", "command": "cl.exe", "args": [ "/O2", // This is the maximum optimization level available for MSVC cl "/nologo", //"/DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "/Wall", "/wd4464", // Ignore warnings about relative paths "/wd4710", // Ignore warnings about not inlining scanf, sprintf, printf, fprintf, etc. "/wd4711", // Ignore warnings about automatic inline expansion, as we are not concerned with code size "/wd4774", // Ignore warnings about format string not being a string literal; requires developer caution! "/wd4996", // Ignore safety warnings about sprintf, scanf, strcpy, strcat, fopen, etc. "/wd5045", // Ignore Spectre mitigation warning "/wd4068", // Ignore warnings about unrecognized pragmas (like the ones that suppress format-nonliteral warnings) "/wd4100", // Ignore warnings about unused parameters (because they may be used by future function overloads or may help with programming consistency) "/wd4820", // Ignore warnings from antediluvian compiler about padding, e.g., for normal use of bool type "/external:anglebrackets", "/external:W0", // Ignore warnings thrown by external headers "/Fe${workspaceFolder}\\Release\\planarity.exe", "${workspaceFolder}\\c\\planarityApp\\**.c", "${workspaceFolder}\\c\\graphLib\\**.c", "${workspaceFolder}\\c\\graphLib\\extensionSystem\\**.c", "${workspaceFolder}\\c\\graphLib\\homeomorphSearch\\**.c", "${workspaceFolder}\\c\\graphLib\\io\\**.c", "${workspaceFolder}\\c\\graphLib\\lowLevelUtils\\**.c", "${workspaceFolder}\\c\\graphLib\\planarityRelated\\**.c" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$msCompile" ], "group": { "kind": "build" }, "detail": "Task generated by Debugger." }, { "type": "cppbuild", "label": "C/C++: (MacOS) [Debug] clang build planarity project", "command": "clang", "args": [ "-fcolor-diagnostics", "-fansi-escape-codes", "-fno-strict-aliasing", "-DDEBUG", // Added so that _DEBUG is defined; see appconst.h //"-DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "-Wall", "-pedantic", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdeprecated-non-prototype", "-Wincompatible-pointer-types-discards-qualifiers", "-Wignored-qualifiers", "-g", // Generate debug symbols "${workspaceFolder}/c/planarityApp/**.c", "${workspaceFolder}/c/graphLib/**.c", "${workspaceFolder}/c/graphLib/extensionSystem/**.c", "${workspaceFolder}/c/graphLib/homeomorphSearch/**.c", "${workspaceFolder}/c/graphLib/io/**.c", "${workspaceFolder}/c/graphLib/lowLevelUtils/**.c", "${workspaceFolder}/c/graphLib/planarityRelated/**.c", "-o", "${workspaceFolder}/Debug/planarity" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "Task generated by Debugger." }, { "type": "cppbuild", "label": "C/C++: (MacOS) [Release] clang build planarity project", "command": "clang", "args": [ "-O3", "-fno-strict-aliasing", //"-DUSE_0BASEDARRAYS", // Commented out to use default of 1-based array indexing "-Wall", "-pedantic", "-Warray-bounds", "-Wcast-align", "-Wdeclaration-after-statement", "-Wdouble-promotion", "-Wextra", "-Wformat-nonliteral", "-Wformat-security", "-Wformat=2", "-Wimplicit-function-declaration", "-Winit-self", "-Winline", "-Wmissing-declarations", "-Wmissing-format-attribute", "-Wmissing-include-dirs", "-Wmissing-noreturn", "-Wmissing-prototypes", "-Wnested-externs", "-Wno-error=missing-field-initializers", "-Wno-error=unused-parameter", "-Wno-missing-field-initializers", "-Wno-unused-parameter", "-Wnull-dereference", "-Wold-style-definition", "-Wpacked", "-Wpointer-arith", "-Wredundant-decls", "-Wreturn-type", "-Wshadow", "-Wsign-compare", "-Wstrict-aliasing", "-Wstrict-prototypes", "-Wswitch-default", "-Wswitch-enum", "-Wundef", "-Wunused-but-set-variable", "-Wwrite-strings", "-Wdeprecated-non-prototype", "-Wincompatible-pointer-types-discards-qualifiers", "-Wignored-qualifiers", // "-g", // Uncomment to generate debug symbols for Release build and debug with VSCode "${workspaceFolder}/c/planarityApp/**.c", "${workspaceFolder}/c/graphLib/**.c", "${workspaceFolder}/c/graphLib/extensionSystem/**.c", "${workspaceFolder}/c/graphLib/homeomorphSearch/**.c", "${workspaceFolder}/c/graphLib/io/**.c", "${workspaceFolder}/c/graphLib/lowLevelUtils/**.c", "${workspaceFolder}/c/graphLib/planarityRelated/**.c", "-o", "${workspaceFolder}/Release/planarity" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": "build", "detail": "Task generated by Debugger." } ], "version": "2.0.0" } edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/TestSupport/000077500000000000000000000000001521450711600301055ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/TestSupport/planaritytesting/000077500000000000000000000000001521450711600335065ustar00rootroot00000000000000.pylintrc000066400000000000000000000525021521450711600353000ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/TestSupport/planaritytesting[MAIN] # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Clear in-memory caches upon conclusion of linting. Useful if running pylint # in a server-like mode. clear-cache-post-run=no # Load and enable all available extensions. Use --list-extensions to see a list # all available extensions. #enable-all-extensions= # In error mode, messages with a category besides ERROR or FATAL are # suppressed, and no reports are done by default. Error mode is compatible with # disabling specific errors. #errors-only= # Always return a 0 (non-error) status code, even if lint errors are found. # This is primarily useful in continuous integration scripts. #exit-zero= # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. extension-pkg-allow-list= # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. (This is an alternative name to extension-pkg-allow-list # for backward compatibility.) extension-pkg-whitelist= # Return non-zero exit code if any of these messages/categories are detected, # even if score is above --fail-under value. Syntax same as enable. Messages # specified are enabled, while categories only check already-enabled messages. fail-on= # Specify a score threshold under which the program will exit with error. fail-under=10 # Interpret the stdin as a python script, whose filename needs to be passed as # the module_or_package argument. #from-stdin= # Files or directories to be skipped. They should be base names, not paths. ignore=CVS # Add files or directories matching the regular expressions patterns to the # ignore-list. The regex matches against paths and can be in Posix or Windows # format. Because '\\' represents the directory delimiter on Windows systems, # it can't be used as an escape character. ignore-paths= # Files or directories matching the regular expression patterns are skipped. # The regex matches against base names, not paths. The default value ignores # Emacs file locks ignore-patterns=^\.# # List of module names for which member attributes should not be checked and # will not be imported (useful for modules/projects where namespaces are # manipulated during runtime and thus existing member attributes cannot be # deduced by static analysis). It supports qualified module names, as well as # Unix pattern matching. ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use, and will cap the count on Windows to # avoid hangs. jobs=1 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or # complex, nested conditions. limit-inference-results=100 # List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Resolve imports to .pyi stubs if available. May reduce no-member messages and # increase not-an-iterable messages. ; prefer-stubs=no ; pylint throws unrecognized command error??? # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. py-version=3.11 # Discover python modules and packages in the file system subtree. recursive=no # Add paths to the list of the source roots. Supports globbing patterns. The # source root is an absolute path or a path relative to the current working # directory used to determine a package namespace for modules located under the # source root. source-roots= # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # In verbose mode, extra non-checker-related info will be displayed. #verbose= [BASIC] # Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. If left empty, argument names will be checked with the set # naming style. #argument-rgx= # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- # style. If left empty, attribute names will be checked with the set naming # style. #attr-rgx= # Bad variable names which should always be refused, separated by a comma. bad-names=foo, bar, baz, toto, tutu, tata # Bad variable names regexes, separated by a comma. If names match any regex, # they will always be refused bad-names-rgxs= # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- # attribute-naming-style. If left empty, class attribute names will be checked # with the set naming style. #class-attribute-rgx= # Naming style matching correct class constant names. class-const-naming-style=UPPER_CASE # Regular expression matching correct class constant names. Overrides class- # const-naming-style. If left empty, class constant names will be checked with # the set naming style. #class-const-rgx= # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- # style. If left empty, class names will be checked with the set naming style. #class-rgx= # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- # style. If left empty, constant names will be checked with the set naming # style. #const-rgx= # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- # naming-style. If left empty, function names will be checked with the set # naming style. #function-rgx= # Good variable names which should always be accepted, separated by a comma. good-names=i, j, k, ex, Run, _ # Good variable names regexes, separated by a comma. If names match any regex, # they will always be accepted good-names-rgxs= # Include a hint for the correct naming format with invalid-name. include-naming-hint=no # Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides # inlinevar-naming-style. If left empty, inline iteration names will be checked # with the set naming style. #inlinevar-rgx= # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- # style. If left empty, method names will be checked with the set naming style. #method-rgx= # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- # style. If left empty, module names will be checked with the set naming style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty # Regular expression matching correct type alias names. If left empty, type # alias names will be checked with the set naming style. #typealias-rgx= # Regular expression matching correct type variable names. If left empty, type # variable names will be checked with the set naming style. #typevar-rgx= # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- # naming-style. If left empty, variable names will be checked with the set # naming style. #variable-rgx= [CLASSES] # Warn about protected attribute access inside special methods check-protected-access-in-special-methods=no # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp, asyncSetUp, __post_init__ # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [DESIGN] # List of regular expressions of class ancestor names to ignore when counting # public methods (see R0903) exclude-too-few-public-methods= # List of qualified class names to ignore when counting class parents (see # R0901) ignored-parents= # Maximum number of arguments for function / method. max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of branch for function / method body. max-branches=12 # Maximum number of locals for function / method body. max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of return / yield for function / method body. max-returns=6 # Maximum number of statements in function / method body. max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. overgeneral-exceptions=builtins.BaseException,builtins.Exception [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [IMPORTS] # List of modules that can be imported at any level, not just the top level # one. allow-any-import-level= # Allow explicit reexports by alias from a package __init__. allow-reexport-from-package=no # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Deprecated modules which should not be used, separated by a comma. deprecated-modules= # Output a graph (.gv or any supported image format) of external dependencies # to the given file (report RP0402 must not be disabled). ext-import-graph= # Output a graph (.gv or any supported image format) of all (i.e. internal and # external) dependencies to the given file (report RP0402 must not be # disabled). import-graph= # Output a graph (.gv or any supported image format) of internal dependencies # to the given file (report RP0402 must not be disabled). int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Couples of modules and preferred modules, separated by a comma. preferred-modules= [LOGGING] # The type of string formatting that logging methods do. `old` means using % # formatting, `new` is for `{}` formatting. logging-format-style=old # Logging modules to check that the string format arguments are in logging # function parameter format. logging-modules=logging [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, # UNDEFINED. confidence=HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once). You can also use "--disable=all" to # disable everything first and then re-enable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=raw-checker-failed, bad-inline-option, locally-disabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, use-symbolic-message-instead, use-implicit-booleaness-not-comparison-to-string, use-implicit-booleaness-not-comparison-to-zero, invalid-name # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable= [METHOD_ARGS] # List of qualified names (i.e., library.method) which require a timeout # parameter e.g. 'requests.api.get,requests.api.post' timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX, TODO # Regular expression of note tags to take in consideration. notes-rgx= [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=sys.exit,argparse.parse_error # Let 'consider-using-join' be raised when the separator to join on would be # non-empty (resulting in expected fixes of the type: ``"- " + " - # ".join(items)``) ; suggest-join-with-non-empty-separator=yes ; pylint throws unrecognized command error??? [REPORTS] # Python expression which should return a score less than or equal to 10. You # have access to the variables 'fatal', 'error', 'warning', 'refactor', # 'convention', and 'info' which contain the number of messages in each # category, as well as 'statement' which is the total number of statements # analyzed. This score is used by the global evaluation report (RP0004). evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. msg-template= # Set the output format. Available formats are: text, parseable, colorized, # json2 (improved json format), json (old json format) and msvs (visual # studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. #output-format= # Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. score=yes [SIMILARITIES] # Comments are removed from the similarity computation ignore-comments=yes # Docstrings are removed from the similarity computation ignore-docstrings=yes # Imports are removed from the similarity computation ignore-imports=yes # Signatures are removed from the similarity computation ignore-signatures=yes # Minimum lines number of a similarity. min-similarity-lines=4 [SPELLING] # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=4 # Spelling dictionary name. No available dictionaries : You need to install # both the python package and the system dependency for enchant to work. spelling-dict= # List of comma separated words that should be considered directives if they # appear at the beginning of a comment and should not be checked. spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains the private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to the private dictionary (see the # --spelling-private-dict-file option) instead of raising a message. spelling-store-unknown-words=no [STRING] # This flag controls whether inconsistent-quotes generates a warning when the # character used as a quote delimiter is used inconsistently within a module. check-quote-consistency=no # This flag controls whether the implicit-str-concat should generate a warning # on implicit string concatenation in sequences defined over several lines. check-str-concat-over-line-jumps=no [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. ignore-none=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of symbolic message names to ignore for Mixin members. ignored-checks-for-mixins=no-member, not-async-context-manager, not-context-manager, attribute-defined-outside-init # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 # Regex pattern to define which classes are considered mixins. mixin-class-rgx=.*[Mm]ixin # List of decorators that change the signature of a decorated function. signature-mutators= [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid defining new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of names allowed to shadow builtins allowed-redefined-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expected to # not be used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io leaksorchestrator/000077500000000000000000000000001521450711600371665ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/TestSupport/planaritytestingplanarity_leaks_config.ini000066400000000000000000000011601521450711600443740ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/TestSupport/planaritytesting/leaksorchestrator[RandomGraphs] enabled = False perform_full_analysis = True num_graphs = 500 order = 1000 commands_to_run = [RandomMaxPlanarGraphGenerator] enabled = False perform_full_analysis = True order = 1000 [SpecificGraph] enabled = False perform_full_analysis = True infile_path = >>CHANGEME<< commands_to_run = [RandomNonplanarGraphGenerator] enabled = False perform_full_analysis = True order = 1000 [TransformGraph] enabled = False perform_full_analysis = True infile_path = >>CHANGEME<< output_formats_to_test = [TestAllGraphs] enabled = False perform_full_analysis = True infile_path = >>CHANGEME<< commands_to_run = edge-addition-planarity-suite-Version_5.0.0.0/devEnvSetupAndDefaults/devEnvSetup.sh000077500000000000000000000012371521450711600304030ustar00rootroot00000000000000#!/usr/bin/env bash echo -e "Copying VSCode configuration files.\n" cp -R .vscode ../ echo -e "Copying Python TestSupport pylint configuration file.\n" cp TestSupport/planaritytesting/.pylintrc ../TestSupport/planaritytesting/ echo -e "Copying planarity leaks orchestrator default configuration file.\n" cp TestSupport/planaritytesting/leaksorchestrator/planarity_leaks_config.ini ../TestSupport/planaritytesting/leaksorchestrator/ echo -e "Making directories for Random Graphs output.\n" mkdir -p ../random mkdir -p ../embedded mkdir -p ../adjlist mkdir -p ../obstructed mkdir -p ../error echo -e "Making m4 directory for autotools build process.\n" mkdir -p ../m4 edge-addition-planarity-suite-Version_5.0.0.0/libplanarity.pc.in000066400000000000000000000004711521450711600246330ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: @PACKAGE_NAME@ URL: https://github.com/graph-algorithms/edge-addition-planarity-suite Description: Edge Addition Planarity Suite Graph Library Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lplanarity Cflags: -I${includedir} edge-addition-planarity-suite-Version_5.0.0.0/m4/000077500000000000000000000000001521450711600215265ustar00rootroot00000000000000edge-addition-planarity-suite-Version_5.0.0.0/m4/ax_append_compile_flags.m4000066400000000000000000000033451521450711600266200ustar00rootroot00000000000000# ============================================================================ # https://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html # ============================================================================ # # SYNOPSIS # # AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # For every FLAG1, FLAG2 it is checked whether the compiler works with the # flag. If it does, the flag is added FLAGS-VARIABLE # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. During the check the flag is always added to the # current language's flags. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: This macro depends on the AX_APPEND_FLAG and # AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with # AX_APPEND_LINK_FLAGS. # # LICENSE # # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 7 AC_DEFUN([AX_APPEND_COMPILE_FLAGS], [AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) for flag in $1; do AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3], [$4]) done ])dnl AX_APPEND_COMPILE_FLAGS edge-addition-planarity-suite-Version_5.0.0.0/m4/ax_append_flag.m4000066400000000000000000000030201521450711600247130ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_append_flag.html # =========================================================================== # # SYNOPSIS # # AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) # # DESCRIPTION # # FLAG is appended to the FLAGS-VARIABLE shell variable, with a space # added in between. # # If FLAGS-VARIABLE is not specified, the current language's flags (e.g. # CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains # FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly # FLAG. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([AX_APPEND_FLAG], [dnl AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) AS_VAR_SET_IF(FLAGS,[ AS_CASE([" AS_VAR_GET(FLAGS) "], [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], [ AS_VAR_APPEND(FLAGS,[" $1"]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) ], [ AS_VAR_SET(FLAGS,[$1]) AC_RUN_LOG([: FLAGS="$FLAGS"]) ]) AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_APPEND_FLAG edge-addition-planarity-suite-Version_5.0.0.0/m4/ax_check_compile_flag.m4000066400000000000000000000046601521450711600262440ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether the _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS if test x"m4_case(_AC_LANG, [C], [$GCC], [C++], [$GXX], [Fortran], [$GFC], [Fortran 77], [$G77], [Objective C], [$GOBJC], [Objective C++], [$GOBJCXX], [no])" = xyes ; then add_gnu_werror="-Werror" fi _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1 $add_gnu_werror" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS edge-addition-planarity-suite-Version_5.0.0.0/m4/ax_compiler_flags_cflags.m4000066400000000000000000000132311521450711600267650ustar00rootroot00000000000000# ============================================================================= # https://www.gnu.org/software/autoconf-archive/ax_compiler_flags_cflags.html # ============================================================================= # # SYNOPSIS # # AX_COMPILER_FLAGS_CFLAGS([VARIABLE], [IS-RELEASE], [EXTRA-BASE-FLAGS], [EXTRA-YES-FLAGS]) # # DESCRIPTION # # Add warning flags for the C compiler to VARIABLE, which defaults to # WARN_CFLAGS. VARIABLE is AC_SUBST-ed by this macro, but must be # manually added to the CFLAGS variable for each target in the code base. # # This macro depends on the environment set up by AX_COMPILER_FLAGS. # Specifically, it uses the value of $ax_enable_compile_warnings to decide # which flags to enable. # # LICENSE # # Copyright (c) 2014, 2015 Philip Withnall # Copyright (c) 2017, 2018 Reini Urban # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 17 AC_DEFUN([AX_COMPILER_FLAGS_CFLAGS],[ AC_REQUIRE([AC_PROG_SED]) AX_REQUIRE_DEFINED([AX_APPEND_COMPILE_FLAGS]) AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) # Variable names m4_define([ax_warn_cflags_variable], [m4_normalize(ifelse([$1],,[WARN_CFLAGS],[$1]))]) AC_LANG_PUSH([C]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ [#ifndef __cplusplus #error "no C++" #endif]])], [ax_compiler_cxx=yes;], [ax_compiler_cxx=no;]) # Always pass -Werror=unknown-warning-option to get Clang to fail on bad # flags, otherwise they are always appended to the warn_cflags variable, and # Clang warns on them for every compilation unit. # If this is passed to GCC, it will explode, so the flag must be enabled # conditionally. AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option],[ ax_compiler_flags_test="-Werror=unknown-warning-option" ],[ ax_compiler_flags_test="" ]) # Check that -Wno-suggest-attribute=format is supported AX_CHECK_COMPILE_FLAG([-Wno-suggest-attribute=format],[ ax_compiler_no_suggest_attribute_flags="-Wno-suggest-attribute=format" ],[ ax_compiler_no_suggest_attribute_flags="" ]) # Base flags AX_APPEND_COMPILE_FLAGS([ dnl -fno-strict-aliasing dnl $3 dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) AS_IF([test "$ax_enable_compile_warnings" != "no"],[ if test "$ax_compiler_cxx" = "no" ; then # C-only flags. Warn in C++ AX_APPEND_COMPILE_FLAGS([ dnl -Wnested-externs dnl -Wmissing-prototypes dnl -Wstrict-prototypes dnl -Wdeclaration-after-statement dnl -Wimplicit-function-declaration dnl -Wold-style-definition dnl -Wjump-misses-init dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) fi # "yes" flags AX_APPEND_COMPILE_FLAGS([ dnl -Wall dnl -Wextra dnl -Wundef dnl -Wwrite-strings dnl -Wpointer-arith dnl -Wmissing-declarations dnl -Wredundant-decls dnl -Wno-unused-parameter dnl -Wno-missing-field-initializers dnl -Wformat=2 dnl -Wcast-align dnl -Wformat-nonliteral dnl -Wformat-security dnl -Wsign-compare dnl -Wstrict-aliasing dnl -Wshadow dnl -Winline dnl -Wpacked dnl -Wmissing-format-attribute dnl -Wmissing-noreturn dnl -Winit-self dnl -Wredundant-decls dnl -Wmissing-include-dirs dnl -Wunused-but-set-variable dnl -Warray-bounds dnl -Wreturn-type dnl -Wswitch-enum dnl -Wswitch-default dnl -Wduplicated-cond dnl -Wduplicated-branches dnl -Wlogical-op dnl -Wrestrict dnl -Wnull-dereference dnl -Wdouble-promotion dnl $4 dnl $5 dnl $6 dnl $7 dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) ]) AS_IF([test "$ax_enable_compile_warnings" = "error"],[ # "error" flags; -Werror has to be appended unconditionally because # it's not possible to test for # # suggest-attribute=format is disabled because it gives too many false # positives AX_APPEND_FLAG([-Werror],ax_warn_cflags_variable) AX_APPEND_COMPILE_FLAGS([ dnl [$ax_compiler_no_suggest_attribute_flags] dnl ],ax_warn_cflags_variable,[$ax_compiler_flags_test]) ]) # In the flags below, when disabling specific flags, always add *both* # -Wno-foo and -Wno-error=foo. This fixes the situation where (for example) # we enable -Werror, disable a flag, and a build bot passes CFLAGS=-Wall, # which effectively turns that flag back on again as an error. for flag in $ax_warn_cflags_variable; do AS_CASE([$flag], [-Wno-*=*],[], [-Wno-*],[ AX_APPEND_COMPILE_FLAGS([-Wno-error=$(AS_ECHO([$flag]) | $SED 's/^-Wno-//')], ax_warn_cflags_variable, [$ax_compiler_flags_test]) ]) done AC_LANG_POP([C]) # Substitute the variables AC_SUBST(ax_warn_cflags_variable) ])dnl AX_COMPILER_FLAGS edge-addition-planarity-suite-Version_5.0.0.0/m4/ax_require_defined.m4000066400000000000000000000023021521450711600256070ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED edge-addition-planarity-suite-Version_5.0.0.0/test-samples.sh.in000077500000000000000000000004331521450711600245730ustar00rootroot00000000000000#!/bin/sh # # Run "planarity -test" using planarity from the build tree and the # samples from the source tree. # samplesdir="@abs_top_srcdir@/c/samples" planaritydir="@abs_top_builddir@" cd "${planaritydir}" || exit 1 ./planarity -test "${samplesdir}" || exit 2