pax_global_header 0000666 0000000 0000000 00000000064 15214507116 0014514 g ustar 00root root 0000000 0000000 52 comment=20002307cdff6f566b2bff87ade13e3d04e531d3
edge-addition-planarity-suite-Version_5.0.0.0/ 0000775 0000000 0000000 00000000000 15214507116 0021206 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/.editorconfig 0000664 0000000 0000000 00000000362 15214507116 0023664 0 ustar 00root root 0000000 0000000 # 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/.gitattributes 0000664 0000000 0000000 00000000243 15214507116 0024100 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15214507116 0022546 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15214507116 0024731 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000002047 15214507116 0027426 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000000034 15214507116 0026716 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
edge-addition-planarity-suite-Version_5.0.0.0/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000001105 15214507116 0030453 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000000763 15214507116 0025404 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000002247 15214507116 0027514 0 ustar 00root root 0000000 0000000 Resolves #__**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/ 0000775 0000000 0000000 00000000000 15214507116 0024603 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/.github/workflows/compile-warnings.yml 0000664 0000000 0000000 00000001662 15214507116 0030611 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000003451 15214507116 0031011 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000014611 15214507116 0030114 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000002444 15214507116 0027221 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000004011 15214507116 0027506 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000000514 15214507116 0027143 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000002723 15214507116 0023202 0 ustar 00root root 0000000 0000000 # 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.TXT 0000664 0000000 0000000 00000005045 15214507116 0022675 0 ustar 00root root 0000000 0000000 The 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.am 0000664 0000000 0000000 00000007533 15214507116 0023252 0 ustar 00root root 0000000 0000000 SUBDIRS = 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.md 0000664 0000000 0000000 00000016750 15214507116 0022476 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15214507116 0023522 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/generate_graphs.bat 0000664 0000000 0000000 00000001130 15214507116 0027343 0 ustar 00root root 0000000 0000000 @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.bat 0000664 0000000 0000000 00000001404 15214507116 0027605 0 ustar 00root root 0000000 0000000 @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/ 0000775 0000000 0000000 00000000000 15214507116 0027123 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/planaritytesting/__init__.py 0000664 0000000 0000000 00000001517 15214507116 0031240 0 ustar 00root root 0000000 0000000 """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.py0000664 0000000 0000000 00000133703 15214507116 0034176 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000057464 15214507116 0032350 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000125666 15214507116 0036614 0 ustar 00root root 0000000 0000000 edge-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.py 0000664 0000000 0000000 00000025535 15214507116 0030610 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000015764 15214507116 0035546 0 ustar 00root root 0000000 0000000 edge-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/ 0000775 0000000 0000000 00000000000 15214507116 0032662 5 ustar 00root root 0000000 0000000 planarity_leaks_config_manager.py 0000664 0000000 0000000 00000015412 15214507116 0041361 0 ustar 00root root 0000000 0000000 edge-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.py 0000664 0000000 0000000 00000110520 15214507116 0041135 0 ustar 00root root 0000000 0000000 edge-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.py 0000664 0000000 0000000 00000032220 15214507116 0037054 0 ustar 00root root 0000000 0000000 edge-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.py 0000664 0000000 0000000 00000023756 15214507116 0037436 0 ustar 00root root 0000000 0000000 edge-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.py0000664 0000000 0000000 00000012744 15214507116 0034326 0 ustar 00root root 0000000 0000000 """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.py 0000664 0000000 0000000 00000137236 15214507116 0037446 0 ustar 00root root 0000000 0000000 edge-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.py 0000664 0000000 0000000 00000056014 15214507116 0033677 0 ustar 00root root 0000000 0000000 """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/ 0000775 0000000 0000000 00000000000 15214507116 0024774 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/ 0000775 0000000 0000000 00000000000 15214507116 0025214 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/10/n10.mALL.2.out.txt 0000664 0000000 0000000 00000015220 15214507116 0030105 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000015364 15214507116 0030117 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000015220 15214507116 0030107 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000010624 15214507116 0030172 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000010460 15214507116 0030203 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000010624 15214507116 0030206 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025215 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/11/n11.mALL.2.out.txt 0000664 0000000 0000000 00000012524 15214507116 0030113 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000014044 15214507116 0030113 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000012524 15214507116 0030115 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000012714 15214507116 0030176 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000012524 15214507116 0030210 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000012714 15214507116 0030212 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025140 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/5/n5.mALL.2.out.txt 0000664 0000000 0000000 00000003751 15214507116 0027763 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000004045 15214507116 0027761 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000003751 15214507116 0027765 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000002431 15214507116 0030037 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000002431 15214507116 0030052 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000002431 15214507116 0030053 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025141 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/6/n6.mALL.2.out.txt 0000664 0000000 0000000 00000005214 15214507116 0027761 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000005214 15214507116 0027762 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000005214 15214507116 0027763 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000003314 15214507116 0030042 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000003314 15214507116 0030055 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000003314 15214507116 0030056 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025142 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/7/n7.mALL.2.out.txt 0000664 0000000 0000000 00000006666 15214507116 0027777 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000006666 15214507116 0030000 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000006666 15214507116 0030001 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000004326 15214507116 0030050 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000004326 15214507116 0030063 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000004326 15214507116 0030064 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025143 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/8/n8.mALL.2.out.txt 0000664 0000000 0000000 00000010547 15214507116 0027772 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000010547 15214507116 0027773 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000010547 15214507116 0027774 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000005467 15214507116 0030061 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000005467 15214507116 0030074 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000006242 15214507116 0030065 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0025144 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/TestSupport/tables/9/n9.mALL.2.out.txt 0000664 0000000 0000000 00000012637 15214507116 0027776 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000013542 15214507116 0027773 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000012637 15214507116 0030000 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000007030 15214507116 0030047 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000006757 15214507116 0030101 0 ustar 00root root 0000000 0000000 | 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.txt 0000664 0000000 0000000 00000007030 15214507116 0030063 0 ustar 00root root 0000000 0000000 | 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/ 0000775 0000000 0000000 00000000000 15214507116 0021430 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/.gdbinit 0000664 0000000 0000000 00000000023 15214507116 0023044 0 ustar 00root root 0000000 0000000 set new-console on
edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib.h 0000664 0000000 0000000 00000001234 15214507116 0023331 0 ustar 00root root 0000000 0000000 #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/ 0000775 0000000 0000000 00000000000 15214507116 0023160 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/ 0000775 0000000 0000000 00000000000 15214507116 0026401 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/extensionSystem/graphExtensions.c 0000664 0000000 0000000 00000053432 15214507116 0031735 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001732 15214507116 0031736 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000001310 15214507116 0033377 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000004300 15214507116 0032326 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000323603 15214507116 0024434 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000056364 15214507116 0024450 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000027073 15214507116 0026114 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000053644 15214507116 0026017 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000010654 15214507116 0026016 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000003557 15214507116 0027473 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000002450 15214507116 0025055 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000004515 15214507116 0025066 0 ustar 00root root 0000000 0000000 #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/ 0000775 0000000 0000000 00000000000 15214507116 0026463 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/homeomorphSearch/graphK23Search.c 0000664 0000000 0000000 00000022650 15214507116 0031343 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001127 15214507116 0031344 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000000753 15214507116 0033021 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000023045 15214507116 0033502 0 ustar 00root root 0000000 0000000 edge-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.c 0000664 0000000 0000000 00000236536 15214507116 0031356 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001114 15214507116 0031341 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000002717 15214507116 0033024 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000075633 15214507116 0033515 0 ustar 00root root 0000000 0000000 edge-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.c 0000664 0000000 0000000 00000166057 15214507116 0031274 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001114 15214507116 0031257 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000003201 15214507116 0032727 0 ustar 00root root 0000000 0000000 #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.c0000664 0000000 0000000 00000051753 15214507116 0033507 0 ustar 00root root 0000000 0000000 /*
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/ 0000775 0000000 0000000 00000000000 15214507116 0023567 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/io/g6-api-utilities.c 0000664 0000000 0000000 00000012211 15214507116 0027024 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000054741 15214507116 0027022 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001537 15214507116 0027022 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000051456 15214507116 0027241 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001462 15214507116 0027236 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000113077 15214507116 0025275 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001634 15214507116 0025275 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000060570 15214507116 0025654 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000003146 15214507116 0025655 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000016544 15214507116 0025252 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000003357 15214507116 0025255 0 ustar 00root root 0000000 0000000 /*
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/ 0000775 0000000 0000000 00000000000 15214507116 0025772 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/lowLevelUtils/apiutils.c 0000664 0000000 0000000 00000012433 15214507116 0027773 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000003401 15214507116 0027773 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000003036 15214507116 0031450 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000004133 15214507116 0027773 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000022311 15214507116 0027762 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000012561 15214507116 0027775 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000010267 15214507116 0027251 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000010545 15214507116 0027255 0 ustar 00root root 0000000 0000000 /*
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/ 0000775 0000000 0000000 00000000000 15214507116 0026464 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/graphLib/planarityRelated/graphDrawPlanar.c 0000664 0000000 0000000 00000135733 15214507116 0031721 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000001717 15214507116 0031720 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000006657 15214507116 0033401 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000067672 15214507116 0034067 0 ustar 00root root 0000000 0000000 edge-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, "%s>\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.c 0000664 0000000 0000000 00000202735 15214507116 0030677 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000074122 15214507116 0031454 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000104241 15214507116 0031604 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000016311 15214507116 0034325 0 ustar 00root root 0000000 0000000 edge-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.h 0000664 0000000 0000000 00000001545 15214507116 0032706 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000000600 15214507116 0034267 0 ustar 00root root 0000000 0000000 edge-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.c 0000664 0000000 0000000 00000004210 15214507116 0035031 0 ustar 00root root 0000000 0000000 edge-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.h 0000664 0000000 0000000 00000005427 15214507116 0031632 0 ustar 00root root 0000000 0000000 #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.h 0000664 0000000 0000000 00000042027 15214507116 0033300 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000004123 15214507116 0033755 0 ustar 00root root 0000000 0000000 edge-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.c 0000664 0000000 0000000 00000110250 15214507116 0030753 0 ustar 00root root 0000000 0000000 /*
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/ 0000775 0000000 0000000 00000000000 15214507116 0024074 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/planarityApp/planarity.1 0000664 0000000 0000000 00000007661 15214507116 0026173 0 ustar 00root root 0000000 0000000 .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.c 0000664 0000000 0000000 00000002070 15214507116 0026242 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000006152 15214507116 0026254 0 ustar 00root root 0000000 0000000 #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.c 0000664 0000000 0000000 00000076126 15214507116 0030366 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000012717 15214507116 0027064 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000037446 15214507116 0027106 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000063333 15214507116 0030561 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000024154 15214507116 0030701 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000041653 15214507116 0030712 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000007034 15214507116 0031125 0 ustar 00root root 0000000 0000000 /*
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.c 0000664 0000000 0000000 00000104152 15214507116 0027267 0 ustar 00root root 0000000 0000000 /*
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.h 0000664 0000000 0000000 00000002742 15214507116 0026715 0 ustar 00root root 0000000 0000000 #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/ 0000775 0000000 0000000 00000000000 15214507116 0023074 5 ustar 00root root 0000000 0000000 edge-addition-planarity-suite-Version_5.0.0.0/c/samples/K10.g6 0000664 0000000 0000000 00000000024 15214507116 0023661 0 ustar 00root root 0000000 0000000 >>graph6<