diff --git a/.drone.jsonnet b/.drone.jsonnet index 18bcf22..8bf2a2a 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,7 +1,7 @@ local image = "localhost:5000/jla/mod"; local boostArg(v) = "-DBOOST_ROOT=/opt/boost/%s" % v; -local CoverageStep(withCoverage, compiler, boost) = if !withCoverage then null else { +local CoverageStep(withCoverage, compiler, boost) = if !withCoverage then [] else [{ name: "coverage", image: image, environment: { @@ -10,9 +10,10 @@ local CoverageStep(withCoverage, compiler, boost) = if !withCoverage then null e CXXFLAGS: "-Werror", }, commands: [ + "bindep testing", "mkdir covBuild", "cd covBuild", - "cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_TESTING=on -DBUILD_COVERAGE=on %s" % [boostArg(boost)], + "cmake ../ -DENABLE_IPO=off -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_TESTING=on -DBUILD_COVERAGE=on %s" % [boostArg(boost)], "make", "make install", "make tests", @@ -33,7 +34,7 @@ local CoverageStep(withCoverage, compiler, boost) = if !withCoverage then null e path: "/www", }, ], -}; +}]; local Volumes(withCoverage) = if !withCoverage then [] else [ { @@ -63,9 +64,10 @@ local Configure(compiler, boost, dep=false) = { CXXFLAGS: "-Werror", }, commands: [ + "bindep testing", "mkdir build", "cd build", - "cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off %s" % [boostArg(boost)], + "cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off %s" % [boostArg(boost)], ], [ if dep then "depends_on"]: [ "bootstrap" ], }; @@ -136,8 +138,7 @@ local Pipeline(withCoverage, compiler, boost) = { ] }, }, - CoverageStep(withCoverage, compiler, boost), - ], + ] + CoverageStep(withCoverage, compiler, boost), volumes: Volumes(withCoverage), }; @@ -177,7 +178,7 @@ local Pipeline(withCoverage, compiler, boost) = { name: "Docker", steps: [ Bootstrap(false), - Configure("g++", "1_75_0", false), + Configure("g++", "1_80_0", false), { name: "dist", image: image, @@ -229,12 +230,14 @@ local Pipeline(withCoverage, compiler, boost) = { ] }, ] + [ - Pipeline(boost == "1_74_0" && compiler == "g++-9", compiler, boost) + Pipeline(boost == "1_80_0" && compiler == "g++-11", compiler, boost) for compiler in [ "g++-8", "g++-9", "g++-10", "g++-11", - "clang++-8", "clang++-9", "clang++-10", "clang++-11", "clang++-12", + "clang++-8", + #"clang++-9", + "clang++-10", "clang++-11", "clang++-12", ] for boost in [ - "1_73_0", "1_74_0", "1_75_0", "1_76_0", + "1_76_0", "1_77_0", "1_78_0", "1_79_0", "1_80_0", ] ] diff --git a/.drone.yml b/.drone.yml index a5b0f59..468f285 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,9 +47,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: g++ CXXFLAGS: -Werror @@ -98,7 +99,7 @@ steps: --- kind: pipeline -name: g++-8, Boost 1_73_0 +name: g++-8, Boost 1_76_0 platform: os: linux @@ -115,9 +116,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: CXX: g++-8 CXXFLAGS: -Werror @@ -126,7 +128,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 - make - name: install @@ -170,7 +172,7 @@ steps: --- kind: pipeline -name: g++-8, Boost 1_74_0 +name: g++-8, Boost 1_77_0 platform: os: linux @@ -187,9 +189,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: g++-8 CXXFLAGS: -Werror @@ -198,7 +201,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -242,7 +245,7 @@ steps: --- kind: pipeline -name: g++-8, Boost 1_75_0 +name: g++-8, Boost 1_78_0 platform: os: linux @@ -259,9 +262,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: g++-8 CXXFLAGS: -Werror @@ -270,7 +274,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -314,7 +318,7 @@ steps: --- kind: pipeline -name: g++-8, Boost 1_76_0 +name: g++-8, Boost 1_79_0 platform: os: linux @@ -331,9 +335,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: g++-8 CXXFLAGS: -Werror @@ -342,7 +347,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -386,7 +391,7 @@ steps: --- kind: pipeline -name: g++-9, Boost 1_73_0 +name: g++-8, Boost 1_80_0 platform: os: linux @@ -403,18 +408,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: - CXX: g++-9 + CXX: g++-8 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install @@ -458,7 +464,7 @@ steps: --- kind: pipeline -name: g++-9, Boost 1_74_0 +name: g++-9, Boost 1_76_0 platform: os: linux @@ -475,9 +481,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: CXX: g++-9 CXXFLAGS: -Werror @@ -486,7 +493,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 - make - name: install @@ -528,39 +535,155 @@ steps: - refs/heads/master - refs/tags/v* -- name: coverage +--- +kind: pipeline +name: g++-9, Boost 1_77_0 + +platform: + os: linux + arch: amd64 + +steps: +- name: bootstrap image: localhost:5000/jla/mod commands: - - mkdir covBuild - - cd covBuild - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_TESTING=on -DBUILD_COVERAGE=on -DBOOST_ROOT=/opt/boost/1_74_0 + - git fetch --tags + - git submodule update --init --recursive + - ./bootstrap.sh + +- name: configure + image: localhost:5000/jla/mod + commands: + - bindep testing + - mkdir build + - cd build + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 + environment: + CXX: g++-9 + CXXFLAGS: -Werror + +- name: build + image: localhost:5000/jla/mod + commands: + - cd build + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make + +- name: install + image: localhost:5000/jla/mod + commands: + - cd build - make install + +- name: build-test + image: localhost:5000/jla/mod + commands: + - cd build - make tests - - make coverage_collect - - make coverage_build - - /copyCoverage.sh + +- name: test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -E cmake_add_subdirectory_build + +- name: simple test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - cd ../ + - mod -e "smiles('O').print()" + +- name: test subdirectory build + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -R cmake_add_subdirectory_build + when: + ref: + - refs/heads/develop + - refs/heads/master + - refs/tags/v* + +--- +kind: pipeline +name: g++-9, Boost 1_78_0 + +platform: + os: linux + arch: amd64 + +steps: +- name: bootstrap + image: localhost:5000/jla/mod + commands: + - git fetch --tags + - git submodule update --init --recursive + - ./bootstrap.sh + +- name: configure + image: localhost:5000/jla/mod + commands: + - bindep testing + - mkdir build + - cd build + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: - CTEST_OUTPUT_ON_FAILURE: 1 CXX: g++-9 CXXFLAGS: -Werror - volumes: - - name: www - path: /www + +- name: build + image: localhost:5000/jla/mod + commands: + - cd build + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 + - make + +- name: install + image: localhost:5000/jla/mod + commands: + - cd build + - make install + +- name: build-test + image: localhost:5000/jla/mod + commands: + - cd build + - make tests + +- name: test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -E cmake_add_subdirectory_build + +- name: simple test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - cd ../ + - mod -e "smiles('O').print()" + +- name: test subdirectory build + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -R cmake_add_subdirectory_build when: ref: - refs/heads/develop - refs/heads/master - refs/tags/v* -volumes: -- name: www - host: - path: /www/results/mod - --- kind: pipeline -name: g++-9, Boost 1_75_0 +name: g++-9, Boost 1_79_0 platform: os: linux @@ -577,9 +700,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: g++-9 CXXFLAGS: -Werror @@ -588,7 +712,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -632,7 +756,7 @@ steps: --- kind: pipeline -name: g++-9, Boost 1_76_0 +name: g++-9, Boost 1_80_0 platform: os: linux @@ -649,13 +773,87 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: g++-9 CXXFLAGS: -Werror +- name: build + image: localhost:5000/jla/mod + commands: + - cd build + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 + - make + +- name: install + image: localhost:5000/jla/mod + commands: + - cd build + - make install + +- name: build-test + image: localhost:5000/jla/mod + commands: + - cd build + - make tests + +- name: test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -E cmake_add_subdirectory_build + +- name: simple test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - cd ../ + - mod -e "smiles('O').print()" + +- name: test subdirectory build + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -R cmake_add_subdirectory_build + when: + ref: + - refs/heads/develop + - refs/heads/master + - refs/tags/v* + +--- +kind: pipeline +name: g++-10, Boost 1_76_0 + +platform: + os: linux + arch: amd64 + +steps: +- name: bootstrap + image: localhost:5000/jla/mod + commands: + - git fetch --tags + - git submodule update --init --recursive + - ./bootstrap.sh + +- name: configure + image: localhost:5000/jla/mod + commands: + - bindep testing + - mkdir build + - cd build + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + environment: + CXX: g++-10 + CXXFLAGS: -Werror + - name: build image: localhost:5000/jla/mod commands: @@ -704,7 +902,7 @@ steps: --- kind: pipeline -name: g++-10, Boost 1_73_0 +name: g++-10, Boost 1_77_0 platform: os: linux @@ -721,9 +919,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: g++-10 CXXFLAGS: -Werror @@ -732,7 +931,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -776,7 +975,7 @@ steps: --- kind: pipeline -name: g++-10, Boost 1_74_0 +name: g++-10, Boost 1_78_0 platform: os: linux @@ -793,9 +992,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: g++-10 CXXFLAGS: -Werror @@ -804,7 +1004,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -848,7 +1048,7 @@ steps: --- kind: pipeline -name: g++-10, Boost 1_75_0 +name: g++-10, Boost 1_79_0 platform: os: linux @@ -865,9 +1065,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: g++-10 CXXFLAGS: -Werror @@ -876,7 +1077,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -920,7 +1121,7 @@ steps: --- kind: pipeline -name: g++-10, Boost 1_76_0 +name: g++-10, Boost 1_80_0 platform: os: linux @@ -937,9 +1138,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: g++-10 CXXFLAGS: -Werror @@ -948,7 +1150,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install @@ -992,7 +1194,7 @@ steps: --- kind: pipeline -name: g++-11, Boost 1_73_0 +name: g++-11, Boost 1_76_0 platform: os: linux @@ -1009,9 +1211,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: CXX: g++-11 CXXFLAGS: -Werror @@ -1020,7 +1223,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 - make - name: install @@ -1064,7 +1267,7 @@ steps: --- kind: pipeline -name: g++-11, Boost 1_74_0 +name: g++-11, Boost 1_77_0 platform: os: linux @@ -1081,9 +1284,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: g++-11 CXXFLAGS: -Werror @@ -1092,7 +1296,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -1136,7 +1340,7 @@ steps: --- kind: pipeline -name: g++-11, Boost 1_75_0 +name: g++-11, Boost 1_78_0 platform: os: linux @@ -1153,9 +1357,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: g++-11 CXXFLAGS: -Werror @@ -1164,7 +1369,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -1208,7 +1413,7 @@ steps: --- kind: pipeline -name: g++-11, Boost 1_76_0 +name: g++-11, Boost 1_79_0 platform: os: linux @@ -1225,9 +1430,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: g++-11 CXXFLAGS: -Werror @@ -1236,7 +1442,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -1280,7 +1486,7 @@ steps: --- kind: pipeline -name: clang++-8, Boost 1_73_0 +name: g++-11, Boost 1_80_0 platform: os: linux @@ -1297,18 +1503,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: - CXX: clang++-8 + CXX: g++-11 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install @@ -1350,9 +1557,40 @@ steps: - refs/heads/master - refs/tags/v* +- name: coverage + image: localhost:5000/jla/mod + commands: + - bindep testing + - mkdir covBuild + - cd covBuild + - cmake ../ -DENABLE_IPO=off -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_TESTING=on -DBUILD_COVERAGE=on -DBOOST_ROOT=/opt/boost/1_80_0 + - make + - make install + - make tests + - make coverage_collect + - make coverage_build + - /copyCoverage.sh + environment: + CTEST_OUTPUT_ON_FAILURE: 1 + CXX: g++-11 + CXXFLAGS: -Werror + volumes: + - name: www + path: /www + when: + ref: + - refs/heads/develop + - refs/heads/master + - refs/tags/v* + +volumes: +- name: www + host: + path: /www/results/mod + --- kind: pipeline -name: clang++-8, Boost 1_74_0 +name: clang++-8, Boost 1_76_0 platform: os: linux @@ -1369,9 +1607,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: CXX: clang++-8 CXXFLAGS: -Werror @@ -1380,7 +1619,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 - make - name: install @@ -1424,7 +1663,7 @@ steps: --- kind: pipeline -name: clang++-8, Boost 1_75_0 +name: clang++-8, Boost 1_77_0 platform: os: linux @@ -1441,9 +1680,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: clang++-8 CXXFLAGS: -Werror @@ -1452,7 +1692,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -1496,7 +1736,7 @@ steps: --- kind: pipeline -name: clang++-8, Boost 1_76_0 +name: clang++-8, Boost 1_78_0 platform: os: linux @@ -1513,9 +1753,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: clang++-8 CXXFLAGS: -Werror @@ -1524,7 +1765,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -1568,7 +1809,7 @@ steps: --- kind: pipeline -name: clang++-9, Boost 1_73_0 +name: clang++-8, Boost 1_79_0 platform: os: linux @@ -1585,18 +1826,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: - CXX: clang++-9 + CXX: clang++-8 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -1640,7 +1882,7 @@ steps: --- kind: pipeline -name: clang++-9, Boost 1_74_0 +name: clang++-8, Boost 1_80_0 platform: os: linux @@ -1657,18 +1899,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: - CXX: clang++-9 + CXX: clang++-8 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install @@ -1712,7 +1955,7 @@ steps: --- kind: pipeline -name: clang++-9, Boost 1_75_0 +name: clang++-10, Boost 1_76_0 platform: os: linux @@ -1729,18 +1972,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: - CXX: clang++-9 + CXX: clang++-10 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 - make - name: install @@ -1784,7 +2028,7 @@ steps: --- kind: pipeline -name: clang++-9, Boost 1_76_0 +name: clang++-10, Boost 1_77_0 platform: os: linux @@ -1801,18 +2045,19 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: - CXX: clang++-9 + CXX: clang++-10 CXXFLAGS: -Werror - name: build image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -1856,7 +2101,7 @@ steps: --- kind: pipeline -name: clang++-10, Boost 1_73_0 +name: clang++-10, Boost 1_78_0 platform: os: linux @@ -1873,9 +2118,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: clang++-10 CXXFLAGS: -Werror @@ -1884,7 +2130,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -1928,7 +2174,7 @@ steps: --- kind: pipeline -name: clang++-10, Boost 1_74_0 +name: clang++-10, Boost 1_79_0 platform: os: linux @@ -1945,9 +2191,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: clang++-10 CXXFLAGS: -Werror @@ -1956,7 +2203,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -2000,7 +2247,7 @@ steps: --- kind: pipeline -name: clang++-10, Boost 1_75_0 +name: clang++-10, Boost 1_80_0 platform: os: linux @@ -2017,9 +2264,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: clang++-10 CXXFLAGS: -Werror @@ -2028,7 +2276,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install @@ -2072,7 +2320,7 @@ steps: --- kind: pipeline -name: clang++-10, Boost 1_76_0 +name: clang++-11, Boost 1_76_0 platform: os: linux @@ -2089,11 +2337,12 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 environment: - CXX: clang++-10 + CXX: clang++-11 CXXFLAGS: -Werror - name: build @@ -2144,7 +2393,7 @@ steps: --- kind: pipeline -name: clang++-11, Boost 1_73_0 +name: clang++-11, Boost 1_77_0 platform: os: linux @@ -2161,9 +2410,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: clang++-11 CXXFLAGS: -Werror @@ -2172,7 +2422,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -2216,7 +2466,7 @@ steps: --- kind: pipeline -name: clang++-11, Boost 1_74_0 +name: clang++-11, Boost 1_78_0 platform: os: linux @@ -2233,9 +2483,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: clang++-11 CXXFLAGS: -Werror @@ -2244,7 +2495,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -2288,7 +2539,7 @@ steps: --- kind: pipeline -name: clang++-11, Boost 1_75_0 +name: clang++-11, Boost 1_79_0 platform: os: linux @@ -2305,9 +2556,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: clang++-11 CXXFLAGS: -Werror @@ -2316,7 +2568,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -2360,7 +2612,7 @@ steps: --- kind: pipeline -name: clang++-11, Boost 1_76_0 +name: clang++-11, Boost 1_80_0 platform: os: linux @@ -2377,13 +2629,87 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: clang++-11 CXXFLAGS: -Werror +- name: build + image: localhost:5000/jla/mod + commands: + - cd build + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 + - make + +- name: install + image: localhost:5000/jla/mod + commands: + - cd build + - make install + +- name: build-test + image: localhost:5000/jla/mod + commands: + - cd build + - make tests + +- name: test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -E cmake_add_subdirectory_build + +- name: simple test + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - cd ../ + - mod -e "smiles('O').print()" + +- name: test subdirectory build + image: localhost:5000/jla/mod + commands: + - cd build + - make install + - ctest --output-on-failure -R cmake_add_subdirectory_build + when: + ref: + - refs/heads/develop + - refs/heads/master + - refs/tags/v* + +--- +kind: pipeline +name: clang++-12, Boost 1_76_0 + +platform: + os: linux + arch: amd64 + +steps: +- name: bootstrap + image: localhost:5000/jla/mod + commands: + - git fetch --tags + - git submodule update --init --recursive + - ./bootstrap.sh + +- name: configure + image: localhost:5000/jla/mod + commands: + - bindep testing + - mkdir build + - cd build + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + environment: + CXX: clang++-12 + CXXFLAGS: -Werror + - name: build image: localhost:5000/jla/mod commands: @@ -2432,7 +2758,7 @@ steps: --- kind: pipeline -name: clang++-12, Boost 1_73_0 +name: clang++-12, Boost 1_77_0 platform: os: linux @@ -2449,9 +2775,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 environment: CXX: clang++-12 CXXFLAGS: -Werror @@ -2460,7 +2787,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_73_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_77_0 - make - name: install @@ -2504,7 +2831,7 @@ steps: --- kind: pipeline -name: clang++-12, Boost 1_74_0 +name: clang++-12, Boost 1_78_0 platform: os: linux @@ -2521,9 +2848,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 environment: CXX: clang++-12 CXXFLAGS: -Werror @@ -2532,7 +2860,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_74_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_78_0 - make - name: install @@ -2576,7 +2904,7 @@ steps: --- kind: pipeline -name: clang++-12, Boost 1_75_0 +name: clang++-12, Boost 1_79_0 platform: os: linux @@ -2593,9 +2921,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 environment: CXX: clang++-12 CXXFLAGS: -Werror @@ -2604,7 +2933,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_75_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_79_0 - make - name: install @@ -2648,7 +2977,7 @@ steps: --- kind: pipeline -name: clang++-12, Boost 1_76_0 +name: clang++-12, Boost 1_80_0 platform: os: linux @@ -2665,9 +2994,10 @@ steps: - name: configure image: localhost:5000/jla/mod commands: + - bindep testing - mkdir build - cd build - - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DCMAKE_BUILD_TYPE=OptDebug -DENABLE_IPO=off -DBUILD_DOC=on -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 environment: CXX: clang++-12 CXXFLAGS: -Werror @@ -2676,7 +3006,7 @@ steps: image: localhost:5000/jla/mod commands: - cd build - - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_76_0 + - cmake ../ -DBUILD_TESTING=on -DBUILD_TESTING_SANITIZERS=off -DBOOST_ROOT=/opt/boost/1_80_0 - make - name: install diff --git a/.github/workflows/Checks.yml b/.github/workflows/Checks.yml index cca04e0..6a5056e 100644 --- a/.github/workflows/Checks.yml +++ b/.github/workflows/Checks.yml @@ -2,7 +2,7 @@ name: Checks on: [push, pull_request] jobs: Checks: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/Conda.yml b/.github/workflows/Conda.yml index 6533c08..3a56210 100644 --- a/.github/workflows/Conda.yml +++ b/.github/workflows/Conda.yml @@ -2,7 +2,7 @@ name: Conda on: [push, pull_request] jobs: Conda-Linux: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -19,9 +19,9 @@ jobs: run: sudo apt install -y $(bindep -b | tr '\n' ' ') - name: Install Boost run: | - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz - tar -xf boost_1_74_0.tar.gz - cd boost_1_74_0 + wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz + tar -xf boost_1_80_0.tar.gz + cd boost_1_80_0 ./bootstrap.sh --with-python=python3 --prefix=/opt/boost ./b2 -j 2 --with-python --with-graph --with-iostreams install - name: Bootstrap diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml index ee2dd8f..ed6cfe9 100644 --- a/.github/workflows/Docker.yml +++ b/.github/workflows/Docker.yml @@ -2,7 +2,7 @@ name: Docker on: [push, pull_request] jobs: Ubuntu: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -19,9 +19,9 @@ jobs: run: sudo apt install -y $(bindep -b | tr '\n' ' ') - name: Install Boost run: | - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz - tar -xf boost_1_74_0.tar.gz - cd boost_1_74_0 + wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz + tar -xf boost_1_80_0.tar.gz + cd boost_1_80_0 ./bootstrap.sh --with-python=python3 --prefix=/opt/boost ./b2 -j 2 --with-python --with-graph --with-iostreams install - name: Bootstrap @@ -36,7 +36,7 @@ jobs: - name: Build run: docker build -t jakobandersen/mod:ubuntu-test -f docker/Ubuntu.Dockerfile --build-arg j=2 . Fedora: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -53,9 +53,9 @@ jobs: run: sudo apt install -y $(bindep -b | tr '\n' ' ') - name: Install Boost run: | - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz - tar -xf boost_1_74_0.tar.gz - cd boost_1_74_0 + wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz + tar -xf boost_1_80_0.tar.gz + cd boost_1_80_0 ./bootstrap.sh --with-python=python3 --prefix=/opt/boost ./b2 -j 2 --with-python --with-graph --with-iostreams install - name: Bootstrap @@ -70,7 +70,7 @@ jobs: - name: Build run: docker build -t jakobandersen/mod:fedora-test -f docker/Fedora.Dockerfile --build-arg j=2 . Arch: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -87,9 +87,9 @@ jobs: run: sudo apt install -y $(bindep -b | tr '\n' ' ') - name: Install Boost run: | - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz - tar -xf boost_1_74_0.tar.gz - cd boost_1_74_0 + wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz + tar -xf boost_1_80_0.tar.gz + cd boost_1_80_0 ./bootstrap.sh --with-python=python3 --prefix=/opt/boost ./b2 -j 2 --with-python --with-graph --with-iostreams install - name: Bootstrap diff --git a/.github/workflows/Main.yml b/.github/workflows/Main.yml index 78341d4..551ff8a 100644 --- a/.github/workflows/Main.yml +++ b/.github/workflows/Main.yml @@ -2,7 +2,7 @@ name: Main on: [push, pull_request] jobs: Ubuntu: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v2 @@ -16,12 +16,12 @@ jobs: sudo apt install python3-setuptools python3-wheel pip3 install -r requirements.txt - name: Install apt dependencies - run: sudo apt install -y $(bindep -b | tr '\n' ' ') + run: sudo apt install -y $(bindep -b testing | tr '\n' ' ') - name: Install Boost run: | - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz - tar -xf boost_1_74_0.tar.gz - cd boost_1_74_0 + wget https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz + tar -xf boost_1_80_0.tar.gz + cd boost_1_80_0 ./bootstrap.sh --with-python=python3 --prefix=/opt/boost ./b2 -j 2 --with-python --with-graph --with-iostreams install - name: Install Graphviz @@ -52,8 +52,8 @@ jobs: run: cd build && ctest --output-on-failure -j 2 - name: Run simple test run: mod -e "smiles('O').print()" - macOS: - runs-on: macos-10.15 + macOS-11: + runs-on: macos-11 steps: - name: Checkout uses: actions/checkout@v2 @@ -61,11 +61,46 @@ jobs: fetch-depth: 0 submodules: 'recursive' - name: Install Brew dependencies + run: brew bundle + - name: Install pip dependencies + run: pip3 install -r requirements.txt + - name: Set PATH run: | - brew bundle /usr/libexec/path_helper | sed -e 's/^PATH="//' -e 's/"; export PATH;//' | tr ":" "\n" | tail -r >> $GITHUB_PATH + python3 -c 'import os,sysconfig;print(sysconfig.get_path("scripts",f"{os.name}_prefix"))' >> $GITHUB_PATH + - name: Bootstrap + run: ./bootstrap.sh + - name: Configure + run: | + mkdir build + cd build + cmake ../ -DBUILD_DOC=on -DBUILD_TESTING=on + - name: Build + run: cd build && make -j 3 + - name: Install + run: cd build && sudo make install + - name: Build tests + run: cd build && make tests -j 3 + - name: Run tests + run: cd build && ctest --output-on-failure -j 3 + - name: Run simple test + run: mod -e "smiles('O').print()" + macOS-12: + runs-on: macos-12 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: 'recursive' + - name: Install Brew dependencies + run: brew bundle - name: Install pip dependencies run: pip3 install -r requirements.txt + - name: Set PATH + run: | + /usr/libexec/path_helper | sed -e 's/^PATH="//' -e 's/"; export PATH;//' | tr ":" "\n" | tail -r >> $GITHUB_PATH + python3 -c 'import os,sysconfig;print(sysconfig.get_path("scripts",f"{os.name}_prefix"))' >> $GITHUB_PATH - name: Bootstrap run: ./bootstrap.sh - name: Configure @@ -74,12 +109,12 @@ jobs: cd build cmake ../ -DBUILD_DOC=on -DBUILD_TESTING=on - name: Build - run: cd build && make -j 2 + run: cd build && make -j 3 - name: Install run: cd build && sudo make install - name: Build tests - run: cd build && make tests -j 2 + run: cd build && make tests -j 3 - name: Run tests - run: cd build && ctest --output-on-failure -j 2 + run: cd build && ctest --output-on-failure -j 3 - name: Run simple test run: mod -e "smiles('O').print()" diff --git a/.gitignore b/.gitignore index 61a2842..c328bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,9 @@ __pycache__ /test/py/**/out/ /test/py/**/summary/ /test/py/**/.gdb_history -/test/py/graph/myGraph.gml +/test/py/graph/myGraph* /test/py/rule/myRule.gml -/doc/source/index.rst -/doc/source/references.rst -/doc/source/toc.rst /doc/source/libmod/[A-Z]*.rst /doc/source/pymod/[A-Z]*.rst /doc/source/libmod/[^/]*/ diff --git a/Brewfile b/Brewfile index f4fd291..fe66d5c 100644 --- a/Brewfile +++ b/Brewfile @@ -1,3 +1,4 @@ +brew "cmake" brew "boost" brew "boost-python3" brew "open-babel" diff --git a/CMakeLists.txt b/CMakeLists.txt index 20d8987..1b908e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,9 @@ string(STRIP "${PROJECT_VERSION}" PROJECT_VERSION) # remove the newline project(mod VERSION ${PROJECT_VERSION}) set(mod_VERSION ${PROJECT_VERSION} CACHE INTERNAL "" FORCE) set(PNAME_FILE mod) # how the project name should be written in file names -set(CPACK_PACKAGE_CONTACT "Jakob Lykke Andersen ") +set(mod_AUTHOR "Jakob Lykke Andersen") +set(mod_AUTHOR_EMAIL "jlandersen@imada.sdu.dk") +set(CPACK_PACKAGE_CONTACT "${mod_AUTHOR} <${mod_AUTHOR_EMAIL}>") set(CMAKE_CXX_FLAGS_OPTDEBUG "-g -O3") @@ -34,6 +36,7 @@ option(BUILD_DOC "Enable documentation building." OFF) option(BUILD_POST_MOD "Enable building of the post processor." ON) option(BUILD_POST_MOD_FMT "Enable building of the post processor Latex format files." ON) option(BUILD_PY_MOD "Enable building of the Python bindings." ON) +option(BUILD_PY_MOD_PIP "If BUILD_PY_MOD, then install the bindings also with pip in the default location." ON) option(BUILD_EPIM "Enable building of the EpiM extension (requires PyMØD)." ON) if(BUILD_EPIM AND NOT BUILD_PY_MOD) @@ -86,9 +89,9 @@ set(libmod_config_find_files "FindPackageHandleStandardArgs.cmake;FindPackageMes # Boost # ------------------------------------------------------------------------- -set(v 1.73.0) +set(v 1.76.0) if(BUILD_PY_MOD) - foreach(PY 3 34 35 36 37 38 39) + foreach(PY 3 37 38 39 310 311 312 313 314 315 316 317 318 319) set(lib "python${PY}") find_package(Boost ${v} QUIET COMPONENTS ${lib}) if(Boost_FOUND) @@ -98,8 +101,8 @@ if(BUILD_PY_MOD) endif() endforeach() if(NOT Boost_FOUND) - find_package(Boost ${v} REQUIRED COMPONENTS python3) - message(FATAL_ERROR "Could not find Boost.Python for Python 3. Tried 'python' wih suffixes 3, 34, 35, 36, 37, 38, and 39.") + find_package(Boost ${v} COMPONENTS python3) + message(FATAL_ERROR "Could not find Boost.Python for Python 3. Tried 'python' wih suffixes 3, 37, 38, 39, and 310 to 319.") endif() endif() find_package(Boost ${v} REQUIRED COMPONENTS graph iostreams) @@ -291,6 +294,7 @@ add_subdirectory(libs/epim) add_subdirectory(libs/gml) add_subdirectory(libs/jla_boost) add_subdirectory(libs/libmod) +add_subdirectory(libs/pymodutils) add_subdirectory(libs/pymod) add_subdirectory(libs/post_mod) add_subdirectory(doc) diff --git a/ChangeLog.rst b/ChangeLog.rst index 0da9f4e..e9a7791 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -4,6 +4,131 @@ Changes ####### +v0.14.0 (2022-11-29) +==================== + +Incompatible Changes +-------------------- + +Doc, several pages have changed URL: + +- ``libmod/libmod.html`` to ``libmod/index.html`` +- ``pymod/pymod.html`` to ``pymod/index.html`` +- ``postmod/postmod.html`` to ``postmod/index.html`` +- ``modWrapper/modWrapper.html`` to ``exe/index.html`` +- ``epim/epim.html`` to ``epim/index.html`` +- ``dataDesc/dataDesc.html`` to ``formats/index.html`` +- ``dgStrat/dgStrat.html`` to ``dgStrat/index.html`` +- As default, a Python package is now installed with ``pip`` which enables + import of ``mod`` without extra ``PYTHONPATH`` manipulation. + This may potentially clash with other packages of the same name. + Use ``-DBUILD_PY_MOD_PIP=off`` for CMake to disable this + (see :ref:`compiling`). +- Due to a change in escaping of ``#`` characters in DG vertex/hyperedge labels + when printing, some custom labels with additional escaping may need + adjustment. See also the bug fix entry regarding this. +- Bump Boost version required to 1.76 or higher to avoid an incompatibility with + Python 3.10 (https://github.com/boostorg/python/pull/344). + + +New Features +------------ + +- Doc, a new section, :ref:`graph-model`, and restructuring of + :ref:`formats`. +- The :ref:`GraphDFS format ` now supports disconnected graphs + through ``.``-edges, simiilar to :ref:`SMILES `. + The graph loading functions + :cpp:func:`graph::Graph::fromDFSMulti` and + :py:func:`Graph.fromDFSMulti` has been added to load disconnected graphs. +- Added :cpp:func:`rule::Rule::fromDFS`/:py:func:`Rule.fromDFS` for loading + rules from a :ref:`RuleDFS ` string, a new line-notation for + rules based on :ref:`GraphDFS `. +- Added support for :ref:`MOL and SD ` formats for loading graphs. + The loadeing can be done through the functions + + - :cpp:func:`graph::Graph::fromMOLString`/:py:func:`Graph.fromMOLString`, + - :cpp:func:`graph::Graph::fromMOLFile`/:py:func:`Graph.fromMOLFile`, + - :cpp:func:`graph::Graph::fromMOLStringMulti`/:py:func:`Graph.fromMOLStringMulti`, + - :cpp:func:`graph::Graph::fromMOLFileMulti`/:py:func:`Graph.fromMOLFileMulti`, + - :cpp:func:`graph::Graph::fromSDString`/:py:func:`Graph.fromSDString`, + - :cpp:func:`graph::Graph::fromSDFile`/:py:func:`Graph.fromSDFile`, + - :cpp:func:`graph::Graph::fromSDStringMulti`/:py:func:`Graph.fromSDStringMulti`, and + - :cpp:func:`graph::Graph::fromSDFileMulti`/:py:func:`Graph.fromSDFileMulti`, +- PyMØD: add installation of the bindings via ``pip``. + See the setting ``-DBUILD_PY_MOD_PIP=on`` in :ref:`compiling`. +- Added :cpp:func:`dg::Builder::addHyperEdge`/:py:meth:`DGBuilder.addHyperEdge`. +- Added :cpp:func:`graph::Printer::setRaiseIsotopes`/:cpp:func:`graph::Printer::getRaiseIsotopes`/:py:attr:`GraphPrinter.raiseIsotopes`. + It was previously only available in the internal interface. +- Added :cpp:func:`graph::Printer::setWithGraphvizCoords`/:cpp:func:`graph::Printer::getWithGraphvizCoords`/:py:attr:`GraphPrinter.withGraphvizCoords`. +- Added :cpp:func:`graph::Printer::setGraphvizPrefix`/:cpp:func:`graph::Printer::getGraphvizPrefix`/:py:attr:`GraphPrinter.graphvizPrefix`. +- Whitespace is now allowed inside :ref:`format-dfs` strings. +- Make :option:`mod --memcheck` cause Valgrind to return non-zero on problems. + Additionally add an ``atexit`` handler in Python to delete remaining global + objects as this is not guaranteed otherwise. +- Several undocumented post-processing functions are now documented, + and several internal functions are now exposed. + See :ref:`cpp-Post`/:ref:`py-Post`. +- Added :cpp:func:`graph::Graph::enumerateMonomorphisms`/:py:meth:`Graph.enumerateMonomorphisms`. +- Added :cpp:func:`dg::Printer::setImageOverwrite`/:py:meth:`DGPrinter.setImageOverwrite`. +- Added :cpp:func:`dg::Builder::getDG`/:py:attr:`DGBuilder.dg` and + :py:attr:`DGBuilder.isActive`. + +Bugs Fixed +---------- + +- Rule GML loading, check for edges dangling due to wrong vertex membership. +- :cpp:func:`dg::Builder::execute`/:py:meth:`DGBuilder.execute` and + :cpp:func:`dg::Builder::apply`/:py:meth:`DGBuilder.apply`, + properly ignore direct derivations with empty right-hand sides, + instead of crashing. +- :cpp:func:`dg::DG::load`/:py:meth:`DG.load` and + :cpp:func:`dg::Builder::load`/:py:meth:`DGBuilder.load`, + reenable loading of very old dump formats. +- Fix critical bugs in + :cpp:class:`rule::CompositionMatch`/:py:class:`RCMatch`. +- Doc, added missing ``cd mod`` step in :ref:`compiling`. +- Doc, add missing ``"`` in usage description for the Docker image. +- Doc, fix typo (:math:`C_3` to :math:`C_4`) in :ref:`format-graphDFS`, + and improve description of ring-closure semantics. +- Fix :cpp:func:`graph::Graph::getGraphDFS`/:py:attr:`Graph.graphDFS` + and :cpp:func:`graph::Graph::getGraphDFSWithIds`/:py:attr:`Graph.graphDFSWithIds` + to not produce a :token:`~graphDFS:defRingId` directly followed by a + :token:`~graphDFS:ringClosure` which is indistinguishable from just a + :token:`~graphDFS:defRingId` when parsing the string again. +- Check for loop edges and parallel edges when loading graphs from DFS. +- :ref:`PostMØD `, avoid use of inline ``sed`` in ``compileTikz`` + to make it work on macOS. +- For compiling from source on macOS, add ``cmake`` to ``Brewfile``. +- Check for Boost.Python compiled against Python 3.10 through 3.20 as well. +- Py, use :py:class:`collections.abc.Iterable` instead of the deprecated/removed + ``collections.Iterable``. +- Py, use :py:func:`inspect.getfullargspec` instead of the deprecated/removed + ``inspect.getargspec()``. +- ``mod_post`` scrub more unreproducible meta-info from figure PDFs. +- Fix memory leaks in :cpp:func:`dg::Builder::apply`/:py:meth:`DGBuilder.apply`. +- Fix colour on changed stereo-information in the right-side graph when printing + rules and direct derivations. +- Stop recreating vertex-orders for connected components of rule sides, + thereby speeding up rule application (5-6% reduced run-time observed). +- Fix missing coordinates for rule depiction in rare non-chemical cases with + vertices with label "H". +- Fix rule composition with :cpp:any:`LabelType::Term`/:py:obj:`LabelType.Term`, + when two vertices are overlapping and there is an edge in the left side of the + second rule, but not in the right side of the first rule. +- Fix Tikz coordinate node names in rule and stereo depictions to always include + ``\modIdPrefix``, to allow post-printing namespacing of node names. +- :cpp:func:`graph::Graph::fromSMILES`/:py:meth:`Graph.fromSMILES`, properly parse + abstract labels when multiple nests of balanced brackets are present. +- Fix handling of null pointers: + + - :cpp:func:`graph::Graph::isomorphism`/:py:meth:`Graph.isomorphism`. + - :cpp:func:`graph::Graph::monomorphism`/:py:meth:`Graph.monomorphism`. + - :cpp:func:`graph::Union::Union`/:py:meth:`UnionGraph.__init__`. +- Fix escaping of ``#`` characters in DG vertex/hyperedge labels when printing + using a :cpp:class:`dg::Printer`/:py:class:`DGPrinter` with + ``labelsAsLatexMath=True`` (the default). + v0.13.0 (2021-07-08) ==================== diff --git a/bindep.txt b/bindep.txt index fd30ae9..b4ee322 100644 --- a/bindep.txt +++ b/bindep.txt @@ -20,6 +20,9 @@ python3-devel [platform:rpm] openbabel [platform:arch] libopenbabel-dev [platform:dpkg] openbabel-devel [platform:rpm] +python-openbabel [platform:arch testing] +python3-openbabel [platform:dpkg testing] +python3-openbabel [platform:rpm testing] texlive-science [platform:arch] texlive-pictures [platform:arch] diff --git a/bootstrap.sh b/bootstrap.sh index c642849..0376512 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -28,10 +28,12 @@ function doDir { find $inc -iname "*.hpp" | indentAndDir libs/$1 echo ")" echo "" - echo "set(mod_$1_SRC_FILES" - find src -iname "*.cpp" | indentAndDir libs/$1 - echo ")" - echo "" + if test -d src; then + echo "set(mod_$1_SRC_FILES" + find src -iname "*.cpp" | indentAndDir libs/$1 + echo ")" + echo "" + fi if test -d test; then echo "set(mod_$1_TEST_CPP_FILES" find test -iname "*.cpp" | sed "s/^test\/\(.*\)\.cpp$/\1/" | indent @@ -50,6 +52,7 @@ function gen_file_lists { doDir gml doDir jla_boost doDir libmod src + doDir pymodutils src doDir pymod src echo "set(mod_pymod_PY_FILES" find libs/pymod/lib -iname "*.py" | indentAndDir @@ -64,6 +67,12 @@ function gen_file_lists { echo "set(mod_EXAMPLES_PY_FILES" find examples/py -iname "*.py" | sed -e "s!^examples/!!" -e 's!\.py$!!' | indent echo ")" + # this doesn't cover every single file, but it should be enough for normal edit-compile usage + echo "set(mod_DOC_FILES" + echo '${CMAKE_CURRENT_LIST_DIR}/doc/source/conf.py' | indent + echo '${CMAKE_CURRENT_LIST_DIR}/ChangeLog.rst' | indent + find doc/source -iname "*.rst" | sed -e 's!^!${CMAKE_CURRENT_LIST_DIR}/!' | indent + echo ")" } echo "VERSION" @@ -72,10 +81,10 @@ echo "VERSION" v=$(git describe --tags --always --exclude "archive/*" | sed -e "s/^v//" -e 's/priv-\([0-9]*\.[0-9]*\)\.0/\1.1/' -e "s/-g.*$//" -e "s/-/./") echo $v > VERSION cat VERSION -echo "CMakeFiles.txt" -gen_file_lists > CMakeFiles.txt echo "Docs" doc/makeDocs.sh . || exit 1 +echo "CMakeFiles.txt" +gen_file_lists > CMakeFiles.txt echo "Recursing in external/graph_canon" echo "---------------------------------" diff --git a/cmake/MODUtils.cmake b/cmake/MODUtils.cmake index 24b3a99..b7d39be 100644 --- a/cmake/MODUtils.cmake +++ b/cmake/MODUtils.cmake @@ -7,6 +7,6 @@ function(make_py_test fileName testName extraEnv) COMMAND ${CMAKE_INSTALL_FULL_BINDIR}/mod -f ${CMAKE_CURRENT_LIST_DIR}/${fileName}.py WORKING_DIRECTORY ${workDir}) set_tests_properties(${testName} PROPERTIES - ENVIRONMENT "MOD_NUM_POST_THREADS=1${extraEnv}") + ENVIRONMENT "MOD_NUM_POST_THREADS=1;PYTHONWARNINGS=error${extraEnv}") add_coverage_case(${testName}) endfunction() diff --git a/conda/build.Dockerfile b/conda/build.Dockerfile index a9474fe..1fc53cc 100644 --- a/conda/build.Dockerfile +++ b/conda/build.Dockerfile @@ -7,7 +7,7 @@ ENV PATH /opt/conda/bin:$PATH RUN apt-get update --fix-missing \ && apt-get install -y wget bzip2 ca-certificates curl git -RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O ~/miniconda.sh && \ +RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py38_4.12.0-Linux-x86_64.sh -O ~/miniconda.sh && \ /bin/bash ~/miniconda.sh -b -p /opt/conda && \ rm ~/miniconda.sh && \ /opt/conda/bin/conda clean -tipsy && \ diff --git a/conda/conda_build_config.yaml b/conda/conda_build_config.yaml index 8d04a71..2bab2cc 100644 --- a/conda/conda_build_config.yaml +++ b/conda/conda_build_config.yaml @@ -1,11 +1,15 @@ +# Note: conda-forge has global pinning, e.g., of boost, so make sure we include +# variations for those verions. +# https://github.com/conda-forge/conda-forge-pinning-feedstock/tree/main/recipe python: - - 3.6 - 3.7 - 3.8 - 3.9 + - 3.10 + - 3.11 boost: - - 1.74 - - 1.76 + - 1.78 + - 1.80 pin_run_as_build: python: x.x boost: x.x diff --git a/conda/meta.yaml b/conda/meta.yaml index 8ca3571..dbe4cb1 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -21,6 +21,11 @@ requirements: - make - pkg-config - cmake + # lib packages, needed here to make sure we get the exact same version + # as in the host environment + - boost {{ boost }} + - openbabel + - python {{ python }} # Add both exe and py packages to the build environemnt # so configuration checks succeed and tests can be run. # exe packages @@ -32,16 +37,18 @@ requirements: - networkx - parse - typing_extensions + # Add openbabel here to get pybel for test. + - openbabel host: # lib packages - boost {{ boost }} - openbabel - - python + - python {{ python }} run: # lib packages - boost - openbabel - - python + - python {{ python }} # exe packages - graphviz>=2.46.1 # to get the rsvg plugin - pdf2svg diff --git a/conda/test.py b/conda/test.py index 0771d02..5fd7ba0 100644 --- a/conda/test.py +++ b/conda/test.py @@ -1,5 +1,5 @@ -g = smiles('C') -r = ruleGMLString("""rule [ +g = Graph.fromSMILES('C') +r = Rule.fromGMLString("""rule [ left [ node [ id 1 label "C" ] node [ id 2 label "H" ] diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 35ada88..af544c3 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -6,7 +6,8 @@ set(output ${CMAKE_CURRENT_BINARY_DIR}/html) set(doctrees ${CMAKE_CURRENT_BINARY_DIR}/doctrees) add_custom_command( OUTPUT ${output} - COMMAND ${SPHINX} ${CMAKE_CURRENT_SOURCE_DIR}/source -n -b html ${output} -d ${doctrees}) + COMMAND ${SPHINX} ${CMAKE_CURRENT_SOURCE_DIR}/source -n -b html ${output} -d ${doctrees} + DEPENDS ${mod_DOC_FILES}) if(TARGET doc) add_dependencies(doc ${output}) else() diff --git a/doc/makeDocs.sh b/doc/makeDocs.sh index a070593..a88f375 100755 --- a/doc/makeDocs.sh +++ b/doc/makeDocs.sh @@ -136,81 +136,6 @@ function getPyModCPPs { done; } -function makeIndex { - function indexFiles { - cat << "EOF" - installation - compiling - libmod/libmod - pymod/pymod - postmod/postmod - modWrapper/modWrapper - epim/epim - dataDesc/dataDesc - dgStrat/dgStrat - examples/index - - knownIssues - changes - references -EOF - } - function data { - cat << "EOF" -################################################ -MØD -################################################ - -.. toctree:: - :maxdepth: 1 - :numbered: - -EOF - indexFiles -cat << "EOF" - - -.. _overview: - -Overview -======== - -This is the documentation for the MØD software package. -The sources can be found in the GitHub repository: ``_. -For additional information see the webpage: ``_. - -The package contains the following components. - -* libMØD, shared library (``mod``) - The main library. -* PyMØD, Python 3 module (``mod``) - Python bindings for the library and extra functionality for easier usage of - many features. - It additionally include the submodule :ref:`epim`. -* PostMØD, Bash Script (``mod_post``) - The post processor for compiling figures and summaries. -* The wrapper script, Bash Script (``mod``) - A convenience wrapper for invoking a virtual machine (e.g., ``python3``) - with user-supplied code. - The wrapper handles output and invokes the post processor. - Additionally, it can run the chosen virtual machine through ``gdb`` - and/or ``valgrind`` (either normal memcheck or callgrind). - - -Contributors -============ - -* `Jakob Lykke Andersen `__: main author. -* `Nikolai Nøjgaard `__: author of :ref:`EpiM`. - -EOF -cat << "EOF" - -EOF - } - data | outputRST index -} - function makeLibMod { function data { local f=$1 @@ -269,7 +194,7 @@ EOF echo "" getFolders | sed 's/$/\/index/' | sed 's/^/ /' } - dataToc | outputRST libmod/Libmodtoc + dataToc | outputRST libmod/Toc if [ ${PIPESTATUS[0]} -ne 0 ]; then return 1 fi @@ -333,15 +258,18 @@ EOF echo "" getFolders | sed 's/$/\/index/' | sed 's/^/ /' } - dataToc | outputRST pymod/Pymodtoc + dataToc | outputRST pymod/Toc +} + +function makeExamples { + rm -rf $topSrcDir/doc/source/_static/examples + mkdir -p $topSrcDir/doc/source/_static/examples + cp -a $topSrcDir/examples/libmod_cmake $topSrcDir/doc/source/_static/examples/ + cp -a $topSrcDir/examples/pymod_extension $topSrcDir/doc/source/_static/examples/ + cp -a $topSrcDir/examples/py $topSrcDir/doc/source/_static/examples/ + $topSrcDir/scripts/makePyExamples.py $topSrcDir rst } -makeIndex || exit 1 makeLibMod || exit 1 makePyMod || exit 1 - -rm -rf $topSrcDir/doc/source/_static/examples -mkdir -p $topSrcDir/doc/source/_static/examples -cp -a $topSrcDir/examples/libmod_cmake $topSrcDir/doc/source/_static/examples/ -cp -a $topSrcDir/examples/pymod_extension $topSrcDir/doc/source/_static/examples/ -cp -a $topSrcDir/examples/py $topSrcDir/doc/source/_static/examples/ +makeExamples || exit 1 diff --git a/doc/source/compiling.rst b/doc/source/compiling.rst index b5409cd..a554135 100644 --- a/doc/source/compiling.rst +++ b/doc/source/compiling.rst @@ -19,6 +19,7 @@ First, get MØD and install the easy dependencies: .. code-block:: bash git clone --recursive https://github.com/jakobandersen/mod.git + cd mod ./bootstrap.sh pip3 install -r requirements.txt # may need --user to install in home folder instead of system folders # Ubuntu: @@ -139,6 +140,13 @@ See also :ref:`dependencies` for elaboration on some of them. :option:`mod_post --install-format`/:option:`mod_post --install-format-sudo` options. - ``-DBUILD_PY_MOD=on``, whether to build the Python bindings or not. +- ``-DBUILD_PY_MOD_PIP=on``, whether to install the Python bindings via pip or + not. The bindings are always installed in the ``/lib`` folder, so + a normal ``import`` in Python will probably not find the module. + Having this setting on will enable a build of a fake Python package to be + installed via ``pip`` in the default system folder. This fake package will + redirect the import to the real location. + This package can be uninstalled with ``pip uninstall mod-jakobandersen``. - ``-DBUILD_TESTING=off``, whether to allow test building or not. This is forced to ``off`` when used via ``add_subdirectory``. When ``on`` the tests can be build with ``make tests`` and run with ``ctest``. @@ -197,7 +205,7 @@ related to them. - libMØD: - A C++ compiler with reasonable C++17 support is needed. - - `Boost `__ dev >= 1.73 + - `Boost `__ dev >= 1.76 (use ``-DBOOST_ROOT=`` for non-standard locations). - `GraphCanon `__ >= 0.5. This is fulfilled via a Git submodule (make sure to do @@ -207,7 +215,8 @@ related to them. This is fulfilled via a Git submodule (make sure to do ``git submodule update --init --recursive``), but if another source is needed, set ``-DUSE_NESTED_NLOHMANN_JSON=off``. - - (optional) `Open Babel`_ dev, >= 2.3.2 (``-DWITH_OPENBABEL=on``). + - (optional) `Open Babel `__ dev, >= 2.3.2 + (``-DWITH_OPENBABEL=on``). - PyMØD (``-DBUILD_PY_MOD=on``): @@ -268,13 +277,13 @@ Non-standard Python Installation """""""""""""""""""""""""""""""" Passing ``--with-python=python3`` to ``bootstrap.sh`` should work. -This adds a line similar to "``using python : 3.3 ;``" to +This adds a line similar to "``using python : 3.7 ;``" to ``project-config.jam``. After compilation (running ``b2``) the file ``stage/lib/libboost_python3.so`` should exist. If not, it did not detect Python 3 properly. If Python is installed in a non-standard location, add the a line similar to -"``using python : 3.3 : python3 : /path/to/python/3/installtion/include ;``" to +"``using python : 3.7 : python3 : /path/to/python/3/installtion/include ;``" to ``project-config.jam``, where the last path is the path to the ``include``-folder of the Python-installation. @@ -286,4 +295,4 @@ Before running ``b2`` create the file ``user-config.jam`` in the root of the home dir (see `here `__ for the full documentation). Put a line similar to -"``using gcc : : /path/to/g++-4.8``" in the file. +"``using gcc : : /path/to/g++-10``" in the file. diff --git a/doc/source/conf.py b/doc/source/conf.py index b1c6dd5..613c870 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,101 +22,8 @@ #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ - -refLinks = { - "Boost" : "http://boost.org", - "ChemFig" : "http://www.ctan.org/pkg/chemfig", - "GGL" : "http://www.tbi.univie.ac.at/software/GGL", - "Graphviz" : "http://www.graphviz.org", - "Open Babel" : "http://openbabel.org", - "Sphinx" : "http://sphinx-doc.org", - "Tikz" : "http://www.ctan.org/pkg/pgf", -} -rst_prolog = ""; -with open("references.rst", "w") as f: - f.write(""" -********** -References -********** - -""") - for name, link in sorted(refLinks.items(), key=lambda x: x[0].lower()): - f.write("* " + name + " " + link + "\n") - rst_prolog += ".. _" + name + ": " + link + "\n" - rst_prolog += "\n" - import sys import os -import shlex - -def generateExamples(): - import json - with open("_static/examples/py/sections.json") as j: - sections = json.load(j) - os.makedirs("examples", exist_ok=True) - with open("examples/index.rst", "w") as f: - f.truncate() - f.write(".. Autogenerated in conf.py\n\n") - f.write(".. _examples:\n\n") - f.write("Examples\n########\n\n") - f.write(".. toctree::\n\n") - for s in sections: - f.write("\t{}\n".format(s["id"])) - s["f"] = open("examples/{}.rst".format(s["id"]), "w", encoding='utf-8') - s["f"].truncate() - s["f"].write(".. Autogenerated in conf.py\n\n") - s["f"].write(".. _examples-{}:\n\n".format(s["id"])) - if "future" in s and s["future"]: - s["f"].write("Upcomming Feature: {}\n".format(s["title"])) - else: - s["f"].write("{}\n".format(s["title"])) - s["f"].write(80*"#" + "\n\n") - - for fName in sorted(os.listdir("_static/examples/py/")): - if not fName.endswith(".py"): - continue - assert fName[4] == '_' - id_ = int(fName[:4]) - s = next(s for s in sections - if id_ >= int(s["first"]) and id_ < int(s["last"])) - - with open("_static/examples/py/{}".format(fName), encoding='utf-8') as f: - endLine = 1 - for line in f.readlines(): - if not line.startswith("# rst"): - endLine += 1 - continue - line = line[5:] - if line.startswith("-name: "): - dash = line.find(" --- ") - if dash >= 0: - title = line[7:dash] - else: - title = line[7:-1] - s["f"].write("\n\n%s\n" % (line[7:-1])) - s["f"].write(80*"^" + "\n\n") - else: - assert line.startswith(": ") - s["f"].write(line[2:]) - s["f"].write(""" -`Explore in the playground -`__. - -.. literalinclude:: /_static/examples/py/{} - :language: python - :linenos: - :lines: 1-{} - :tab-width: 3 -""".format(fName, fName, endLine - 1)) - - for s in sections: - s["f"].close() - -generateExamples() - - - - # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '3.5.0' @@ -188,13 +95,17 @@ def generateExamples(): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [ - 'libmod/Libmodtoc.rst', - 'pymod/Pymodtoc.rst', - 'dataDesc/molEnc.rst', - 'dataDesc/graph.rst', - 'dataDesc/rule.rst', - 'dataDesc/term.rst', - 'dataDesc/dg.rst', + 'libmod/Toc.rst', + 'pymod/Toc.rst', + 'graphModel/molEnc.rst', + 'formats/dfs.rst', + 'formats/dg.rst', + 'formats/dot.rst', + 'formats/gml.rst', + 'formats/graphDFS.rst', + 'formats/mdl.rst', + 'formats/smiles.rst', + 'formats/tikz.rst', ] # The name of the Pygments (syntax highlighting) style to use. diff --git a/doc/source/dataDesc/graph.rst b/doc/source/dataDesc/graph.rst deleted file mode 100644 index db14fd8..0000000 --- a/doc/source/dataDesc/graph.rst +++ /dev/null @@ -1,193 +0,0 @@ -.. _graph-tikz: - -Tikz (Graph) -############ - -Graphs are visualised using generated `Tikz`_ code. -The coordinates for the layout is either generated using `Open Babel`_ or `Graphviz`_. -The visualisation style is controlled by passing instances of the classes -:cpp:class:`mod::graph::Printer` (C++) and :py:class:`mod.GraphPrinter` (Python) -to the printing functions. -The drawing style is inspired by `ChemFig`_ and `Open Babel`_. -See also :doc:`/postmod/postmod`. - - -.. _graph-dot: - -DOT (Graph) -########### - -The DOT format (from `Graphviz`_) is used for generating vertex coordinates for the Tikz format, -when `Open Babel`_ can not be used. - - -.. _graph-smiles: - -SMILES -###### - -The `Simplified molecular-input line-entry system` is a line notation for -molecules. MØD can load most SMILES strings, and converts them internally to -labelled graphs. For graphs that are sufficiently molecule-like, a SMILES -string can be generated. The generated strings are canonical in the sense that -the same version of MØD will print the same SMILES string for isomorphic -molecules. - -The reading of SMILES strings is based on the `OpenSMILES -`_ specification, but with the following -notes/changes. - -- Only single SMILES strings are accepted, i.e., not multiple strings separated - by white-space. -- Up and down bonds are regarded as implicit bonds, i.e., they might represent - either a sngle bond or an aromatic bond. The stereo information is ignored. -- Atom classes are (mostly) ignored. They can be used to specify unique IDs to - atoms. -- Wildcard atoms (specified with ``*``) are converted to vertices with label - ``*``. When inside brakcets, only the hydrogen count and atom class is then - permitted. -- Abstract vertex labels can be specified inside brakcets. The bracket must in - that case only contain the label and an optional class label. - The label must be a non-empty string without ``:`` and with balanced square - brackets. -- Charges of magnitude 2 and 3 may be specified with repeated ``-`` and ``+``. -- The bond type ``$`` is currently not allowed. -- Aromaticity can only be specified using the bond type ``:`` - or using the special lower case atoms. - I.e., ``c1ccccc1`` and ``C1:C:C:C:C:C:1`` represent the same molecule, - but ``C1=CC=CC=C1`` is a different molecule. -- Ring-bonds and branches may appear in mixed order. The normal order is to - have all ring-bonds first and all branches, e.g., ``C123(O)(N)``. - The parser accepts them in mixed order, e.g., ``C1(O)2(N)3``. -- The final graph will conform to the molecule encoding scheme described below. -- Implicit hydrogens are added following a more complicated procedure. -- A bracketed atom can have a radical by writing a dot (``.``) between the - position of the charge and the position of the class. - -The written SMILES strings are intended to be canonical and may not conform to any "prettyness" standards. - -Implicit Hydrogen Atoms ------------------------ - -When SMILES strings are written they will use implicit hydrogens whenever they -can be inferred when reading the string. -For the purposes of implicit hydrogens we use the following definition of -valence for an atom. -The valence of an atom is the weighted sum of its incident edges, where single -(``-``) and aromatic (``:``) bonds have weight 1, double bounds (``=``) have -weight 2, and triple bonds (``#``) have weight 3. -If an atom has an incident aromatic bond, its valence is increased by 1. -The atoms that can have implicit hydrogens are B, C, N, O, P, S, F, Cl, Br, and I. -Each have a set of so-called "normal" valences as shown in the following table. -The atoms N and S additionally have certain sets of incident edges that are -also considered "normal", which are also listed in the table. - -============= ===================================================================== -Atom Normal Valences and Neighbourhoods -============= ===================================================================== -B 3 -C 4 -N 3, 5, :math:`\{-, :, :\}`, :math:`\{-, -, =\}`, :math:`\{:, :, :\}` -O 2 -P 3, 5 -S 2, 4, 6, :math:`\{:, :\}` -F, Cl, Br, I 1 -============= ===================================================================== - -If the set of incident edges is listed in the table, then no hydrogens are added. -If the valence is higher than the highest normal valence, then no hydrogens are -added. -Otherwise, hydrogens are added until the valence is at the next higher normal -valence. - -When writing SMILES strings the inverse procedure is used. - - -.. _graph-graphDFS: - -GraphDFS -######## - -The GraphDFS format is intended to provide a convenient line notation for -general undirected labelled graphs. Thus it is in many aspects similar to -SMILES strings, but a string being both a valid SMILES string and GraphDFS -string **may not represent the same graph**. -The semantics of ring-closures/back-edges are in particular not the same. - -Grammar -------- - -.. productionlist:: graphDFS - graphDFS: `chain` - chain: `vertex` `evPair`* - vertex: (`labelVertex` | `ringClosure`) `branch`* - evPair: `edge` `vertex` - labelVertex: '[' bracketEscapedString ']' [ `defRingId` ] - : `implicitHydrogenVertexLabels` [ `defRingId` ] - implicitHydrogenVertexLabels: 'B' | 'C' | 'N' | 'O' | 'P' | 'S' | 'F' \ - | 'Cl' | 'Br' | 'I' - defRingId: unsignedInt - ringClosure: unsignedInt - edge: '{' braceEscapedString '}' - : `shorthandEdgeLabels` - shorthandEdgeLabels: '-' | ':' | '=' | '#' | '' - branch: '(' `evPair`+ ')' - -A ``bracketEscapedString`` and ``braceEscapedString`` are zero or more -characters except respectively ``]`` and ``}``. To have these characters in -each of their strings they must be escaped, i.e., ``\]`` and ``\}`` -respectively. - -The parser additionally enforces that a :token:`~graphDFS:defRingId` may not be -a number which has previously been used. -Similarly, a :token:`~graphDFS:ringClosure` may only be a number which has -previously occured in a :token:`~graphDFS:defRingId`. - -A vertex specified via the :token:`~graphDFS:implicitHydrogenVertexLabels` rule -will potentially have ekstra neighbours added after parsning. The rules are the -exact same as for implicit hydrogen atoms in :ref:`graph-smiles`. - - -Semantics ---------- - -A GraphDFS string is, like the SMILES strings, an encoding of a depth-first traversal of the -graph it encodes. -Vertex labels are enclosed in square brackets and edge labels are enclosed in curly brackets. -However, a special set of labels can be specified without the enclosing brackets. -An edge label may additionally be completely omitted as a shorthand for a dash (``-``). - -A vertex can have a numeric identifier, defined by the -:token:`~graphDFS:defRingId` non-terminal. -At a later stage this identifier can be used as a vertex specification to -specify a back-edge in the depth-first traversal. -Example: ``[v1]1-[v2]-[v3]-[v4]-1``, specifies a labelled :math:`C_3` -(which equivalently can be specified shorter as ``[v1]1[v2][v3][v4]1``). - -A :token:`~graphDFS:vertex` being a :token:`~graphDFS:ringClosure` can never be -the first vertex in a string, and is thus preceded with a -:token:`~graphDFS:edge`. As in a depth-first traversal, such a back-edge is a -kind of degenerated branch. Example: ``[v1]1[v2][v3][v4]1[v5][v6]1``, this -specifies a graph which is two fused :math:`C_4` with a common edge (and not -just a common vertex). - -.. warning:: The semantics of back-edges/ring closures are **not** the same as in SMILES strings. - In SMILES, a pair of matching numeric identifiers denote the individual back-edges. - -A branch in the depth-first traversal is enclosed in parentheses. - -Abstracted Molecules --------------------- - -The short-hand labels for vertices and edges makes it easier to specify partial molecules -than using :ref:`GML ` files. - -As example, consider modelling Acetyl-CoA in which we wish to abstract most of the CoA part. -The GraphDFS string ``CC(=O)S[CoA]`` can be used and we let the library add missing hydrogen -atoms to the vertices which encode atoms. A plain CoA molecule would in this modelling be -``[CoA]S``, or a bit more verbosely as ``[CoA]S[H]``. - -The format can also be used to create completely abstract structures -(it can encode any undirected labelled graph), e.g., RNA strings. -Note that in this case it may not be appropriate to add "missing" hydrogen atoms. -This can be controlled by an optional parameter to the loading function. diff --git a/doc/source/dataDesc/molEnc.rst b/doc/source/dataDesc/molEnc.rst deleted file mode 100644 index 10c3366..0000000 --- a/doc/source/dataDesc/molEnc.rst +++ /dev/null @@ -1,39 +0,0 @@ - -.. _mol-enc: - -Molecule Encoding -################# - -There is no strict requirement that graphs encode molecules, however several optimizations -are in place when they do. -The following describes how to encode molecules as undirected, simple, labelled graphs and thus -when the library assumes a graph is a molecule. - -Edges / Bonds -------------- - -An edge encodes a chemical bond if and only if its label is listed in the table below. - -====== ================== -Label Interpretation -====== ================== -``-`` Single bond -``:`` "Aromatic" bond -``=`` Double bond -``#`` Triple bond -====== ================== - -Vertices / Atoms ----------------- - -A vertex encodes an atom with a charge if and only if its label conforms to the following grammar. - -.. productionlist:: molecularVertexLabel - vertexLabel: [ isotope ] `atomSymbol` [ `charge` ] [ `radical` ] - isotope: unsignedInt - charge: singleDigit ('-' | '+') - radical: '.' - atomSymbol: an atom symbol with the first letter capitalised - -Currently there are no valence requirements for a graph being recognised as a -molecule. diff --git a/doc/source/dataDesc/rule.rst b/doc/source/dataDesc/rule.rst deleted file mode 100644 index 2e09944..0000000 --- a/doc/source/dataDesc/rule.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _rule-tikz: - -Tikz (Rule) -########### - -This format is used for visualising rules similarly to how the :ref:`graph-tikz` format is used -for graphs. A rule is depicted as its span :math:`(L\leftarrow K\rightarrow R)` with the vertex -positions in the plane indicating the embedding of :math:`K` in :math:`L` and :math:`R`. -Additionally, :math:`L\backslash K` and :math:`R\backslash K` are shown in different colour in -:math:`L` and :math:`R` respectively. - - -.. _rule-dot: - -DOT (Rule) -########## - -The DOT format (from `Graphviz`_) is used for generating vertex coordinates for the Tikz format, -when `Open Babel`_ can not be used. diff --git a/doc/source/dataDesc/term.rst b/doc/source/dataDesc/term.rst deleted file mode 100644 index e230b85..0000000 --- a/doc/source/dataDesc/term.rst +++ /dev/null @@ -1,35 +0,0 @@ - -.. py:currentmodule:: mod -.. cpp:namespace:: mod - -.. _term-desc: - -First-Order Terms -################# - -Vertex/edge labels on graphs/rules can be interpreted either as text strings -or as `firt-order terms `__. -Additionally, for first-order terms there is a choice in which type of relation between terms -should be required in morphisms. -This can be controlled in each algorithm through label settings objects -(C++: :cpp:class:`LabelSettings`, Python: :py:class:`LabelSettings`). - -A constant or function symbol is a word that can be matched -by the regex ``[A-Za-z0-9=#:.+-][A-Za-z0-9=#:.+-_]*``. -This means that all strings that are usually considered "molecular" can be reinterpreted -as constant symbols. - -A variable symbol is a word that can be matched -by the regex ``_[A-Za-z0-9=#:.+-][A-Za-z0-9=#:.+-_]*``. -That is, variable is like a constant/function symbol, but with a ``_`` prepended. -An unnamed variable can be specified by the special wildcard symbol ``*``. - -.. note:: Variable names matched by the regex ``_[HT][0-9][0-9]*`` may be generated - when printing out graphs/rules. Any original variable names are not saved. - -Function terms start with a function symbol followed by -a parenthesis with a comma-separated list of terms. -They may contain white-space. - -If parsing of terms fails a specific exception is thrown -(C++: :cpp:class:`TermParsingError`, Python: :py:class:`TermParsingError`). diff --git a/doc/source/dgStrat/dgStrat.rst b/doc/source/dgStrat/index.rst similarity index 100% rename from doc/source/dgStrat/dgStrat.rst rename to doc/source/dgStrat/index.rst diff --git a/doc/source/epim/epim.rst b/doc/source/epim/index.rst similarity index 100% rename from doc/source/epim/epim.rst rename to doc/source/epim/index.rst diff --git a/doc/source/modWrapper/modWrapper.rst b/doc/source/exe/index.rst similarity index 99% rename from doc/source/modWrapper/modWrapper.rst rename to doc/source/exe/index.rst index 2ddcbe5..2339b11 100644 --- a/doc/source/modWrapper/modWrapper.rst +++ b/doc/source/exe/index.rst @@ -5,7 +5,7 @@ .. _mod-wrapper: **************************** -The Wrapper Script (``mod``) +The Wrapper Script **************************** .. program:: mod diff --git a/doc/source/formats/dfs.rst b/doc/source/formats/dfs.rst new file mode 100644 index 0000000..4f22a80 --- /dev/null +++ b/doc/source/formats/dfs.rst @@ -0,0 +1,135 @@ +.. _format-dfs: + +DFS Line Notation +################# + +The DFS formats are intended to provide a convenient line +notation for general undirected labelled graphs and rules. +Thus it is in many aspects similar to SMILES strings and reaction SMILES +strings, but a string being both a valid (reaction SMILES) string and +GraphDFS/RuleDFS string **does not mean they represent the same objects**. +In particular, the semantics of ring-closures/back-edges are not the same. + +.. _format-graphDFS: + +GraphDFS +======== + +.. productionlist:: graphDFS + graphDFS: `chain` + chain: `vertex` `evPair`* + vertex: (`labelVertex` | `ringClosure`) `branch`* + evPair: `edge` `vertex` + labelVertex: '[' bracketEscapedString ']' [ `defRingId` ] + : `implicitHydrogenVertexLabels` [ `defRingId` ] + implicitHydrogenVertexLabels: 'B' | 'C' | 'N' | 'O' | 'P' | 'S' | 'F' \ + | 'Cl' | 'Br' | 'I' + defRingId: unsignedInt + ringClosure: unsignedInt + edge: '{' braceEscapedString '}' + : `shorthandEdgeLabel` + shorthandEdgeLabel: '-' | ':' | '=' | '#' | '.' | '' + branch: '(' `evPair`+ ')' + +A ``bracketEscapedString`` and ``braceEscapedString`` are zero or more +characters except respectively ``]`` and ``}``. To have these characters in +each of their strings they must be escaped, i.e., ``\]`` and ``\}`` +respectively. + +Whitespace is ignored, except inside ``bracketEscapedString`` and +``braceEscapedString``. + +The parser additionally enforces that a :token:`~graphDFS:defRingId` may not be +a number which has previously been used. +Similarly, a :token:`~graphDFS:ringClosure` may only be a number which has +previously occured in a :token:`~graphDFS:defRingId`. + +A vertex specified via the :token:`~graphDFS:implicitHydrogenVertexLabels` rule +will potentially have ekstra neighbours added after parsning. The rules are the +exact same as for implicit hydrogen atoms in :ref:`graph-smiles`. + +Semantics +--------- + +A GraphDFS string is, like the SMILES strings, an encoding of a depth-first +traversal of the graph it encodes. Vertex labels are enclosed in square +brackets and edge labels are enclosed in curly brackets. However, a special +set of labels can be specified without the enclosing brackets. +An edge label may additionally be completely omitted as a shorthand for a dash +(``-``). + +A vertex can have a numeric identifier, defined by the +:token:`~graphDFS:defRingId` non-terminal. +At a later stage this identifier can be used as a vertex specification to +specify a back-edge in the depth-first traversal. +Example: ``[v1]1-[v2]-[v3]-[v4]-1``, specifies a labelled :math:`C_4` +(which equivalently can be specified shorter as ``[v1]1[v2][v3][v4]1``). + +A :token:`~graphDFS:vertex` being a :token:`~graphDFS:ringClosure` can never be +the first vertex in a string, and is thus preceded with a +:token:`~graphDFS:edge`. As in a depth-first traversal, such a back-edge is a +kind of degenerated branch. Example: ``[v1]1[v2][v3][v4]1[v5][v6]1``, this +specifies a graph which is two fused :math:`C_4`, +:math:`v_1, v_2, v_3, v_4` and :math:`v_4, v_5, v_6, v_1`, +with a common edge, :math:`(v_1, v_4)`. + +.. warning:: The semantics of back-edges/ring closures are **not** the same as + in SMILES strings. In SMILES, a pair of matching numeric identifiers denote + the individual back-edges. + +A branch in the depth-first traversal is enclosed in parentheses. + +The :token:`~graphDFS:shorthandEdgeLabel` ``.`` indicates a non-edge, +i.e., a jump to a new vertex without creating an edge. +For example ``[v1].[v2]`` encodes a graph with two vertices and no edges, +while ``[v1]{.}[v2]`` encodes a graph with two vertcies connected with an edge +with label ``.``. + +Abstracted Molecules +-------------------- + +The short-hand labels for vertices and edges makes it easier to specify partial +molecules than using :ref:`GML ` files. + +As example, consider modelling Acetyl-CoA in which we wish to abstract most of +the CoA part. The GraphDFS string ``CC(=O)S[CoA]`` can be used and we let the +library add missing hydrogen atoms to the vertices which encode atoms. A plain +CoA molecule would in this modelling be ``[CoA]S``, or a bit more verbosely as +``[CoA]S[H]``. + +The format can also be used to create completely abstract structures +(it can encode any undirected labelled graph), e.g., RNA strings. +Note that in this case it may not be appropriate to add "missing" hydrogen +atoms. This can be controlled by an optional parameter to the loading function. + + +.. _format-ruleDFS: + +RuleDFS +======= + +The rule format builds on the graph format by using two GraphDFS strings to +encode a rule: + +.. productionlist:: graphDFS + ruleDFS: [ `graphDFS` ] '>>' [ `graphDFS` ] + +The two (possibly empty) GraphDFS strings encode the left-hand and right-hand +side of a rule, with the vertex IDs being used to relate them. +That is, a pair of vertices in the left side and right side with the same ID +will be identified and the vertex put in the context graph of the rule as well. +A similar pair of edges where both end-points are in the context graph will be +put in the context graph as well. + +Examples: + +- ``>>``: the empty rule. +- ``[A]>>``: a rule with a single vertex in :math:`L`, and empty :math:`K` and + :math:`R`. +- ``[A]>>[B]``: a rule with empty :math:`K` but with a vertex in :math:`L` + which is removed by the rule, and a vertex in :math:`R` being created by the + rule. +- ``[A]1>>[B]1``: a rule with a vertex changing label from "A" to "B". + +.. note:: Currently it is not possible to use vertices with implicit hydrogens + in RuleDFS. diff --git a/doc/source/dataDesc/dg.rst b/doc/source/formats/dg.rst similarity index 94% rename from doc/source/dataDesc/dg.rst rename to doc/source/formats/dg.rst index 61e7c5e..30c37e7 100644 --- a/doc/source/dataDesc/dg.rst +++ b/doc/source/formats/dg.rst @@ -1,6 +1,3 @@ -.. py:currentmodule:: mod -.. cpp:namespace:: mod - .. _dg_abstract-desc: Abstract Derivation Graphs diff --git a/doc/source/formats/dot.rst b/doc/source/formats/dot.rst new file mode 100644 index 0000000..7955ead --- /dev/null +++ b/doc/source/formats/dot.rst @@ -0,0 +1,6 @@ +DOT (Graphviz) +############## + +The DOT format (from `Graphviz `__) is used for +generating vertex coordinates for the Tikz format, when Open Babel can not be +used. diff --git a/doc/source/dataDesc/dataDesc.rst b/doc/source/formats/gml.rst similarity index 76% rename from doc/source/dataDesc/dataDesc.rst rename to doc/source/formats/gml.rst index 8ef4d3a..af51f54 100644 --- a/doc/source/dataDesc/dataDesc.rst +++ b/doc/source/formats/gml.rst @@ -1,10 +1,3 @@ -************ -Data Formats -************ - -MØD utilises several data formats and encoding schemes. - - .. _gml: GML @@ -36,7 +29,7 @@ They are ignored during parsing. .. _graph-gml: Graph ------ +===== A graph can be specified as :ref:`GML` by giving a list of vertices and edges with the key ``graph``. @@ -53,7 +46,7 @@ Note though that list elements can appear in any order. .. _rule-gml: Rule ----- +==== A rule :math:`(L\leftarrow K\rightarrow R)` in :ref:`GML` format is specified as three graph fragments; ``left``, ``context``, and ``right``. @@ -92,12 +85,35 @@ The key-value structure is exemplified by the following grammar. Note though that list elements can appear in any order. -.. include:: rule.rst +A Note on Term Labels +--------------------- + +As described in :ref:`graph-model-terms` it is possible to interpret the +ordinary vertex and edges labels as first-order terms. +When using the label ``*`` it will be interpreted as an unnamed term variable. +Consider the rule:: + + rule [ + left [ node [ id 0 label "*" ] ] + right [ node [ id 0 label "*" ] ] + ] + +In string mode this is simply an identity rule, but in term mode each ``*`` +is interpreted as an unnamed variable. Be careful that in this case the +two labels are interepreted as *the same* variable. That is, it is equivalent +to:: + + rule [ + left [ node [ id 0 label "_A" ] ] + right [ node [ id 0 label "_A" ] ] + ] -.. include:: graph.rst +If you wish to replace any vertex label with an explicit new variable, you can +write it as:: -.. include:: molEnc.rst + rule [ + left [ node [ id 0 label "_A" ] ] + right [ node [ id 0 label "_B" ] ] + ] -.. include:: term.rst -.. include:: dg.rst diff --git a/doc/source/formats/index.rst b/doc/source/formats/index.rst new file mode 100644 index 0000000..47072b8 --- /dev/null +++ b/doc/source/formats/index.rst @@ -0,0 +1,23 @@ +.. py:currentmodule:: mod +.. cpp:namespace:: mod + +.. _formats: + +************ +Data Formats +************ + +This page gives an overview of the *external* data formats that MØD works with. +For the formats that describe graphs and rules there are details on how +the external format is interpreted when creating the corresponding in-memmory +data structure. The in-memory model is described in :ref:`graph-model`. + + +.. include:: gml.rst +.. include:: smiles.rst +.. include:: dfs.rst +.. include:: mdl.rst +.. include:: dg.rst + +.. include:: tikz.rst +.. include:: dot.rst diff --git a/doc/source/formats/mdl.rst b/doc/source/formats/mdl.rst new file mode 100644 index 0000000..fcdc4b5 --- /dev/null +++ b/doc/source/formats/mdl.rst @@ -0,0 +1,31 @@ +.. _graph-mdl: + +MOL and SD +########## + +MØD can load graphs stored in the +`CT File `__ formats +MOL (single structure) and SD (multiple structures). +See the loading functions in :cpp:class:`graph::Graph`/:py:class:`Graph` for +the API. +The loaded structures are converted to labelled graphs according to a specific +:ref:`molecule encoding `. + +The reading of structures is based on the +`published specification `__, +but with the following notes/changes. + +- the :cpp:class:`MDLOptions`/:py:class:`MDLOptions` can be used to customize + the loading procedure. +- radical value 2 is converted to ``.`` in the vertex labels. +- the atom symbols "LP" and "L" are used as is, as an atom with an abstract + label. +- the atom symbols "A", "Q", and "*" are all considered as wildcard atoms and + are converted to vertices with label ``*``. + See the use of :ref:`first-order terms ` as labels. +- the bond orders 5, 6, and 7 for constrained wildcard bonds are converted + to edges with labels starting with ``_Q``, i.e., term variables. + See the use of :ref:`first-order terms ` as labels. +- the bond order 8 for unconstrianed bonds are converted to edges with label + ``*``. + See the use of :ref:`first-order terms ` as labels. diff --git a/doc/source/formats/smiles.rst b/doc/source/formats/smiles.rst new file mode 100644 index 0000000..e8e01ad --- /dev/null +++ b/doc/source/formats/smiles.rst @@ -0,0 +1,82 @@ +.. _graph-smiles: + +SMILES +###### + +The `Simplified molecular-input line-entry system` is a line notation for +molecules. MØD can load most SMILES strings, and converts them internally to +labelled graphs according to a specific :ref:`molecule encoding `. +For graphs that are sufficiently molecule-like, a SMILES string can be +generated. The generated strings are canonical in the sense that the same +version of MØD will print the same SMILES string for isomorphic molecules. + +The reading of SMILES strings is based on the `OpenSMILES +`_ specification, but with the following +notes/changes. + +- Only single SMILES strings are accepted, i.e., not multiple strings separated + by white-space. +- Up and down bonds are regarded as implicit bonds, i.e., they might represent + either a sngle bond or an aromatic bond. The stereo information is ignored. +- Atom classes are (mostly) ignored. They can be used to specify unique IDs to + atoms. +- Wildcard atoms (specified with ``*``) are converted to vertices with label + ``*``. When inside brakcets, only the hydrogen count and atom class is then + permitted. +- Abstract vertex labels can be specified inside brakcets. The bracket must in + that case only contain the label and an optional class label. + The label must be a non-empty string without ``:`` and with balanced square + brackets. +- Charges of magnitude 2 and 3 may be specified with repeated ``-`` and ``+``. +- The bond type ``$`` is currently not allowed. +- Aromaticity can only be specified using the bond type ``:`` + or using the special lower case atoms. + I.e., ``c1ccccc1`` and ``C1:C:C:C:C:C:1`` represent the same molecule, + but ``C1=CC=CC=C1`` is a different molecule. + The lower-case atoms are converted to normal case when used as a label. +- Ring-bonds and branches may appear in mixed order. The normal order is to + have all ring-bonds first and all branches, e.g., ``C123(O)(N)``. + The parser accepts them in mixed order, e.g., ``C1(O)2(N)3``. +- Implicit hydrogens are added following a more complicated procedure + (see below). +- A bracketed atom can have a radical by writing a dot (``.``) between the + position of the charge and the position of the class. + +The written SMILES strings are intended to be canonical and may not conform to +any "prettyness" standards. + +Implicit Hydrogen Atoms +======================= + +When SMILES strings are written they will use implicit hydrogens whenever they +can be inferred when reading the string back in. +For the purposes of implicit hydrogens we use the following definition of +valence for an atom. +The valence of an atom is the weighted sum of its incident edges, where single +(``-``) and aromatic (``:``) bonds have weight 1, double bounds (``=``) have +weight 2, and triple bonds (``#``) have weight 3. +If an atom has an incident aromatic bond, its valence is increased by 1. +The atoms that can have implicit hydrogens are +B, C, N, O, P, S, F, Cl, Br, and I. +Each have a set of so-called "normal" valences as shown in the following table. +The atoms N and S additionally have certain sets of incident edges that are +also considered "normal", which are also listed in the table. + +============= ===================================================================== +Atom Normal Valences and Neighbourhoods +============= ===================================================================== +B 3 +C 4 +N 3, 5, :math:`\{-, :, :\}`, :math:`\{-, -, =\}`, :math:`\{:, :, :\}` +O 2 +P 3, 5 +S 2, 4, 6, :math:`\{:, :\}` +F, Cl, Br, I 1 +============= ===================================================================== + +If the set of incident edges is listed in the table, then no hydrogens are +added. If the valence is higher than the highest normal valence, then no +hydrogens are added. Otherwise, hydrogens are added until the valence is at the +next higher normal valence. + +When writing SMILES strings the inverse procedure is used. diff --git a/doc/source/formats/tikz.rst b/doc/source/formats/tikz.rst new file mode 100644 index 0000000..60808fe --- /dev/null +++ b/doc/source/formats/tikz.rst @@ -0,0 +1,19 @@ +Tikz +#### + +Both graphs and rules are visualized through :ref:`PostMØD ` by the +library generating `Tikz `__ code and compiling it +with Latex. + +The visualisation style is controlled by passing instances of +:cpp:class:`mod::graph::Printer`/:py:class:`mod.GraphPrinter` +to the printing functions. +The drawing style is inspired by `ChemFig `__ +and `Open Babel `__. + +The coordinates for the layout is either generated using Open Babel when the +graphs a chemical enough, but otherwise `Graphviz `__ +is invoked to generate coordinates. + +For visualizing a rule or DPO diagram, the position of vertices is used to +indicate how morphisms map vertices to each other. diff --git a/doc/source/graphModel/index.rst b/doc/source/graphModel/index.rst new file mode 100644 index 0000000..7fcafbc --- /dev/null +++ b/doc/source/graphModel/index.rst @@ -0,0 +1,272 @@ +.. cpp:namespace:: mod +.. py:currentmodule:: mod + +.. _graph-model: + +******************************* +Graph, Rule, and Molecule Model +******************************* + +A central component of MØD is the :cpp:class:`graph::Graph`/:py:class:`Graph` +which simplified represents simple graphs with labels on vertices and edges. +However, how the labels are interepreted and how graph morphisms are defined +depends which :cpp:class:`LabelSettings`/:py:class:`LabelSettings` object you +pass to various algorithms. +Another complication is that in the formal mathematics for describing +graph transformation through a :cpp:class:`rule::Rule`/:py:class:`Rule` classes, +we need need a slightly more complicated description of the graphs in order +to make the mathematics work. + +We therefore start with the :ref:`simplified graph model ` +with the labels being opaque types. +:ref:`Then ` we explore the different label settings +which switches the mathematical category of the graphs. +Afterwards the :ref:`rule model ` is explained where we +get into the mathematical details for label-encoding which is necessary to +formally make the graph transformation composable. +Lastly we see how MØD interprets graphs as molecule (fragments) in order to +create depictions that resembles ordinary molecule depictions. + + +.. _graph-model-basic: + +Simple Graphs with String Labels +################################ + +In the simplified model, a graph +:math:`G = (V, E, l\colon V\cup E\rightarrow \Omega)` +is undirected, and neither has loop edges nor parallel edges. +The function :math:`l` assigns a label to each vertex and edge. +A morphism between two graphs :math:`m\colon G\rightarrow H` is a graph morphism +which commutes with the labelling functions. +That is, + +- graph morphism: for every edge :math:`(u, v) \in E_G` we must have an + corresponding edge + :math:`(m(u), m(v)) \in E_H` (:math:`m` is a graph morphism), and +- vertex labels: each vertex :math:`v \in V_G` is mapped to a vertex with the + same label, :math:`l_G(v) = l_H(m(v))`. +- edge labels: each edge :math:`e \in E_G` is mapped to a vertex with the + same label, :math:`l_G(e) = l_H(m(e))`. + +The only requirement of :math:`\Omega` is thus that the elements can be +compared for equality. +In MØD this correspons to using +:cpp:any:`LabelType::String`/:py:obj:`LabelType.String` +in the a :cpp:class:`LabelSettings`/:py:class:`LabelSettings` object, +and in the API of PyMØD, this is the default. + +.. note:: When using :cpp:any:`LabelType::String`/:py:obj:`LabelType.String` + the :cpp:any:`LabelRelation`/:py:obj:`LabelRelation` in + :cpp:class:`LabelSettings`/:py:class:`LabelSettings` doesn't matter, + +.. note:: Technically, in this basic model the stereo setting in + :cpp:class:`LabelSettings`/:py:class:`LabelSettings` should be disabled as + well. Though, the stereo model is in an experimental stage and we will + therefore not explore it further. + +A :cpp:class:`graph::Graph`/:py:class:`Graph` is further required to be +connected. + + +.. _graph-model-terms: + +Switching Category --- First-Order Terms as Labels +################################################## + +We can imbue the label set :math:`Omega` with extra structure by using +:cpp:any:`LabelType::Term`/:py:obj:`LabelType.Term` instead of +:cpp:any:`LabelType::String`/:py:obj:`LabelType.String`. +The labels are now interpreted as `firt-order terms `__, +and formally the graph/objects are now in a different +`category `__. + +In the graph/rule objects it is the same text strings being used either verbatim +when in string mode or being parsed when in term mode. + +The following describes how the strings are parsed in term mode. + +- A constant or function symbol is a word that can be matched by the regex + ``[A-Za-z0-9=#:.+-][A-Za-z0-9=#:.+-_]*``. + This means that all strings that are :ref:`considered "molecular" ` + can be reinterpreted as constant symbols. +- A variable symbol is a word that can be matched by the regex + ``_[A-Za-z0-9=#:.+-][A-Za-z0-9=#:.+-_]*``. + That is, variable is like a constant/function symbol, but with a ``_`` + prepended. + An unnamed variable can be specified by the special wildcard symbol ``*``. + + The variable symbols matched by the regex ``_[HT][0-9][0-9]*`` are + discouraged, as they are reserved for converting terms back into strings. +- Function terms start with a function symbol followed by a parenthesis with a + comma-separated list of terms. They may contain white-space which is ignored. + +If parsing of terms fails an exception of type +:cpp:class:`TermParsingError`/:py:class:`TermParsingError` +is thrown. Thus, essentially any function that takes a +:cpp:class:`LabelSettings`/:py:class:`LabelSettings` +object may throw such an exception. + +If a graph/rule was created in term mode and then is used in string mode, +the first-order terms are serialized back into strings: + +- Constant/function symbols are serialized verbatim. +- Variable names are generated and will be matchable by the regex + ``_[HT][0-9][0-9]*``. Any original variable names are not saved. +- Function terms will be serialized with a single space character after each + comma. + + +Morphisms +========= + +First-order terms are partially ordered, and we thus have additional choice in +how to define morphisms. +This choice is in the API selected through the +:cpp:any:`LabelRelation`/:py:obj:`LabelRelation` objects which offers the three +choices "unification", "specialisation", and "isomorphism". +Given a graph morphism :math:`m\colon G\rightarrow H` +let :math:`V_G = \{v_1, v_2, \dots, v_n\}` be the vertices of :math:`G` +and :math:`E_G = \{e_1, e_2, \dots, e_p\}` be the edges of :math:`G`. +Consider the following two terms: + +.. math:: + + t_G &= \texttt{assoc}(l_G(v_1), l_G(v_2), \dots, l_G(v_n), + l_G(e_1), l_G(e_2), \dots, l_G(e_p)) \\ + t_H &= \texttt{assoc}(l_H(m(v_1)), l_H(m(v_2)), \dots, l_H(m(v_n)), + l_H(m(e_1)), l_H(m(e_2)), \dots, l_H(m(e_p))) + +They lay out all the labels of :math:`G` in :math:`t_G` and then the labels +of the corresponding vertices and edges in :math:`H` in :math:`t_H`. + +The graph morphism :math:`m` is in term mode a valid morphism if + +- with + :cpp:any:`LabelRelation::Unification`/:py:obj:`LabelRelation.Unification` + there exists a most-general unifier between :math:`t_G` and :math:`t_H`. +- with + :cpp:any:`LabelRelation::Specialisation`/:py:obj:`LabelRelation.Specialisation` + there exists a most-general unifier between :math:`t_G` and :math:`t_H`, + and it only binds variables in :math:`t_G` but not :math:`t_H`. + That is, :math:`t_H` is a specialisation of :math:`t_G`. +- with + :cpp:any:`LabelRelation::Isomorphism`/:py:obj:`LabelRelation.Isomorphism` + there exists a most-general unifier between :math:`t_G` and :math:`t_H`, + and it only binds variables to variables, and that variable mapping is + a bijection. + That is, the only difference between :math:`t_G` and :math:`t_H` is the naming + of the variables they use. + +In most cases you should use +:cpp:any:`LabelRelation::Specialisation`/:py:obj:`LabelRelation.Specialisation` +though, when for example using +:cpp:func:`graph::Graph::isomorphism`/:py:meth:`Graph.isomorphism` +to check if two graphs encode the same mathematical object, you should use +:cpp:any:`LabelRelation::Isomorphism`/:py:obj:`LabelRelation.Isomorphism`. +The funtion name thus refers to the type of the *graph morphism* being found, +while the label settings describes the additional requirements for the labels. + +The third option, +:cpp:any:`LabelRelation::Unification`/:py:obj:`LabelRelation.Unification`, +should be used very carefully. +For example, when performing graph transofrmation, e.g., through a +:cpp:class:`dg::DG`/:py:class:`DG`, it may give results that are unexpected. +One use-case is when calling +:cpp:func:`graph::Graph::isomorphism`/:py:meth:`Graph.isomorphism`, +where using +:cpp:any:`LabelRelation::Unification`/:py:obj:`LabelRelation.Unification` +means that you will find graph isomorphisms for which the labels on both graphs +can be specialized such that the graph objects becomes isomorphic. + +.. note:: For the graph transformation is it necessary to define a distinguished + set of morphisms :math:`\mathcal{M}` for the term mode. + These morphisms are those that are graph monomorphisms, but are label + isomorphisms on the label part. + + +.. _graph-model-rules: + +Rule Model +########## + +A :cpp:class:`rule::Rule`/:py:class:`Rule` represents a +`Double Pushout `__ +(DPO) graph transformation rule :math:`p = (L\xleftarrow{l}K\xrightarrow{r}R)`. +Specifically, they are in the DPO variant with all morphisms being at least +graph monomorphisms. + +Label Change +============ + +The rules allow both for vertices and edges to change labels, but only the +latter is well-defined in traditional DPO transformation. +Thus, mathematically it is not immediately possible to define the graphs as +above. Consider a vertex changing label in a rule, which label should it +have in :math:`K`? +However, as our graphs are not allowed to have loop edges we can as a trick +simply pretend that whenever a vertex would have a label, we attach a loop edge +to the vertex, and put the "vertex" label on that edge. +Note that all morphisms are required to be at least graph monomorphisms, so a +loop edge can not be created inadvertently. + +Avoiding Parallel Edges +======================= + +Our graphs are defined to be without parallel edges, which presents a +mathematical problem in that certain pushouts are not allowed. +In a DPO direct derivation we can avoid parallel edges by simply introducing +algorithmic ad-hoc constraint, that if the second pushout would result in +parallel edges in the result graph, then the direct derviation is not defined. + +However, in the big picture we can avoid this ad-hoc solution and think of the +rules as having implicit negative application conditions (NACs): + +- For each pair of vertices :math:`u, v\in V_K` + which are not connected in :math:`L`, :math:`(l(u), l(v))\not\in E_L`, + but are connected in :math:`R`, :math:`(r(u), r(v))\in E_R`, + there is a NAC on :math:`L` preventing edges :math:`(l(u), l(v))`. +- And symmetrically, for each pair of vertices :math:`u, v\in V_K` + which are connected in :math:`L`, :math:`(l(u), l(v))\not\in E_L`, + but are not connected in :math:`R`, :math:`(r(u), r(v))\in E_R`, + there is a NAC on :math:`R` preventing edges :math:`(l(u), l(v))`. + + +.. _mol-enc: + +Molecule Encoding +################# + +There is no strict requirement that graphs/rules encode molecule (fragments), +however several optimizations are in place when they do, and depictions are +prettified based on "moleculeness". +The following describes the encoding of molecules in the +:ref:`basic graph model `, that MØD recognizes. + +Edges / Bonds +============= + +An edge encodes a chemical bond if and only if its label is listed in the table below. + +====== ================== +Label Interpretation +====== ================== +``-`` Single bond +``:`` "Aromatic" bond +``=`` Double bond +``#`` Triple bond +====== ================== + +Vertices / Atoms +================ + +A vertex encodes an atom if and only if its label conforms to the following grammar. + +.. productionlist:: molecularVertexLabel + vertexLabel: [ `isotope` ] `atomSymbol` [ `charge` ] [ `radical` ] + isotope: unsignedInt + charge: [ singleDigit ] ('-' | '+') + radical: '.' + atomSymbol: an atom symbol with the first letter capitalised + +.. note:: In the basic model there are no valence requirements for a graph being recognised as a molecule. diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..ce57f53 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,53 @@ +################################################ +MØD +################################################ + +This is the documentation for the MØD software package. +The sources can be found in the GitHub repository: +``_. +For additional information see the webpage: ``_. + +The package contains the following components. + +* :ref:`libmod`, a C++ shared library (``mod``) + The main library. +* :ref:`pymod`, a Python module (``mod``) + Python bindings for the library and extra functionality for easier usage of + many features. + It additionally includes the submodule :ref:`epim`, +* :ref:`mod_post`, a Bash script (``mod_post``) + The post-processor for compiling figures and summaries. +* :ref:`mod-wrapper`, a Bash script (``mod``) + A convenience wrapper for invoking ``python3`` with user-supplied code. + The wrapper handles output and invokes the post-processor. + Additionally, it can run ``python3`` through ``gdb`` + and/or ``valgrind`` (either normal memcheck or callgrind). + + +Contents +======== + +.. toctree:: + :maxdepth: 1 + :numbered: + + installation + compiling + libmod/index + pymod/index + postmod/index + exe/index + graphModel/index + formats/index + dgStrat/index + examples/index + + knownIssues + changes + + +Contributors +============ + +* `Jakob Lykke Andersen `__: main author. +* `Nikolai Nøjgaard `__: author of :ref:`EpiM`. diff --git a/doc/source/installation.rst b/doc/source/installation.rst index bdaf41a..c136b70 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -85,7 +85,7 @@ We thus arrive at the following full command docker run -it --rm -v $(pwd):/workdir -u $(id -u):$(id -g) jakobandersen/mod -As a quick test, you can try running ``mod -e "smiles('O').print()`` inside +As a quick test, you can try running ``mod -e "smiles('O').print()"`` inside the container. When it's done, then outside the container you should now be able to open the printed summary ``summary/summary.pdf``. You can thus use the ``mod`` command inside the container, but otherwise diff --git a/doc/source/libmod/api.rst b/doc/source/libmod/api.rst index b4f480f..d700919 100644 --- a/doc/source/libmod/api.rst +++ b/doc/source/libmod/api.rst @@ -6,4 +6,4 @@ declarations directly in the ``mod`` namespace. The nested namespace ``mod::lib` contains the actual implementation, and you probably do not want to use anything from it directly. -.. include:: Libmodtoc.rst +.. include:: Toc.rst diff --git a/doc/source/libmod/libmod.rst b/doc/source/libmod/index.rst similarity index 91% rename from doc/source/libmod/libmod.rst rename to doc/source/libmod/index.rst index 944b445..0e59716 100644 --- a/doc/source/libmod/libmod.rst +++ b/doc/source/libmod/index.rst @@ -1,3 +1,5 @@ +.. _libmod: + ****** libMØD ****** diff --git a/doc/source/postmod/postmod.rst b/doc/source/postmod/index.rst similarity index 95% rename from doc/source/postmod/postmod.rst rename to doc/source/postmod/index.rst index 99ef336..775e9a4 100644 --- a/doc/source/postmod/postmod.rst +++ b/doc/source/postmod/index.rst @@ -1,7 +1,7 @@ .. _mod_post: **************************** -PostMØD (``mod_post``) +PostMØD **************************** .. program:: mod_post @@ -13,7 +13,7 @@ invoked by the wrapper script. The script does the following: #. Clear the folder ``summary/``. If it does not exist, it is created. -#. Source the file ``out/post.sh`` and create ``summary/Makefile`` +#. Source the command file ``out/post.sh`` and create ``summary/Makefile`` (and a bunch of additional Makefile files it includes). #. Run ``make -f summary/Makefile all`` (with a few more arguments). The Makefile will compile figures and generate a Latex file. diff --git a/doc/source/pymod/api.rst b/doc/source/pymod/api.rst index 194b4eb..bf43f60 100644 --- a/doc/source/pymod/api.rst +++ b/doc/source/pymod/api.rst @@ -1,4 +1,4 @@ API Reference ============= -.. include:: Pymodtoc.rst +.. include:: Toc.rst diff --git a/doc/source/pymod/pymod.rst b/doc/source/pymod/index.rst similarity index 94% rename from doc/source/pymod/pymod.rst rename to doc/source/pymod/index.rst index e1d96e2..f25eb9b 100644 --- a/doc/source/pymod/pymod.rst +++ b/doc/source/pymod/index.rst @@ -1,5 +1,7 @@ .. py:module:: mod +.. _pymod: + ***** PyMØD ***** @@ -14,4 +16,5 @@ which automatically sets ``PYTHONPATH`` and inserts a small preamble. :maxdepth: 2 api + ../epim/index extensions diff --git a/docker/Arch.Dockerfile b/docker/Arch.Dockerfile index aebe980..30fc819 100644 --- a/docker/Arch.Dockerfile +++ b/docker/Arch.Dockerfile @@ -9,11 +9,12 @@ RUN pacman -Suy --noconfirm \ python-pip \ && pip3 install -r requirements_nodoc.txt \ && pacman -Suy --noconfirm \ - $(bindep -b | tr '\n' ' ') \ + $(bindep -b testing | tr '\n' ' ') \ && rm -rf /var/cache/pacman WORKDIR /opt/mod/build +ENV CXXFLAGS=-Werror -Wno-error=maybe-uninitialized RUN cmake ../ -DBUILD_DOC=no \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING_SANITIZERS=off \ diff --git a/docker/Fedora.Dockerfile b/docker/Fedora.Dockerfile index cc2ed31..027cf31 100644 --- a/docker/Fedora.Dockerfile +++ b/docker/Fedora.Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:34 +FROM fedora:36 ARG j=7 WORKDIR /opt/mod @@ -9,12 +9,14 @@ RUN dnf install -y \ python3-pip \ && pip3 install -r requirements_nodoc.txt \ && dnf install -y \ - $(bindep -b | tr '\n' ' ') \ + $(bindep -b testing | tr '\n' ' ') \ && dnf clean all \ && rm -rf /var/cache/yum WORKDIR /opt/mod/build +ENV BABEL_LIBDIR=/usr/lib64/openbabel3 +ENV CXXFLAGS=-Werror RUN cmake ../ -DBUILD_DOC=no \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING_SANITIZERS=off \ diff --git a/docker/Ubuntu.Dockerfile b/docker/Ubuntu.Dockerfile index 4776af9..84fc6e0 100644 --- a/docker/Ubuntu.Dockerfile +++ b/docker/Ubuntu.Dockerfile @@ -23,11 +23,11 @@ RUN apt-get update -qq \ && pip3 install -r requirements_nodoc.txt \ && DEBIAN_FRONTEND=noninteractive \ apt install --no-install-recommends -y \ - $(bindep -b | tr '\n' ' ') \ + $(bindep -b testing | tr '\n' ' ') \ librsvg2-dev libpango1.0-dev \ && DEBIAN_FRONTEND=noninteractive \ apt install --no-install-recommends -y \ - vim \ + vim less \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -47,7 +47,7 @@ RUN \ # the folder can apparently not be called just 'boost', therefore 'boostDir' WORKDIR /opt/boostDir RUN wget \ - https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.gz \ + https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.gz \ -O boost.tar.gz RUN \ tar -xf boost.tar.gz --one-top-level=boostSrc --strip-components=1 \ @@ -61,6 +61,7 @@ RUN \ WORKDIR /opt/mod/build +ENV CXXFLAGS=-Werror RUN cmake ../ -DBUILD_DOC=no \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_MODULE_LINKER_FLAGS="-flto=$j" -DCMAKE_SHARED_LINKER_FLAGS="-flto=$j" \ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e52590a..3c0b00a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -58,7 +58,7 @@ if(BUILD_PY_MOD) COMMAND ${CMAKE_INSTALL_FULL_BINDIR}/mod -f ${CMAKE_CURRENT_LIST_DIR}/${fileName}.py WORKING_DIRECTORY ${workDir}) set_tests_properties(${testName} PROPERTIES - ENVIRONMENT "MOD_NUM_POST_THREADS=1;MOD_NO_DEPRECATED=1") + ENVIRONMENT "MOD_NUM_POST_THREADS=1;PYTHONWARNINGS=error;MOD_NO_DEPRECATED=1") add_coverage_case(${testName}) endforeach() endif() \ No newline at end of file diff --git a/examples/py/0000_hello.py b/examples/py/000_basics/000_hello.py similarity index 88% rename from examples/py/0000_hello.py rename to examples/py/000_basics/000_hello.py index 06b6986..d3a2cf6 100644 --- a/examples/py/0000_hello.py +++ b/examples/py/000_basics/000_hello.py @@ -1,8 +1,8 @@ # Normal printing to the terminal: print("Hello world") # Make some headers in the summary: -postChapter("Hello") -postSection("World") +post.summaryChapter("Hello") +post.summarySection("World") # Load a moleucle from a SMILES string: mol = smiles("Cn1cnc2c1c(=O)n(c(=O)n2C)C", name="Caffeine") # Put a visualisation of the molecule in the summary: diff --git a/examples/py/0010_graphLoading.py b/examples/py/000_basics/010_graphLoading.py similarity index 100% rename from examples/py/0010_graphLoading.py rename to examples/py/000_basics/010_graphLoading.py diff --git a/examples/py/0011_graphPrinting.py b/examples/py/000_basics/011_graphPrinting.py similarity index 100% rename from examples/py/0011_graphPrinting.py rename to examples/py/000_basics/011_graphPrinting.py diff --git a/examples/py/0012_graphInterface.py b/examples/py/000_basics/012_graphInterface.py similarity index 100% rename from examples/py/0012_graphInterface.py rename to examples/py/000_basics/012_graphInterface.py diff --git a/examples/py/0013_graphMorphisms.py b/examples/py/000_basics/013_graphMorphisms.py similarity index 100% rename from examples/py/0013_graphMorphisms.py rename to examples/py/000_basics/013_graphMorphisms.py diff --git a/examples/py/0030_ruleLoading.py b/examples/py/000_basics/030_ruleLoading.py similarity index 100% rename from examples/py/0030_ruleLoading.py rename to examples/py/000_basics/030_ruleLoading.py diff --git a/examples/py/0031_ruleMorphisms.py b/examples/py/000_basics/031_ruleMorphisms.py similarity index 100% rename from examples/py/0031_ruleMorphisms.py rename to examples/py/000_basics/031_ruleMorphisms.py diff --git a/examples/py/0050_formoseGrammar.py b/examples/py/000_basics/050_formoseGrammar.py similarity index 100% rename from examples/py/0050_formoseGrammar.py rename to examples/py/000_basics/050_formoseGrammar.py diff --git a/examples/py/000_basics/051_fileInclusion.py b/examples/py/000_basics/051_fileInclusion.py new file mode 100644 index 0000000..77243bd --- /dev/null +++ b/examples/py/000_basics/051_fileInclusion.py @@ -0,0 +1,9 @@ +include("050_formoseGrammar.py") +post.summarySection("Input Graphs") +for a in inputGraphs: + a.print() +post.summarySection("Input Rules") +for a in inputRules: + a.print() +# rst-name: Including Files +# rst: We can include other files (a la C/C++) to separate functionality. diff --git a/examples/py/000_basics/meta.json b/examples/py/000_basics/meta.json new file mode 100644 index 0000000..5c20fe7 --- /dev/null +++ b/examples/py/000_basics/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Basics, Graphs, and Rules", + "oldIds": ["basics"] +} diff --git a/examples/py/0051_fileInclusion.py b/examples/py/0051_fileInclusion.py deleted file mode 100644 index 93d57ac..0000000 --- a/examples/py/0051_fileInclusion.py +++ /dev/null @@ -1,9 +0,0 @@ -include("0050_formoseGrammar.py") -postSection("Input Graphs") -for a in inputGraphs: - a.print() -postSection("Input Rules") -for a in inputRules: - a.print() -# rst-name: Including Files -# rst: We can include other files (a la C/C++) to seperate functionality. diff --git a/examples/py/0100_rcGraphOps.py b/examples/py/010_rc/100_rcGraphOps.py similarity index 81% rename from examples/py/0100_rcGraphOps.py rename to examples/py/010_rc/100_rcGraphOps.py index 0802dcd..63dc6f9 100644 --- a/examples/py/0100_rcGraphOps.py +++ b/examples/py/010_rc/100_rcGraphOps.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") glycolaldehyde.print() # A graph G can be used to construct special rules: # (\emptyset <- \emptyset -> G) @@ -13,13 +13,13 @@ bindRules = rc.eval(bindExp) unbindRules = rc.eval(unbindExp) idRules = rc.eval(idExp) -postSection("Bind Rules") +post.summarySection("Bind Rules") for p in bindRules: p.print() -postSection("Unbind Rules") +post.summarySection("Unbind Rules") for p in unbindRules: p.print() -postSection("Id Rules") +post.summarySection("Id Rules") for p in idRules: p.print() # rst-name: Unary Operators diff --git a/examples/py/0101_rcParallel.py b/examples/py/010_rc/101_rcParallel.py similarity index 88% rename from examples/py/0101_rcParallel.py rename to examples/py/010_rc/101_rcParallel.py index 6385363..8282b05 100644 --- a/examples/py/0101_rcParallel.py +++ b/examples/py/010_rc/101_rcParallel.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") rc = rcEvaluator(inputRules) # The special global object 'rcParallel' is used to make a pseudo-operator: exp = rcId(formaldehyde) *rcParallel* rcUnbind(glycolaldehyde) diff --git a/examples/py/0102_rcSuper.py b/examples/py/010_rc/102_rcSuper.py similarity index 85% rename from examples/py/0102_rcSuper.py rename to examples/py/010_rc/102_rcSuper.py index b059aa2..f51172a 100644 --- a/examples/py/0102_rcSuper.py +++ b/examples/py/010_rc/102_rcSuper.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") rc = rcEvaluator(inputRules) exp = rcId(formaldehyde) *rcParallel* rcId(glycolaldehyde) exp = exp *rcSuper* ketoEnol_F diff --git a/examples/py/0120_rcFormose.py b/examples/py/010_rc/120_rcFormose.py similarity index 92% rename from examples/py/0120_rcFormose.py rename to examples/py/010_rc/120_rcFormose.py index ee5ee0f..9a6c959 100644 --- a/examples/py/0120_rcFormose.py +++ b/examples/py/010_rc/120_rcFormose.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") rc = rcEvaluator(inputRules) exp = ( rcId(glycolaldehyde) diff --git a/examples/py/010_rc/meta.json b/examples/py/010_rc/meta.json new file mode 100644 index 0000000..334fb06 --- /dev/null +++ b/examples/py/010_rc/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Rule Composition", + "oldIds": ["rc"] +} diff --git a/examples/py/0210_dgFixed.py b/examples/py/020_dg/210_dgFixed.py similarity index 93% rename from examples/py/0210_dgFixed.py rename to examples/py/020_dg/210_dgFixed.py index 09ae59e..40529f6 100644 --- a/examples/py/0210_dgFixed.py +++ b/examples/py/020_dg/210_dgFixed.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") # Reaction networks are expaned using a strategy: strat = ( # A molecule can be active or passive during evaluation. diff --git a/examples/py/0211_dgRepeat.py b/examples/py/020_dg/211_dgRepeat.py similarity index 85% rename from examples/py/0211_dgRepeat.py rename to examples/py/020_dg/211_dgRepeat.py index f69007b..2e71375 100644 --- a/examples/py/0211_dgRepeat.py +++ b/examples/py/020_dg/211_dgRepeat.py @@ -1,4 +1,4 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") strat = ( addUniverse(formaldehyde) >> addSubset(glycolaldehyde) diff --git a/examples/py/0212_dgPredicate.py b/examples/py/020_dg/212_dgPredicate.py similarity index 83% rename from examples/py/0212_dgPredicate.py rename to examples/py/020_dg/212_dgPredicate.py index 01fcd41..5a526ac 100644 --- a/examples/py/0212_dgPredicate.py +++ b/examples/py/020_dg/212_dgPredicate.py @@ -1,9 +1,9 @@ -include("0050_formoseGrammar.py") +include("../000_basics/050_formoseGrammar.py") strat = ( addUniverse(formaldehyde) >> addSubset(glycolaldehyde) # Constrain the reactions: - # No molecules with more than 20 atom can be created. + # No molecules with more than 20 atoms can be created. >> rightPredicate[ lambda derivation: all(g.numVertices <= 20 for g in derivation.right) ]( diff --git a/examples/py/0250_dgPrinting.py b/examples/py/020_dg/250_dgPrinting.py similarity index 89% rename from examples/py/0250_dgPrinting.py rename to examples/py/020_dg/250_dgPrinting.py index 8bac1fd..577abb4 100644 --- a/examples/py/0250_dgPrinting.py +++ b/examples/py/020_dg/250_dgPrinting.py @@ -1,4 +1,4 @@ -include("0212_dgPredicate.py") +include("212_dgPredicate.py") # Create a printer with default options: p = DGPrinter() # Hide "large" molecules: those with > 4 Cs: @@ -10,7 +10,7 @@ def edgePred(e): return True p.pushEdgeVisible(edgePred) # Add the number of Cs to the molecule labels: -p.pushVertexLabel(lambda v: "\\#C=" + str(v.graph.vLabelCount("C"))) +p.pushVertexLabel(lambda v: "#C=" + str(v.graph.vLabelCount("C"))) # Highlight the molecules with 4 Cs: p.pushVertexColour(lambda v: "blue" if v.graph.vLabelCount("C") == 4 else "") # Print the network with the customised printer. diff --git a/examples/py/0260_dpoPrinting.py b/examples/py/020_dg/260_dpoPrinting.py similarity index 81% rename from examples/py/0260_dpoPrinting.py rename to examples/py/020_dg/260_dpoPrinting.py index bdb63a9..f61d6d4 100644 --- a/examples/py/0260_dpoPrinting.py +++ b/examples/py/020_dg/260_dpoPrinting.py @@ -1,4 +1,4 @@ -include("0212_dgPredicate.py") +include("212_dgPredicate.py") for e in dg.edges: e.print() # rst-name: Double Pushout Printing diff --git a/examples/py/020_dg/meta.json b/examples/py/020_dg/meta.json new file mode 100644 index 0000000..8d9aa21 --- /dev/null +++ b/examples/py/020_dg/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Derivations and Reaction Networks", + "oldIds": ["dg"] +} diff --git a/examples/py/0320_aconitase.py b/examples/py/030_stereo/320_aconitase.py similarity index 100% rename from examples/py/0320_aconitase.py rename to examples/py/030_stereo/320_aconitase.py diff --git a/examples/py/0330_tartaric.py b/examples/py/030_stereo/330_tartaric.py similarity index 100% rename from examples/py/0330_tartaric.py rename to examples/py/030_stereo/330_tartaric.py diff --git a/examples/py/0340_tree.py b/examples/py/030_stereo/340_tree.py similarity index 100% rename from examples/py/0340_tree.py rename to examples/py/030_stereo/340_tree.py diff --git a/examples/py/030_stereo/meta.json b/examples/py/030_stereo/meta.json new file mode 100644 index 0000000..d32cc32 --- /dev/null +++ b/examples/py/030_stereo/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Local Geometry and Stereochemistry", + "oldIds": ["stereo"] +} diff --git a/examples/py/5000_encoding_simple.py b/examples/py/900_epim/0_encoding_simple.py similarity index 100% rename from examples/py/5000_encoding_simple.py rename to examples/py/900_epim/0_encoding_simple.py diff --git a/examples/py/5001_encoding_restriction.py b/examples/py/900_epim/1_encoding_restriction.py similarity index 100% rename from examples/py/5001_encoding_restriction.py rename to examples/py/900_epim/1_encoding_restriction.py diff --git a/examples/py/5002_encoding_recursive.py b/examples/py/900_epim/2_encoding_recursive.py similarity index 100% rename from examples/py/5002_encoding_recursive.py rename to examples/py/900_epim/2_encoding_recursive.py diff --git a/examples/py/5003_encoding_config.py b/examples/py/900_epim/3_encoding_config.py similarity index 100% rename from examples/py/5003_encoding_config.py rename to examples/py/900_epim/3_encoding_config.py diff --git a/examples/py/5004_exec_space_simple.py b/examples/py/900_epim/4_exec_space_simple.py similarity index 100% rename from examples/py/5004_exec_space_simple.py rename to examples/py/900_epim/4_exec_space_simple.py diff --git a/examples/py/5005_exec_space_recursive.py b/examples/py/900_epim/5_exec_space_recursive.py similarity index 100% rename from examples/py/5005_exec_space_recursive.py rename to examples/py/900_epim/5_exec_space_recursive.py diff --git a/examples/py/5006_hospital.py b/examples/py/900_epim/6_hospital.py similarity index 100% rename from examples/py/5006_hospital.py rename to examples/py/900_epim/6_hospital.py diff --git a/examples/py/900_epim/meta.json b/examples/py/900_epim/meta.json new file mode 100644 index 0000000..43e35b9 --- /dev/null +++ b/examples/py/900_epim/meta.json @@ -0,0 +1,4 @@ +{ + "title": "EpiM", + "oldIds": ["epim"] +} diff --git a/examples/py/fileRenames.json b/examples/py/fileRenames.json new file mode 100644 index 0000000..90abd90 --- /dev/null +++ b/examples/py/fileRenames.json @@ -0,0 +1,63 @@ +[ + { + "000_hello.py": "0000_hello.py", + "010_graphLoading.py": "0010_graphLoading.py", + "011_graphPrinting.py": "0011_graphPrinting.py", + "012_graphInterface.py": "0012_graphInterface.py", + "013_graphMorphisms.py": "0013_graphMorphisms.py", + "030_ruleLoading.py": "0030_ruleLoading.py", + "031_ruleMorphisms.py": "0031_ruleMorphisms.py", + "050_formoseGrammar.py": "0050_formoseGrammar.py", + "051_fileInclusion.py": "0051_fileInclusion.py", + "100_rcGraphOps.py": "0100_rcGraphOps.py", + "101_rcParallel.py": "0101_rcParallel.py", + "102_rcSuper.py": "0102_rcSuper.py", + "120_rcFormose.py": "0120_rcFormose.py", + "210_dgFixed.py": "0210_dgFixed.py", + "211_dgRepeat.py": "0211_dgRepeat.py", + "212_dgPredicate.py": "0212_dgPredicate.py", + "250_dgPrinting.py": "0250_dgPrinting.py", + "260_dpoPrinting.py": "0260_dpoPrinting.py", + "320_aconitase.py": "0320_aconitase.py", + "330_tartaric.py": "0330_tartaric.py", + "340_tree.py": "0340_tree.py", + "500_basicFlow.py": "0500_basicFlow.py", + "501_extraConstraints.py": "0501_extraConstraints.py", + "502_multipleSolutions.py": "0502_multipleSolutions.py", + "510_autocatalysis.py": "0510_autocatalysis.py" + }, + { + "0000_hello.py": "000_basics/000_hello.py", + "0010_graphLoading.py": "000_basics/010_graphLoading.py", + "0011_graphPrinting.py": "000_basics/011_graphPrinting.py", + "0012_graphInterface.py": "000_basics/012_graphInterface.py", + "0013_graphMorphisms.py": "000_basics/013_graphMorphisms.py", + "0030_ruleLoading.py": "000_basics/030_ruleLoading.py", + "0031_ruleMorphisms.py": "000_basics/031_ruleMorphisms.py", + "0050_formoseGrammar.py": "000_basics/050_formoseGrammar.py", + "0051_fileInclusion.py": "000_basics/051_fileInclusion.py", + "0100_rcGraphOps.py": "010_rc/100_rcGraphOps.py", + "0101_rcParallel.py": "010_rc/101_rcParallel.py", + "0102_rcSuper.py": "010_rc/102_rcSuper.py", + "0120_rcFormose.py": "010_rc/120_rcFormose.py", + "0210_dgFixed.py": "020_dg/210_dgFixed.py", + "0211_dgRepeat.py": "020_dg/211_dgRepeat.py", + "0212_dgPredicate.py": "020_dg/212_dgPredicate.py", + "0250_dgPrinting.py": "020_dg/250_dgPrinting.py", + "0260_dpoPrinting.py": "020_dg/260_dpoPrinting.py", + "0320_aconitase.py": "030_stereo/320_aconitase.py", + "0330_tartaric.py": "030_stereo/330_tartaric.py", + "0340_tree.py": "030_stereo/340_tree.py", + "0500_basicFlow.py": "050_flow/500_basicFlow.py", + "0501_extraConstraints.py": "050_flow/501_extraConstraints.py", + "0502_multipleSolutions.py": "050_flow/502_multipleSolutions.py", + "0510_autocatalysis.py": "050_flow/510_autocatalysis.py", + "5000_encoding_simple.py": "900_epim/0_encoding_simple.py", + "5001_encoding_restriction.py": "900_epim/1_encoding_restriction.py", + "5002_encoding_recursive.py": "900_epim/2_encoding_recursive.py", + "5003_encoding_config.py": "900_epim/3_encoding_config.py", + "5004_exec_space_simple.py": "900_epim/4_exec_space_simple.py", + "5005_exec_space_recursive.py": "900_epim/5_exec_space_recursive.py", + "5006_hospital.py": "900_epim/6_hospital.py" + } +] diff --git a/examples/py/sections.json b/examples/py/sections.json deleted file mode 100644 index b80e074..0000000 --- a/examples/py/sections.json +++ /dev/null @@ -1,7 +0,0 @@ -[ -{"first":0, "last":100, "id":"basics","title":"Basics, Graphs, and Rules"}, -{"first":100, "last":200, "id":"rc", "title":"Rule Composition"}, -{"first":200, "last":300, "id":"dg", "title":"Derivations and Reaction Networks"}, -{"first":300, "last":350, "id":"stereo","title":"Local Geometry and Stereochemistry"}, -{"first":5000,"last":5010,"id":"epim", "title":"EpiM"} -] diff --git a/examples/pymod_extension/CMakeLists.txt b/examples/pymod_extension/CMakeLists.txt index 805616c..a7175c1 100644 --- a/examples/pymod_extension/CMakeLists.txt +++ b/examples/pymod_extension/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(mod REQUIRED) # Boost.Python # ------------------------------------------------------------------------- set(v 1.64.0) -foreach(PY 3 34 35 36 37 38 39) +foreach(PY 3 37 38 39 310 311 312 313 314 315 316 317 318 319) set(lib "python${PY}") find_package(Boost ${v} QUIET COMPONENTS ${lib}) if(Boost_FOUND) @@ -25,7 +25,7 @@ foreach(PY 3 34 35 36 37 38 39) endforeach() if(NOT Boost_FOUND) find_package(Boost ${v} REQUIRED COMPONENTS python3) - message(FATAL_ERROR "Could not find Boost.Python for Python 3. Tried 'python' wih suffixes 3, 34, 35, 36, 37, 38, and 39.") + message(FATAL_ERROR "Could not find Boost.Python for Python 3. Tried 'python' wih suffixes 3, 37, 38, 39, and 310 to 319.") endif() diff --git a/libs/jla_boost/CMakeLists.txt b/libs/jla_boost/CMakeLists.txt index c56dbfc..eaf8127 100644 --- a/libs/jla_boost/CMakeLists.txt +++ b/libs/jla_boost/CMakeLists.txt @@ -2,22 +2,20 @@ # Targets and Artefacts ########################################################################### -add_library(jla_boost STATIC - ${mod_jla_boost_INCLUDE_FILES} - ${mod_jla_boost_SRC_FILES}) +add_library(jla_boost INTERFACE) +# TODO: when CMake 3.19 can be assumed the source files can be added to an INTERFACE target +# ${mod_jla_boost_INCLUDE_FILES} +# ${mod_jla_boost_SRC_FILES}) add_library(JLA::boost ALIAS jla_boost) target_include_directories(jla_boost - PUBLIC + INTERFACE $ $) -target_link_libraries(jla_boost PUBLIC GraphCanon::graph_canon Boost::boost) -target_compile_options(jla_boost PRIVATE -Wall -Wextra -pedantic - -Wno-unused-parameter) -set_target_properties(jla_boost PROPERTIES - POSITION_INDEPENDENT_CODE ON - CXX_VISIBILITY_PRESET hidden - VISIBILITY_INLINES_HIDDEN ON) -target_compile_definitions(jla_boost PRIVATE JLA_BOOST_SOURCE) +target_link_libraries(jla_boost INTERFACE GraphCanon::graph_canon Boost::boost) +#set_target_properties(jla_boost PROPERTIES +# POSITION_INDEPENDENT_CODE ON +# CXX_VISIBILITY_PRESET hidden +# VISIBILITY_INLINES_HIDDEN ON) target_add_coverage(jla_boost) diff --git a/libs/jla_boost/include/jla_boost/graph/EdgeIndexedAdjacencyList.hpp b/libs/jla_boost/include/jla_boost/graph/EdgeIndexedAdjacencyList.hpp index d5fe62d..de34639 100644 --- a/libs/jla_boost/include/jla_boost/graph/EdgeIndexedAdjacencyList.hpp +++ b/libs/jla_boost/include/jla_boost/graph/EdgeIndexedAdjacencyList.hpp @@ -6,17 +6,18 @@ namespace jla_boost { template + typename VertexProperty = boost::no_property, + typename EdgeProperty = boost::no_property, + typename GraphProperty = boost::no_property> struct EdgeIndexedAdjacencyList { using Self = EdgeIndexedAdjacencyList; - using GraphType = boost::adjacency_list< boost::vecS, boost::vecS, DirectedS, + using GraphType = boost::adjacency_list, GraphProperty, - boost::vecS>; + boost::listS>; + // Do not use vecS for the edges, as it seems that the edge properties are stored by value there + // and the edge descriptors contain a pointer to the property. public: - - EdgeIndexedAdjacencyList() { } + EdgeIndexedAdjacencyList() = default; public: // Graph using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; using edge_descriptor = typename boost::graph_traits::edge_descriptor; @@ -132,9 +133,9 @@ struct EdgeIndexedAdjacencyList { } template - friend void put(const boost::edge_index_t&, Self &g, VertexOrEdge ve, Value &&v) { + friend void put(const boost::edge_index_t &, Self &g, VertexOrEdge ve, Value &&v) { // TODO: change to use "= delete" when GCC 5 is required (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62101)) - static_assert(sizeof (ve) == 0, "You should not mess with this."); + static_assert(sizeof(ve) == 0, "You should not mess with this."); } template @@ -163,14 +164,14 @@ namespace boost { template struct property_map, Property> -: property_map::GraphType, Property> { + : property_map::GraphType, Property> { }; template struct property_map, Property> -: property_map::GraphType, Property> { + : property_map::GraphType, Property> { }; } // namespace boost -#endif /* JLA_BOOST_GRAPH_EDGEINDEXEDADJACENCYLIST_HPP */ \ No newline at end of file +#endif // JLA_BOOST_GRAPH_EDGEINDEXEDADJACENCYLIST_HPP \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/dpo/IO.hpp b/libs/jla_boost/include/jla_boost/graph/dpo/IO.hpp deleted file mode 100644 index 00504b0..0000000 --- a/libs/jla_boost/include/jla_boost/graph/dpo/IO.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef JLA_BOOST_GRAPH_DPO_IO_HPP -#define JLA_BOOST_GRAPH_DPO_IO_HPP - -#include - -#include - -namespace jla_boost { -namespace GraphDPO { - -std::ostream &operator<<(std::ostream &s, Membership m); - -} // namespace GraphDPO -} // namespace jla_boost - -#endif /* JLA_BOOST_GRAPH_DPO_IO_HPP */ - diff --git a/libs/jla_boost/include/jla_boost/graph/dpo/Rule.hpp b/libs/jla_boost/include/jla_boost/graph/dpo/Rule.hpp deleted file mode 100644 index c55e772..0000000 --- a/libs/jla_boost/include/jla_boost/graph/dpo/Rule.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef JLA_BOOST_GRAPH_DPO_RULE_HPP -#define JLA_BOOST_GRAPH_DPO_RULE_HPP - -#include - -namespace jla_boost { -namespace GraphDPO { - -enum class Membership { - Left, Right, Context -}; - -inline Membership invert(Membership m) { - switch(m) { - case Membership::Left: return Membership::Right; - case Membership::Right: return Membership::Left; - default: return Membership::Context; - } -} - -template -struct PushoutRuleTraits; - -template -struct PushoutRuleConcept { - using Traits = PushoutRuleTraits; - using GraphType = typename Traits::GraphType; - using LeftGraphType = typename Traits::LeftGraphType; - using ContextGraphType = typename Traits::ContextGraphType; - using RightGraphType = typename Traits::RightGraphType; - - using Vertex = typename boost::graph_traits::vertex_descriptor; - using Edge = typename boost::graph_traits::edge_descriptor; - - BOOST_CONCEPT_ASSERT((boost::GraphConcept)); - - BOOST_CONCEPT_USAGE(PushoutRuleConcept) { - const R &rConst = r; - const GraphType &gConst = get_graph(rConst); - (void) gConst; - const LeftGraphType &gLeft = get_left(rConst); - const ContextGraphType &gContext = get_context(rConst); - const RightGraphType &gRight = get_right(rConst); - (void) gLeft; - (void) gContext; - (void) gRight; - m = membership(r, v); - m = membership(r, e); - } -private: - R r; - Vertex v; - Edge e; -private: - Membership m; -}; - -template -struct WritablePushoutRuleConcept : PushoutRuleConcept { - using Traits = PushoutRuleTraits; - using GraphType = typename Traits::GraphType; - using LeftGraphType = typename Traits::LeftGraphType; - using ContextGraphType = typename Traits::ContextGraphType; - using RightGraphType = typename Traits::RightGraphType; - - using Vertex = typename boost::graph_traits::vertex_descriptor; - using Edge = typename boost::graph_traits::edge_descriptor; - - BOOST_CONCEPT_USAGE(WritablePushoutRuleConcept) { - GraphType &g = get_graph(r); - (void) g; - put_membership(r, v, m); - put_membership(r, e, m); - } -private: - R r; - Vertex v; - Edge e; -private: - Membership m; -}; - -template -struct PushoutRuleTraits { - using GraphType = typename R::GraphType; - using LeftGraphType = typename R::LeftGraphType; - using ContextGraphType = typename R::ContextGraphType; - using RightGraphType = typename R::RightGraphType; -}; - -} // namespace GraphDPO -} // namespace jla_boost - -#endif /* JLA_BOOST_GRAPH_DPO_RULE_HPP */ \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/AsPropertyMap.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/AsPropertyMap.hpp index 9c75943..9761dd2 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/AsPropertyMap.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/AsPropertyMap.hpp @@ -1,7 +1,7 @@ #ifndef JLA_BOOST_GRAPHMORPHISM_AS_PROPERTY_MAP_HPP #define JLA_BOOST_GRAPHMORPHISM_AS_PROPERTY_MAP_HPP -#include +#include #include diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/Concepts.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/Concepts.hpp new file mode 100644 index 0000000..8e47593 --- /dev/null +++ b/libs/jla_boost/include/jla_boost/graph/morphism/Concepts.hpp @@ -0,0 +1,160 @@ +#ifndef JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H +#define JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H + +#include +#include +#include + +#include +#include +#include + +// - VertexMapConcept +// - InvertibleVertexMapConcept +// - WritableVertexMapConcept + +namespace jla_boost { +namespace GraphMorphism { + +template +struct VertexMapTraits; + +// Uni-directional +// ============================================================================= + +template +struct VertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + using Storable = typename Traits::Storable; + + BOOST_CONCEPT_ASSERT((boost::GraphConcept)); + BOOST_CONCEPT_ASSERT((boost::GraphConcept)); + + using VertexDom = typename boost::graph_traits::vertex_descriptor; + using VertexCodom = typename boost::graph_traits::vertex_descriptor; + + BOOST_CONCEPT_USAGE(VertexMapConcept) { + [[maybe_unused]] constexpr bool storable = Storable::value; + const M &cVertexMap = vertexMap; + [[maybe_unused]] VertexCodom vCodom = get(cVertexMap, gDom, gCodom, vDom); + [[maybe_unused]] const auto mReinterpret = Traits::template reinterpret( + std::move(vertexMap), gDom, gCodom, gDom, gCodom); + const auto trans = [](auto &&m, auto &gDom, auto &gCodom) { return std::move(m); }; + [[maybe_unused]] const auto mTransform = Traits::transform(trans, std::move(vertexMap), gDom, gCodom); + } +private: + M vertexMap; + GraphDom gDom; + GraphCodom gCodom; + VertexDom vDom; +}; + +template +struct GraphMapConcept : VertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + + using EdgeDom = typename boost::graph_traits::edge_descriptor; + using EdgeCodom = typename boost::graph_traits::edge_descriptor; + + BOOST_CONCEPT_USAGE(GraphMapConcept) { + const M &cGraphMap = graphMap; + [[maybe_unused]] const EdgeCodom eCodom = get(cGraphMap, gDom, gCodom, eDom); + } +private: + M graphMap; + GraphDom gDom; + GraphCodom gCodom; + EdgeDom eDom; +}; + +// Bi-directional +// ============================================================================= + +template +struct InvertibleVertexMapConcept : VertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + using VertexDom = typename boost::graph_traits::vertex_descriptor; + using VertexCodom = typename boost::graph_traits::vertex_descriptor; + + BOOST_CONCEPT_USAGE(InvertibleVertexMapConcept) { + const M &cVertexMap = vertexMap; + [[maybe_unused]] const VertexDom vDom = get_inverse(cVertexMap, gDom, gCodom, vCodom); + } +private: + M vertexMap; + GraphDom gDom; + GraphCodom gCodom; + VertexCodom vCodom; +}; + +template +struct InvertibleGraphMapConcept : InvertibleVertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + using EdgeDom = typename boost::graph_traits::edge_descriptor; + using EdgeCodom = typename boost::graph_traits::edge_descriptor; + + BOOST_CONCEPT_USAGE(InvertibleGraphMapConcept) { + const M &cGraphMap = graphMap; + [[maybe_unused]] const EdgeDom eDom = get_inverse(cGraphMap, gDom, gCodom, eCodom); + } +private: + M graphMap; + GraphDom gDom; + GraphCodom gCodom; + EdgeCodom eCodom; +}; + +// Writeable +// ============================================================================= + +template +struct WritableVertexMapConcept : VertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + using VertexDom = typename boost::graph_traits::vertex_descriptor; + using VertexCodom = typename boost::graph_traits::vertex_descriptor; + + BOOST_CONCEPT_USAGE(WritableVertexMapConcept) { + const VertexDom vDom; + const VertexCodom vCodom; + put(vertexMap, gDom, gCodom, vDom, vCodom); + syncSize(gDom, gCodom); + } +private: + M vertexMap; + GraphDom gDom; + GraphCodom gCodom; +}; + +template +struct WritableGraphMapConcept : WritableVertexMapConcept { + using Traits = VertexMapTraits; + using GraphDom = typename Traits::GraphDom; + using GraphCodom = typename Traits::GraphCodom; + using EdgeDom = typename boost::graph_traits::edge_descriptor; + using EdgeCodom = typename boost::graph_traits::edge_descriptor; + + BOOST_CONCEPT_USAGE(WritableGraphMapConcept) { + const EdgeDom eDom; + const EdgeCodom eCodom; + put(graphMap, gDom, gCodom, eDom, eCodom); + } +private: + M graphMap; + GraphDom gDom; + GraphCodom gCodom; +}; + +} // namespace GraphMorphism +} // namespace jla_boost + +#endif /* JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H */ \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/VertexMap.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/VertexMap.hpp deleted file mode 100644 index f61991a..0000000 --- a/libs/jla_boost/include/jla_boost/graph/morphism/VertexMap.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H -#define JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H - -#include -#include -#include - -#include -#include -#include - -// - VertexMapConcept -// - InvertibleVertexMapConcept -// - WritableVertexMapConcept - -namespace jla_boost { -namespace GraphMorphism { - -template -struct VertexMapTraits; - -// VertexMapConcept -// ----------------------------------------------------------------------------- - -template -struct VertexMapConcept { - using Traits = VertexMapTraits; - using GraphDom = typename Traits::GraphDom; - using GraphCodom = typename Traits::GraphCodom; - using Storable = typename Traits::Storable; - - BOOST_CONCEPT_ASSERT((boost::GraphConcept)); - BOOST_CONCEPT_ASSERT((boost::GraphConcept)); - - using VertexDom = typename boost::graph_traits::vertex_descriptor; - using VertexCodom = typename boost::graph_traits::vertex_descriptor; - - BOOST_CONCEPT_USAGE(VertexMapConcept) { - bool storable = Storable::value; - (void) storable; - const M &cVertexMap = vertexMap; - VertexCodom vCodom = get(cVertexMap, gDom, gCodom, vDom); - (void) vCodom; - auto mReinterpret = Traits::template reinterpret(std::move(vertexMap), gDom, gCodom, gDom, gCodom); - (void) mReinterpret; - auto trans = [](auto &&m, auto &gDom, auto &gCodom) { - return std::move(m); - }; - auto mTransform = Traits::transform(trans, std::move(vertexMap), gDom, gCodom); - (void) mTransform; - } -private: - M vertexMap; - GraphDom gDom; - GraphCodom gCodom; - VertexDom vDom; -}; - -// InvertibleVertexMapConcept -// ----------------------------------------------------------------------------- - -template -struct InvertibleVertexMapConcept : VertexMapConcept { - using Traits = VertexMapTraits; - using GraphDom = typename Traits::GraphDom; - using GraphCodom = typename Traits::GraphCodom; - using VertexDom = typename boost::graph_traits::vertex_descriptor; - using VertexCodom = typename boost::graph_traits::vertex_descriptor; - - BOOST_CONCEPT_USAGE(InvertibleVertexMapConcept) { - const M &cVertexMap = vertexMap; - VertexDom vDom = get_inverse(cVertexMap, gDom, gCodom, vCodom); - (void) vDom; - } -private: - M vertexMap; - GraphDom gDom; - GraphCodom gCodom; - VertexCodom vCodom; -}; - -// WritableVertexMapConcept -// ----------------------------------------------------------------------------- - -template -struct WritableVertexMapConcept : VertexMapConcept { - using Traits = VertexMapTraits; - using GraphDom = typename Traits::GraphDom; - using GraphCodom = typename Traits::GraphCodom; - using VertexDom = typename boost::graph_traits::vertex_descriptor; - using VertexCodom = typename boost::graph_traits::vertex_descriptor; - - BOOST_CONCEPT_USAGE(WritableVertexMapConcept) { - VertexDom vDom; - VertexCodom vCodom; - put(vertexMap, gDom, gCodom, vDom, vCodom); - } -private: - M vertexMap; - GraphDom gDom; - GraphCodom gCodom; -}; - -} // namespace GraphMorphism -} // namespace jla_boost - -#endif /* JLA_BOOST_GRAPH_MORPHISM_VERTEX_MAP_H */ \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Filter.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Filter.hpp index 6bb6c5e..61f2651 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Filter.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Filter.hpp @@ -1,7 +1,7 @@ #ifndef JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_FILTER_HPP #define JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_FILTER_HPP -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Limit.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Limit.hpp index 9a0d355..f285fb8 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Limit.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Limit.hpp @@ -2,7 +2,7 @@ #define JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_LIMIT_HPP #include -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Store.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Store.hpp index 84f73b9..41197c7 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Store.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Store.hpp @@ -1,7 +1,7 @@ #ifndef JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_STORE_HPP #define JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_STORE_HPP -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Transform.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Transform.hpp index d05c3d9..19a3514 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Transform.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Transform.hpp @@ -1,7 +1,7 @@ #ifndef JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_TRANSFORM_HPP #define JLA_BOOST_GRAPH_MORPHISM_CALLBACKS_TRANSFORM_HPP -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Unwrapper.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Unwrapper.hpp index a175f33..583d2bf 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Unwrapper.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/callbacks/Unwrapper.hpp @@ -5,7 +5,7 @@ // been unwrapped if they match the given graph. #include -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/finders/CommonSubgraph.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/finders/CommonSubgraph.hpp index 6c78e0a..6922b24 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/finders/CommonSubgraph.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/finders/CommonSubgraph.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -66,16 +67,16 @@ struct CommonSubgraphEnumerator : InjectiveEnumerationState< // NOTE: This will not work with parallel edges, since the // first matching edge is always chosen. - EdgeLeft edge_to_new1; - bool edge_to_new_exists1 = false; - EdgeRight edge_to_new2; - bool edge_to_new_exists2 = false; + EdgeLeft edgeToNewLeft; + bool edgeToNewExistsLeft = false; + EdgeRight edgeToNewRight; + bool edgeToNewExistsRight = false; // Search for edge from existing to new vertex (gLeft) for(auto eOutLeft : asRange(out_edges(vOtherLeft, this->gLeft))) { if(target(eOutLeft, this->gLeft) == vLeft) { - edge_to_new1 = eOutLeft; - edge_to_new_exists1 = true; + edgeToNewLeft = eOutLeft; + edgeToNewExistsLeft = true; break; } } @@ -83,8 +84,8 @@ struct CommonSubgraphEnumerator : InjectiveEnumerationState< // Search for edge from existing to new vertex (gRight) for(auto eOutRight : asRange(out_edges(vOtherRight, this->gRight))) { if(target(eOutRight, this->gRight) == vRight) { - edge_to_new2 = eOutRight; - edge_to_new_exists2 = true; + edgeToNewRight = eOutRight; + edgeToNewExistsRight = true; break; } } @@ -94,12 +95,16 @@ struct CommonSubgraphEnumerator : InjectiveEnumerationState< #ifdef MORPHISM_INJECTIVE_ENUMERATION_DEBUG std::cout << this->indent(3) << "undirected? " << std::boolalpha << is_undirected1 << ", " << is_undirected2 << std::endl; - std::cout << this->indent(3) << "edge_to_new_exists? " - << std::boolalpha << edge_to_new_exists1 << ", " << edge_to_new_exists1 << std::endl; + std::cout << this->indent(3) << "edgeToNew_exists? " + << std::boolalpha << edgeToNewExistsLeft << ", " << edgeToNewExistsLeft << std::endl; #endif - if(edge_to_new_exists1 && edge_to_new_exists2) { - if(this->edgePred(edge_to_new1, edge_to_new2)) { + if(edgeToNewExistsLeft && edgeToNewExistsRight) { +#ifdef MORPHISM_INJECTIVE_ENUMERATION_DEBUG + std::cout << this->indent(3) << "edgeToNewLeft: " << edgeToNewLeft << std::endl; + std::cout << this->indent(3) << "edgeToNewRight: " << edgeToNewRight << std::endl; +#endif + if(this->edgePred(edgeToNewLeft, edgeToNewRight)) { has_one_edge = true; } else { #ifdef MORPHISM_INJECTIVE_ENUMERATION_DEBUG @@ -317,7 +322,7 @@ struct CommonSubgraphCallbackHelper { } private: const GraphLeft &gLeft; - const GraphLeft &gRight; + const GraphRight &gRight; Next next; std::vector cache; }; diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/finders/mcgregor_common_subgraphs.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/finders/mcgregor_common_subgraphs.hpp new file mode 100644 index 0000000..af02576 --- /dev/null +++ b/libs/jla_boost/include/jla_boost/graph/morphism/finders/mcgregor_common_subgraphs.hpp @@ -0,0 +1,1126 @@ +//======================================================================= +// Copyright 2009 Trustees of Indiana University. +// Authors: Michael Hansen, Andrew Lumsdaine +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= + +// Note: this is a copy from Boost 1.77 with the following modifications: +// - in maximum_subgraph_interceptor: +// - const GraphFirst& m_graph2; +// + const GraphSecond& m_graph2; +// - and in the same class +// - BGL_FORALL_VERTICES_T(vertex2, m_graph2, GraphFirst) +// + BGL_FORALL_VERTICES_T(vertex2, m_graph2, GraphSecond) + + +#ifndef BOOST_GRAPH_MCGREGOR_COMMON_SUBGRAPHS_HPP +#define BOOST_GRAPH_MCGREGOR_COMMON_SUBGRAPHS_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost +{ + +namespace detail +{ + +// Traits associated with common subgraphs, used mainly to keep a +// consistent type for the correspondence maps. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond > +struct mcgregor_common_subgraph_traits +{ + typedef typename graph_traits< GraphFirst >::vertex_descriptor + vertex_first_type; + typedef typename graph_traits< GraphSecond >::vertex_descriptor + vertex_second_type; + + typedef shared_array_property_map< vertex_second_type, + VertexIndexMapFirst > + correspondence_map_first_to_second_type; + + typedef shared_array_property_map< vertex_first_type, + VertexIndexMapSecond > + correspondence_map_second_to_first_type; +}; + +} // namespace detail + +// ========================================================================== + +// Binary function object that returns true if the values for item1 +// in property_map1 and item2 in property_map2 are equivalent. +template < typename PropertyMapFirst, typename PropertyMapSecond > +struct property_map_equivalent +{ + + property_map_equivalent(const PropertyMapFirst property_map1, + const PropertyMapSecond property_map2) + : m_property_map1(property_map1), m_property_map2(property_map2) + { + } + + template < typename ItemFirst, typename ItemSecond > + bool operator()(const ItemFirst item1, const ItemSecond item2) + { + return (get(m_property_map1, item1) == get(m_property_map2, item2)); + } + +private: + const PropertyMapFirst m_property_map1; + const PropertyMapSecond m_property_map2; +}; + +// Returns a property_map_equivalent object that compares the values +// of property_map1 and property_map2. +template < typename PropertyMapFirst, typename PropertyMapSecond > +property_map_equivalent< PropertyMapFirst, PropertyMapSecond > +make_property_map_equivalent( + const PropertyMapFirst property_map1, const PropertyMapSecond property_map2) +{ + + return (property_map_equivalent< PropertyMapFirst, PropertyMapSecond >( + property_map1, property_map2)); +} + +// Binary function object that always returns true. Used when +// vertices or edges are always equivalent (i.e. have no labels). +struct always_equivalent +{ + + template < typename ItemFirst, typename ItemSecond > + bool operator()(const ItemFirst&, const ItemSecond&) + { + return (true); + } +}; + +// ========================================================================== + +namespace detail +{ + +// Return true if new_vertex1 and new_vertex2 can extend the +// subgraph represented by correspondence_map_1_to_2 and +// correspondence_map_2_to_1. The vertices_equivalent and +// edges_equivalent predicates are used to test vertex and edge +// equivalency between the two graphs. +template < typename GraphFirst, typename GraphSecond, + typename CorrespondenceMapFirstToSecond, + typename CorrespondenceMapSecondToFirst, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate > +bool can_extend_graph(const GraphFirst& graph1, const GraphSecond& graph2, + CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + CorrespondenceMapSecondToFirst /*correspondence_map_2_to_1*/, + typename graph_traits< GraphFirst >::vertices_size_type subgraph_size, + typename graph_traits< GraphFirst >::vertex_descriptor new_vertex1, + typename graph_traits< GraphSecond >::vertex_descriptor new_vertex2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs) +{ + typedef typename graph_traits< GraphSecond >::vertex_descriptor + VertexSecond; + + typedef typename graph_traits< GraphFirst >::edge_descriptor EdgeFirst; + typedef + typename graph_traits< GraphSecond >::edge_descriptor EdgeSecond; + + // Check vertex equality + if (!vertices_equivalent(new_vertex1, new_vertex2)) + { + return (false); + } + + // Vertices match and graph is empty, so we can extend the subgraph + if (subgraph_size == 0) + { + return (true); + } + + bool has_one_edge = false; + + // Verify edges with existing sub-graph + BGL_FORALL_VERTICES_T(existing_vertex1, graph1, GraphFirst) + { + + VertexSecond existing_vertex2 + = get(correspondence_map_1_to_2, existing_vertex1); + + // Skip unassociated vertices + if (existing_vertex2 == graph_traits< GraphSecond >::null_vertex()) + { + continue; + } + + // NOTE: This will not work with parallel edges, since the + // first matching edge is always chosen. + EdgeFirst edge_to_new1, edge_from_new1; + bool edge_to_new_exists1 = false, edge_from_new_exists1 = false; + + EdgeSecond edge_to_new2, edge_from_new2; + bool edge_to_new_exists2 = false, edge_from_new_exists2 = false; + + // Search for edge from existing to new vertex (graph1) + BGL_FORALL_OUTEDGES_T(existing_vertex1, edge1, graph1, GraphFirst) + { + if (target(edge1, graph1) == new_vertex1) + { + edge_to_new1 = edge1; + edge_to_new_exists1 = true; + break; + } + } + + // Search for edge from existing to new vertex (graph2) + BGL_FORALL_OUTEDGES_T(existing_vertex2, edge2, graph2, GraphSecond) + { + if (target(edge2, graph2) == new_vertex2) + { + edge_to_new2 = edge2; + edge_to_new_exists2 = true; + break; + } + } + + // Make sure edges from existing to new vertices are equivalent + if ((edge_to_new_exists1 != edge_to_new_exists2) + || ((edge_to_new_exists1 && edge_to_new_exists2) + && !edges_equivalent(edge_to_new1, edge_to_new2))) + { + + return (false); + } + + bool is_undirected1 = is_undirected(graph1), + is_undirected2 = is_undirected(graph2); + + if (is_undirected1 && is_undirected2) + { + + // Edge in both graphs exists and both graphs are undirected + if (edge_to_new_exists1 && edge_to_new_exists2) + { + has_one_edge = true; + } + + continue; + } + else + { + + if (!is_undirected1) + { + + // Search for edge from new to existing vertex (graph1) + BGL_FORALL_OUTEDGES_T( + new_vertex1, edge1, graph1, GraphFirst) + { + if (target(edge1, graph1) == existing_vertex1) + { + edge_from_new1 = edge1; + edge_from_new_exists1 = true; + break; + } + } + } + + if (!is_undirected2) + { + + // Search for edge from new to existing vertex (graph2) + BGL_FORALL_OUTEDGES_T( + new_vertex2, edge2, graph2, GraphSecond) + { + if (target(edge2, graph2) == existing_vertex2) + { + edge_from_new2 = edge2; + edge_from_new_exists2 = true; + break; + } + } + } + + // Make sure edges from new to existing vertices are equivalent + if ((edge_from_new_exists1 != edge_from_new_exists2) + || ((edge_from_new_exists1 && edge_from_new_exists2) + && !edges_equivalent(edge_from_new1, edge_from_new2))) + { + + return (false); + } + + if ((edge_from_new_exists1 && edge_from_new_exists2) + || (edge_to_new_exists1 && edge_to_new_exists2)) + { + has_one_edge = true; + } + + } // else + + } // BGL_FORALL_VERTICES_T + + // Make sure new vertices are connected to the existing subgraph + if (only_connected_subgraphs && !has_one_edge) + { + return (false); + } + + return (true); +} + +// Recursive method that does a depth-first search in the space of +// potential subgraphs. At each level, every new vertex pair from +// both graphs is tested to see if it can extend the current +// subgraph. If so, the subgraph is output to subgraph_callback +// in the form of two correspondence maps (one for each graph). +// Returning false from subgraph_callback will terminate the +// search. Function returns true if the entire search space was +// explored. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename CorrespondenceMapFirstToSecond, + typename CorrespondenceMapSecondToFirst, typename VertexStackFirst, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphInternalCallback > +bool mcgregor_common_subgraphs_internal(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst& vindex_map1, + const VertexIndexMapSecond& vindex_map2, + CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + CorrespondenceMapSecondToFirst correspondence_map_2_to_1, + VertexStackFirst& vertex_stack1, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, + SubGraphInternalCallback subgraph_callback) +{ + typedef + typename graph_traits< GraphFirst >::vertex_descriptor VertexFirst; + typedef typename graph_traits< GraphSecond >::vertex_descriptor + VertexSecond; + typedef typename graph_traits< GraphFirst >::vertices_size_type + VertexSizeFirst; + + // Get iterators for vertices from both graphs + typename graph_traits< GraphFirst >::vertex_iterator vertex1_iter, + vertex1_end; + + typename graph_traits< GraphSecond >::vertex_iterator vertex2_begin, + vertex2_end, vertex2_iter; + + boost::tie(vertex1_iter, vertex1_end) = vertices(graph1); + boost::tie(vertex2_begin, vertex2_end) = vertices(graph2); + vertex2_iter = vertex2_begin; + + // Iterate until all vertices have been visited + BGL_FORALL_VERTICES_T(new_vertex1, graph1, GraphFirst) + { + + VertexSecond existing_vertex2 + = get(correspondence_map_1_to_2, new_vertex1); + + // Skip already matched vertices in first graph + if (existing_vertex2 != graph_traits< GraphSecond >::null_vertex()) + { + continue; + } + + BGL_FORALL_VERTICES_T(new_vertex2, graph2, GraphSecond) + { + + VertexFirst existing_vertex1 + = get(correspondence_map_2_to_1, new_vertex2); + + // Skip already matched vertices in second graph + if (existing_vertex1 + != graph_traits< GraphFirst >::null_vertex()) + { + continue; + } + + // Check if current sub-graph can be extended with the matched + // vertex pair + if (can_extend_graph(graph1, graph2, correspondence_map_1_to_2, + correspondence_map_2_to_1, + (VertexSizeFirst)vertex_stack1.size(), new_vertex1, + new_vertex2, edges_equivalent, vertices_equivalent, + only_connected_subgraphs)) + { + + // Keep track of old graph size for restoring later + VertexSizeFirst old_graph_size + = (VertexSizeFirst)vertex_stack1.size(), + new_graph_size = old_graph_size + 1; + + // Extend subgraph + put(correspondence_map_1_to_2, new_vertex1, new_vertex2); + put(correspondence_map_2_to_1, new_vertex2, new_vertex1); + vertex_stack1.push(new_vertex1); + + // Returning false from the callback will cancel iteration + if (!subgraph_callback(correspondence_map_1_to_2, + correspondence_map_2_to_1, new_graph_size)) + { + return (false); + } + + // Depth-first search into the state space of possible + // sub-graphs + bool continue_iteration + = mcgregor_common_subgraphs_internal(graph1, graph2, + vindex_map1, vindex_map2, correspondence_map_1_to_2, + correspondence_map_2_to_1, vertex_stack1, + edges_equivalent, vertices_equivalent, + only_connected_subgraphs, subgraph_callback); + + if (!continue_iteration) + { + return (false); + } + + // Restore previous state + if (vertex_stack1.size() > old_graph_size) + { + + VertexFirst stack_vertex1 = vertex_stack1.top(); + VertexSecond stack_vertex2 + = get(correspondence_map_1_to_2, stack_vertex1); + + // Contract subgraph + put(correspondence_map_1_to_2, stack_vertex1, + graph_traits< GraphSecond >::null_vertex()); + + put(correspondence_map_2_to_1, stack_vertex2, + graph_traits< GraphFirst >::null_vertex()); + + vertex_stack1.pop(); + } + + } // if can_extend_graph + + } // BGL_FORALL_VERTICES_T (graph2) + + } // BGL_FORALL_VERTICES_T (graph1) + + return (true); +} + +// Internal method that initializes blank correspondence maps and +// a vertex stack for use in mcgregor_common_subgraphs_internal. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphInternalCallback > +inline void mcgregor_common_subgraphs_internal_init( + const GraphFirst& graph1, const GraphSecond& graph2, + const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, + SubGraphInternalCallback subgraph_callback) +{ + typedef mcgregor_common_subgraph_traits< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond > + SubGraphTraits; + + typename SubGraphTraits::correspondence_map_first_to_second_type + correspondence_map_1_to_2(num_vertices(graph1), vindex_map1); + + BGL_FORALL_VERTICES_T(vertex1, graph1, GraphFirst) + { + put(correspondence_map_1_to_2, vertex1, + graph_traits< GraphSecond >::null_vertex()); + } + + typename SubGraphTraits::correspondence_map_second_to_first_type + correspondence_map_2_to_1(num_vertices(graph2), vindex_map2); + + BGL_FORALL_VERTICES_T(vertex2, graph2, GraphSecond) + { + put(correspondence_map_2_to_1, vertex2, + graph_traits< GraphFirst >::null_vertex()); + } + + typedef + typename graph_traits< GraphFirst >::vertex_descriptor VertexFirst; + + std::stack< VertexFirst > vertex_stack1; + + mcgregor_common_subgraphs_internal(graph1, graph2, vindex_map1, + vindex_map2, correspondence_map_1_to_2, correspondence_map_2_to_1, + vertex_stack1, edges_equivalent, vertices_equivalent, + only_connected_subgraphs, subgraph_callback); +} + +} // namespace detail + +// ========================================================================== + +// Enumerates all common subgraphs present in graph1 and graph2. +// Continues until the search space has been fully explored or false +// is returned from user_callback. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphCallback > +void mcgregor_common_subgraphs(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, SubGraphCallback user_callback) +{ + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, vindex_map1, + vindex_map2, edges_equivalent, vertices_equivalent, + only_connected_subgraphs, user_callback); +} + +// Variant of mcgregor_common_subgraphs with all default parameters +template < typename GraphFirst, typename GraphSecond, + typename SubGraphCallback > +void mcgregor_common_subgraphs(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback) +{ + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, + get(vertex_index, graph1), get(vertex_index, graph2), + always_equivalent(), always_equivalent(), only_connected_subgraphs, + user_callback); +} + +// Named parameter variant of mcgregor_common_subgraphs +template < typename GraphFirst, typename GraphSecond, typename SubGraphCallback, + typename Param, typename Tag, typename Rest > +void mcgregor_common_subgraphs(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback, + const bgl_named_params< Param, Tag, Rest >& params) +{ + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, + choose_const_pmap( + get_param(params, vertex_index1), graph1, vertex_index), + choose_const_pmap( + get_param(params, vertex_index2), graph2, vertex_index), + choose_param( + get_param(params, edges_equivalent_t()), always_equivalent()), + choose_param( + get_param(params, vertices_equivalent_t()), always_equivalent()), + only_connected_subgraphs, user_callback); +} + +// ========================================================================== + +namespace detail +{ + +// Binary function object that intercepts subgraphs from +// mcgregor_common_subgraphs_internal and maintains a cache of +// unique subgraphs. The user callback is invoked for each unique +// subgraph. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename SubGraphCallback > +struct unique_subgraph_interceptor +{ + + typedef typename graph_traits< GraphFirst >::vertices_size_type + VertexSizeFirst; + + typedef mcgregor_common_subgraph_traits< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond > + SubGraphTraits; + + typedef typename SubGraphTraits::correspondence_map_first_to_second_type + CachedCorrespondenceMapFirstToSecond; + + typedef typename SubGraphTraits::correspondence_map_second_to_first_type + CachedCorrespondenceMapSecondToFirst; + + typedef std::pair< VertexSizeFirst, + std::pair< CachedCorrespondenceMapFirstToSecond, + CachedCorrespondenceMapSecondToFirst > > + SubGraph; + + typedef std::vector< SubGraph > SubGraphList; + + unique_subgraph_interceptor(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + SubGraphCallback user_callback) + : m_graph1(graph1) + , m_graph2(graph2) + , m_vindex_map1(vindex_map1) + , m_vindex_map2(vindex_map2) + , m_subgraphs(make_shared< SubGraphList >()) + , m_user_callback(user_callback) + { + } + + template < typename CorrespondenceMapFirstToSecond, + typename CorrespondenceMapSecondToFirst > + bool operator()( + CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + CorrespondenceMapSecondToFirst correspondence_map_2_to_1, + VertexSizeFirst subgraph_size) + { + + for (typename SubGraphList::const_iterator subgraph_iter + = m_subgraphs->begin(); + subgraph_iter != m_subgraphs->end(); ++subgraph_iter) + { + + SubGraph subgraph_cached = *subgraph_iter; + + // Compare subgraph sizes + if (subgraph_size != subgraph_cached.first) + { + continue; + } + + if (!are_property_maps_different(correspondence_map_1_to_2, + subgraph_cached.second.first, m_graph1)) + { + + // New subgraph is a duplicate + return (true); + } + } + + // Subgraph is unique, so make a cached copy + CachedCorrespondenceMapFirstToSecond new_subgraph_1_to_2 + = CachedCorrespondenceMapFirstToSecond( + num_vertices(m_graph1), m_vindex_map1); + + CachedCorrespondenceMapSecondToFirst new_subgraph_2_to_1 + = CorrespondenceMapSecondToFirst( + num_vertices(m_graph2), m_vindex_map2); + + BGL_FORALL_VERTICES_T(vertex1, m_graph1, GraphFirst) + { + put(new_subgraph_1_to_2, vertex1, + get(correspondence_map_1_to_2, vertex1)); + } + + BGL_FORALL_VERTICES_T(vertex2, m_graph2, GraphFirst) + { + put(new_subgraph_2_to_1, vertex2, + get(correspondence_map_2_to_1, vertex2)); + } + + m_subgraphs->push_back(std::make_pair(subgraph_size, + std::make_pair(new_subgraph_1_to_2, new_subgraph_2_to_1))); + + return (m_user_callback(correspondence_map_1_to_2, + correspondence_map_2_to_1, subgraph_size)); + } + +private: + const GraphFirst& m_graph1; + const GraphFirst& m_graph2; + const VertexIndexMapFirst m_vindex_map1; + const VertexIndexMapSecond m_vindex_map2; + shared_ptr< SubGraphList > m_subgraphs; + SubGraphCallback m_user_callback; +}; + +} // namespace detail + +// Enumerates all unique common subgraphs between graph1 and graph2. +// The user callback is invoked for each unique subgraph as they are +// discovered. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphCallback > +void mcgregor_common_subgraphs_unique(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, SubGraphCallback user_callback) +{ + detail::unique_subgraph_interceptor< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond, SubGraphCallback > + unique_callback( + graph1, graph2, vindex_map1, vindex_map2, user_callback); + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, vindex_map1, + vindex_map2, edges_equivalent, vertices_equivalent, + only_connected_subgraphs, unique_callback); +} + +// Variant of mcgregor_common_subgraphs_unique with all default +// parameters. +template < typename GraphFirst, typename GraphSecond, + typename SubGraphCallback > +void mcgregor_common_subgraphs_unique(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback) +{ + mcgregor_common_subgraphs_unique(graph1, graph2, get(vertex_index, graph1), + get(vertex_index, graph2), always_equivalent(), always_equivalent(), + only_connected_subgraphs, user_callback); +} + +// Named parameter variant of mcgregor_common_subgraphs_unique +template < typename GraphFirst, typename GraphSecond, typename SubGraphCallback, + typename Param, typename Tag, typename Rest > +void mcgregor_common_subgraphs_unique(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback, + const bgl_named_params< Param, Tag, Rest >& params) +{ + mcgregor_common_subgraphs_unique(graph1, graph2, + choose_const_pmap( + get_param(params, vertex_index1), graph1, vertex_index), + choose_const_pmap( + get_param(params, vertex_index2), graph2, vertex_index), + choose_param( + get_param(params, edges_equivalent_t()), always_equivalent()), + choose_param( + get_param(params, vertices_equivalent_t()), always_equivalent()), + only_connected_subgraphs, user_callback); +} + +// ========================================================================== + +namespace detail +{ + +// Binary function object that intercepts subgraphs from +// mcgregor_common_subgraphs_internal and maintains a cache of the +// largest subgraphs. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename SubGraphCallback > +struct maximum_subgraph_interceptor +{ + + typedef typename graph_traits< GraphFirst >::vertices_size_type + VertexSizeFirst; + + typedef mcgregor_common_subgraph_traits< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond > + SubGraphTraits; + + typedef typename SubGraphTraits::correspondence_map_first_to_second_type + CachedCorrespondenceMapFirstToSecond; + + typedef typename SubGraphTraits::correspondence_map_second_to_first_type + CachedCorrespondenceMapSecondToFirst; + + typedef std::pair< VertexSizeFirst, + std::pair< CachedCorrespondenceMapFirstToSecond, + CachedCorrespondenceMapSecondToFirst > > + SubGraph; + + typedef std::vector< SubGraph > SubGraphList; + + maximum_subgraph_interceptor(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + SubGraphCallback user_callback) + : m_graph1(graph1) + , m_graph2(graph2) + , m_vindex_map1(vindex_map1) + , m_vindex_map2(vindex_map2) + , m_subgraphs(make_shared< SubGraphList >()) + , m_largest_size_so_far(make_shared< VertexSizeFirst >(0)) + , m_user_callback(user_callback) + { + } + + template < typename CorrespondenceMapFirstToSecond, + typename CorrespondenceMapSecondToFirst > + bool operator()( + CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + CorrespondenceMapSecondToFirst correspondence_map_2_to_1, + VertexSizeFirst subgraph_size) + { + + if (subgraph_size > *m_largest_size_so_far) + { + m_subgraphs->clear(); + *m_largest_size_so_far = subgraph_size; + } + + if (subgraph_size == *m_largest_size_so_far) + { + + // Make a cached copy + CachedCorrespondenceMapFirstToSecond new_subgraph_1_to_2 + = CachedCorrespondenceMapFirstToSecond( + num_vertices(m_graph1), m_vindex_map1); + + CachedCorrespondenceMapSecondToFirst new_subgraph_2_to_1 + = CachedCorrespondenceMapSecondToFirst( + num_vertices(m_graph2), m_vindex_map2); + + BGL_FORALL_VERTICES_T(vertex1, m_graph1, GraphFirst) + { + put(new_subgraph_1_to_2, vertex1, + get(correspondence_map_1_to_2, vertex1)); + } + + BGL_FORALL_VERTICES_T(vertex2, m_graph2, GraphSecond) + { + put(new_subgraph_2_to_1, vertex2, + get(correspondence_map_2_to_1, vertex2)); + } + + m_subgraphs->push_back(std::make_pair(subgraph_size, + std::make_pair(new_subgraph_1_to_2, new_subgraph_2_to_1))); + } + + return (true); + } + + void output_subgraphs() + { + for (typename SubGraphList::const_iterator subgraph_iter + = m_subgraphs->begin(); + subgraph_iter != m_subgraphs->end(); ++subgraph_iter) + { + + SubGraph subgraph_cached = *subgraph_iter; + m_user_callback(subgraph_cached.second.first, + subgraph_cached.second.second, subgraph_cached.first); + } + } + +private: + const GraphFirst& m_graph1; + const GraphSecond& m_graph2; + const VertexIndexMapFirst m_vindex_map1; + const VertexIndexMapSecond m_vindex_map2; + shared_ptr< SubGraphList > m_subgraphs; + shared_ptr< VertexSizeFirst > m_largest_size_so_far; + SubGraphCallback m_user_callback; +}; + +} // namespace detail + +// Enumerates the largest common subgraphs found between graph1 +// and graph2. Note that the ENTIRE search space is explored before +// user_callback is actually invoked. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphCallback > +void mcgregor_common_subgraphs_maximum(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, SubGraphCallback user_callback) +{ + detail::maximum_subgraph_interceptor< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond, SubGraphCallback > + max_interceptor( + graph1, graph2, vindex_map1, vindex_map2, user_callback); + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, vindex_map1, + vindex_map2, edges_equivalent, vertices_equivalent, + only_connected_subgraphs, max_interceptor); + + // Only output the largest subgraphs + max_interceptor.output_subgraphs(); +} + +// Variant of mcgregor_common_subgraphs_maximum with all default +// parameters. +template < typename GraphFirst, typename GraphSecond, + typename SubGraphCallback > +void mcgregor_common_subgraphs_maximum(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback) +{ + mcgregor_common_subgraphs_maximum(graph1, graph2, get(vertex_index, graph1), + get(vertex_index, graph2), always_equivalent(), always_equivalent(), + only_connected_subgraphs, user_callback); +} + +// Named parameter variant of mcgregor_common_subgraphs_maximum +template < typename GraphFirst, typename GraphSecond, typename SubGraphCallback, + typename Param, typename Tag, typename Rest > +void mcgregor_common_subgraphs_maximum(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback, + const bgl_named_params< Param, Tag, Rest >& params) +{ + mcgregor_common_subgraphs_maximum(graph1, graph2, + choose_const_pmap( + get_param(params, vertex_index1), graph1, vertex_index), + choose_const_pmap( + get_param(params, vertex_index2), graph2, vertex_index), + choose_param( + get_param(params, edges_equivalent_t()), always_equivalent()), + choose_param( + get_param(params, vertices_equivalent_t()), always_equivalent()), + only_connected_subgraphs, user_callback); +} + +// ========================================================================== + +namespace detail +{ + +// Binary function object that intercepts subgraphs from +// mcgregor_common_subgraphs_internal and maintains a cache of the +// largest, unique subgraphs. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename SubGraphCallback > +struct unique_maximum_subgraph_interceptor +{ + + typedef typename graph_traits< GraphFirst >::vertices_size_type + VertexSizeFirst; + + typedef mcgregor_common_subgraph_traits< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond > + SubGraphTraits; + + typedef typename SubGraphTraits::correspondence_map_first_to_second_type + CachedCorrespondenceMapFirstToSecond; + + typedef typename SubGraphTraits::correspondence_map_second_to_first_type + CachedCorrespondenceMapSecondToFirst; + + typedef std::pair< VertexSizeFirst, + std::pair< CachedCorrespondenceMapFirstToSecond, + CachedCorrespondenceMapSecondToFirst > > + SubGraph; + + typedef std::vector< SubGraph > SubGraphList; + + unique_maximum_subgraph_interceptor(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + SubGraphCallback user_callback) + : m_graph1(graph1) + , m_graph2(graph2) + , m_vindex_map1(vindex_map1) + , m_vindex_map2(vindex_map2) + , m_subgraphs(make_shared< SubGraphList >()) + , m_largest_size_so_far(make_shared< VertexSizeFirst >(0)) + , m_user_callback(user_callback) + { + } + + template < typename CorrespondenceMapFirstToSecond, + typename CorrespondenceMapSecondToFirst > + bool operator()( + CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + CorrespondenceMapSecondToFirst correspondence_map_2_to_1, + VertexSizeFirst subgraph_size) + { + + if (subgraph_size > *m_largest_size_so_far) + { + m_subgraphs->clear(); + *m_largest_size_so_far = subgraph_size; + } + + if (subgraph_size == *m_largest_size_so_far) + { + + // Check if subgraph is unique + for (typename SubGraphList::const_iterator subgraph_iter + = m_subgraphs->begin(); + subgraph_iter != m_subgraphs->end(); ++subgraph_iter) + { + + SubGraph subgraph_cached = *subgraph_iter; + + if (!are_property_maps_different(correspondence_map_1_to_2, + subgraph_cached.second.first, m_graph1)) + { + + // New subgraph is a duplicate + return (true); + } + } + + // Subgraph is unique, so make a cached copy + CachedCorrespondenceMapFirstToSecond new_subgraph_1_to_2 + = CachedCorrespondenceMapFirstToSecond( + num_vertices(m_graph1), m_vindex_map1); + + CachedCorrespondenceMapSecondToFirst new_subgraph_2_to_1 + = CachedCorrespondenceMapSecondToFirst( + num_vertices(m_graph2), m_vindex_map2); + + BGL_FORALL_VERTICES_T(vertex1, m_graph1, GraphFirst) + { + put(new_subgraph_1_to_2, vertex1, + get(correspondence_map_1_to_2, vertex1)); + } + + BGL_FORALL_VERTICES_T(vertex2, m_graph2, GraphFirst) + { + put(new_subgraph_2_to_1, vertex2, + get(correspondence_map_2_to_1, vertex2)); + } + + m_subgraphs->push_back(std::make_pair(subgraph_size, + std::make_pair(new_subgraph_1_to_2, new_subgraph_2_to_1))); + } + + return (true); + } + + void output_subgraphs() + { + for (typename SubGraphList::const_iterator subgraph_iter + = m_subgraphs->begin(); + subgraph_iter != m_subgraphs->end(); ++subgraph_iter) + { + + SubGraph subgraph_cached = *subgraph_iter; + m_user_callback(subgraph_cached.second.first, + subgraph_cached.second.second, subgraph_cached.first); + } + } + +private: + const GraphFirst& m_graph1; + const GraphFirst& m_graph2; + const VertexIndexMapFirst m_vindex_map1; + const VertexIndexMapSecond m_vindex_map2; + shared_ptr< SubGraphList > m_subgraphs; + shared_ptr< VertexSizeFirst > m_largest_size_so_far; + SubGraphCallback m_user_callback; +}; + +} // namespace detail + +// Enumerates the largest, unique common subgraphs found between +// graph1 and graph2. Note that the ENTIRE search space is explored +// before user_callback is actually invoked. +template < typename GraphFirst, typename GraphSecond, + typename VertexIndexMapFirst, typename VertexIndexMapSecond, + typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, + typename SubGraphCallback > +void mcgregor_common_subgraphs_maximum_unique(const GraphFirst& graph1, + const GraphSecond& graph2, const VertexIndexMapFirst vindex_map1, + const VertexIndexMapSecond vindex_map2, + EdgeEquivalencePredicate edges_equivalent, + VertexEquivalencePredicate vertices_equivalent, + bool only_connected_subgraphs, SubGraphCallback user_callback) +{ + detail::unique_maximum_subgraph_interceptor< GraphFirst, GraphSecond, + VertexIndexMapFirst, VertexIndexMapSecond, SubGraphCallback > + unique_max_interceptor( + graph1, graph2, vindex_map1, vindex_map2, user_callback); + + detail::mcgregor_common_subgraphs_internal_init(graph1, graph2, vindex_map1, + vindex_map2, edges_equivalent, vertices_equivalent, + only_connected_subgraphs, unique_max_interceptor); + + // Only output the largest, unique subgraphs + unique_max_interceptor.output_subgraphs(); +} + +// Variant of mcgregor_common_subgraphs_maximum_unique with all default +// parameters +template < typename GraphFirst, typename GraphSecond, + typename SubGraphCallback > +void mcgregor_common_subgraphs_maximum_unique(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback) +{ + + mcgregor_common_subgraphs_maximum_unique(graph1, graph2, + get(vertex_index, graph1), get(vertex_index, graph2), + always_equivalent(), always_equivalent(), only_connected_subgraphs, + user_callback); +} + +// Named parameter variant of +// mcgregor_common_subgraphs_maximum_unique +template < typename GraphFirst, typename GraphSecond, typename SubGraphCallback, + typename Param, typename Tag, typename Rest > +void mcgregor_common_subgraphs_maximum_unique(const GraphFirst& graph1, + const GraphSecond& graph2, bool only_connected_subgraphs, + SubGraphCallback user_callback, + const bgl_named_params< Param, Tag, Rest >& params) +{ + mcgregor_common_subgraphs_maximum_unique(graph1, graph2, + choose_const_pmap( + get_param(params, vertex_index1), graph1, vertex_index), + choose_const_pmap( + get_param(params, vertex_index2), graph2, vertex_index), + choose_param( + get_param(params, edges_equivalent_t()), always_equivalent()), + choose_param( + get_param(params, vertices_equivalent_t()), always_equivalent()), + only_connected_subgraphs, user_callback); +} + +// ========================================================================== + +// Fills a membership map (vertex -> bool) using the information +// present in correspondence_map_1_to_2. Every vertex in a +// membership map will have a true value only if it is not +// associated with a null vertex in the correspondence map. +template < typename GraphSecond, typename GraphFirst, + typename CorrespondenceMapFirstToSecond, typename MembershipMapFirst > +void fill_membership_map(const GraphFirst& graph1, + const CorrespondenceMapFirstToSecond correspondence_map_1_to_2, + MembershipMapFirst membership_map1) +{ + + BGL_FORALL_VERTICES_T(vertex1, graph1, GraphFirst) + { + put(membership_map1, vertex1, + get(correspondence_map_1_to_2, vertex1) + != graph_traits< GraphSecond >::null_vertex()); + } +} + +// Traits associated with a membership map filtered graph. Provided +// for convenience to access graph and vertex filter types. +template < typename Graph, typename MembershipMap > +struct membership_filtered_graph_traits +{ + typedef property_map_filter< MembershipMap > vertex_filter_type; + typedef filtered_graph< Graph, keep_all, vertex_filter_type > graph_type; +}; + +// Returns a filtered sub-graph of graph whose edge and vertex +// inclusion is dictated by membership_map. +template < typename Graph, typename MembershipMap > +typename membership_filtered_graph_traits< Graph, MembershipMap >::graph_type +make_membership_filtered_graph( + const Graph& graph, MembershipMap& membership_map) +{ + + typedef membership_filtered_graph_traits< Graph, MembershipMap > MFGTraits; + typedef typename MFGTraits::graph_type MembershipFilteredGraph; + + typename MFGTraits::vertex_filter_type v_filter(membership_map); + + return (MembershipFilteredGraph(graph, keep_all(), v_filter)); +} + +} // namespace boost + +#endif // BOOST_GRAPH_MCGREGOR_COMMON_SUBGRAPHS_HPP \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/Inverted.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/Inverted.hpp index 8896105..d070b8e 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/models/Inverted.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/Inverted.hpp @@ -1,7 +1,7 @@ #ifndef JLA_BOOST_GRAPHMORPHISM_AS_MODELS_INVERTED_HPP #define JLA_BOOST_GRAPHMORPHISM_AS_MODELS_INVERTED_HPP -#include +#include #include diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleAdaptor.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleAdaptor.hpp index 295d8c5..891b775 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleAdaptor.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleAdaptor.hpp @@ -4,7 +4,7 @@ // This is just some convenience that wraps a map and its inverse, // and exposes them as an invertible vertex map. -#include +#include namespace jla_boost { namespace GraphMorphism { diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleVector.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleVector.hpp new file mode 100644 index 0000000..19be0a7 --- /dev/null +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/InvertibleVector.hpp @@ -0,0 +1,256 @@ +#ifndef JLA_BOOST_GRAPH_MORPHISM_MODELS_INVERTIBLE_VECTOR_HPP +#define JLA_BOOST_GRAPH_MORPHISM_MODELS_INVERTIBLE_VECTOR_HPP + +#include + +#include + +namespace jla_boost::GraphMorphism { + +// InvertibleVertexMap +// ============================================================================= + +template +struct InvertibleVectorVertexMap { + using GraphDom = GraphDomT; + using GraphCodom = GraphCodomT; + using Storable = std::true_type; + + template + static auto + reinterpret(InvertibleVectorVertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom, + const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { + // this of course assumes that the vertex_descriptors represent exactly the same on the reinterpreted graphs + return InvertibleVectorVertexMap(std::move(m.forward), std::move(m.backward), + gDomReinterpreted, gCodomReinterpreted); + } + + InvertibleVectorVertexMap(const GraphDom &gDom, const GraphCodom &gCodom) + : forward(num_vertices(gDom), boost::graph_traits::null_vertex()), + backward(num_vertices(gCodom), boost::graph_traits::null_vertex()) {} + + template + InvertibleVectorVertexMap(const VertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) + : InvertibleVectorVertexMap(gDom, gCodom) { + BOOST_CONCEPT_ASSERT((InvertibleVertexMapConcept)); + for(auto vDom: asRange(vertices(gDom))) { + auto vId = get(boost::vertex_index_t(), gDom, vDom); + forward[vId] = get(m, gDom, gCodom, vDom); + } + for(auto vCodom: asRange(vertices(gCodom))) { + auto vId = get(boost::vertex_index_t(), gCodom, vCodom); + backward[vId] = get_inverse(m, gDom, gCodom, vCodom); + } + { // just asserts + for(auto vDom: asRange(vertices(gDom))) { + auto vCodom = get(*this, gDom, gCodom, vDom); + if(vCodom != boost::graph_traits::null_vertex()) + assert(get_inverse(*this, gDom, gCodom, vCodom) == vDom); + } + for(auto vCodom: asRange(vertices(gCodom))) { + auto vDom = get_inverse(*this, gDom, gCodom, vCodom); + if(vDom != boost::graph_traits::null_vertex()) + assert(get(*this, gDom, gCodom, vDom) == vCodom); + } + } + } +private: + template + friend + class InvertibleVectorVertexMap; + + InvertibleVectorVertexMap( + std::vector::vertex_descriptor> &&forward, + std::vector::vertex_descriptor> &&backward, + const GraphDom &gDom, const GraphCodom &gCodom) + : forward(std::move(forward)), backward(std::move(backward)) { + assert(this->forward.size() == num_vertices(gDom)); + assert(this->backward.size() == num_vertices(gCodom)); + } +private: + std::vector::vertex_descriptor> forward; + std::vector::vertex_descriptor> backward; +public: + friend typename boost::graph_traits::vertex_descriptor + get(const InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::vertex_descriptor v) { + assert(v != boost::graph_traits::null_vertex()); + auto vId = get(boost::vertex_index_t(), gDom, v); + assert(vId < m.forward.size()); + return m.forward[vId]; + } + + friend typename boost::graph_traits::vertex_descriptor + get_inverse(const InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::vertex_descriptor v) { + assert(v != boost::graph_traits::null_vertex()); + auto vId = get(boost::vertex_index_t(), gCodom, v); + assert(vId < m.backward.size()); + return m.backward[vId]; + } + + friend void put(InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::vertex_descriptor vDom, + typename boost::graph_traits::vertex_descriptor vCodom) { + assert(vDom != boost::graph_traits::null_vertex()); + auto vIdDom = get(boost::vertex_index_t(), gDom, vDom); + if(vCodom == boost::graph_traits::null_vertex()) { + // we need to reset the inverse before we forget it, + auto vCodomOld = m.forward[vIdDom]; + if(vCodomOld != boost::graph_traits::null_vertex()) { + m.forward[vIdDom] = boost::graph_traits::null_vertex(); + auto vIdCodomOld = get(boost::vertex_index_t(), gCodom, vCodomOld); + m.backward[vIdCodomOld] = boost::graph_traits::null_vertex(); + } + } else { + auto vIdCodom = get(boost::vertex_index_t(), gCodom, vCodom); + assert(vIdDom < m.forward.size()); + assert(vIdCodom < m.backward.size()); + m.forward[vIdDom] = vCodom; + m.backward[vIdCodom] = vDom; + } + } +public: + friend void syncSize(InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) { + m.forward.resize(num_vertices(gDom), boost::graph_traits::null_vertex()); + m.backward.resize(num_vertices(gCodom), boost::graph_traits::null_vertex()); + } +}; + +struct ToInvertibleVectorVertexMap { + template + auto operator()(VertexMap &&m, + const typename VertexMapTraits::GraphDom &gDom, + const typename VertexMapTraits::GraphCodom &gCodom) const { + using Map = InvertibleVectorVertexMap::GraphDom, typename VertexMapTraits::GraphCodom>; + return Map(std::forward(m), gDom, gCodom); + } +}; + + +// InvertibleGraphMap +// ============================================================================= + +template +struct InvertibleVectorGraphMap : InvertibleVectorVertexMap { + using Base = InvertibleVectorVertexMap; + using GraphDom = GraphDomT; + using GraphCodom = GraphCodomT; + using Storable = std::true_type; + +// template +// static auto +// reinterpret(InvertibleVectorVertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom, +// const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { +// // this of course assumes that the vertex_descriptors represent exactly the same on the reinterpreted graphs +// return InvertibleVectorVertexMap(std::move(m.forward), std::move(m.backward), +// gDomReinterpreted, gCodomReinterpreted); +// } + + InvertibleVectorGraphMap(const GraphDom &gDom, const GraphCodom &gCodom) + : Base(gDom, gCodom), forward(num_edges(gDom)), backward(num_edges(gCodom)) {} + +// template +// InvertibleVectorVertexMap(const VertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) +// : InvertibleVectorVertexMap(gDom, gCodom) { +// BOOST_CONCEPT_ASSERT((InvertibleVertexMapConcept)); +// for(auto vDom: asRange(vertices(gDom))) { +// auto vId = get(boost::vertex_index_t(), gDom, vDom); +// forward[vId] = get(m, gDom, gCodom, vDom); +// } +// for(auto vCodom: asRange(vertices(gCodom))) { +// auto vId = get(boost::vertex_index_t(), gCodom, vCodom); +// backward[vId] = get_inverse(m, gDom, gCodom, vCodom); +// } +// { // just asserts +// for(auto vDom: asRange(vertices(gDom))) { +// auto vCodom = get(*this, gDom, gCodom, vDom); +// if(vCodom != boost::graph_traits::null_vertex()) +// assert(get_inverse(*this, gDom, gCodom, vCodom) == vDom); +// } +// for(auto vCodom: asRange(vertices(gCodom))) { +// auto vDom = get_inverse(*this, gDom, gCodom, vCodom); +// if(vDom != boost::graph_traits::null_vertex()) +// assert(get(*this, gDom, gCodom, vDom) == vCodom); +// } +// } +// } +private: + template + friend + class InvertibleVectorGraphMap; + +// InvertibleVectorVertexMap( +// std::vector::vertex_descriptor> &&forward, +// std::vector::vertex_descriptor> &&backward, +// const GraphDom &gDom, const GraphCodom &gCodom) +// : forward(std::move(forward)), backward(std::move(backward)) { +// assert(this->forward.size() == num_vertices(gDom)); +// assert(this->backward.size() == num_vertices(gCodom)); +// } +private: + std::vector::edge_descriptor> forward; + std::vector::edge_descriptor> backward; +public: + friend typename boost::graph_traits::edge_descriptor + get(const InvertibleVectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::edge_descriptor e) { + assert(e != typename boost::graph_traits::edge_descriptor()); + const auto eId = get(boost::edge_index_t(), gDom, e); + assert(eId < m.forward.size()); + return m.forward[eId]; + } + + friend typename boost::graph_traits::edge_descriptor + get_inverse(const InvertibleVectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::edge_descriptor e) { + assert(e != typename boost::graph_traits::edge_descriptor()); + const auto eId = get(boost::edge_index_t(), gCodom, e); + assert(eId < m.backward.size()); + return m.backward[eId]; + } + + friend void put(InvertibleVectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::edge_descriptor eDom, + typename boost::graph_traits::edge_descriptor eCodom) { + using DomEdge = typename boost::graph_traits::edge_descriptor; + using CodomEdge = typename boost::graph_traits::edge_descriptor; + assert(eDom != DomEdge()); + const auto eIdDom = get(boost::edge_index_t(), gDom, eDom); + if(eCodom == CodomEdge()) { + // we need to reset the inverse before we forget it, + const auto eCodomOld = m.forward[eIdDom]; + if(eCodomOld != CodomEdge()) { + m.forward[eIdDom] = CodomEdge(); + const auto eIdCodomOld = get(boost::edge_index_t(), gCodom, eCodomOld); + m.backward[eIdCodomOld] = DomEdge(); + } + } else { + const auto eIdCodom = get(boost::edge_index_t(), gCodom, eCodom); + assert(eIdDom < m.forward.size()); + assert(eIdCodom < m.backward.size()); + m.forward[eIdDom] = eCodom; + m.backward[eIdCodom] = eDom; + } + } +public: + friend void syncSize(InvertibleVectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom) { + syncSize(static_cast(m), gDom, gCodom); + m.forward.resize(num_edges(gDom)); + m.backward.resize(num_edges(gCodom)); + } +}; + +struct ToInvertibleVectorGraphMap { + template + auto operator()(VertexMap &&m, + const typename VertexMapTraits::GraphDom &gDom, + const typename VertexMapTraits::GraphCodom &gCodom) const { + using Map = InvertibleVectorGraphMap::GraphDom, typename VertexMapTraits::GraphCodom>; + return Map(std::forward(m), gDom, gCodom); + } +}; + +} // namespace jla_boost::GraphMorphism + +#endif // JLA_BOOST_GRAPH_MORPHISM_MODELS_INVERTIBLE_VECTOR_HPP \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyMap.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyMap.hpp index 8352963..bcb9219 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyMap.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyMap.hpp @@ -1,10 +1,10 @@ #ifndef JLA_BOOST_GRAPH_MORPHISM_MODELS_PROPERTY_MAP_H -#define JLA_BOOST_GRAPH_MORPHISM_MODELS_PROPERTY_MAP_H +#define JLA_BOOST_GRAPH_MORPHISM_MODELS_PROPERTY_MAP_H // This is just some convenience that wraps a map and it's inverse, // and exposes them as an invertible vertex map. -#include +#include namespace jla_boost { namespace GraphMorphism { @@ -17,33 +17,34 @@ struct MatchAsVertexMap { template static MatchAsVertexMap - reinterpret(MatchAsVertexMap &&m, const GraphDom&, const GraphCodom&, const GraphDomU&, const GraphCodomU&) { + reinterpret(MatchAsVertexMap &&m, const GraphDom &, const GraphCodom &, + const GraphDomU &, const GraphCodomU &) { return MatchAsVertexMap(m.map, m.mapInv); } - MatchAsVertexMap(Map map, MapInv mapInv) : map(std::move(map)), mapInv(std::move(mapInv)) { } + MatchAsVertexMap(Map map, MapInv mapInv) : map(std::move(map)), mapInv(std::move(mapInv)) {} private: Map map; MapInv mapInv; public: - friend typename boost::graph_traits::vertex_descriptor get(const MatchAsVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor v) { + typename boost::graph_traits::vertex_descriptor v) { return get(m.map, v); } friend typename boost::graph_traits::vertex_descriptor - get_inverse(const MatchAsVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor v) { + get_inverse(const MatchAsVertexMap &m, const GraphDom &gDom, + const GraphCodom &gCodom, + typename boost::graph_traits::vertex_descriptor v) { return get(m.mapInv, v); } }; template struct MatchAsVertexMapWrapper { - - MatchAsVertexMapWrapper(const GraphDom &gDom, const GraphCodom &gCodom, Next next) : gDom(gDom), gCodom(gCodom), next(next) { } + MatchAsVertexMapWrapper(const GraphDom &gDom, const GraphCodom &gCodom, Next next) + : gDom(gDom), gCodom(gCodom), next(next) {} template bool operator()(MapDC mDomCodom, MapCD mCodomDom) const { @@ -56,11 +57,12 @@ struct MatchAsVertexMapWrapper { }; template -MatchAsVertexMapWrapper makeMatchAsVertexMapWrapper(const GraphDom &gDom, const GraphCodom &gCodom, Next next) { +MatchAsVertexMapWrapper +makeMatchAsVertexMapWrapper(const GraphDom &gDom, const GraphCodom &gCodom, Next next) { return MatchAsVertexMapWrapper(gDom, gCodom, next); } } // namespace GraphMorphism } // namespace jla_boost -#endif /* JLA_BOOST_GRAPH_MORPHISM_MODELS_PROPERTY_MAP_H */ \ No newline at end of file +#endif /* JLA_BOOST_GRAPH_MORPHISM_MODELS_PROPERTY_MAP_H */ \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyVertexMap.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyVertexMap.hpp index babd605..8c5b436 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyVertexMap.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/PropertyVertexMap.hpp @@ -3,7 +3,7 @@ // A wrapper that attaches arbitrary tagged data to a vertex map implementation. -#include +#include #include diff --git a/libs/jla_boost/include/jla_boost/graph/morphism/models/Vector.hpp b/libs/jla_boost/include/jla_boost/graph/morphism/models/Vector.hpp index 66b5ef4..719756f 100644 --- a/libs/jla_boost/include/jla_boost/graph/morphism/models/Vector.hpp +++ b/libs/jla_boost/include/jla_boost/graph/morphism/models/Vector.hpp @@ -1,19 +1,14 @@ #ifndef JLA_BOOST_GRAPH_MORPHISM_MODELS_VECTOR_HPP #define JLA_BOOST_GRAPH_MORPHISM_MODELS_VECTOR_HPP -#include +#include #include -// - VectorVertexMap -// - ToVectorVertexMap -// - InvertibleVectorVertexMap -// - ToInvertibleVectorVertexMap - namespace jla_boost::GraphMorphism { -// VectorVertexMap -//------------------------------------------------------------------------------ +// VertexMap +// ============================================================================= template struct VectorVertexMap { @@ -23,19 +18,19 @@ struct VectorVertexMap { template static auto reinterpret(VectorVertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom, - const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { + const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { // this of course assumes that the vertex_descriptors represent exactly the same on the reinterpreted graphs return VectorVertexMap(std::move(m.data), gDomReinterpreted, gCodomReinterpreted); } public: VectorVertexMap(const GraphDom &gDom, const GraphCodom &gCodom) - : data(num_vertices(gDom), boost::graph_traits::null_vertex()) { } + : data(num_vertices(gDom), boost::graph_traits::null_vertex()) {} template VectorVertexMap(VertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom) - : VectorVertexMap(gDom, gCodom) { + : VectorVertexMap(gDom, gCodom) { BOOST_CONCEPT_ASSERT((VertexMapConcept)); - for(auto v : asRange(vertices(gDom))) { + for(auto v: asRange(vertices(gDom))) { auto vId = get(boost::vertex_index_t(), gDom, v); data[vId] = get(m, gDom, gCodom, v); } @@ -46,12 +41,15 @@ struct VectorVertexMap { } private: template - friend class VectorVertexMap; + friend + class VectorVertexMap; template - friend class VertexMapTraits; + friend + class VertexMapTraits; - VectorVertexMap(std::vector::vertex_descriptor> &&data, const GraphDom &gDom, const GraphCodom &gCodom) - : data(std::move(data)) { + VectorVertexMap(std::vector::vertex_descriptor> &&data, + const GraphDom &gDom, const GraphCodom &gCodom) + : data(std::move(data)) { assert(this->data.size() == num_vertices(gDom)); } private: @@ -59,150 +57,113 @@ struct VectorVertexMap { public: friend typename boost::graph_traits::vertex_descriptor get(const VectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor v) { + typename boost::graph_traits::vertex_descriptor v) { auto vId = get(boost::vertex_index_t(), gDom, v); assert(vId < m.data.size()); return m.data[vId]; } friend void put(VectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor vDom, - typename boost::graph_traits::vertex_descriptor vCodom) { + typename boost::graph_traits::vertex_descriptor vDom, + typename boost::graph_traits::vertex_descriptor vCodom) { auto vId = get(boost::vertex_index_t(), gDom, vDom); assert(vId < m.data.size()); m.data[vId] = vCodom; } -}; -// ToVectorVertexMap -//------------------------------------------------------------------------------ + friend void syncSize(VectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) { + m.data.resize(num_vertices(gDom), boost::graph_traits::null_vertex()); + } +}; struct ToVectorVertexMap { template auto operator()(VertexMap &&m, - const typename VertexMapTraits::GraphDom &gDom, - const typename VertexMapTraits::GraphCodom &gCodom) const { - using Map = VectorVertexMap::GraphDom, typename VertexMapTraits::GraphCodom >; + const typename VertexMapTraits::GraphDom &gDom, + const typename VertexMapTraits::GraphCodom &gCodom) const { + using Map = VectorVertexMap::GraphDom, typename VertexMapTraits::GraphCodom>; return Map(std::forward(m), gDom, gCodom); } }; -// InvertibleVectorVertexMap -//------------------------------------------------------------------------------ +// GraphMap +// ============================================================================= template -struct InvertibleVectorVertexMap { +struct VectorGraphMap : VectorVertexMap { + using Base = VectorVertexMap; using GraphDom = GraphDomT; using GraphCodom = GraphCodomT; using Storable = std::true_type; - template - static auto reinterpret(InvertibleVectorVertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom, - const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { - // this of course assumes that the vertex_descriptors represent exactly the same on the reinterpreted graphs - return InvertibleVectorVertexMap(std::move(m.forward), std::move(m.backward), gDomReinterpreted, gCodomReinterpreted); - } - - InvertibleVectorVertexMap(const GraphDom &gDom, const GraphCodom &gCodom) - : forward(num_vertices(gDom), boost::graph_traits::null_vertex()), - backward(num_vertices(gCodom), boost::graph_traits::null_vertex()) { } +// template +// static auto reinterpret(VectorGraphMap &&m, const GraphDom &gDom, const GraphCodom &gCodom, +// const GraphDomU &gDomReinterpreted, const GraphCodomU &gCodomReinterpreted) { +// // this of course assumes that the descriptors represent exactly the same on the reinterpreted graphs +// return VectorGraphMap(std::move(m.data), gDomReinterpreted, gCodomReinterpreted); +// } +public: + VectorGraphMap(const GraphDom &gDom, const GraphCodom &gCodom) + : Base(gDom, gCodom), edgeData(num_edges(gDom)) {} + +// template +// VectorGraphMap(VertexMap &&m, const GraphDom &gDom, const GraphCodom &gCodom) +// : VectorGraphMap(gDom, gCodom) { +// BOOST_CONCEPT_ASSERT((VertexMapConcept)); +// for(const auto e: asRange(edges(gDom))) { +// const auto eId = get(boost::edge_index_t(), gDom, e); +// edgeData[eId] = get(m, gDom, gCodom, e); +// } +// } - template - InvertibleVectorVertexMap(const VertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) - : InvertibleVectorVertexMap(gDom, gCodom) { - BOOST_CONCEPT_ASSERT((InvertibleVertexMapConcept)); - for(auto vDom : asRange(vertices(gDom))) { - auto vId = get(boost::vertex_index_t(), gDom, vDom); - forward[vId] = get(m, gDom, gCodom, vDom); - } - for(auto vCodom : asRange(vertices(gCodom))) { - auto vId = get(boost::vertex_index_t(), gCodom, vCodom); - backward[vId] = get_inverse(m, gDom, gCodom, vCodom); - } - { // just asserts - for(auto vDom : asRange(vertices(gDom))) { - auto vCodom = get(*this, gDom, gCodom, vDom); - if(vCodom != boost::graph_traits::null_vertex()) - assert(get_inverse(*this, gDom, gCodom, vCodom) == vDom); - } - for(auto vCodom : asRange(vertices(gCodom))) { - auto vDom = get_inverse(*this, gDom, gCodom, vCodom); - if(vDom != boost::graph_traits::null_vertex()) - assert(get(*this, gDom, gCodom, vDom) == vCodom); - } - } + std::size_t size() const { + return edgeData.size(); } private: template - friend class InvertibleVectorVertexMap; - - InvertibleVectorVertexMap( - std::vector::vertex_descriptor> &&forward, - std::vector::vertex_descriptor> &&backward, - const GraphDom &gDom, const GraphCodom &gCodom) - : forward(std::move(forward)), backward(std::move(backward)) { - assert(this->forward.size() == num_vertices(gDom)); - assert(this->backward.size() == num_vertices(gCodom)); - } + friend + class VectorGraphMap; + template + friend + class VertexMapTraits; + +// VectorGraphMap(std::vector::edge_descriptor> &&edgeData, +// const GraphDom &gDom, const GraphCodom &gCodom) +// : edgeData(std::move(edgeData)) { +// assert(this->data.size() == num_vertices(gDom)); +// } private: - std::vector::vertex_descriptor> forward; - std::vector::vertex_descriptor> backward; + std::vector::edge_descriptor> edgeData; public: - friend typename boost::graph_traits::vertex_descriptor - get(const InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor v) { - assert(v != boost::graph_traits::null_vertex()); - auto vId = get(boost::vertex_index_t(), gDom, v); - assert(vId < m.forward.size()); - return m.forward[vId]; + friend typename boost::graph_traits::edge_descriptor + get(const VectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::edge_descriptor e) { + const auto eId = get(boost::edge_index_t(), gDom, e); + assert(eId < m.edgeData.size()); + return m.edgeData[eId]; } - friend typename boost::graph_traits::vertex_descriptor - get_inverse(const InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor v) { - assert(v != boost::graph_traits::null_vertex()); - auto vId = get(boost::vertex_index_t(), gCodom, v); - assert(vId < m.backward.size()); - return m.backward[vId]; + friend void put(VectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom, + typename boost::graph_traits::edge_descriptor eDom, + typename boost::graph_traits::edge_descriptor eCodom) { + const auto eId = get(boost::edge_index_t(), gDom, eDom); + assert(eId < m.edgeData.size()); + m.edgeData[eId] = eCodom; } - friend void put(InvertibleVectorVertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom, - typename boost::graph_traits::vertex_descriptor vDom, - typename boost::graph_traits::vertex_descriptor vCodom) { - assert(vDom != boost::graph_traits::null_vertex()); - auto vIdDom = get(boost::vertex_index_t(), gDom, vDom); - if(vCodom == boost::graph_traits::null_vertex()) { - // we need to reset the inverse before we forget it, - auto vCodomOld = m.forward[vIdDom]; - if(vCodomOld != boost::graph_traits::null_vertex()) { - m.forward[vIdDom] = boost::graph_traits::null_vertex(); - auto vIdCodomOld = get(boost::vertex_index_t(), gCodom, vCodomOld); - m.backward[vIdCodomOld] = boost::graph_traits::null_vertex(); - } - } else { - auto vIdCodom = get(boost::vertex_index_t(), gCodom, vCodom); - assert(vIdDom < m.forward.size()); - assert(vIdCodom < m.backward.size()); - m.forward[vIdDom] = vCodom; - m.backward[vIdCodom] = vDom; - } - } -public: - void resizeRight(const GraphDom &gDom, const GraphCodom &gCodom) { - backward.resize(num_vertices(gCodom), boost::graph_traits::null_vertex()); + friend void syncSize(VectorGraphMap &m, const GraphDom &gDom, const GraphCodom &gCodom) { + syncSize(static_cast(m), gDom, gCodom); + m.edgeData.resize(num_edges(gDom)); } }; -// ToInvertibleVectorVertexMap -//------------------------------------------------------------------------------ - -struct ToInvertibleVectorVertexMap { - template - auto operator()(VertexMap &&m, - const typename VertexMapTraits::GraphDom &gDom, - const typename VertexMapTraits::GraphCodom &gCodom) const { - using Map = InvertibleVectorVertexMap::GraphDom, typename VertexMapTraits::GraphCodom >; - return Map(std::forward(m), gDom, gCodom); +struct ToVectorGraphMap { + template + auto operator()(GraphMap &&m, + const typename VertexMapTraits::GraphDom &gDom, + const typename VertexMapTraits::GraphCodom &gCodom) const { + using Map = VectorGraphMap::GraphDom, typename VertexMapTraits::GraphCodom>; + return Map(std::forward(m), gDom, gCodom); } }; diff --git a/libs/jla_boost/src/graph/dpo/IO.cpp b/libs/jla_boost/src/graph/dpo/IO.cpp deleted file mode 100644 index 4490b22..0000000 --- a/libs/jla_boost/src/graph/dpo/IO.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - -namespace jla_boost { -namespace GraphDPO { - -std::ostream &operator<<(std::ostream &s, Membership m) { - switch(m) { - case Membership::Left: return s << "Left"; - case Membership::Right: return s << "Right"; - case Membership::Context: return s << "Context"; - } - assert(false); - return s; -} - -} // namespace GraphDPO -} // namespace jla_boost \ No newline at end of file diff --git a/libs/libmod/CMakeLists.txt b/libs/libmod/CMakeLists.txt index 28743e7..29024cf 100644 --- a/libs/libmod/CMakeLists.txt +++ b/libs/libmod/CMakeLists.txt @@ -39,7 +39,7 @@ target_link_libraries(libmod # Boost::graph needs Boost::regex which needs the RPATH of libmod to have Boost in it. # But because all Boost libs are linked privately the path is not exposed. # So if someone links against libmod, but no Boost lib, then the linking will fail. -# Perhapse fixed in https://gitlab.kitware.com/cmake/cmake/-/merge_requests/3642 +# Perhaps fixed in https://gitlab.kitware.com/cmake/cmake/-/merge_requests/3642 # TODO: remove the next line with some higher CMAKE version is required? target_link_libraries(libmod PUBLIC Boost::iostreams) target_link_libraries(libmod PRIVATE diff --git a/libs/libmod/src/mod/Chem.cpp b/libs/libmod/src/mod/Chem.cpp index df6eaea..3356def 100644 --- a/libs/libmod/src/mod/Chem.cpp +++ b/libs/libmod/src/mod/Chem.cpp @@ -32,11 +32,7 @@ std::ostream &operator<<(std::ostream &s, const AtomData &data) { if(data.atomId == AtomIds::Invalid) throw LogicError("Can not print atom data with atom id AtomIds::Invalid."); if(data.isotope != Isotope()) s << data.isotope; s << lib::Chem::symbolFromAtomId(data.atomId); - if(data.charge != 0) { - if(data.charge > 1 || data.charge < -1) s << std::abs(data.charge); - if(data.charge < 0) s << '-'; - else s << '+'; - } + if(data.charge != 0) s << lib::Chem::chargeSuffix(data.charge); if(data.radical) s << '.'; return s; } @@ -46,7 +42,7 @@ std::ostream &operator<<(std::ostream &s, const AtomData &data) { //------------------------------------------------------------------------------ std::ostream &operator<<(std::ostream &s, BondType bt) { - if(bt == BondType::Invalid) throw LogicError("Can not print bond type BondType::Invalid."); + if(bt == BondType::Invalid) throw LogicError("Can not print BondType::Invalid."); return s << lib::Chem::bondToChar(bt); } diff --git a/libs/libmod/src/mod/Config.cpp b/libs/libmod/src/mod/Config.cpp index 8c937c2..b6ac02d 100644 --- a/libs/libmod/src/mod/Config.cpp +++ b/libs/libmod/src/mod/Config.cpp @@ -8,13 +8,11 @@ namespace mod { std::ostream &operator<<(std::ostream &s, const LabelType lt) { switch(lt) { case LabelType::String: - s << "string"; - break; + return s << "string"; case LabelType::Term: - s << "term"; - break; + return s << "term"; } - return s; + __builtin_unreachable(); } std::ostream &operator<<(std::ostream &s, const LabelRelation lt) { @@ -26,7 +24,7 @@ std::ostream &operator<<(std::ostream &s, const LabelRelation lt) { case LabelRelation::Unification: return s << "unification"; } - return s; + __builtin_unreachable(); } bool operator==(LabelSettings a, LabelSettings b) { @@ -59,7 +57,7 @@ std::ostream &operator<<(std::ostream &s, IsomorphismPolicy p) { case IsomorphismPolicy::TrustMe: return s << "trustMe"; } - return s; + __builtin_unreachable(); } std::ostream &operator<<(std::ostream &s, SmilesClassPolicy p) { @@ -71,7 +69,19 @@ std::ostream &operator<<(std::ostream &s, SmilesClassPolicy p) { case SmilesClassPolicy::MapUnique: return s << "mapUnique"; } - return s; + __builtin_unreachable(); +} + +std::ostream &operator<<(std::ostream &s, Action a) { + switch(a) { + case Action::Error: + return s << "error"; + case Action::Warn: + return s << "warn"; + case Action::Ignore: + return s << "ignore"; + } + __builtin_unreachable(); } Config &getConfig() { diff --git a/libs/libmod/src/mod/Config.hpp b/libs/libmod/src/mod/Config.hpp index 3b820c4..5a1a18a 100644 --- a/libs/libmod/src/mod/Config.hpp +++ b/libs/libmod/src/mod/Config.hpp @@ -75,7 +75,6 @@ MOD_DECL std::ostream &operator<<(std::ostream &s, LabelRelation lt); // rst: // rst: A class simply for grouping label settings. // rst: - struct LabelSettings { // rst: .. function:: LabelSettings(LabelType type, LabelRelation relation) // rst: @@ -154,6 +153,105 @@ enum class SmilesClassPolicy { }; MOD_DECL std::ostream &operator<<(std::ostream &s, SmilesClassPolicy p); +// rst: .. enum-struct:: Action +// rst: +// rst: Utility enum for deciding what to do in certain cases. +// rst: +enum class Action { + // rst: .. enumerator:: Error + // rst: + // rst: Abort the function and produce an error message, e.g., through and exception. + Error, + // rst: .. enumerator:: Warn + // rst: + // rst: Write a warning, but otherwise do as if it was `Ignore`. + Warn, + // rst: .. enumerator:: Ignore + // rst: + // rst: Ignore the case. The function taking the action as argument should describe what this means. + Ignore +}; +MOD_DECL std::ostream &operator<<(std::ostream &s, Action a); + +// rst: .. class:: MDLOptions +// rst: +// rst: An aggregation of options for the various loading functions for MDL formats. +// rst: Generally each option is defaulted to follow the specification of the formats, +// rst: unless it is harmless to deviate (e.g., relaxed white-space parsing). +// rst: +struct MOD_DECL MDLOptions { + // rst: .. member:: bool addHydrogens = true + // rst: + // rst: Use the MDL valence model to add hydrogens to atoms with default valence, or disable all hydrogen addition. + bool addHydrogens = true; + // rst: .. member:: bool allowAbstract = false + // rst: + // rst: Allow non-standard atom symbols. The standard symbols are the element symbols and those specifying wildcard atoms. + bool allowAbstract = false; + // rst: .. member:: bool applyV2000AtomAliases = true + // rst: + // rst: In MOL V2000 CTAB blocks, replace atom labels by their aliases. + // rst: After application, the atom is considered abstract without errors, and hydrogen addition is suppressed. + bool applyV2000AtomAliases = true; + // rst: .. member:: Action onPatternIsotope = Action::Error + // rst: Action onPatternCharge = Action::Error; + // rst: Action onPatternRadical = Action::Error; + // rst: + // rst: What to do when an atom with symbol ``*`` has an isotope, charge, or radical. + // rst: `Action::Ignore` means assuming the isotope, charge, or radical was not there. + Action onPatternIsotope = Action::Error; + Action onPatternCharge = Action::Error; + Action onPatternRadical = Action::Error; + // rst: .. member:: Action onImplicitValenceOnAbstract = Action::Error + // rst: + // rst: What to do when `addHydrogens && allowAbstract` and an abstract atom is encountered with implicit valence. + // rst: `Action::Ignore` means adding no hydrogens. + Action onImplicitValenceOnAbstract = Action::Error; + // rst: .. member:: Action onV2000UnhandledProperty = Action::Warn + // rst: + // rst: What to do when a property line in a V2000 MOL file is not recognized. + // rst: `Action::Ignore` means simply ignoring that particular line. + Action onV2000UnhandledProperty = Action::Warn; + // rst: .. member:: bool fullyIgnoreV2000UnhandledKnownProperty = false + // rst: + // rst: Warnings are usually stored as "loading warnings", even when they are ignored during parsing. + // rst: Setting this to `true` will act as if `onV2000UnhandledProperty = Action::Ignore` and + // rst: skip the storage, but only for a pre-defined known subset of properties. + bool fullyIgnoreV2000UnhandledKnownProperty = false; + // rst: .. member:: Action onV3000UnhandledAtomProperty = Action::Warn + // rst: + // rst: What to do when a property in atom line in a V3000 MOL file is not recognized. + // rst: `Action::Ignore` means simply ignoring that particular property. + Action onV3000UnhandledAtomProperty = Action::Warn; + // rst: .. member:: Action onV2000Charge4 = Action::Error + // rst: + // rst: What to do when an atom in a V2000 MOL file has the charge 4 (doublet radical). + // rst: `Action::Ignore` means assuming it was charge 0. + Action onV2000Charge4 = Action::Error; + // rst: .. member:: Action onV2000AbstractISO = Action::Error + // rst: + // rst: What to do when an abstract atom in a V2000 MOL file has a non-default ISO or mass difference value. + // rst: `Action::Ignore` means assuming it had no ISO or mass difference value. + Action onV2000AbstractISO = Action::Error; + // rst: .. member:: Action onRAD1 = Action::Error + // rst: Action onRAD3 = Action::Error + // rst: Action onRAD4 = Action::Error + // rst: Action onRAD5 = Action::Error + // rst: Action onRAD6 = Action::Error + // rst: + // rst: What to do when an atom has assigned the indicated radical state. + // rst: `Action::Ignore` means pretending the atom has no radical state assigned. + Action onRAD1 = Action::Error; + Action onRAD3 = Action::Error; + Action onRAD4 = Action::Error; + Action onRAD5 = Action::Error; + Action onRAD6 = Action::Error; + // rst: .. member:: Action onUnsupportedQueryBondType = Action::Error + // rst: + // rst: What to do when a bond type 5, 6, or 7 are encountered (constrained query bond types). + // rst: `Action::Ignore` means assigning a term variable, as if the type was 8. + Action onUnsupportedQueryBondType = Action::Error; +}; // rst: .. function:: Config &getConfig() // rst: @@ -258,16 +356,8 @@ struct Config { ((bool, checkIsoInPermutation, false)) \ ((unsigned long, numIsomorphismCalls, 0)) \ )) \ - ((IO, io, \ - ((std::string, dotCoordOptions, "")) \ - ((bool, useOpenBabelCoords, true)) \ - )) \ - ((OBabel, obabel, \ - ((bool, verbose, false)) \ - )) \ ((Rule, rule, \ ((bool, ignoreConstraintsDuringInversion, false)) \ - ((std::string, changeColour, "")) \ ((std::string, changeColourL, "NavyBlue")) \ ((std::string, changeColourK, "Purple")) \ ((std::string, changeColourR, "Green")) \ @@ -284,9 +374,6 @@ struct Config { )) \ ((Stereo, stereo, \ ((bool, silenceDeductionWarnings, false)) \ - )) \ - ((Term, unification, \ - ((bool, verboseMGU, false)) \ )) #define MOD_CONFIG_nsIter(rNS, dataNS, tNS) \ diff --git a/libs/libmod/src/mod/Derivation.hpp b/libs/libmod/src/mod/Derivation.hpp index a784baa..a0db081 100644 --- a/libs/libmod/src/mod/Derivation.hpp +++ b/libs/libmod/src/mod/Derivation.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_DERIVATION_H -#define MOD_DERIVATION_H +#ifndef MOD_DERIVATION_HPP +#define MOD_DERIVATION_HPP #include #include @@ -20,7 +20,6 @@ struct Derivations; // rst: :math:`G\Rightarrow^p H`, though the validity of the data is not checked. // rst: // rst-class-start: - struct MOD_DECL Derivation { // rst: .. type:: GraphList = std::vector> // rst: @@ -61,7 +60,6 @@ struct MOD_DECL Derivation { // rst: though the validity of the data is not checked. // rst: // rst-class-start: - struct MOD_DECL Derivations { // rst: .. type:: GraphList = std::vector> // rst: @@ -88,4 +86,4 @@ struct MOD_DECL Derivations { } // namespace mod -#endif /* MOD_DERIVATION_H */ +#endif // MOD_DERIVATION_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/Error.cpp b/libs/libmod/src/mod/Error.cpp index e48abfc..1b47c4b 100644 --- a/libs/libmod/src/mod/Error.cpp +++ b/libs/libmod/src/mod/Error.cpp @@ -83,7 +83,7 @@ const char *FatalError::what() const noexcept { std::stringstream ss; Exception::printStacktrace(0, ss); ss << getName() << ": " << text; - ss << "(Do _not_ try to recover from this exception!)\n"; + ss << "\n(Do _not_ try to recover from this exception!)\n"; whatString = ss.str(); return whatString.c_str(); } diff --git a/libs/libmod/src/mod/Function.cpp b/libs/libmod/src/mod/Function.cpp deleted file mode 100644 index 0beb668..0000000 --- a/libs/libmod/src/mod/Function.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "Function.hpp" - -namespace mod::detail_function { - -void dont_call_only_for_test() { - std::function f = []() { - }; - auto a = fromStdFunction(f); - auto b = fromStdFunction("test", f); -} - -} // namespace mod::detail_function \ No newline at end of file diff --git a/libs/libmod/src/mod/GraphConcepts.hpp b/libs/libmod/src/mod/GraphConcepts.hpp index e87ac74..aa195d2 100644 --- a/libs/libmod/src/mod/GraphConcepts.hpp +++ b/libs/libmod/src/mod/GraphConcepts.hpp @@ -37,7 +37,7 @@ auto getGraphFromHandle(GraphHandle g) { } template -auto &getGraphFromHandle(std::shared_ptr &g) { +auto &getGraphFromHandle(const std::shared_ptr &g) { return *g; } diff --git a/libs/libmod/src/mod/Misc.cpp b/libs/libmod/src/mod/Misc.cpp index 968fe6c..f3506cf 100644 --- a/libs/libmod/src/mod/Misc.cpp +++ b/libs/libmod/src/mod/Misc.cpp @@ -6,10 +6,10 @@ #include #include #include -#include -#include +#include #include #include +#include #include #include @@ -44,7 +44,7 @@ void showDump(const std::string &file) { } void printGeometryGraph() { - lib::IO::Stereo::Write::summary(lib::Stereo::getGeometryGraph()); + lib::Stereo::Write::summary(lib::Stereo::getGeometryGraph()); } } // namespace mod diff --git a/libs/libmod/src/mod/Post.cpp b/libs/libmod/src/mod/Post.cpp index 0f40563..fed456e 100644 --- a/libs/libmod/src/mod/Post.cpp +++ b/libs/libmod/src/mod/Post.cpp @@ -7,37 +7,71 @@ namespace mod::post { -FileHandle::FileHandle(std::string name) : name(name) { - stream.open(name.c_str()); - if(!stream) throw LogicError("Can not open file '" + name + "'."); +FileHandle::FileHandle(const std::string &name) : name(name) { + stream.open(name.data()); + if(!stream) throw LogicError("Can not open file '" + this->name + "'."); } -void command(const std::string &text) { - lib::IO::post() << text << std::endl; +std::string makeUniqueFilePrefix() { + return lib::IO::makeUniqueFilePrefix(); } -void reset() { - lib::IO::postReset(); +void command(const std::string &line) { + lib::IO::post() << line << '\n'; } -void flush() { +void flushCommands() { lib::IO::post() << std::flush; } -void disable() { +void disableCommands() { lib::IO::postDisable(); } -void summaryChapter(const std::string &chapterTitle) { - lib::IO::post() << "summaryChapter \"" << chapterTitle << "\"" << std::endl; +void enableCommands() { + lib::IO::postEnable(); } -void summarySection(const std::string §ionTitle) { - lib::IO::post() << "summarySection \"" << sectionTitle << "\"" << std::endl; +void reopenCommandFile() { + lib::IO::postReopenCommandFile(); } -std::string makeUniqueFilePrefix() { - return lib::IO::getUniqueFilePrefix(); +void summaryChapter(const std::string &heading) { + lib::IO::post() << "summaryChapter \"" << heading << "\"\n"; +} + +void summarySection(const std::string &heading) { + lib::IO::post() << "summarySection \"" << heading << "\"\n"; +} + +void summaryRaw(const std::string &latexCode) { + summaryRaw(latexCode, "raw.tex"); +} + +void summaryRaw(const std::string &latexCode, const std::string &file) { + FileHandle s(lib::IO::makeUniqueFilePrefix() + file); + s << latexCode; + lib::IO::post() << "summaryInput '" << std::string(s) << "'\n"; +} + +void summaryInput(const std::string &filename) { + lib::IO::post() << "summaryInput '" << filename << "'\n"; +} + +void disableInvokeMake() { + lib::IO::post() << "disableInvokeMake\n"; +} + +void enableInvokeMake() { + lib::IO::post() << "enableInvokeMake\n"; +} + +void disableCompileSummary() { + lib::IO::post() << "disableCompileSummary\n"; +} + +void enableCompileSummary() { + lib::IO::post() << "enableInvokeMake\n"; } } // namespace mod::post \ No newline at end of file diff --git a/libs/libmod/src/mod/Post.hpp b/libs/libmod/src/mod/Post.hpp index 59e6efe..612ba77 100644 --- a/libs/libmod/src/mod/Post.hpp +++ b/libs/libmod/src/mod/Post.hpp @@ -6,11 +6,16 @@ #include #include +// rst: This header contains various functions to manipulate post-processing (:ref:`mod_post`). +// rst: Commands for the post-processor are written to the command file ``out/post.sh`` +// rst: which the post-processor executes internally as a Bash script. +// rst: + namespace mod::post { struct MOD_DECL FileHandle { // throws LogicError if the file can not be opened - explicit FileHandle(std::string name); + explicit FileHandle(const std::string &name); operator std::ostream &() { return stream; @@ -29,19 +34,71 @@ struct MOD_DECL FileHandle { std::string name; }; -MOD_DECL void command(const std::string &text); -MOD_DECL void reset(); -MOD_DECL void flush(); -MOD_DECL void disable(); - -MOD_DECL void summaryChapter(const std::string &chapterTitle); -MOD_DECL void summarySection(const std::string §ionTitle); // rst: .. function:: std::string post::makeUniqueFilePrefix() // rst: -// rst: :returns: a unique file prefix from the ``out/`` folder. +// rst: :returns: a string on the form ``out/iii_`` where ```iii``` is the next zero-padded integer +// rst: from an internal counter. MOD_DECL std::string makeUniqueFilePrefix(); +// rst: .. function:: void post::command(const std::string &line) +// rst: +// rst: Write the given text to the command file and write a newline character. +// rst: +// rst: .. warning:: The contents of the command file is executed without any security checks. +MOD_DECL void command(const std::string &line); +// rst: .. function:: void post::flushCommands() +// rst: +// rst: Flush the command file buffer. +MOD_DECL void flushCommands(); +// rst: .. function:: void post::disableCommands() +// rst: void post::enableCommands() +// rst: +// rst: Disable/enable command writing and flushing, also for commands emitted internally in the library. +MOD_DECL void disableCommands(); +MOD_DECL void enableCommands(); + +// rst: .. function:: void post::reopenCommandFile() +// rst: +// rst: Reopen the command file, which may be useful if it was modified externally while open by the library. +MOD_DECL void reopenCommandFile(); + +// rst: .. function:: void post::summaryChapter(const std::string &heading) +// rst: +// rst: Command the post-processor to insert a ``\chapter`` macro in the summary. +MOD_DECL void summaryChapter(const std::string &heading); +// rst: .. function:: void post::summarySection(const std::string &heading) +// rst: +// rst: Command the post-processor to insert a ``\section`` macro in the summary. +MOD_DECL void summarySection(const std::string &heading); +// rst: .. function:: void post::summaryRaw(const std::string &latexCode) +// rst: void post::summaryRaw(const std::string &latexCode, const std::string &file) +// rst: +// rst: Command the post-processor to insert the given code verbatim in the summary. +// rst: If `file` is given then that will be appended to a unique prefix for the final filename the code is stored in. +MOD_DECL void summaryRaw(const std::string &latexCode); +MOD_DECL void summaryRaw(const std::string &latexCode, const std::string &file); +// rst: .. function:: void post::summaryInput(const std::string &filename) +// rst: +// rst: Command the post-processor to insert a ``\input`` macro in the summary. +MOD_DECL void summaryInput(const std::string &filename); + +// rst: .. function:: void post::disableInvokeMake() +// rst: void post::enableInvokeMake() +// rst: +// rst: Disable/enable the invocation of Make in the post-processor. +// rst: The processing of commands and generation of Makefiles will still be carried out, +// rst: and Make invocation can be done manually afterwards through the post-processor +MOD_DECL void disableInvokeMake(); +MOD_DECL void enableInvokeMake(); +// rst: .. function:: void post::disableCompileSummary() +// rst: void post::enableCompileSummary() +// rst: +// rst: Disable/enable the compilation of the final summary during post-processing. +// rst: The compilation can be invoked manually afterwards through the post-processor. +MOD_DECL void disableCompileSummary(); +MOD_DECL void enableCompileSummary(); + } // namespace mod::post #endif // MOD_POST_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/Term.cpp b/libs/libmod/src/mod/Term.cpp index 0e872c0..bc4d3e7 100644 --- a/libs/libmod/src/mod/Term.cpp +++ b/libs/libmod/src/mod/Term.cpp @@ -2,9 +2,10 @@ #include #include -#include #include #include +#include +#include #include @@ -13,8 +14,8 @@ namespace mod::Term { void mgu(const std::string &left, const std::string &right) { lib::Term::RawTerm tRawLeft, tRawRight; try { - tRawLeft = lib::IO::Term::Read::rawTerm(left, lib::Term::getStrings()); - tRawRight = lib::IO::Term::Read::rawTerm(right, lib::Term::getStrings()); + tRawLeft = lib::Term::Read::rawTerm(left, lib::Term::getStrings()); + tRawRight = lib::Term::Read::rawTerm(right, lib::Term::getStrings()); } catch(const lib::IO::ParsingError &e) { std::cout << e.msg << '\n'; return; @@ -27,25 +28,25 @@ void mgu(const std::string &left, const std::string &right) { machine.setTemp(machineTemp); addrRight.type = lib::Term::AddressType::Temp; std::cout << "=================================================" << std::endl; - lib::IO::Term::Write::wam(machine, lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(machine, lib::Term::getStrings(), std::cout); std::cout << "Left = " << addrLeft << ", Right = " << addrRight << std::endl; std::cout << "Most general unifier of" << std::endl << "\t"; - lib::IO::Term::Write::term(machine, addrLeft, lib::Term::getStrings(), std::cout); + lib::Term::Write::term(machine, addrLeft, lib::Term::getStrings(), std::cout); std::cout << " =? "; - lib::IO::Term::Write::term(machine, addrRight, lib::Term::getStrings(), std::cout); + lib::Term::Write::term(machine, addrRight, lib::Term::getStrings(), std::cout); std::cout << std::endl; std::cout << "\t"; - lib::IO::Term::Write::rawTerm(tRawLeft, lib::Term::getStrings(), std::cout); + lib::Term::Write::rawTerm(tRawLeft, lib::Term::getStrings(), std::cout); std::cout << " =? "; - lib::IO::Term::Write::rawTerm(tRawRight, lib::Term::getStrings(), std::cout); + lib::Term::Write::rawTerm(tRawRight, lib::Term::getStrings(), std::cout); std::cout << std::endl; auto mgu = machine.unifyHeapTemp(addrLeft.addr, addrRight.addr); - lib::IO::Term::Write::wam(machine, lib::Term::getStrings(), std::cout << "is "); + lib::Term::Write::wam(machine, lib::Term::getStrings(), std::cout << "is "); std::cout << "Left = " << machine.deref(addrLeft) << ", Right = " << machine.deref(addrRight) << std::endl; - lib::IO::Term::Write::mgu(machine, mgu, lib::Term::getStrings(), std::cout); + lib::Term::Write::mgu(machine, mgu, lib::Term::getStrings(), std::cout); std::cout << std::endl; } diff --git a/libs/libmod/src/mod/VertexMap.hpp b/libs/libmod/src/mod/VertexMap.hpp index 0e4f2c8..38f1dc7 100644 --- a/libs/libmod/src/mod/VertexMap.hpp +++ b/libs/libmod/src/mod/VertexMap.hpp @@ -31,12 +31,14 @@ struct VertexMap { std::function forward, std::function backward) : dom(dom), codom(codom), forward(forward), backward(backward) {} + // rst: .. function:: friend std::ostream &operator<<(std::ostream &s, const VertexMap &m) friend std::ostream &operator<<(std::ostream &s, const VertexMap &m) { - return s << "VertexMap{" - << getGraphFromHandle(m.getDomain()) << ", " - << getGraphFromHandle(m.getCodomain()) << "}"; + const Domain &dom = getGraphFromHandle(m.getDomain()); + const Codomain &codom = getGraphFromHandle(m.getCodomain()); + return s << "VertexMap{" << dom << ", " << codom << "}"; } + // rst: .. function:: DomainHandle getDomain() const // rst: CodomainHandle getCodomain() const // rst: @@ -54,6 +56,7 @@ struct VertexMap { if(v.getGraph() != getDomain()) throw LogicError("Vertex does not belong to the domain graph."); return forward(v); } + // rst: .. function:: DomVertex getInverse(CodomVertex v) const // rst: // rst: :returns: the domain vertex that maps to the given codomain vertex. diff --git a/libs/libmod/src/mod/dg/Builder.cpp b/libs/libmod/src/mod/dg/Builder.cpp index cdc0e42..1072ca1 100644 --- a/libs/libmod/src/mod/dg/Builder.cpp +++ b/libs/libmod/src/mod/dg/Builder.cpp @@ -6,8 +6,8 @@ #include #include #include +#include #include -#include #include @@ -28,6 +28,10 @@ Builder::Builder(Builder &&other) = default; Builder &Builder::operator=(Builder &&other) = default; Builder::~Builder() = default; +std::shared_ptr Builder::getDG() const { + return p->dg_; +} + bool Builder::isActive() const { return p != nullptr; } @@ -63,6 +67,26 @@ DG::HyperEdge Builder::addDerivation(const Derivations &d, IsomorphismPolicy gra return p->dg_->getHyper().getInterfaceEdge(p->dg_->getNonHyper().getHyperEdge(innerRes.first)); } +DG::HyperEdge Builder::addHyperEdge(const DG::HyperEdge &e) { + return addHyperEdge(e, IsomorphismPolicy::Check); +} + +DG::HyperEdge Builder::addHyperEdge(const DG::HyperEdge &e, IsomorphismPolicy graphPolicy) { + check(p); + if(!e) throw LogicError("The hyperedge is null."); + Derivations d; + d.left.reserve(e.numSources()); + d.right.reserve(e.numTargets()); + d.rules.reserve(e.rules().size()); + for(const auto &v : e.sources()) + d.left.push_back(v.getGraph()); + for(const auto &v : e.targets()) + d.right.push_back(v.getGraph()); + for(const auto &r: e.rules()) + d.rules.push_back(r); + return addDerivation(d, graphPolicy); +} + ExecuteResult Builder::execute(std::shared_ptr strategy) { return execute(strategy, 1); } diff --git a/libs/libmod/src/mod/dg/Builder.hpp b/libs/libmod/src/mod/dg/Builder.hpp index 0edff0d..0db83a1 100644 --- a/libs/libmod/src/mod/dg/Builder.hpp +++ b/libs/libmod/src/mod/dg/Builder.hpp @@ -31,6 +31,10 @@ class MOD_DECL Builder { Builder(Builder &&other); Builder &operator=(Builder &&other); ~Builder(); + // rst: .. function:: std::shared_ptr getDG() const + // rst: + // rst: :returns: the derivation graph this builder can modify. + std::shared_ptr getDG() const; // rst: .. function:: bool isActive() const // rst: // rst: :returns: whether this object is associated with a :cpp:class:`DG`. @@ -54,6 +58,23 @@ class MOD_DECL Builder { // rst: in the internal graph database in the associated derivation graph. DG::HyperEdge addDerivation(const Derivations &d); DG::HyperEdge addDerivation(const Derivations &d, IsomorphismPolicy graphPolicy); + // rst: .. function:: DG::HyperEdge addHyperEdge(const DG::HyperEdge &e) + // rst: DG::HyperEdge addHyperEdge(const DG::HyperEdge &e, IsomorphismPolicy graphPolicy) + // rst: + // rst: Adds a hyperedge to the associated :class:`DG` from a copy of the given hyperedge + // rst: (from a different :class:`DG`). + // rst: If it already exists, only add the rules to the edge. + // rst: The given :var:`graphPolicy` refers to adding the graphs associated with :var:`e`, + // rst: and it defaults to :enumerator:`IsomorphismPolicy::Check`. + // rst: + // rst: :returns: the hyperedge corresponding to the given derivation. + // rst: :throws: :class:`LogicError` if `!isActive()`. + // rst: :throws: :class:`LogicError` if `!e`. + // rst: :throws: :class:`LogicError` if `graphPolicy == IsomorphismPolicy::Check` and a given graph object + // rst: is different but isomorphic to another given graph object or to a graph object already + // rst: in the internal graph database in the associated derivation graph. + DG::HyperEdge addHyperEdge(const DG::HyperEdge &e); + DG::HyperEdge addHyperEdge(const DG::HyperEdge &e, IsomorphismPolicy graphPolicy); // rst: .. function:: ExecuteResult execute(std::shared_ptr strategy) // rst: ExecuteResult execute(std::shared_ptr strategy, int verbosity) // rst: ExecuteResult execute(std::shared_ptr strategy, int verbosity, bool ignoreRuleLabelTypes) diff --git a/libs/libmod/src/mod/dg/DG.cpp b/libs/libmod/src/mod/dg/DG.cpp index d9b59d8..5c323d9 100644 --- a/libs/libmod/src/mod/dg/DG.cpp +++ b/libs/libmod/src/mod/dg/DG.cpp @@ -10,9 +10,9 @@ #include #include #include +#include +#include #include -#include -#include #include #include @@ -172,11 +172,11 @@ std::pair DG::print(const Printer &printer, const Prin << ") than this (id=" << getId() << ")" << std::endl; throw LogicError(err.str()); } - return lib::IO::DG::Write::summary(data.getData(), printer.getPrinter(), printer.getGraphPrinter().getOptions()); + return lib::DG::Write::summary(data.getData(), printer.getPrinter(), printer.getGraphPrinter().getOptions()); } std::string DG::printNonHyper() const { - return lib::IO::DG::Write::summaryNonHyper(getNonHyper()); + return lib::DG::Write::summaryNonHyper(getNonHyper()); } std::string DG::dump() const { @@ -186,11 +186,11 @@ std::string DG::dump() const { std::string DG::dump(const std::string &filename) const { if(!isLocked()) throw LogicError("Can not dump DG before it is locked."); if(filename.empty()) { - std::string name = lib::IO::getUniqueFilePrefix() + "DG.dg"; - lib::IO::writeJsonFile(name, lib::IO::DG::Write::dumpToJson(getNonHyper())); + std::string name = lib::IO::makeUniqueFilePrefix() + "DG.dg"; + lib::IO::writeJsonFile(name, lib::DG::Write::dumpToJson(getNonHyper())); return name; } else { - lib::IO::writeJsonFile(filename, lib::IO::DG::Write::dumpToJson(getNonHyper())); + lib::IO::writeJsonFile(filename, lib::DG::Write::dumpToJson(getNonHyper())); return filename; } } @@ -250,7 +250,7 @@ std::shared_ptr DG::load(const std::vector> &g std::ostringstream err; std::unique_ptr dgInternal( - lib::IO::DG::Read::dump(graphDatabase, ruleDatabase, file, graphPolicy, err, verbosity)); + lib::DG::Read::dump(graphDatabase, ruleDatabase, file, graphPolicy, err, verbosity)); if(!dgInternal) throw InputError("DG load error: " + err.str()); return wrapIt(new DG(std::move(dgInternal))); } diff --git a/libs/libmod/src/mod/dg/ForwardDecl.hpp b/libs/libmod/src/mod/dg/ForwardDecl.hpp index b55dc40..7e760a7 100644 --- a/libs/libmod/src/mod/dg/ForwardDecl.hpp +++ b/libs/libmod/src/mod/dg/ForwardDecl.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_DG_FORWARDDECL_H -#define MOD_DG_FORWARDDECL_H +#ifndef MOD_DG_FORWARDDECL_HPP +#define MOD_DG_FORWARDDECL_HPP namespace mod::dg { struct Builder; @@ -9,19 +9,17 @@ struct PrintData; struct Printer; struct Strategy; } // namespace mod::dg -namespace mod::lib { -namespace DG { +namespace mod::lib::DG { struct Hyper; struct NonHyper; struct NonHyperBuilder; -namespace Strategies { +} // namespace mod::lib::DG +namespace mod::lib::DG::Strategies { struct Strategy; -} // namespace Strategies -} // namespace DG -namespace IO::DG::Write { +} // namespace mod::lib::DG::Strategies +namespace mod::lib::DG::Write { struct Data; struct Printer; -} // namespace IO::DG::Write -} // namespace mod::lib +} // namespace mod::lib::DG::Write -#endif /* MOD_DG_FORWARDDECL_H */ \ No newline at end of file +#endif // MOD_DG_FORWARDDECL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/dg/GraphInterface.cpp b/libs/libmod/src/mod/dg/GraphInterface.cpp index 008c85f..112fe89 100644 --- a/libs/libmod/src/mod/dg/GraphInterface.cpp +++ b/libs/libmod/src/mod/dg/GraphInterface.cpp @@ -2,9 +2,9 @@ #include #include +#include #include #include -#include #include namespace mod::dg { @@ -13,7 +13,7 @@ namespace mod::dg { // Vertex //------------------------------------------------------------------------------ -MOD_GRAPHPIMPL_Define_Vertex_noGraph(DG, DG,g->getHyper().getGraph(), g, /* VertexPrint */) +MOD_GRAPHPIMPL_Define_Vertex_noGraph(DG, DG, g->getHyper().getGraph(), g, /* VertexPrint */) std::shared_ptr DG::Vertex::getDG() const { if(!g) throw LogicError("Can not get DG on a null vertex."); @@ -156,7 +156,7 @@ DG::HyperEdge::print(const graph::Printer &printer, const std::string &nomatchCo if(rules().size() == 0) throw LogicError("The edge has no rules."); const auto &dg = g->getHyper(); const auto v = dg.getInternalVertex(*this); - return lib::IO::Derivation::Write::summary(g->getNonHyper(), v, printer.getOptions(), nomatchColour, matchColour); + return lib::DG::Write::summaryDerivation(g->getNonHyper(), v, printer.getOptions(), nomatchColour, matchColour); } //------------------------------------------------------------------------------ diff --git a/libs/libmod/src/mod/dg/GraphInterface.hpp b/libs/libmod/src/mod/dg/GraphInterface.hpp index d220669..4a0b7a8 100644 --- a/libs/libmod/src/mod/dg/GraphInterface.hpp +++ b/libs/libmod/src/mod/dg/GraphInterface.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_DG_GRAPHINTERFACE_H -#define MOD_DG_GRAPHINTERFACE_H +#ifndef MOD_DG_GRAPHINTERFACE_HPP +#define MOD_DG_GRAPHINTERFACE_HPP // rst: This header contains the definitions for the hypergraph interface for :class:`dg::DG`. // rst: @@ -626,4 +626,4 @@ struct std::hash { } }; -#endif /* MOD_DG_GRAPHINTERFACE_H */ \ No newline at end of file +#endif // MOD_DG_GRAPHINTERFACE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/dg/Printer.cpp b/libs/libmod/src/mod/dg/Printer.cpp index 6915c7f..c2b6ca0 100644 --- a/libs/libmod/src/mod/dg/Printer.cpp +++ b/libs/libmod/src/mod/dg/Printer.cpp @@ -5,8 +5,8 @@ #include #include #include +#include #include -#include #include @@ -50,24 +50,24 @@ void reconnectCommon(std::shared_ptr dg, DG::HyperEdge e, unsigned int eDup, PrintData::PrintData(std::shared_ptr dg) : dg(dg), data(nullptr) { if(!dg->isLocked()) throw LogicError("Can not create print data. The DG is not locked yet."); - data.reset(new lib::IO::DG::Write::Data(dg->getHyper())); + data.reset(new lib::DG::Write::Data(dg->getHyper())); } -PrintData::PrintData(const PrintData &other) : dg(other.dg), data(new lib::IO::DG::Write::Data(*other.data)) {} +PrintData::PrintData(const PrintData &other) : dg(other.dg), data(new lib::DG::Write::Data(*other.data)) {} PrintData &PrintData::operator=(const PrintData &other) { dg = other.dg; - data = std::make_unique(*other.data); + data = std::make_unique(*other.data); return *this; } PrintData::~PrintData() = default; -lib::IO::DG::Write::Data &PrintData::getData() { +lib::DG::Write::Data &PrintData::getData() { return *data; } -lib::IO::DG::Write::Data &PrintData::getData() const { +const lib::DG::Write::Data &PrintData::getData() const { return *data; } @@ -112,14 +112,14 @@ void PrintData::reconnectTarget(DG::HyperEdge e, int eDup, DG::Vertex v, int vDu //------------------------------------------------------------------------------ Printer::Printer() : graphPrinter(std::make_unique()), - printer(std::make_unique()) { + printer(std::make_unique()) { graphPrinter->enableAll(); graphPrinter->setWithIndex(false); } -Printer::~Printer() {} +Printer::~Printer() = default; -lib::IO::DG::Write::Printer &Printer::getPrinter() const { +lib::DG::Write::Printer &Printer::getPrinter() const { return *printer; } @@ -293,15 +293,26 @@ void Printer::popEdgeColour() { } void Printer::setRotationOverwrite(std::function)> f) { - if(!f) throw LogicError("Can not push empty callback."); + if(!f) throw LogicError("Can not set empty callback."); printer->setRotationOverwrite(f); } void Printer::setMirrorOverwrite(std::function)> f) { - if(!f) throw LogicError("Can not push empty callback."); + if(!f) throw LogicError("Can not set empty callback."); printer->setMirrorOverwrite(f); } +void Printer::setImageOverwrite(std::function(DG::Vertex v, int dupNum)> f) { + if(!f) { + printer->setImageOverwrite(nullptr); + } else { + printer->setImageOverwrite([f](lib::DG::HyperVertex v, int dupNum, const lib::DG::Hyper &dg) { + assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Vertex); + return f(dg.getInterfaceVertex(v), dupNum); + }); + } +} + void Printer::setGraphvizPrefix(const std::string &prefix) { printer->setGraphvizPrefix(prefix); } diff --git a/libs/libmod/src/mod/dg/Printer.hpp b/libs/libmod/src/mod/dg/Printer.hpp index 09cc0e3..dcf8375 100644 --- a/libs/libmod/src/mod/dg/Printer.hpp +++ b/libs/libmod/src/mod/dg/Printer.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_DG_PRINTER_H -#define MOD_DG_PRINTER_H +#ifndef MOD_DG_PRINTER_HPP +#define MOD_DG_PRINTER_HPP #include #include @@ -37,8 +37,8 @@ struct MOD_DECL PrintData { PrintData(const PrintData &other); PrintData &operator=(const PrintData &other); ~PrintData(); - lib::IO::DG::Write::Data &getData(); - lib::IO::DG::Write::Data &getData() const; + lib::DG::Write::Data &getData(); + const lib::DG::Write::Data &getData() const; // rst: .. function:: std::shared_ptr getDG() const // rst: // rst: :returns: the derivation graph the object holds data for. @@ -86,7 +86,7 @@ struct MOD_DECL PrintData { void reconnectTarget(DG::HyperEdge e, int eDup, DG::Vertex v, int vDupTar); private: std::shared_ptr dg; - std::unique_ptr data; + std::unique_ptr data; }; // rst-class-end: @@ -101,7 +101,7 @@ struct MOD_DECL Printer { Printer(const Printer &) = delete; Printer &operator=(const Printer &) = delete; ~Printer(); - lib::IO::DG::Write::Printer &getPrinter() const; + lib::DG::Write::Printer &getPrinter() const; // rst: .. function:: graph::Printer &getGraphPrinter() // rst: const graph::Printer &getGraphPrinter() const // rst: @@ -118,7 +118,7 @@ struct MOD_DECL Printer { // rst: .. function:: void setWithGraphImages(bool value) // rst: bool getWithGraphImages() const // rst: - // rst: Control whether or not each vertex is printed with a image of its graph in it. + // rst: Control whether or not each vertex is printed with an image of its graph in it. void setWithGraphImages(bool value); bool getWithGraphImages() const; // rst: .. function:: void setLabelsAsLatexMath(bool value) @@ -265,6 +265,27 @@ struct MOD_DECL Printer { // rst: // rst: :throws: :class:`LogicError` if `!f`. void setMirrorOverwrite(std::function)> f); +public: + // rst: .. function:: void setImageOverwrite(std::function(DG::Vertex v, int dupNum)> f) + // rst: + // rst: Overwrite the image generation for graphs depicted in the vertices of the printed derivation graph. + // rst: For each duplicate of each vertex to be depicted, the given callback is called. + // rst: It must then return two strings: + // rst: + // rst: 1. Either + // rst: + // rst: - an empty string if the standard depiction should be used, in which case the second string is ignored, or + // rst: - the filename of the PDF that should be included in the final compiled figure. + // rst: + // rst: 2. Either + // rst: + // rst: - an empty string if no additional post-processing command is needed, or + // rst: - a string of Bash code which will be inserted in the post-processing instructions. + // rst: For example, one can write out source code in the callback, and then return a command that compiles + // rst: that code into the PDF needed by inclusion. + // rst: + // rst: The image overwrite can be removed by calling with `nullptr`. + void setImageOverwrite(std::function(DG::Vertex v, int dupNum)> f); public: // rst: .. function:: void setGraphvizPrefix(const std::string &prefix) // rst: const std::string &getGraphvizPrefix() const @@ -282,10 +303,10 @@ struct MOD_DECL Printer { const std::string &getTikzpictureOption() const; private: std::unique_ptr graphPrinter; - std::unique_ptr printer; + std::unique_ptr printer; }; // rst-class-end: } // namespace mod::dg -#endif /* MOD_DG_PRINTER_H */ \ No newline at end of file +#endif // MOD_DG_PRINTER_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/graph/Graph.cpp b/libs/libmod/src/mod/graph/Graph.cpp index 1661a47..833b053 100644 --- a/libs/libmod/src/mod/graph/Graph.cpp +++ b/libs/libmod/src/mod/graph/Graph.cpp @@ -1,22 +1,23 @@ #include "Graph.hpp" #include +#include +#include #include #include #include #include -#include +#include +#include +#include #include #include #include #include -#include -#include #include #include -#include #include namespace mod::graph { @@ -79,25 +80,25 @@ std::pair Graph::print() const { } std::pair Graph::print(const Printer &first, const Printer &second) const { - return lib::IO::Graph::Write::summary(*g, first.getOptions(), second.getOptions()); + return lib::Graph::Write::summary(*g, first.getOptions(), second.getOptions()); } void Graph::printTermState() const { - lib::IO::Graph::Write::termState(*g); + lib::Graph::Write::termState(*g); } std::string Graph::getGMLString(bool withCoords) const { if(withCoords && !g->getDepictionData().getHasCoordinates()) throw LogicError("Coordinates are not available for this graph (" + getName() + ")."); std::stringstream ss; - lib::IO::Graph::Write::gml(*g, withCoords, ss); + lib::Graph::Write::gml(*g, withCoords, ss); return ss.str(); } std::string Graph::printGML(bool withCoords) const { if(withCoords && !g->getDepictionData().getHasCoordinates()) throw LogicError("Coordinates are not available for this graph (" + getName() + ")."); - return lib::IO::Graph::Write::gml(*g, withCoords); + return lib::Graph::Write::gml(*g, withCoords); } const std::string &Graph::getName() const { @@ -171,17 +172,29 @@ void checkTermParsing(const lib::Graph::Single &g, LabelSettings ls) { } // namespace std::size_t -Graph::isomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const { +Graph::isomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const { + if(!codomain) throw LogicError("codomain is null."); checkTermParsing(*g, labelSettings); - checkTermParsing(*host->g, labelSettings); - return lib::Graph::Single::isomorphism(*g, *host->g, maxNumMatches, labelSettings); + checkTermParsing(*codomain->g, labelSettings); + return lib::Graph::Single::isomorphism(*g, *codomain->g, maxNumMatches, labelSettings); } std::size_t -Graph::monomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const { +Graph::monomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const { + if(!codomain) throw LogicError("codomain is null."); checkTermParsing(*g, labelSettings); - checkTermParsing(*host->g, labelSettings); - return lib::Graph::Single::monomorphism(*g, *host->g, maxNumMatches, labelSettings); + checkTermParsing(*codomain->g, labelSettings); + return lib::Graph::Single::monomorphism(*g, *codomain->g, maxNumMatches, labelSettings); +} + +void Graph::enumerateMonomorphisms(std::shared_ptr codomain, + std::shared_ptr)>> callback, + LabelSettings labelSettings) const { + if(!codomain) throw LogicError("codomain is null."); + if(!callback) throw LogicError("callback is null."); + checkTermParsing(*g, labelSettings); + checkTermParsing(*codomain->g, labelSettings); + return lib::Graph::Single::enumerateMonomorphisms(*g, *codomain->g, toStdFunction(callback), labelSettings); } std::shared_ptr Graph::makePermutation() const { @@ -239,7 +252,7 @@ std::vector> Graph::getLoadingWarnings() const { namespace { std::shared_ptr -makeGraphFromData(lib::IO::Graph::Read::Data data, std::vector> warnings) { +makeGraphFromData(lib::Graph::Read::Data data, std::vector> warnings) { auto gInternal = std::make_unique( std::move(data.g), std::move(data.pString), std::move(data.pStereo)); std::shared_ptr g = Graph::create(std::move(gInternal), @@ -249,87 +262,118 @@ makeGraphFromData(lib::IO::Graph::Read::Data data, std::vector -handleLoadedGraph(lib::IO::Result> dataRes, lib::IO::Warnings warnings, - const std::string &source) { - std::cout << warnings << std::flush; - if(!dataRes) throw InputError("Error in graph loading from " + source + ".\n" + dataRes.extractError()); - auto data = std::move(*dataRes); +handleLoadedGraph(std::vector data, lib::IO::Warnings warnings, + const std::string &type, const std::string &source) { if(data.size() != 1) - throw InputError("Error in graph loading from " + source - + ".\nThe graph is not connected (" + std::to_string(data.size()) + " components)."); + throw InputError("Error in loading " + type + " from " + source + + ".\nA/the graph is not connected (" + std::to_string(data.size()) + " components)."); return makeGraphFromData(std::move(data.front()), warnings.extractWarnings()); } std::vector> -handleLoadedGraphs(lib::IO::Result> dataRes, lib::IO::Warnings warnings, - const std::string &source) { - std::cout << warnings << std::flush; - if(!dataRes) throw InputError("Error in graph loading from " + source + ".\n" + dataRes.extractError()); - auto data = std::move(*dataRes); +handleLoadedGraphs(std::vector data, lib::IO::Warnings warnings, + const std::string &type, const std::string &source) { // the warnings are copied into each graph const auto warningList = warnings.extractWarnings(); std::vector> res; res.reserve(data.size()); - for(auto &d : data) + for(auto &d: data) res.push_back(makeGraphFromData(std::move(d), warningList)); return res; } -} // namespace +std::vector> +handleLoadedGraphVector(std::vector> data, lib::IO::Warnings warnings, + const std::string &type, const std::string &source) { + std::vector> res; + res.reserve(data.size()); + for(auto &ds: data) + res.push_back(handleLoadedGraph(std::move(ds), lib::IO::Warnings(warnings), type, source)); + return res; +} -std::shared_ptr Graph::fromGMLString(const std::string &data) { - lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::gml(warnings, data); - return handleLoadedGraph(std::move(res), std::move(warnings), "inline GML string"); +std::vector>> +handleLoadedGraphsVector(std::vector> data, lib::IO::Warnings warnings, + const std::string &type, const std::string &source) { + std::vector>> res; + res.reserve(data.size()); + for(auto &ds: data) + res.push_back(handleLoadedGraphs(std::move(ds), lib::IO::Warnings(warnings), type, source)); + return res; } -std::shared_ptr Graph::fromGMLFile(const std::string &file) { +boost::iostreams::mapped_file_source openFile(const std::string &file, const std::string &type) { boost::iostreams::mapped_file_source ifs; try { ifs.open(file); } catch(const BOOST_IOSTREAMS_FAILURE &e) { - throw InputError("Could not open graph GML file '" + file + "':\n" + e.what()); + throw InputError("Could not open " + type + " '" + file + "':\n" + e.what()); } - if(!ifs) throw InputError("Could not open graph GML file '" + file + "'.\n"); + if(!ifs) throw InputError("Could not open " + type + " '" + file + "'.\n"); + return ifs; +} + + +template +auto load(const std::string &data, const std::string &type, FParse parse, FProcess process, ParseArgs &&... parseArgs) { lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::gml(warnings, {ifs.begin(), ifs.size()}); - return handleLoadedGraph(std::move(res), std::move(warnings), "file, '" + file + "'"); + auto parsedData = [&]() { + if constexpr(IsFile) { + auto ifs = openFile(data, type + " file"); + return parse(warnings, {ifs.begin(), ifs.size()}, std::forward(parseArgs)...); + } else { + return parse(warnings, data, std::forward(parseArgs)...); + } + }(); + std::cout << warnings << std::flush; + std::string source = IsFile ? type + " file '" + data + "'" : "inline " + type + " string"; + if(!parsedData) throw InputError("Error in loading from " + source + ".\n" + parsedData.extractError()); + return process(std::move(*parsedData), std::move(warnings), type, source); +} + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::shared_ptr Graph::fromGMLString(const std::string &data) { + return load(data, "GML", &lib::Graph::Read::gml, &handleLoadedGraph); +} + +std::shared_ptr Graph::fromGMLFile(const std::string &file) { + return load(file, "GML", &lib::Graph::Read::gml, &handleLoadedGraph); } std::vector> Graph::fromGMLStringMulti(const std::string &data) { - lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::gml(warnings, data); - return handleLoadedGraphs(std::move(res), std::move(warnings), "inline GML string"); + return load(data, "GML", &lib::Graph::Read::gml, &handleLoadedGraphs); } std::vector> Graph::fromGMLFileMulti(const std::string &file) { - boost::iostreams::mapped_file_source ifs; - try { - ifs.open(file); - } catch(const BOOST_IOSTREAMS_FAILURE &e) { - throw InputError("Could not open graph GML file '" + file + "':\n" + e.what()); - } - if(!ifs) throw InputError("Could not open graph GML file '" + file + "'.\n"); - lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::gml(warnings, {ifs.begin(), ifs.size()}); - return handleLoadedGraphs(std::move(res), std::move(warnings), "file, '" + file + "'"); + return load(file, "GML", &lib::Graph::Read::gml, &handleLoadedGraphs); } -std::shared_ptr Graph::fromDFS(const std::string &graphDFS) { - auto data = lib::IO::Graph::Read::dfs(graphDFS); - if(!data) throw InputError("Error in graph loading from graphDFS, '" + graphDFS + "'.\n" + data.extractError()); - return makeGraphFromData(std::move(*data), {}); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::shared_ptr Graph::fromDFS(const std::string &data) { + return load(data, "GraphDFS", &lib::Graph::Read::dfs, &handleLoadedGraph); +} + +std::vector> Graph::fromDFSMulti(const std::string &data) { + return load(data, "GraphDFS", &lib::Graph::Read::dfs, &handleLoadedGraphs); } + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + std::shared_ptr Graph::fromSMILES(const std::string &smiles) { return Graph::fromSMILES(smiles, false, SmilesClassPolicy::NoneOnDuplicate); } std::shared_ptr Graph::fromSMILES(const std::string &smiles, const bool allowAbstract, SmilesClassPolicy classPolicy) { - lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::smiles(warnings, smiles, allowAbstract, classPolicy); - return handleLoadedGraph(std::move(res), std::move(warnings), "smiles string, '" + smiles + "'"); + return load(smiles, "SMILES", &lib::Graph::Read::smiles, &handleLoadedGraph, allowAbstract, classPolicy); } std::vector> Graph::fromSMILESMulti(const std::string &smiles) { @@ -338,11 +382,54 @@ std::vector> Graph::fromSMILESMulti(const std::string &sm std::vector> Graph::fromSMILESMulti(const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy) { - lib::IO::Warnings warnings; - auto res = lib::IO::Graph::Read::smiles(warnings, smiles, allowAbstract, classPolicy); - return handleLoadedGraphs(std::move(res), std::move(warnings), "smiles string, '" + smiles + "'"); + return load(smiles, "SMILES", &lib::Graph::Read::smiles, &handleLoadedGraphs, allowAbstract, classPolicy); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::shared_ptr Graph::fromMOLString(const std::string &data, const MDLOptions &options) { + return load(data, "MOL", &lib::Graph::Read::MDLMOL, &handleLoadedGraph, options); +} + +std::shared_ptr Graph::fromMOLFile(const std::string &file, const MDLOptions &options) { + return load(file, "MOL", &lib::Graph::Read::MDLMOL, &handleLoadedGraph, options); } +std::vector> Graph::fromMOLStringMulti(const std::string &data, const MDLOptions &options) { + return load(data, "MOL", &lib::Graph::Read::MDLMOL, &handleLoadedGraphs, options); +} + +std::vector> Graph::fromMOLFileMulti(const std::string &file, const MDLOptions &options) { + return load(file, "MOL", &lib::Graph::Read::MDLMOL, &handleLoadedGraphs, options); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +std::vector> Graph::fromSDString(const std::string &data, const MDLOptions &options) { + + return load(data, "SD", &lib::Graph::Read::MDLSD, &handleLoadedGraphVector, options); +} + +std::vector> Graph::fromSDFile(const std::string &file, const MDLOptions &options) { + return load(file, "SD", &lib::Graph::Read::MDLSD, &handleLoadedGraphVector, options); +} + + +std::vector>> +Graph::fromSDStringMulti(const std::string &data, const MDLOptions &options) { + return load(data, "SD", &lib::Graph::Read::MDLSD, &handleLoadedGraphsVector, options); +} + +std::vector>> +Graph::fromSDFileMulti(const std::string &file, const MDLOptions &options) { + return load(file, "SD", &lib::Graph::Read::MDLSD, &handleLoadedGraphsVector, options); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + std::shared_ptr Graph::create(std::unique_ptr g) { if(!g) MOD_ABORT; std::shared_ptr wrapped = std::shared_ptr(new Graph(std::move(g))); diff --git a/libs/libmod/src/mod/graph/Graph.hpp b/libs/libmod/src/mod/graph/Graph.hpp index 554d264..27b69fb 100644 --- a/libs/libmod/src/mod/graph/Graph.hpp +++ b/libs/libmod/src/mod/graph/Graph.hpp @@ -12,8 +12,13 @@ #include namespace mod { + template struct Function; + +template +struct VertexMap; + } // namespace mod namespace mod::graph { @@ -21,8 +26,7 @@ namespace mod::graph { // rst: // rst: This class models an undirected graph with labels on vertices and edges, // rst: without loops and without parallel edges. -// rst: Certain labels are regarded as models of chemical atoms and bonds. -// rst: See :ref:`mol-enc` for more information on this. +// rst: See :ref:`graph-model` for more details. // rst: See also :ref:`cpp-graph/GraphInterface` for the documentation for the // rst: graph interface for this class. // rst: @@ -124,16 +128,16 @@ struct MOD_DECL Graph { const std::string &getSmilesWithIds() const; // rst: .. function:: const std::string &getGraphDFS() const // rst: - // rst: :returns: a :ref:`GraphDFS ` string of the graph. + // rst: :returns: a :ref:`GraphDFS ` string of the graph. const std::string &getGraphDFS() const; // rst: .. function:: const std::string &getGraphDFSWithIds() const // rst: - // rst: :returns: a :ref:`GraphDFS ` string of the graph, where each vertices have an explicit id, + // rst: :returns: a :ref:`GraphDFS ` string of the graph, where each vertices have an explicit id, // rst: corresponding to its internal vertex id. const std::string &getGraphDFSWithIds() const; // rst: .. function:: const std::string &getLinearEncoding() const // rst: - // rst: :returns: the :ref:`SMILES ` string if the graph is a molecule, otherwise the :ref:`GraphDFS ` string. + // rst: :returns: the :ref:`SMILES ` string if the graph is a molecule, otherwise the :ref:`GraphDFS ` string. const std::string &getLinearEncoding() const; // rst: .. function:: bool getIsMolecule() const // rst: @@ -166,14 +170,32 @@ struct MOD_DECL Graph { // rst: // rst: :returns: the number of edges in the graph with the given label. unsigned int eLabelCount(const std::string &label) const; - // rst: .. function:: std::size_t isomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const - // rst: - // rst: :returns: the number of isomorphisms found from this graph to `host`, but at most `maxNumMatches`. - std::size_t isomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const; - // rst: .. function:: std::size_t monomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const - // rst: - // rst: :returns: the number of monomorphisms from this graph to `host`, though at most `maxNumMatches`. - std::size_t monomorphism(std::shared_ptr host, std::size_t maxNumMatches, LabelSettings labelSettings) const; +public: // Morphisms + // rst: .. function:: std::size_t isomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const + // rst: + // rst: :returns: the number of isomorphisms found from this graph to `codomain`, but at most `maxNumMatches`. + // rst: :throws LogicError: if `codomain` is null. + std::size_t isomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const; + // rst: .. function:: std::size_t monomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const + // rst: + // rst: :returns: the number of monomorphisms from this graph to `codomain`, though at most `maxNumMatches`. + // rst: :throws LogicError: if `codomain` is null. + std::size_t monomorphism(std::shared_ptr codomain, std::size_t maxNumMatches, LabelSettings labelSettings) const; + // rst: .. function:: void enumerateMonomorphisms(std::shared_ptr codomain, \ + // rst: std::shared_ptr)>> callback, \ + // rst: LabelSettings labelSettings) const + // rst: + // rst: Perform substructure search of this graph into the given codomain graph. + // rst: Whenever a match is found, the corresponding monomorphism is copied into a vertex map + // rst: and the given callback is invoked with it. + // rst: The return value from the callback determines whether to continue the search or not. + // rst: + // rst: :throws LogicError: if `codomain` is null. + // rst: :throws LogicError: if `callback` is null. + void enumerateMonomorphisms(std::shared_ptr codomain, + std::shared_ptr)>> callback, + LabelSettings labelSettings) const; +public: // rst: .. function:: std::shared_ptr makePermutation() const // rst: // rst: :returns: a graph isomorphic to this, but with the vertex indices randomly permuted. @@ -255,13 +277,23 @@ struct MOD_DECL Graph { // rst: :throws: :class:`InputError` on bad input. static std::vector> fromGMLStringMulti(const std::string &data); static std::vector> fromGMLFileMulti(const std::string &file); + // =========================================================================== // rst: .. function:: static std::shared_ptr fromDFS(const std::string &graphDFS) // rst: - // rst: :returns: a graph loaded from the given :ref:`GraphDFS ` string. + // rst: :returns: a graph loaded from the given :ref:`GraphDFS ` string. + // rst: The graph must be connected. Use :func:`fromDFSMulti` if it is not. // rst: :throws: :class:`InputError` on bad input. static std::shared_ptr fromDFS(const std::string &graphDFS); + // rst: .. function:: static std::vector> fromDFSMulti(const std::string &graphDFS) + // rst: + // rst: :returns: a list of graphs loaded from the given :ref:`GraphDFS ` string. + // rst: The graphs are the connected components of the graph specified in the data. + // rst: :throws: :class:`InputError` on bad input. + static std::vector> fromDFSMulti(const std::string &graphDFS); +// =========================================================================== // rst: .. function:: static std::shared_ptr fromSMILES(const std::string &smiles) - // rst: static std::shared_ptr fromSMILES(const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy) + // rst: static std::shared_ptr fromSMILES(const std::string &smiles, bool allowAbstract, \ + // rst: SmilesClassPolicy classPolicy) // rst: // rst: :param allowAbstract: whether abstract atoms, e.g., ``*``, are allowed. Defaults to `false`. // rst: :param classPolicy: which policy to use for class labels. Defaults to `SmilesClassPolicy::NoneOnDuplicate`. @@ -270,18 +302,54 @@ struct MOD_DECL Graph { // rst: :throws: :class:`InputError` on bad input. // rst: :throws: :class:`InputError` if `classPolicy == SmilesClassPolicy::NoneOnDuplicate` and a class label is duplicated. static std::shared_ptr fromSMILES(const std::string &smiles); - static std::shared_ptr - fromSMILES(const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy); + static std::shared_ptr fromSMILES(const std::string &smiles, bool allowAbstract, + SmilesClassPolicy classPolicy); // rst: .. function:: static std::vector> fromSMILESMulti(const std::string &smiles) - // rst: static std::vector> fromSMILESMulti(const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy) + // rst: static std::vector> fromSMILESMulti(const std::string &smiles, bool allowAbstract, \ + // rst: SmilesClassPolicy classPolicy) // rst: // rst: See :func:`fromSMILES` for parameter and exception descriptions. // rst: // rst: :returns: a list of graphs representing molecules, loaded from the given :ref:`SMILES ` string. // rst: The graphs are the connected components of the graph specified in the SMILES string. static std::vector> fromSMILESMulti(const std::string &smiles); - static std::vector> - fromSMILESMulti(const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy); + static std::vector> fromSMILESMulti(const std::string &smiles, bool allowAbstract, + SmilesClassPolicy classPolicy); + // =========================================================================== + // rst: .. function:: static std::shared_ptr fromMOLString(const std::string &data, const MDLOptions &options) + // rst: static std::shared_ptr fromMOLFile(const std::string &file, const MDLOptions &options) + // rst: + // rst: :returns: a graph created from the given :ref:`MOL ` data. + // rst: :throws: :class:`InputError` on bad input. + static std::shared_ptr fromMOLString(const std::string &data, const MDLOptions &options); + static std::shared_ptr fromMOLFile(const std::string &file, const MDLOptions &options); + // rst: .. function:: static std::vector> fromMOLStringMulti(const std::string &data, const MDLOptions &options) + // rst: static std::vector> fromMOLFileMulti(const std::string &file, const MDLOptions &options) + // rst: + // rst: See :func:`fromMOLString` and :func:`fromMOLFile` for parameter and exception descriptions. + // rst: + // rst: :returns: a list of graphs representing molecules, loaded from the given string or file with :ref:`MOL ` data. + // rst: The graphs are the connected components of the graph specified in the data. + static std::vector> fromMOLStringMulti(const std::string &data, const MDLOptions &options); + static std::vector> fromMOLFileMulti(const std::string &file, const MDLOptions &options); + // =========================================================================== + // rst: .. function:: static std::vector> fromSDString(const std::string &data, const MDLOptions &options) + // rst: static std::vector> fromSDFile(const std::string &file, const MDLOptions &options) + // rst: + // rst: :returns: a list of graphs graph created from the given :ref:`SD ` data. + // rst: :throws: :class:`InputError` on bad input. + static std::vector> fromSDString(const std::string &data, const MDLOptions &options); + static std::vector> fromSDFile(const std::string &file, const MDLOptions &options); + // rst: .. function:: static std::vector>> fromSDStringMulti(const std::string &data, const MDLOptions &options) + // rst: static std::vector>> fromSDFileMulti(const std::string &file, const MDLOptions &options) + // rst: + // rst: :returns: a list of lists of graphs graph created from the given :ref:`SD ` data. + // rst: :throws: :class:`InputError` on bad input. + static std::vector>> + fromSDStringMulti(const std::string &data, const MDLOptions &options); + static std::vector>> + fromSDFileMulti(const std::string &file, const MDLOptions &options); + // =========================================================================== // rst: .. function:: static std::shared_ptr create(std::unique_ptr g) // rst: static std::shared_ptr create(std::unique_ptr g, \ // rst: std::map externalToInternalIds, \ diff --git a/libs/libmod/src/mod/graph/GraphInterface.cpp b/libs/libmod/src/mod/graph/GraphInterface.cpp index b8478e4..13d3952 100644 --- a/libs/libmod/src/mod/graph/GraphInterface.cpp +++ b/libs/libmod/src/mod/graph/GraphInterface.cpp @@ -3,11 +3,11 @@ #include #include #include +#include #include #include #include #include -#include namespace mod::graph { @@ -76,7 +76,7 @@ std::string Graph::Vertex::printStereo(const Printer &p) const { using boost::vertices; auto v = *(vertices(graph).first + vId); const auto &conf = get_stereo(gLabelled)[v]; - return lib::IO::Stereo::Write::summary(g->getGraph(), v, *conf, p.getOptions(), 0, ""); + return lib::Graph::Write::stereoSummary(g->getGraph(), v, *conf, p.getOptions(), 0, ""); } //------------------------------------------------------------------------------ diff --git a/libs/libmod/src/mod/graph/Printer.cpp b/libs/libmod/src/mod/graph/Printer.cpp index 2583124..f9abf3d 100644 --- a/libs/libmod/src/mod/graph/Printer.cpp +++ b/libs/libmod/src/mod/graph/Printer.cpp @@ -1,6 +1,6 @@ #include "Printer.hpp" -#include +#include namespace mod::graph { @@ -39,7 +39,8 @@ void Printer::setMolDefault() { } void Printer::setReactionDefault() { - options->All().Thick(false).WithIndex(false).SimpleCarbons(false); + setMolDefault(); + options->SimpleCarbons(false); } void Printer::disableAll() { @@ -66,6 +67,14 @@ bool Printer::getCollapseHydrogens() const { return options->collapseHydrogens; } +void Printer::setRaiseIsotopes(bool value) { + options->RaiseIsotopes(value); +} + +bool Printer::getRaiseIsotopes() const { + return options->raiseIsotopes; +} + void Printer::setRaiseCharges(bool value) { options->RaiseCharges(value); } @@ -146,4 +155,22 @@ bool Printer::getMirror() const { return options->mirror; } +void Printer::setWithGraphvizCoords(bool value) { + options->WithGraphvizCoords(value); +} + +bool Printer::getWithGraphvizCoords() const { + return options->withGraphvizCoords; +} + +// =================================================================== + +void Printer::setGraphvizPrefix(const std::string &prefix) { + options->graphvizPrefix = prefix; +} + +const std::string &Printer::getGraphvizPrefix() const { + return options->graphvizPrefix; +} + } // namespace mod::graph \ No newline at end of file diff --git a/libs/libmod/src/mod/graph/Printer.hpp b/libs/libmod/src/mod/graph/Printer.hpp index faf305f..eb4abd4 100644 --- a/libs/libmod/src/mod/graph/Printer.hpp +++ b/libs/libmod/src/mod/graph/Printer.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_GRAPH_PRINTER_H -#define MOD_GRAPH_PRINTER_H +#ifndef MOD_GRAPH_PRINTER_HPP +#define MOD_GRAPH_PRINTER_HPP #include #include @@ -40,11 +40,11 @@ struct MOD_DECL Printer { void setReactionDefault(); // rst: .. function:: void disableAll() // rst: - // rst: Disable all special printing features. + // rst: Disable all special printing features. void disableAll(); // rst: .. function:: void enableAll() // rst: - // rst: Enable all special printing features, except typewriter font. + // rst: Enable all special printing features, except typewriter font. void enableAll(); // rst: .. function:: void setEdgesAsBonds(bool value) // rst: bool getEdgesAsBonds() const @@ -58,6 +58,12 @@ struct MOD_DECL Printer { // rst: Control whether vertices representing hydrogen atoms are collapsed into their neighbours labels. void setCollapseHydrogens(bool value); bool getCollapseHydrogens() const; + // rst: .. function:: void setRaiseIsotopes(bool value) + // rst: bool getRaiseIsotopes() const + // rst: + // rst: Control whether a vertex label prefix encoding an isotope is written as a superscript to the rest of the label. + void setRaiseIsotopes(bool value); + bool getRaiseIsotopes() const; // rst: .. function:: void setRaiseCharges(bool value) // rst: bool getRaiseCharges() const // rst: @@ -118,6 +124,24 @@ struct MOD_DECL Printer { // rst: Mirror internally computed coordinates in the y-axis. void setMirror(bool value); bool getMirror() const; + // rst: .. function:: void setWithGraphvizCoords(bool value) + // rst: bool getWithGraphvizCoords() const + // rst: + // rst: Do not use Open Babel for coordinate generation, but only the Graphviz fallback + // rst: during post-processing. + // rst: When setting this to `true` consider setting `setSimpleCarbons(false)` to avoid + // rst: misleading depictions due to colinear carbon chains. + void setWithGraphvizCoords(bool value); + bool getWithGraphvizCoords() const; +public: + // rst: .. function:: void setGraphvizPrefix(const std::string &prefix) + // rst: const std::string &getGraphvizPrefix() const + // rst: + // rst: Access the string that will be inserted into generated DOT files, + // rst: just after the graph declaration. + // rst: DOT files are only generated when `getWithGraphvizCoords()`. + void setGraphvizPrefix(const std::string &prefix); + const std::string &getGraphvizPrefix() const; private: std::unique_ptr options; }; @@ -125,4 +149,4 @@ struct MOD_DECL Printer { } // namespace mod::graph -#endif /* MOD_GRAPH_PRINTER_H */ \ No newline at end of file +#endif // MOD_GRAPH_PRINTER_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/graph/Union.cpp b/libs/libmod/src/mod/graph/Union.cpp index 94e5a98..1fa5490 100644 --- a/libs/libmod/src/mod/graph/Union.cpp +++ b/libs/libmod/src/mod/graph/Union.cpp @@ -9,10 +9,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -23,13 +23,14 @@ struct Union::Pimpl { friend std::ostream &operator<<(std::ostream &s, const Pimpl &p) { s << "UnionGraph{"; bool first = true; - for(const auto &g : p.graphs) { + for(const auto &g: p.graphs) { if(first) first = false; else s << ", "; s << g->getName(); } return s << "}"; } + public: std::vector> graphs; lib::LabelledUnionGraph g; @@ -38,8 +39,11 @@ struct Union::Pimpl { Union::Union() : p(std::make_unique()) {} Union::Union(std::vector> graphs) { + for(const auto &g: graphs) + if(!g) throw LogicError("A graph is null."); + auto p = std::unique_ptr(new Pimpl{std::move(graphs), {}}); - for(const auto &gOther : p->graphs) + for(const auto &gOther: p->graphs) p->g.push_back(&gOther->getGraph().getLabelledGraph()); this->p = std::move(p); } @@ -109,6 +113,7 @@ Union::EdgeRange Union::edges() const { //------------------------------------------------------------------------------ MOD_GRAPHPIMPL_Define_Vertex(Union, UnionGraph, get_graph(this->g->p->g), g, /* VertexPrint */) + MOD_GRAPHPIMPL_Define_Vertex_Undirected(Union, get_graph(this->g->p->g), g) const std::string &Union::Vertex::getStringLabel() const { @@ -173,7 +178,7 @@ std::string Union::Vertex::printStereo(const Printer &p) const { const auto vInner = v.v; const auto gIdx = v.gIdx; const auto &gInner = g->p->graphs[gIdx]->getGraph(); - return lib::IO::Stereo::Write::summary( + return lib::Graph::Write::stereoSummary( gInner, vInner, *conf, p.getOptions(), graph.get_vertex_idx_offset(gIdx), " (" + boost::lexical_cast(*this)) + ")"; } diff --git a/libs/libmod/src/mod/graph/Union.hpp b/libs/libmod/src/mod/graph/Union.hpp index 0637503..8fe088f 100644 --- a/libs/libmod/src/mod/graph/Union.hpp +++ b/libs/libmod/src/mod/graph/Union.hpp @@ -51,6 +51,8 @@ struct MOD_DECL Union { // rst: .. function:: explicit Union(std::vector> graphs) // rst: // rst: Construct a graph representing the disjoint union of `graphs`. + // rst: + // rst: :throws LogicError: if a given graph is null. explicit Union(std::vector> graphs); Union(const Union &other); Union &operator=(const Union &other); diff --git a/libs/libmod/src/mod/graph/internal/Graph.cpp b/libs/libmod/src/mod/graph/internal/Graph.cpp index 3876177..c0006d6 100644 --- a/libs/libmod/src/mod/graph/internal/Graph.cpp +++ b/libs/libmod/src/mod/graph/internal/Graph.cpp @@ -2,10 +2,10 @@ #include #include +#include #include #include #include -#include namespace mod::graph::internal { @@ -43,7 +43,7 @@ std::shared_ptr makeGraph( } std::string writePDF(const lib::Graph::Single &g, const mod::lib::IO::Graph::Write::Options &options) { - return lib::IO::Graph::Write::pdf(g, options); + return lib::Graph::Write::pdf(g, options); } // LabelledGraph diff --git a/libs/libmod/src/mod/lib/Algorithm/ConnectedComponents.hpp b/libs/libmod/src/mod/lib/Algorithm/ConnectedComponents.hpp new file mode 100644 index 0000000..9d08c72 --- /dev/null +++ b/libs/libmod/src/mod/lib/Algorithm/ConnectedComponents.hpp @@ -0,0 +1,55 @@ +#ifndef MOD_LIB_ALGORITHM_CONNECTEDCOMPONENTS_HPP +#define MOD_LIB_ALGORITHM_CONNECTEDCOMPONENTS_HPP + +#include +#include + +namespace mod::lib { + +struct ConnectedComponents { + ConnectedComponents(int n) : components(n) { + for(int i = 0; i != n; ++i) + components[i] = i; + } + + int findRoot(int i) { + while(components[i] != i) + i = components[i]; + return i; + } + + void join(int a, int b) { + a = findRoot(a); + b = findRoot(b); + // always make the smallest the root + if(a < b) components[b] = a; + else if(b < a) components[a] = b; + } +public: + int finalize() { + int next = 0; + for(int i = 0; i != components.size(); ++i) { + // everything below i has been changed to component numbers + // and joining always goes downward, so just do a single root-finding step + const int root = components[i]; + if(root == i) { + components[i] = next; + ++next; + } else { + assert(root < i); + components[i] = components[root]; + } + } + return next; + } +public: + int operator[](int i) const { + return components[i]; + } +private: + std::vector components; +}; + +} // namespace mod::lib + +#endif // MOD_LIB_ALGORITHM_CONNECTEDCOMPONENTS_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Algorithm/Container.hpp b/libs/libmod/src/mod/lib/Algorithm/Container.hpp new file mode 100644 index 0000000..5cfba0d --- /dev/null +++ b/libs/libmod/src/mod/lib/Algorithm/Container.hpp @@ -0,0 +1,23 @@ +#ifndef MOD_LIB_ALGORITHM_CONTAINER_HPP +#define MOD_LIB_ALGORITHM_CONTAINER_HPP + +#include +#include + +namespace mod::lib { + +template +std::pair findAndInsert(Container &c, T &&t, Pred pred) { + auto iter = std::find_if(begin(c), end(c), [&](const T &tCand) { + return pred(tCand, t); + }); + if(iter != end(c)) return {*iter, false}; + else { + iter = c.insert(end(c), std::move(t)); + return {*iter, true}; + } +} + +} // namesapce mod::lib + +#endif // MOD_LIB_ALGORITHM_CONTAINER_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Algorithm.hpp b/libs/libmod/src/mod/lib/Algorithm/MultiDimSelector.hpp similarity index 86% rename from libs/libmod/src/mod/lib/Algorithm.hpp rename to libs/libmod/src/mod/lib/Algorithm/MultiDimSelector.hpp index af5c2f8..99ff9a6 100644 --- a/libs/libmod/src/mod/lib/Algorithm.hpp +++ b/libs/libmod/src/mod/lib/Algorithm/MultiDimSelector.hpp @@ -1,33 +1,8 @@ -#ifndef MOD_LIB_ALGORITHM_HPP -#define MOD_LIB_ALGORITHM_HPP - -#include - -#include -#include -#include - -// - findAndInsert -// - MultiDimSelector +#ifndef MOD_LIB_ALGORITHM_MULTIDIMSELECTOR_HPP +#define MOD_LIB_ALGORITHM_MULTIDIMSELECTOR_HPP namespace mod::lib { -template -std::pair findAndInsert(Container &c, T t, Pred pred) { - auto iter = std::find_if(begin(c), end(c), [&](const T & tCand) { - return pred(tCand, t); - }); - if(iter != end(c)) return std::make_pair(*iter, false); - else { - iter = c.insert(end(c), t); - return std::make_pair(*iter, true); - } -} - -//------------------------------------------------------------------------------ -// MultiDimSelector -//------------------------------------------------------------------------------ - template struct MultiDimSelector { using Self = MultiDimSelector; @@ -35,11 +10,10 @@ struct MultiDimSelector { using InnerIterator = decltype(std::declval().begin()); struct const_iterator; - friend class const_iterator; + friend struct const_iterator; public: - MultiDimSelector(std::size_t numPatterns, std::size_t numHosts, InnerRangeProvider morphismProvider) - : preDisabled(numPatterns, false) { + : preDisabled(numPatterns, false) { assert(numPatterns > 0); assert(numHosts > 0); morphisms.reserve(numPatterns); @@ -218,4 +192,4 @@ struct MultiDimSelector::const_iterator { } // namespace mod::lib -#endif // MOD_LIB_ALGORITHM_HPP \ No newline at end of file +#endif // MOD_LIB_ALGORITHM_MULTIDIMSELECTOR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Algorithm/Point.hpp b/libs/libmod/src/mod/lib/Algorithm/Point.hpp new file mode 100644 index 0000000..9eacc85 --- /dev/null +++ b/libs/libmod/src/mod/lib/Algorithm/Point.hpp @@ -0,0 +1,27 @@ +#ifndef MOD_LIB_ALGORITHM_POINT_HPP +#define MOD_LIB_ALGORITHM_POINT_HPP + +#include +#include + +namespace mod::lib { + +constexpr double pi = 3.14159265358979323846; + +inline std::pair pointRotation(double xRaw, double yRaw, int rotation) { + double angle = rotation * pi / 180; + auto s = std::sin(angle); + auto c = std::cos(angle); + double x = xRaw * c - yRaw * s; + double y = xRaw * s + yRaw * c; + return std::make_pair(x, y); +} + +inline std::pair pointTransform(double xRaw, double yRaw, int rotation, bool mirror) { + if(mirror) xRaw *= -1; + return pointRotation(xRaw, yRaw, rotation); +} + +} // namespace mod::lib + +#endif // MOD_LIB_ALGORITHM_POINT_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Chem/MDL.cpp b/libs/libmod/src/mod/lib/Chem/MDL.cpp new file mode 100644 index 0000000..3387e4e --- /dev/null +++ b/libs/libmod/src/mod/lib/Chem/MDL.cpp @@ -0,0 +1,1881 @@ +#include "MDL.hpp" + +//#define BOOST_SPIRIT_X3_DEBUG + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +template +using Result = mod::lib::IO::Result; + +#ifdef BOOST_SPIRIT_X3_DEBUG + +namespace boost::spirit::x3::traits { + +template +struct print_attribute_debug, void> { + static void call(Out &out, const iterator_range &val) { + out << "boost::iterator_range<>"; + } +}; + +} // namespace boost::spirit::x3::traits + +#endif + +namespace mod::lib::Chem { +namespace { +// defined in the bottom +// Returns: line, hasLine +std::pair getLine(std::string_view &src); +unsigned int MDLValence(unsigned int elem, int q, unsigned int val); + +Result<> handleAction(lib::IO::Warnings &warnings, const Action action, std::string msg) { + switch(action) { + case Action::Ignore: + warnings.add(std::move(msg), false); + return {}; + case Action::Warn: + warnings.add(std::move(msg), true); + return {}; + case Action::Error: + return Result<>::Error(std::move(msg)); + } + __builtin_unreachable(); +} + + +struct Atom { + std::string symbol; + Isotope iso; + Charge chg; + bool radical = false; + int valence = -1; // optional in V3000 +public: // V3000 data + int id; + int aamap; +public: // derived data + AtomId aId; +}; + +struct Bond { + int src, tar; + std::string type; +public: // V3000 data + int id; +public: // derived data + BondType bondType; +}; + +struct MOL { + std::vector atoms; + std::vector bonds; + int lineFirst, lineLast; +public: + Result> convert(lib::IO::Warnings &warnings, const MDLOptions &options) &&{ + auto res = convertImpl(warnings, options); + if(!res) + return Result<>::Error(res.extractError() + "\nCould not convert MOL at line " + + std::to_string(lineFirst) + " to " + std::to_string(lineLast) + "."); + return res; + } +private: + Result> convertImpl(lib::IO::Warnings &warnings, const MDLOptions &options) { + lib::ConnectedComponents components(atoms.size()); + for(const Bond &b : bonds) + components.join(b.src - 1, b.tar - 1); + const auto numComponents = components.finalize(); + if(numComponents == 0) return Result<>::Error("Molecule has no atoms."); + + std::vector datas; + datas.resize(numComponents); + const auto onError = [&datas](std::string msg) -> Result<> { + for(auto &d : datas) d.reset(); + return Result<>::Error(std::move(msg)); + }; + for(int i = 0; i != numComponents; ++i) { + datas[i].g = std::make_unique(); + datas[i].pString = std::make_unique(*datas[i].g); + } + + for(int i = 0; i != atoms.size(); ++i) { + const Atom &a = atoms[i]; + const auto comp = components[i]; + const auto v = add_vertex(*datas[comp].g); + datas[comp].externalToInternalIds[i + 1] = get(boost::vertex_index_t(), *datas[comp].g, v); + if(a.symbol != "*") { + std::string s; + if(a.iso != Isotope()) + s += std::to_string(a.iso); + s += a.symbol; + s += chargeSuffix(a.chg); + if(a.radical) s += '.'; + datas[comp].pString->addVertex(v, std::move(s)); + } else { + if(a.iso != Isotope()) { + auto res = handleAction(warnings, options.onPatternIsotope, + "Pattern atom with ID " + std::to_string(i + 1) + + " has isotope " + boost::lexical_cast(a.iso) + "."); + if(!res) return onError(res.extractError()); + } + if(a.chg != Charge()) { + auto res = handleAction(warnings, options.onPatternCharge, + "Pattern atom with ID " + std::to_string(i + 1) + + " has charge " + boost::lexical_cast(a.chg) + "."); + if(!res) return onError(res.extractError()); + } + if(a.radical) { + auto res = handleAction(warnings, options.onPatternRadical, + "Pattern atom with ID " + std::to_string(i + 1) + " has radical."); + if(!res) return onError(res.extractError()); + } + datas[comp].pString->addVertex(v, "*"); + } + } + + std::vector valences(atoms.size(), 0); + std::vector numAromatic(atoms.size(), 0); + std::vector hasAnyIncident(atoms.size(), false); + for(const Bond &b : bonds) { + assert(components[b.src - 1] == components[b.tar - 1]); + const auto comp = components[b.src - 1]; + auto &g = *datas[comp].g; + const auto vSrc = vertices(g).first[datas[comp].externalToInternalIds.find(b.src)->second]; + const auto vTar = vertices(g).first[datas[comp].externalToInternalIds.find(b.tar)->second]; + if(const auto eQuery = edge(vSrc, vTar, g); eQuery.second) + return onError("Parallel edges in MOL file between atom " + std::to_string(b.src) + + " and " + std::to_string(b.tar) + "."); + const auto e = add_edge(vSrc, vTar, g).first; + datas[comp].pString->addEdge(e, b.type); + if(b.bondType == BondType::Invalid) { + hasAnyIncident[b.src - 1] = true; + hasAnyIncident[b.tar - 1] = true; + } else { + const int order = [&]() { + switch(b.bondType) { + case BondType::Single: + return 1; + case BondType::Double: + return 2; + case BondType::Triple: + return 3; + case BondType::Aromatic: + return 1; + case BondType::Invalid: + __builtin_unreachable(); + } + __builtin_unreachable(); + }(); + valences[b.src - 1] += order; + valences[b.tar - 1] += order; + if(b.bondType == BondType::Aromatic) { + ++numAromatic[b.src - 1]; + ++numAromatic[b.tar - 1]; + } + } + } + // Handle valence + for(int i = 0; i != atoms.size(); ++i) { + const Atom &a = atoms[i]; + int valence; + if(a.valence == -1) { + if(!options.addHydrogens) continue; + if(a.aId == AtomIds::Invalid) { + auto res = handleAction(warnings, options.onImplicitValenceOnAbstract, + "Implicit valence for atom " + std::to_string(i + 1) + + " can not be handled. Atom has no concrete atom symbol." + + + "\nSet onImplicitValenceOnAbstract=Ignore or Warn to not try to add hydrogens to such abstract atoms." + + " Or set addHydrogens=False to disable addition of hydrogens on all atoms."); + if(!res) return onError(res.extractError()); + } + valence = MDLValence(a.aId, a.chg, valences[i]); + if(a.radical) --valence; + } else { + valence = a.valence; + } + if(valence > valences[i]) { + const auto comp = components[i]; + auto &g = *datas[comp].g; + const auto v = vertices(g).first[datas[comp].externalToInternalIds.find(i + 1)->second]; + for(int j = valences[i]; j != valence; ++j) { + const auto vH = add_vertex(g); + datas[comp].pString->addVertex(vH, "H"); + const auto eH = add_edge(v, vH, g).first; + datas[comp].pString->addEdge(eH, "-"); + } + } + } + return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available + } +}; + +template +Result getUInt(Iter first, const Iter last, Desc &&desc) { + const auto origFirst = first; + for(; first != last && *first == ' '; ++first); + if(first == last) + return Result<>::Error(std::string("Expected ") + desc + ". Got '" + std::string(origFirst, last) + "'."); + int res = 0; + for(; first != last; ++first) { + const char c = *first; + if(c < '0' || c > '9') + return Result<>::Error(std::string("Expected ") + desc + ". Got '" + std::string(origFirst, last) + "'."); + res *= 10; + res += c - '0'; + } + return res; +} + +template +Result getInt(Iter first, const Iter last, Desc &&desc) { + const auto origFirst = first; + for(; first != last && *first == ' '; ++first); + if(first == last) + return Result<>::Error(std::string("Expected ") + desc + ". Got '" + std::string(origFirst, last) + "'."); + bool neg = false; + int res = 0; + if(*first == '-') { + neg = true; + ++first; + if(first == last) + return Result<>::Error(std::string("Expected ") + desc + ". Got '" + std::string(origFirst, last) + "'."); + } + for(; first != last; ++first) { + const char c = *first; + if(c < '0' || c > '9') + return Result<>::Error(std::string("Expected ") + desc + ". Got '" + std::string(origFirst, last) + "'."); + res *= 10; + res += c - '0'; + } + if(neg) return -res; + else return res; +} + +Result<> handleRAD(lib::IO::Warnings &warnings, Atom &a, const int radical, const MDLOptions &options) { + switch(radical) { + case 0: + a.radical = false; + return {}; + case 1: + break; + case 2: + a.radical = true; + return {}; + case 3: + case 4: + case 5: + case 6: + break; + default: + return Result<>::Error("Radical out of range. Got " + std::to_string(radical) + ", must be in 0 to 6."); + } + const Action act = [radical, &options]() { + switch(radical) { + case 1: + return options.onRAD1; + case 3: + return options.onRAD3; + case 4: + return options.onRAD4; + case 5: + return options.onRAD5; + case 6: + return options.onRAD6; + } + __builtin_unreachable(); + }(); + return handleAction(warnings, act, + "Radical value " + std::to_string(radical) + " is currently not supported in atom."); +} + +#define FETCH_LINE(var, desc) \ + std::string_view var; \ + { \ + bool hasLine; \ + if(std::tie(var, hasLine) = getLine(src); !hasLine) \ + return Result<>::Error("Expected " + std::string(desc) + ". Got nothing."); \ + } + +Result +parseMOLV2000(lib::IO::Warnings &warnings, std::string_view &src, const MDLOptions &options, + const std::string_view counts, int &lineCount) { + // Counts line: + // aaabbblllfffcccsssxxxrrrpppiiimmmvvvvvv + // - a: #atoms + // - b: #bonds + // - l: #atom lists [query] + // - f: obsolete + // - c: chiral flag + // - s: #stext entries + // - x, r, p, i: obsolete + // - m: #properties lines, but is now just always 999 + + auto numAtoms = getUInt(counts.begin(), counts.begin() + 3, "V2000 atom count"); + if(!numAtoms) return std::move(numAtoms); + auto numBonds = getUInt(counts.begin() + 3, counts.begin() + 6, "V2000 bond count"); + if(!numBonds) return std::move(numBonds); + ++lineCount; + + MOL mol; + mol.atoms.reserve(*numAtoms); + mol.bonds.reserve(*numBonds); + + // Atom block: + for(int i = 0; i != *numAtoms; ++i) { + // xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee + // x, y, z: coordinates + // a 31:34 - entry in periodic table + // - L for atom list + // - A, Q, * for unspecified atom + // - LP for lone pair + // d 34:36 mass difference, i.e., isotope stuff. Can be overwritten by an ISO line. + // c 36:39 charge. Can be overwritten by CHG and RAD lines. + // - 0: neutral + // - 1,2,3: +3,+2,+1 + // - 4: "doublet radical" + // - 5,6,7: -1,-2,-3 + // s 39:42 atom stereo parity. Ignored. + // h 42:45 hydrogen count. Only query. + // b 45:48 stereo care box. Only query. + // v 48:51 valence, the total valence. Missing bonds implies implicit hydrogens. + // - 0: nothing special + // - 1-14: that valence + // - 15: 0 + // H 51:54 H0 designator + // r 54:57 not used + // i 57:60 not used + // m 60:63 atom-atom map: 1 to numAtoms + // n 63:66 inversion/retention flag + // e 66:69 exact change flag + FETCH_LINE(line, "V2000 atom block line") + constexpr int Len = 69; + if(line.size() != Len) + return Result<>::Error("Wrong length of V2000 atom block line. Expected length " + + std::to_string(Len) + ", got " + std::to_string(line.size()) + + ".\nLine: >>>" + std::string(line) + "<<<"); + Atom atom; + // ========================================================================= + auto fSymbol = line.begin() + 31; + auto lSymbol = line.begin() + 34; + for(; fSymbol != lSymbol && *fSymbol == ' '; ++fSymbol); + for(; fSymbol != lSymbol && *(lSymbol - 1) == ' '; --lSymbol); + atom.symbol = std::string(fSymbol, lSymbol); + if(atom.symbol == "LP") { + // consider it as abstract + } else if(atom.symbol == "A" || atom.symbol == "Q" || atom.symbol == "*") { + atom.symbol = "*"; + } else if(atom.symbol == "L") { + // consider it as abstract + } else { + const auto aId = atomIdFromSymbol(atom.symbol); + if(aId == AtomIds::Invalid && !options.allowAbstract) + return Result<>::Error("Unknown atom symbol. Got '" + atom.symbol + "'." + + " Set allowAbstract=True to allow it."); + atom.aId = aId; + } + // ========================================================================= + auto massDiff = getInt(line.begin() + 34, line.begin() + 36, "V2000 mass difference in atom block line"); + if(!massDiff) return std::move(massDiff); + if(*massDiff != 0) { + if(atom.aId != AtomIds::Invalid) { + atom.iso = Isotope(atom.aId + *massDiff); + } else { + auto res = handleAction(warnings, options.onV2000AbstractISO, + "Can not compute isotope from mass difference and non-concrete atom in V2000 atom block."); + if(!res) return res; + } + } + // ========================================================================= + auto charge = getUInt(line.begin() + 36, line.begin() + 39, "V2000 charge in atom block line"); + if(!charge) return std::move(charge); + switch(*charge) { + case 0: + break; + case 1: + case 2: + case 3: + case 5: + case 6: + case 7: + atom.chg = Charge(4 - *charge); + break; + case 4: { + auto res = handleAction(warnings, options.onV2000Charge4, + "Charge '4' (doublet radical) not yet supported in V2000 atom block line."); + if(!res) return res; + break; + } + default: + return Result<>::Error("Unknown charge in V2000 atom block line. Got " + std::to_string(*charge) + "."); + } + // ========================================================================= + auto valence = getUInt(line.begin() + 48, line.begin() + 51, "V2000 valence in atom block line"); + if(!valence) return std::move(valence); + if(*valence == 0) atom.valence = -1; + else if(*valence >= 1 && *valence <= 14) atom.valence = *valence; + else if(*valence == 15) atom.valence = 0; + else return Result<>::Error("Unknown valence in V2000 atom block line. Got " + std::to_string(*valence) + "."); + // ========================================================================= + auto aamapRes = getUInt(line.begin() + 60, line.begin() + 63, "V2000 atom-atom map in atom block line"); + if(!aamapRes) return std::move(aamapRes); + atom.aamap = *aamapRes; + // ========================================================================= + mol.atoms.push_back(std::move(atom)); + ++lineCount; + } // for each atom line + // Bond block: + for(int i = 0; i != *numBonds; ++i) { + // 111222tttsssxxxrrrccc + FETCH_LINE(line, "V2000 bond block line") + constexpr int Len = 7 * 3; + if(line.size() != Len) + return Result<>::Error("Wrong length of V2000 atom block line. Expected length " + + std::to_string(Len) + ", got " + std::to_string(line.size()) + + ".\nLine: >>>" + std::string(line) + "<<<"); + Bond bond; + // ========================================================================= + { // src, tar + auto src = getUInt(line.begin(), line.begin() + 3, "atom id in V2000 bond block line"); + if(!src) return std::move(src); + if(*src < 1 || *src > mol.atoms.size()) + return Result<>::Error("Atom id in V2000 bond block line is out of range. Got " + + std::to_string(*src) + ", but should be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + auto tar = getUInt(line.begin() + 3, line.begin() + 6, "atom id in V2000 bond block line"); + if(!tar) return std::move(tar); + if(*tar < 1 || *tar > mol.atoms.size()) + return Result<>::Error("Atom id in V2000 bond block line is out of range. Got " + + std::to_string(*tar) + ", but should be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + if(*src == *tar) + return Result<>::Error("Atom IDs for bond endpoints in V2000 bond line are the same. Got " + + std::to_string(*src) + " and " + std::to_string(*tar) + "."); + bond.src = *src; + bond.tar = *tar; + } // src, tar + if(auto order = getUInt(line.begin() + 6, line.begin() + 9, "bond order in V2000 bond block line")) { + switch(*order) { + case 1: + bond.type = "-"; + bond.bondType = BondType::Single; + break; + case 2: + bond.type = "="; + bond.bondType = BondType::Double; + break; + case 3: + bond.type = "#"; + bond.bondType = BondType::Triple; + break; + case 4: + bond.type = ":"; + bond.bondType = BondType::Aromatic; + break; + case 5: + case 6: + case 7: { + auto res = handleAction(warnings, options.onUnsupportedQueryBondType, + "Unsupported bond type in V2000 bond block line. Got " + + std::to_string(*order) + "."); + switch(options.onUnsupportedQueryBondType) { + case Action::Ignore: + case Action::Warn: + bond.type = "_Q" + std::to_string(bond.src) + "_" + std::to_string(bond.tar) + + "_" + std::to_string(*order); + bond.bondType = BondType::Invalid; + assert(res); + break; + case Action::Error: + assert(!res); + return res; + } + break; + } + case 8: + bond.type = "*"; + bond.bondType = BondType::Invalid; + break; + default: + return Result<>::Error("Bond type in V2000 bond block line is out of range. Got " + + std::to_string(*order) + ", but should be in 1 to 8."); + } + } else return std::move(order); + mol.bonds.push_back(std::move(bond)); + ++lineCount; + } + // =========================================================================== + // Properties + auto handleChgRad = [&mol, chargeRadicalCleared = false]( + std::string_view line, const std::string type, const std::string desc, auto output) mutable -> Result<> { + if(!chargeRadicalCleared) { + for(Atom &a : mol.atoms) { + a.chg = Charge(); + a.radical = false; + } + chargeRadicalCleared = true; + } + if(line.size() < 9) + return Result<>::Error("V2000 " + type + " line too short. Line of length " + + std::to_string(line.size()) + " was >>>" + std::string(line) + "<<<"); + auto numEntries = getUInt(line.begin() + 6, line.begin() + 9, "number of " + desc + "s in V2000 CHG line"); + if(!numEntries) return std::move(numEntries); + const auto len = 9 + *numEntries * 4 * 2; + if(line.size() != len) + return Result<>::Error("V2000 " + type + " line has wrong size. Expected " + std::to_string(len) + + ", got line of length " + std::to_string(line.size()) + + " >>>" + std::string(line) + "<<<"); + for(int i = 0; i != *numEntries; ++i) { + const auto f = line.begin() + 9 + i * 8; + const auto l = f + 4; + auto aId = getUInt(f + 1, l, "atom id in V2000 " + type + " line"); + if(!aId) return std::move(aId); + if(*aId < 1 || *aId > mol.atoms.size()) + return Result<>::Error("Atom id in V2000 " + type + " line out of range. Got " + + std::to_string(*aId) + ", must be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + auto val = getInt(f + 4 + 1, l + 4, desc + " in V2000 " + type + " line"); + if(!val) return std::move(val); + auto res = output(mol.atoms[*aId - 1], *val); + if(!res) return res; + } + return {}; + }; + while(true) { + // hax to support certain bad writers that don't use 'M END' + if(!src.empty() && src[0] == '$') + break; // emulate that 'M END' was read + + // and now the normal parsing + FETCH_LINE(line, "V2000 property line or 'M END'") + const auto cmd = line.substr(0, 6); + if(cmd == "M END") break; + if(cmd == "M CHG") { + auto res = handleChgRad(line, "CHG", "charge", [&](Atom &a, int charge) { + a.chg = Charge(charge); + return Result<>(); + }); + if(!res) return res; + } else if(cmd == "M RAD") { + auto res = handleChgRad(line, "RAD", "radical", [&](Atom &a, int rad) { + return handleRAD(warnings, a, rad, options); + }); + if(!res) return res; + } else if(cmd == "M ISO") { + auto numEntries = getUInt(line.begin() + 6, line.begin() + 9, "number of entries in V2000 ISO line"); + if(!numEntries) return std::move(numEntries); + for(int i = 0; i != *numEntries; ++i) { + const auto f = line.begin() + 9 + i * 8; + const auto l = f + 4; + auto aId = getUInt(f + 1, l, "atom id in V2000 ISO line"); + if(!aId) return std::move(aId); + if(*aId < 1 || *aId > mol.atoms.size()) + return Result<>::Error( + "Atom id in V2000 ISO line out of range. Got " + std::to_string(*aId) + ", must be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + auto val = getInt(f + 4 + 1, l + 4, "ISO value in V2000 ISO line"); + if(!val) return std::move(val); + mol.atoms[*aId - 1].iso = Isotope(*val); + } + } else if(line.substr(0, 3) == "A ") { // Atom Alias + if(line.size() != 6) + return Result<>::Error("V2000 alias line has wrong size. Expected 6, got line of length " + + std::to_string(line.size()) + " >>>" + std::string(line) + "<<<"); + auto aId = getUInt(line.begin() + 3, line.begin() + 6, "atom ID in V2000 atom alias line"); + if(!aId) return std::move(aId); + if(*aId < 1 || *aId > mol.atoms.size()) + return Result<>::Error("Atom id in V2000 alias line out of range. Got " + + std::to_string(*aId) + ", must be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + FETCH_LINE(labelLine, "alias text line in V2000 atom alias") + if(options.applyV2000AtomAliases) { + mol.atoms[*aId - 1].symbol = labelLine; + mol.atoms[*aId - 1].aId = AtomIds::Invalid; + mol.atoms[*aId - 1].valence = 0; + } + } else { + if(options.fullyIgnoreV2000UnhandledKnownProperty) { + if(line.substr(0, 3) == "V ") { // Atom Value + } else if(line.substr(0, 3) == "G ") { // Group Abbreviation + } else if(cmd == "M RBC") { // Ring Bond Count [Query] + } else if(cmd == "M SUB") { // Substitution Count [Query] + } else if(cmd == "M UNS") { // Unsaturated Atom [Query] + } else if(cmd == "M LIN") { // Link Atom [Query] + } else if(cmd == "M ALS") { // Atom List [Query] + } else if(cmd == "M APO") { // Attachment Point [Rgroup] + } else if(cmd == "M AAL") { // Atom Attachment Order [Rgroup] + } else if(cmd == "M RGP") { // Rgroup Label Location [Rgroup] + } else if(cmd == "M LOG") { // Rgroup Logic, Unsatisfied Sites, Range of Occurrence [Rgroup] + } else if(cmd == "M STY") { // Sgroup Type [Sgroup] + } else if(cmd == "M SST") { // Sgroup Subtype [Sgroup] + } else if(cmd == "M SLB") { // Sgroup Labels [Sgroup] + } else if(cmd == "M SCN") { // Sgroup Connectivity [Sgroup] + } else if(cmd == "M SDS") { // Sgroup Expansion [Sgroup] + } else if(cmd == "M SAL") { // Sgroup Atom List [Sgroup] + } else if(cmd == "M SBL") { // Sgroup Bond List [Sgroup] + } else if(cmd == "M SPA") { // Multiple Group Parent Atom List [Sgroup] + } else if(cmd == "M SMT") { // Sgroup Subscript [Sgroup] + } else if(cmd == "M CRS") { // Sgroup Correspondence [Sgroup] + } else if(cmd == "M SDI") { // Sgroup Display Information [Sgroup] + } else if(cmd == "M SBV") { // Abbreviation Sgroup Bond and Vector Information [Sgroup] + } else if(cmd == "M SDT") { // Data Sgroup Field Description [Sgroup] + } else if(cmd == "M SDD") { // Data Sgroup Display Information [Sgroup] + } else if(cmd == "M SCD") { // Data Sgroup Data [Sgroup] (initial lines) + } else if(cmd == "M SED") { // Data Sgroup Data [Sgroup] (final line) + } else if(cmd == "M SPL") { // Sgroup Hierarchy Information [Sgroup] + } else if(cmd == "M SCN") { // Sgroup Component Numbers [Sgroup] + } else if(cmd == "M $3D") { // 3D Feature Properties [3D] + } else if(cmd == "M PXA") { // Phantom Extra Atom + } else if(cmd == "M SAP") { // Abbreviation Sgroup Attachment Point + } else if(cmd == "M SCL") { // Abbreviation Sgroup Class + } else if(cmd == "M SCL") { // Abbreviation Sgroup Class + } else if(cmd == "M REG") { // Large REGNO + // the small REGNO is in line 2 + } else if(cmd == "M SBT") { // Sgroup Bracket Style + } else if(cmd == "M SBT") { // Sgroup Bracket Style + } else { + auto res = handleAction(warnings, options.onV2000UnhandledProperty, + "Property line in MOL V2000 not supported. Line was >>>" + std::string(line) + + "<<<"); + if(!res) return res; + } + } else { + auto res = handleAction(warnings, options.onV2000UnhandledProperty, + "Property line in MOL V2000 not supported. Line was >>>" + std::string(line) + + "<<<"); + if(!res) return res; + } + } + ++lineCount; + } + ++lineCount; + return mol; +} + +struct V3000LineResult { + std::vector lines; + std::vector args; + std::unique_ptr fullLine; // a pointer so it doesn't move if small +public: + friend std::ostream &operator<<(std::ostream &s, const V3000LineResult &res) { + if(res.lines.size() == 1) { + s << "Line >>>" << res.lines.front() << "<<<"; + } else { + s << "Lines >>>\n"; + for(const auto line : res.lines) + s << line << '\n'; + s << "<<<"; + } + return s; + } +}; + +namespace argparser { + +using Iter = const char *; +using PosIter = IO::PositionIter; +using IterRange = boost::iterator_range; + +const auto basicValue = x3::rule{"non-string value"} + = x3::lexeme[+(x3::char_ - x3::char_("\" =()"))]; +const auto string = x3::rule{"string"} + = x3::lexeme[x3::char_('"') + > +((x3::char_ - x3::char_('"')) | x3::lit("\"\"")) + > x3::lit('"')]; +const auto val = x3::rule{"value"} + = string | basicValue; +const auto listvalInner = x3::rule{"list values"} + = +val; +const auto listval = x3::rule{"list"} + = x3::lit('(') > listvalInner > ')'; +const auto optval = x3::rule{"optional value"} + = listval | val; +const auto posarg = x3::rule{"positional argument"} + = x3::raw[val >> !x3::char_('=')]; +const auto optarg = x3::rule{"optional argument"} + = x3::raw[basicValue > '=' > optval]; +const auto args = x3::rule>{"arguments"} + = *posarg > *optarg; + +} // namespace argparser + +template +Result<> parseV3000Args(V3000LineResult &res, const std::string_view line, Desc &&desc) { + std::vector args; + try { + IO::parse(line.begin(), line.end(), argparser::args, args, false, x3::ascii::space); + } catch(const lib::IO::ParsingError &e) { + return Result<>::Error("Parsing error in " + std::string(desc) + ".\n" + e.msg); + } + res.args.reserve(args.size()); + for(const auto &arg : args) + res.args.emplace_back(arg.begin().base(), arg.end().base() - arg.begin().base()); + if(res.args.empty()) return Result<>::Error("Unexpected empty " + std::string(desc) + "."); + return {}; +} + +template +Result parseV3000Line(std::string_view &src, Desc &&desc) { + V3000LineResult res; + FETCH_LINE(line, desc) + if(line.find("M V30 ") != 0) + return Result<>::Error("Expected " + std::string(desc) + ", staring with 'M V30 '." + + "\nLine: >>>" + std::string(line) + "<<<"); + res.lines.push_back(line); + if(line.back() == '-') { + res.fullLine.reset(new std::string(line.substr(7, line.size() - 8))); + while(true) { + FETCH_LINE(line, desc) + if(line.find("M V30 ") != 0) + return Result<>::Error("Expected " + std::string(desc) + ", staring with 'M V30 '." + + "\nLine: >>>" + std::string(line) + "<<<"); + res.lines.push_back(line); + if(line.back() == '-') { + *res.fullLine += line.substr(7, line.size() - 8); + } else { + *res.fullLine += line.substr(7, line.size() - 7); + break; + } + } + if(auto subres = parseV3000Args(res, *res.fullLine, desc); !subres) return subres; + } else { + if(auto subres = parseV3000Args(res, line.substr(7), desc); !subres) return subres; + } + return std::move(res); // TODO: remove std::move when C++20/P1825R0 is available +} + +Result +parseMOLV3000(lib::IO::Warnings &warnings, std::string_view &src, const MDLOptions &options, int &lineCount) { + if(auto line = parseV3000Line(src, "V3000 BEGIN CTAB line")) { + if(line->args.size() < 2 || line->args[0] != "BEGIN" || line->args[1] != "CTAB") + return Result<>::Error("Expected beginning of V3000 CTAB block ('M V30 BEGIN CTAB').\n" + + boost::lexical_cast(*line)); + lineCount += line->lines.size(); + } else return std::move(line); + auto counts = parseV3000Line(src, "V3000 counts line"); + if(!counts) return std::move(counts); + if(counts->args.front() != "COUNTS" || counts->args.size() < 6) + return Result<>::Error("Expected V3000 counts line.\n" + boost::lexical_cast(*counts)); + auto numAtoms = getUInt(counts->args[1].begin(), counts->args[1].end(), "atom count"); + if(!numAtoms) return std::move(numAtoms); + + auto numBonds = getUInt(counts->args[2].begin(), counts->args[2].end(), "bond count"); + if(!numBonds) return std::move(numBonds); + lineCount += counts->lines.size(); + + MOL mol; + mol.atoms.reserve(*numAtoms); + mol.bonds.reserve(*numBonds); + + // Atom block: + if(auto line = parseV3000Line(src, "V3000 BEGIN ATOM line")) { + if(line->args.size() < 2 || line->args[0] != "BEGIN" || line->args[1] != "ATOM") + return Result<>::Error("Expected V3000 BEGIN ATOM line.\n" + boost::lexical_cast(*line)); + lineCount += line->lines.size(); + } else return std::move(line); + + std::map lineFromAtomId; + for(int i = 0; i != *numAtoms; ++i) { + auto line = parseV3000Line(src, "V3000 atom line"); + if(!line) return std::move(line); + if(line->args.size() < 6) + return Result<>::Error("Too few arguments in V3000 atom line.\n" + boost::lexical_cast(*line)); + Atom atom; + + auto id = getUInt(line->args[0].begin(), line->args[0].end(), "atom ID"); + if(!id) return std::move(id); + if(const auto iter = lineFromAtomId.find(*id); iter != end(lineFromAtomId)) + return Result<>::Error("Duplicate atom ID " + std::to_string(*id) + ". Also defined in line " + + std::to_string(iter->second) + ".\n" + boost::lexical_cast(*line)); + lineFromAtomId.emplace(*id, lineCount); + atom.id = *id; + if(atom.id == 0) return Result<>::Error("Atom ID must be non-zero.\n" + boost::lexical_cast(*line)); + + atom.symbol = line->args[1]; + if(atom.symbol[0] == '"') + return Result<>::Error("V3000 quoted atom types not supported.\n" + boost::lexical_cast(*line)); + if(atom.symbol == "A" || atom.symbol == "Q" || atom.symbol == "*") { + // TODO: Q is really "not C or H" + atom.symbol = "*"; + } else { + const auto aId = atomIdFromSymbol(atom.symbol); + if(aId == AtomIds::Invalid && !options.allowAbstract) + return Result<>::Error("Unknown atom symbol. Got '" + atom.symbol + "'." + + " Set allowAbstract=True to allow it."); + atom.aId = aId; + } + + auto aamap = getUInt(line->args[5].begin(), line->args[5].end(), "atom aamap ID"); + if(!aamap) return std::move(aamap); + atom.aamap = *aamap; + + for(int iArg = 6; iArg != line->args.size(); ++iArg) { + const auto arg = line->args[iArg]; + const auto eqPos = arg.find('='); + if(eqPos == std::string_view::npos) + return Result<>::Error("Optional argument for V3000 atom does not contain '='. Got '" + std::string(arg) + + "'.\n" + boost::lexical_cast(*line)); + const auto argName = arg.substr(0, eqPos); + const auto argVal = arg.substr(eqPos + 1); + if(argName == "CHG") { + auto charge = getInt(argVal.begin(), argVal.end(), "V3000 atom CHG"); + if(!charge) return std::move(charge); + atom.chg = Charge(*charge); + } else if(argName == "RAD") { + auto radical = getInt(argVal.begin(), argVal.end(), "V3000 atom RAD"); + if(!radical) return std::move(radical); + if(auto res = handleRAD(warnings, atom, *radical, options); !res) return res; + } else if(argName == "VAL") { + auto val = getInt(argVal.begin(), argVal.end(), "V3000 atom VAL"); + if(!val) return std::move(val); + if(*val == 0) atom.valence = -1; + else if(*val == -1) atom.valence = 0; + else atom.valence = 0; + } else if(argName == "MASS") { + auto iso = getUInt(argVal.begin(), argVal.end(), "V3000 atom MASS"); + if(!iso) return std::move(iso); + atom.iso = Isotope(*iso); + } else { + auto res = handleAction(warnings, options.onV3000UnhandledAtomProperty, + "Unknown optional argument to V3000 atom. Got '" + std::string(arg) + "'.\n"); + if(!res) return res; + } + } + mol.atoms.push_back(std::move(atom)); + lineCount += line->lines.size(); + } // for each atom + if(auto line = parseV3000Line(src, "V3000 END ATOM line")) { + if(line->args.size() < 2 || line->args[0] != "END" || line->args[1] != "ATOM") + return Result<>::Error("Expected V3000 END ATOM line.\n" + boost::lexical_cast(*line)); + lineCount += line->lines.size(); + } else return std::move(line); + + // Bond block: + // it's mandatory, but some leave it out if empty + const auto srcBeforeBondBlock = src; + auto bondBlockBegin = parseV3000Line(src, "V3000 BEGIN BOND line"); + if(!bondBlockBegin) return std::move(bondBlockBegin); + if(bondBlockBegin->args.size() < 2 || bondBlockBegin->args[0] != "BEGIN" || + bondBlockBegin->args[1] != "BOND") { + if(*numBonds > 0) { + return Result<>::Error( + "Expected V3000 BEGIN BOND line.\n" + boost::lexical_cast(*bondBlockBegin)); + } else { + src = srcBeforeBondBlock; + } + } else { // actual bond block + lineCount += bondBlockBegin->lines.size(); + + std::map lineFromBondId; + for(int i = 0; i != *numBonds; ++i) { + auto line = parseV3000Line(src, "V3000 bond line"); + if(!line) return std::move(line); + if(line->args.size() < 4) + return Result<>::Error("Too few arguments in V3000 bond line.\n" + boost::lexical_cast(*line)); + + Bond bond; + + auto id = getUInt(line->args[0].begin(), line->args[0].end(), "bond ID"); + if(!id) return std::move(id); + if(const auto iter = lineFromBondId.find(*id); iter != end(lineFromBondId)) + return Result<>::Error("Duplicate bond ID " + std::to_string(*id) + + ". Also defined in line " + std::to_string(iter->second) + ".\n" + + boost::lexical_cast(*line)); + lineFromBondId.emplace(*id, lineCount); + bond.id = *id; + if(bond.id == 0) + return Result<>::Error("Bond ID must be non-zero.\n" + boost::lexical_cast(*line)); + + // do the src and tar before the type to have them available for the type handling + auto src = getUInt(line->args[2].begin(), line->args[2].end(), "first atom ID for bond"); + if(!src) return std::move(src); + if(*src < 1 || *src > mol.atoms.size()) + return Result<>::Error("First atom ID in V3000 bond line is out of range. Got " + + std::to_string(*src) + ", but should be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + auto tar = getUInt(line->args[3].begin(), line->args[3].end(), "second atom ID for bond"); + if(!tar) return std::move(tar); + if(*tar < 1 || *tar > mol.atoms.size()) + return Result<>::Error("Second atom ID in V3000 bond line is out of range. Got " + + std::to_string(*src) + ", but should be in 1 to " + + std::to_string(mol.atoms.size()) + "."); + if(*src == *tar) + return Result<>::Error("Atom IDs for bond endpoints in V3000 bond line are the same. Got " + + std::to_string(*src) + " and " + std::to_string(*tar) + "."); + bond.src = *src; + bond.tar = *tar; + + auto type = getUInt(line->args[1].begin(), line->args[1].end(), "bond type"); + if(!type) return std::move(type); + switch(*type) { + case 1: + bond.type = "-"; + bond.bondType = BondType::Single; + break; + case 2: + bond.type = "="; + bond.bondType = BondType::Double; + break; + case 3: + bond.type = "#"; + bond.bondType = BondType::Triple; + break; + case 4: + bond.type = ":"; + bond.bondType = BondType::Aromatic; + break; + case 5: + case 6: + case 7: { + auto res = handleAction(warnings, options.onUnsupportedQueryBondType, + "Unsupported bond type in V3000 bond block line. Got " + + std::to_string(*type) + "."); + switch(options.onUnsupportedQueryBondType) { + case Action::Ignore: + case Action::Warn: + bond.type = "_Q" + std::to_string(*src) + "_" + std::to_string(*tar) + "_" + std::to_string(*type); + bond.bondType = BondType::Invalid; + assert(res); + break; + case Action::Error: + assert(!res); + return res; + } + break; + } + case 8: + bond.type = "*"; + bond.bondType = BondType::Invalid; + break; + case 9: + case 10: + return Result<>::Error("Unsupported bond type in V3000 bond type. Got " + std::to_string(*type) + "."); + default: + return Result<>::Error("Bond type in V3000 bond line is out of range. Got " + std::to_string(*type) + + ", but should be in 1 to 10."); + } + + mol.bonds.push_back(std::move(bond)); + lineCount += line->lines.size(); + } // for each bond + if(auto line = parseV3000Line(src, "V3000 END BOND line")) { + if(line->args.size() < 2 || line->args[0] != "END" || line->args[1] != "BOND") + return Result<>::Error("Expected V3000 BOND END line.\n" + boost::lexical_cast(*line)); + lineCount += line->lines.size(); + } else return std::move(line); + } // end of bond block + + // Optional blocks: skip + while(true) { + auto line = parseV3000Line(src, "V3000 END CTAB line or BEGIN line"); + if(!line) return std::move(line); + if(line->args.size() >= 2 && line->args[0] == "END" && line->args[1] == "CTAB") { + lineCount += line->lines.size(); + break; + } + // should be an optional block + if(line->args.size() < 2 || line->args[0] != "BEGIN") + return Result<>::Error( + "Expected V3000 END CTAB line or beginning of block.\n" + boost::lexical_cast(*line)); + const auto block = line->args[1]; + lineCount += line->lines.size(); + while(true) { + auto line = parseV3000Line(src, "V3000 END line or content line"); + if(!line) return std::move(line); + if(line->args[0] == "BEGIN") + return Result<>::Error( + "Nested blocks not supported in optional V3000 blocks.\n" + boost::lexical_cast(*line)); + lineCount += line->lines.size(); + if(line->args.size() >= 2 && line->args[0] == "END" && line->args[1] == block) + break; + } + } + return mol; +} + +lib::IO::Result +parseMOL(lib::IO::Warnings &warnings, std::string_view &src, const MDLOptions &options, int &lineCount) { + const int lineFirst = lineCount; + // Name + FETCH_LINE(name, "name line in MOL"); + // May not contain: + // - $MDL (RGfile) + // - $$$$ (SDfile record separator) + // - $RXN (rxnfile) + // - $RDFILE (RDfile headers). + for(auto &&tag :{"$MDL", "$$$$", "$RXN", "$RDFILE"}) + if(name.find(tag) != name.npos) + return Result<>::Error(std::string("Expected name line in MOL. Found '") + tag + "'."); + ++lineCount; + + // Various stuff: + // IIPPPPPPPPMMDDYYHHmmddSSssssssssssEEEEEEEEEEEERRRRRR + // or blank + // - I 0: 2 user's first and last initials + // - P 2:10 program name + // - M,D,Y,H,m 10:20 date/time + // - d 20:22 dimensional codes + // - S 22:24 scaling factor + // - s 24:34 scaling factor, float + // - E 34:46 energy + // - R 46:52 internal registry number + FETCH_LINE(line2, "'line 2 in MOL'"); + // Note: we do not parse that line. + ++lineCount; + + // Comment + FETCH_LINE(comment, "comment line in MOL"); + ++lineCount; + + // Counts (with version) + FETCH_LINE(counts, "counts line in MOL"); + constexpr int CountsLen = 11 * 3 + 6; + if(counts.size() > CountsLen || counts.size() < 6) + return Result<>::Error("Wrong length of counts line in MOL. Expected length " + std::to_string(CountsLen) + + ", got " + std::to_string(counts.size()) + ".\nLine: >>>" + std::string(counts) + "<<<"); + std::string version; + if(counts.size() < CountsLen) { + warnings.add( + "Counts line in MOL too short. Assuming version=' V2000'. Expected length " + std::to_string(CountsLen) + + ", got " + std::to_string(counts.size()) + ".\nLine: >>>" + std::string(counts) + "<<<\n"); + version = " V2000"; + } else { + version = std::string(counts.end() - 6, counts.end()); + } + if(version == " V2000") { + auto res = parseMOLV2000(warnings, src, options, counts, lineCount); + if(res) { + res->lineFirst = lineFirst; + res->lineLast = lineCount; + return res; + } else { + return Result<>::Error(res.extractError() + "\nMOL started at line " + std::to_string(lineFirst) + "."); + } + } else if(version == " V3000") { + ++lineCount; + auto res = parseMOLV3000(warnings, src, options, lineCount); + if(res) { + res->lineFirst = lineFirst; + res->lineLast = lineCount; + } else { + return Result<>::Error(res.extractError() + "\nMOL started at line " + std::to_string(lineFirst) + "."); + } + FETCH_LINE(endLine, "V3000 end line ('M END')") + if(endLine.substr(0, 6) != "M END") + return Result<>::Error("Expected V3000 end line ('M END').\nLine: >>>" + std::string(endLine) + "<<<"); + ++lineCount; + return res; + } else { + return Result<>::Error("Expected version ' V2000' or ' V3000'. Got '" + version + "'."); + } +} + +Result> +parseSD(lib::IO::Warnings &warnings, std::string_view &src, const MDLOptions &options, int &lineCount) { + std::vector res; + while(true) { + auto mol = parseMOL(warnings, src, options, lineCount); + if(!mol) return std::move(mol); + res.push_back(std::move(*mol)); + { + std::string_view line; + bool hasLine; + if(std::tie(line, hasLine) = getLine(src); !hasLine) + return Result<>::Error("Expected SD property line or '$$$$'. Got nothing."); + if(line == "$$$$") { + ++lineCount; + if(src.empty()) break; + continue; + } + if(line.empty()) return Result<>::Error("Expected SD property line or '$$$$'. Got empty line."); + // skip properties + if(line.front() != '>') + return Result<>::Error("Expected SD property line or '$$$$'. Got >>>" + std::string(line) + "<<<"); + } + while(true) { + ++lineCount; + std::string_view line; + bool hasLine; + if(std::tie(line, hasLine) = getLine(src); !hasLine) + return Result<>::Error("Expected SD property line or blank line. Got nothing."); + if(line.empty()) break; + if(line.front() != '>') + return Result<>::Error("Expected SD property line or blank line. Got >>>" + std::string(line) + "<<<"); + } + ++lineCount; + { + std::string_view line; + bool hasLine; + if(std::tie(line, hasLine) = getLine(src); !hasLine) + return Result<>::Error("Expected '$$$$' to end MOL and properties in SD. Got nothing."); + if(line != "$$$$") + return Result<>::Error( + "Expected '$$$$' to end MOL and properties in SD. Got >>>" + std::string(line) + "<<<"); + } + if(src.empty()) break; + } + return res; +} + +} // namespace + +lib::IO::Result> +readMDLMOL(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options) { + int lineCount = 1; + auto res = parseMOL(warnings, src, options, lineCount); + if(!res) return Result<>::Error(res.extractError() + "\nError at line " + std::to_string(lineCount) + "."); + std::string line; + while(!src.empty()) { + const auto[line, hasLine] = getLine(src); + if(line.empty()) continue; + return Result<>::Error("Expected end of input. Got line >>>" + std::string(line) + + "<<<\nError at line " + std::to_string(lineCount) + "."); + } + return std::move(*res).convert(warnings, options); +} + +Result>> +readMDLSD(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options) { + std::vector> data; + int lineCount = 1; + auto res = parseSD(warnings, src, options, lineCount); + if(!res) return Result<>::Error(res.extractError() + "\nError at line " + std::to_string(lineCount) + "."); + std::string_view line; + bool hasLine; + if(std::tie(line, hasLine) = getLine(src); hasLine) + return Result<>::Error("Expected end of input. Got line >>>" + std::string(line) + + "<<<\nError at line " + std::to_string(lineCount) + "."); + data.reserve(res->size()); + for(auto &mol : *res) { + auto d = std::move(mol).convert(warnings, options); + if(!d) return std::move(d); + data.push_back(std::move(*d)); + } + return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available +} + +namespace { + +std::pair getLine(std::string_view &src) { + if(src.empty()) return {{}, false}; + + const char *first = src.begin(); + int count = 0; + for(const char *next = first; next != src.end(); ++next) { + switch(*next) { + case '\n': + src.remove_prefix(count + 1); + return {std::string_view(first, count), true}; + case '\r': + ++next; + if(next != src.end() && *next == '\n') { + src.remove_prefix(count + 2); + } else { + src.remove_prefix(count + 1); + } + return {std::string_view(first, count), true}; + default: + ++count; + } + } + // no trailing newline + src.remove_prefix(count); + return {std::string_view(first, count), true}; +} + +// The following is a copy from Open Babel, of most of src/formats/mdlvalence.h +// Only formatting and a few syntactic things have been changed. + +/********************************************************************** +mdlvalence.h - Implement MDL valence model. + +Copyright (C) 2012 by NextMove Software + +This file is part of the Open Babel project. +For more information, see + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + ***********************************************************************/ + +/* Return the implicit MDL valence for element "elem" with charge "q". */ +unsigned int MDLValence(unsigned int elem, int q, unsigned int val) { + switch(elem) { + case 1: // H + case 3: // Li + case 11: // Na + case 19: // K + case 37: // Rb + case 55: // Cs + case 87: // Fr + if(q == 0 && val <= 1) + return 1; + break; + + case 4: // Be + case 12: // Mg + case 20: // Ca + case 38: // Sr + case 56: // Ba + case 88: // Ra + switch(q) { + case 0: + if(val <= 2) return 2; + break; + case 1: + if(val <= 1) return 1; + break; + } + break; + + case 5: // B + switch(q) { + case -4: + if(val <= 1) return 1; + break; + case -3: + if(val <= 2) return 2; + break; + case -2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case -1: + if(val <= 4) return 4; + break; + case 0: + if(val <= 3) return 3; + break; + case 1: + if(val <= 2) return 2; + break; + case 2: + if(val <= 1) return 1; + break; + } + break; + + case 6: // C + switch(q) { + case -3: + if(val <= 1) return 1; + break; + case -2: + if(val <= 2) return 2; + break; + case -1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 0: + if(val <= 4) return 4; + break; + case 1: + if(val <= 3) return 3; + break; + case 2: + if(val <= 2) return 2; + break; + case 3: + if(val <= 1) return 1; + break; + } + break; + + case 7: // N + switch(q) { + case -2: + if(val <= 1) return 1; + break; + case -1: + if(val <= 2) return 2; + break; + case 0: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 1: + if(val <= 4) return 4; + break; + case 2: + if(val <= 3) return 3; + break; + case 3: + if(val <= 2) return 2; + break; + case 4: + if(val <= 1) return 1; + break; + } + break; + + case 8: // O + switch(q) { + case -1: + if(val <= 1) return 1; + break; + case 0: + if(val <= 2) return 2; + break; + case 1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 2: + if(val <= 4) return 4; + break; + case 3: + if(val <= 3) return 3; + break; + case 4: + if(val <= 2) return 2; + break; + case 5: + if(val <= 1) return 1; + break; + } + break; + + case 9: // F + switch(q) { + case 0: + if(val <= 1) return 1; + break; + case 1: + if(val <= 2) return 2; + break; + case 2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 3: + if(val <= 4) return 4; + break; + case 4: + if(val <= 3) return 3; + break; + case 5: + if(val <= 2) return 2; + break; + case 6: + if(val <= 1) return 1; + break; + } + break; + + case 13: // Al + switch(q) { + case -4: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -3: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case -1: + if(val <= 4) return 4; + break; + case 0: + if(val <= 3) return 3; + break; + case 1: + if(val <= 2) return 2; + break; + case 2: + if(val <= 1) return 1; + break; + } + break; + + case 14: // Si + switch(q) { + case -3: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -2: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 0: + if(val <= 4) return 4; + break; + case 1: + if(val <= 3) return 3; + break; + case 2: + if(val <= 2) return 2; + break; + case 3: + if(val <= 1) return 1; + break; + } + break; + + case 15: // P + switch(q) { + case -2: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 0: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 1: + if(val <= 4) return 4; + break; + case 2: + if(val <= 3) return 3; + break; + case 3: + if(val <= 2) return 2; + break; + case 4: + if(val <= 1) return 1; + break; + } + break; + + case 16: // S + switch(q) { + case -1: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 0: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 2: + if(val <= 4) return 4; + break; + case 3: + if(val <= 3) return 3; + break; + case 4: + if(val <= 2) return 2; + break; + case 5: + if(val <= 1) return 1; + break; + } + break; + + case 17: // Cl + switch(q) { + case 0: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 3: + if(val <= 4) return 4; + break; + case 4: + if(val <= 3) return 3; + break; + case 5: + if(val <= 2) return 2; + break; + case 6: + if(val <= 1) return 1; + break; + } + break; + + case 31: // Ga + switch(q) { + case -4: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -3: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case -1: + if(val <= 4) return 4; + break; + case 0: + if(val <= 3) return 3; + break; + case 2: + if(val <= 1) return 1; + break; + } + break; + + case 32: // Ge + switch(q) { + case -3: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -2: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 0: + if(val <= 4) return 4; + break; + case 1: + if(val <= 3) return 3; + break; + case 3: + if(val <= 1) return 1; + break; + } + break; + + case 33: // As + switch(q) { + case -2: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 0: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 1: + if(val <= 4) return 4; + break; + case 2: + if(val <= 3) return 3; + break; + case 4: + if(val <= 1) return 1; + break; + } + break; + + case 34: // Se + switch(q) { + case -1: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 0: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 2: + if(val <= 4) return 4; + break; + case 3: + if(val <= 3) return 3; + break; + case 5: + if(val <= 1) return 1; + break; + } + break; + + case 35: // Br + switch(q) { + case 0: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 3: + if(val <= 4) return 4; + break; + case 4: + if(val <= 3) return 3; + break; + case 6: + if(val <= 1) return 1; + break; + } + break; + + case 49: // In + switch(q) { + case -4: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -3: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case -1: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 0: + if(val <= 3) return 3; + break; + case 2: + if(val <= 1) return 1; + break; + } + break; + + case 50: // Sn + case 82: // Pb + switch(q) { + case -3: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -2: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 0: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 1: + if(val <= 3) return 3; + break; + case 3: + if(val <= 1) return 1; + break; + } + break; + + case 51: // Sb + case 83: // Bi + switch(q) { + case -2: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 0: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 1: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 2: + if(val <= 3) return 3; + break; + case 4: + if(val <= 1) return 1; + break; + } + break; + + case 52: // Te + case 84: // Po + switch(q) { + case -1: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 0: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 1: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 2: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 3: + if(val <= 3) return 3; + break; + case 5: + if(val <= 1) return 1; + break; + } + break; + + case 53: // I + case 85: // At + switch(q) { + case 0: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case 1: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case 2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case 3: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 4: + if(val <= 3) return 3; + break; + case 6: + if(val <= 1) return 1; + break; + } + break; + + case 81: // Tl + switch(q) { + case -4: + if(val <= 1) return 1; + if(val <= 3) return 3; + if(val <= 5) return 5; + if(val <= 7) return 7; + break; + case -3: + if(val <= 2) return 2; + if(val <= 4) return 4; + if(val <= 6) return 6; + break; + case -2: + if(val <= 3) return 3; + if(val <= 5) return 5; + break; + case -1: + if(val <= 2) return 2; + if(val <= 4) return 4; + break; + case 0: + if(val <= 1) return 1; + if(val <= 3) return 3; + break; + } + break; + + } + return val; +} + +} // namespace +} // namespace mod::lib::Chem \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Chem/MDL.hpp b/libs/libmod/src/mod/lib/Chem/MDL.hpp new file mode 100644 index 0000000..8099710 --- /dev/null +++ b/libs/libmod/src/mod/lib/Chem/MDL.hpp @@ -0,0 +1,19 @@ +#ifndef MOD_LIB_CHEM_MDL_HPP +#define MOD_LIB_CHEM_MDL_HPP + +#include +#include + +#include +#include + +namespace mod::lib::Chem { + +auto readMDLMOL(lib::IO::Warnings &warnings, std::string_view src, + const MDLOptions &options) -> lib::IO::Result>; +auto readMDLSD(lib::IO::Warnings &warnings, std::string_view src, + const MDLOptions &options) -> lib::IO::Result>>; + +} // namespace mod::lib::Chem + +#endif // MOD_LIB_CHEM_MDL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Chem/MoleculeUtil.cpp b/libs/libmod/src/mod/lib/Chem/MoleculeUtil.cpp index fe16721..8cb7df1 100644 --- a/libs/libmod/src/mod/lib/Chem/MoleculeUtil.cpp +++ b/libs/libmod/src/mod/lib/Chem/MoleculeUtil.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -375,4 +376,13 @@ char bondToChar(BondType bt) { } } +std::string chargeSuffix(Charge c) { + std::string s; + if(c == 0) return s; + if(c > 1 || c < -1) s += boost::lexical_cast(std::abs(c)); + if(c < 0) s += '-'; + else s += '+'; + return s; +} + } // namespace mod::lib::Chem \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Chem/MoleculeUtil.hpp b/libs/libmod/src/mod/lib/Chem/MoleculeUtil.hpp index d79df81..dbc8075 100644 --- a/libs/libmod/src/mod/lib/Chem/MoleculeUtil.hpp +++ b/libs/libmod/src/mod/lib/Chem/MoleculeUtil.hpp @@ -29,16 +29,18 @@ void markSpecialAtomsUsed(std::vector &used); std::string symbolFromAtomId(AtomId atomId); void appendSymbolFromAtomId(std::string &s, AtomId atomId); char bondToChar(BondType bt); +std::string chargeSuffix(Charge c); + double exactMass(AtomId a, Isotope i); // implemented in Mass.cpp constexpr double electronMass = 0.000548580; constexpr double GasConstant = 1.98720425864083e-3; // kcal/(K * mol) template -bool isCollapsible(const typename boost::graph_traits::vertex_descriptor v, - const Graph &g, - const AtomDataT &atomData, - const EdgeDataT &edgeData, - const HasImportantStereo &hasImportantStereo) { +bool isCollapsibleHydrogen(const typename boost::graph_traits::vertex_descriptor v, + const Graph &g, + const AtomDataT &atomData, + const EdgeDataT &edgeData, + const HasImportantStereo &hasImportantStereo) { if(!isCleanHydrogen(atomData(v))) return false; if(out_degree(v, g) != 1) return false; const auto e = *out_edges(v, g).first; diff --git a/libs/libmod/src/mod/lib/Chem/OBabel.cpp b/libs/libmod/src/mod/lib/Chem/OBabel.cpp index 7b8f5a3..7243802 100644 --- a/libs/libmod/src/mod/lib/Chem/OBabel.cpp +++ b/libs/libmod/src/mod/lib/Chem/OBabel.cpp @@ -2,12 +2,11 @@ #ifdef MOD_HAVE_OPENBABEL -#include #include #include #include -#include +#include #include #include @@ -38,14 +37,9 @@ struct OBMolHandle::Pimpl { std::map from; }; -OBMolHandle::OBMolHandle() {} - -OBMolHandle::OBMolHandle(OBMolHandle &&o) : p(std::move(o.p)) {} - -OBMolHandle &OBMolHandle::operator=(OBMolHandle &&o) { - p = std::move(o.p); - return *this; -} +OBMolHandle::OBMolHandle() = default; +OBMolHandle::OBMolHandle(OBMolHandle &&o) = default; +OBMolHandle &OBMolHandle::operator=(OBMolHandle &&o) = default; OBMolHandle::OBMolHandle(std::unique_ptr p) : p(std::move(p)) {} @@ -59,7 +53,7 @@ void OBMolHandle::setCoordinates(const std::vector &x, const std::vector assert(x.size() == y.size()); auto &mol = const_cast (*p->m); // becuase bah mol.BeginModify(); - for(std::size_t i = 0; i < x.size(); ++i) { + for(std::size_t i = 0; i != x.size(); ++i) { auto *aPtr = mol.GetAtomById(i); if(!aPtr) continue; aPtr->SetVector(x[i], y[i], 0); @@ -70,27 +64,21 @@ void OBMolHandle::setCoordinates(const std::vector &x, const std::vector mol.EndModify(); } -double OBMolHandle::getMolarMass() const { - double weight = p->m->GetMolWt(false); - unsigned int intWeight = weight * 1024; - return static_cast (intWeight) / 1024; -} - -double OBMolHandle::getEnergy() const { +double OBMolHandle::getEnergy(bool verbose) const { auto &mol = const_cast (*p->m); // becuase bah - if(getConfig().obabel.verbose.get()) - std::cout << "OBabel energy: '" << mol.GetTitle() << "'" + if(verbose) + std::cout << "OBMolHandle::getEnergy: '" << mol.GetTitle() << "'" << "\t" << mol.NumAtoms() << " atoms\t" << mol.NumBonds() << " bonds" << std::endl; // code originally from GGL OpenBabel::OBForceField *pFF = nullptr; - unsigned int conformers = 25; - unsigned int geomSteps = 100; + constexpr int conformers = 25; + constexpr int geomSteps = 100; { // gen3D stuff - if(getConfig().obabel.verbose.get()) std::cout << "\tgen 3D" << std::endl; + if(verbose) std::cout << "OBMolHandle::getEnergy: gen 3D" << std::endl; pFF = OpenBabel::OBForceField::FindForceField("MMFF94"); assert(pFF); OpenBabel::OBBuilder builder; @@ -98,10 +86,7 @@ double OBMolHandle::getEnergy() const { mol.SetDimension(3); const bool setupOK = pFF->Setup(mol); - if(!setupOK) { - std::cout << "ERROR: Setup of Open Babel forcefield failed, using 0 as energy." << std::endl; - return 0; - } + if(!setupOK) throw FatalError("OBMolHandle::getEnergy: ERROR: Setup of Open Babel forcefield failed."); // Since we only want a rough geometry, use distance cutoffs for VDW, Electrostatics pFF->EnableCutOff(true); @@ -116,7 +101,7 @@ double OBMolHandle::getEnergy() const { } { // conformer stuff - if(getConfig().obabel.verbose.get()) std::cout << "\tconformer" << std::endl; + if(verbose) std::cout << "OBMolHandle::getEnergy: conformer" << std::endl; pFF = OpenBabel::OBForceField::FindForceField("MMFF94"); assert(pFF); const bool setupOK = pFF->Setup(mol); @@ -129,8 +114,8 @@ double OBMolHandle::getEnergy() const { } // calculate energy - if(getConfig().obabel.verbose.get()) std::cout << "\tenergy" << std::endl; - double energy = pFF->Energy(false); + if(verbose) std::cout << "OBMolHandle::getEnergy: energy" << std::endl; + const double energy = pFF->Energy(false); // calculate appropriate unit scaling std::string unit = pFF->GetUnit(); @@ -162,23 +147,23 @@ double OBMolHandle::getCoordScaling() const { return p->m->NumBonds() / sumLengths; // == 1 / (length / count); } -bool OBMolHandle::hasAtom(unsigned int id) const { +bool OBMolHandle::hasAtom(int id) const { return p->m->GetAtomById(id) != nullptr; } -double OBMolHandle::getAtomX(unsigned int id) const { - auto *a = p->m->GetAtomById(id); +double OBMolHandle::getAtomX(int id) const { + const auto *a = p->m->GetAtomById(id); assert(a); return a->GetX(); } -double OBMolHandle::getAtomY(unsigned int id) const { - auto *a = p->m->GetAtomById(id); +double OBMolHandle::getAtomY(int id) const { + const auto *a = p->m->GetAtomById(id); assert(a); return a->GetY(); } -lib::IO::Graph::Write::EdgeFake3DType OBMolHandle::getBondFake3D(unsigned int idSrc, unsigned int idTar) const { +lib::IO::Graph::Write::EdgeFake3DType OBMolHandle::getBondFake3D(int idSrc, int idTar) const { // mimic what OpenBabel is doing in openbabel/depict/depict.cpp using Type = lib::IO::Graph::Write::EdgeFake3DType; auto *aSrc = p->m->GetAtomById(idSrc); @@ -204,33 +189,33 @@ void generateCoordinates(OpenBabel::OBMol &mol) { OpenBabel::OBConversion conv; // because the constructor apparently takes care of lib loading OpenBabel::OBOp *op = OpenBabel::OBOp::FindType("gen2D"); if(!op) { - std::cerr << "MØD Error: Could not load Open Babel gen2D operation." - " This can happen if the MØD library or an extension library was loaded with dlopen," - " but without the RTLD_GLOBAL flag. If you are executing from Python then you can insert\n" - "\timport ctypes\n" - "\timport sys\n" - "\tsys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL)\n" - "before importing any modules. If this does not work, please open an issue.\n"; - std::exit(1); + std::string msg = "MØD Error: Could not load Open Babel gen2D operation." + " This can happen if the MØD library or an extension library was loaded with dlopen," + " but without the RTLD_GLOBAL flag. If you are executing from Python then you can insert\n" + "\timport ctypes\n" + "\timport sys\n" + "\tsys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL)\n" + "before importing any modules. If this does not work, please open an issue."; + throw FatalError(std::move(msg)); } bool res = op->Do(&mol); if(!res) MOD_ABORT; } template -OBMolHandle makeOBMolImpl(const Graph &g, - std::function::vertex_descriptor)> atomData, - std::function::edge_descriptor)> bondData, - MayCollapse mayCollapse, - const bool ignoreDuplicateBonds, const bool withCoordinates, Callback callback) { - std::unique_ptr mol(new OpenBabel::OBMol()); +OBMolHandle makeOBMolImpl( + const Graph &g, + std::function::vertex_descriptor)> atomData, + std::function::edge_descriptor)> bondData, + MayCollapse mayCollapse, + const bool ignoreDuplicateBonds, const bool withCoordinates, Callback callback) { + auto mol = std::make_unique(); mol->BeginModify(); // add all visible vertices - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vId = get(boost::vertex_index_t(), g, v); - if(mayCollapse(v, g, atomData, bondData)) continue; + if(mayCollapse(v)) continue; OpenBabel::OBAtom *atom = mol->NewAtom(vId); assert(atomData(v).getAtomId() != AtomIds::Invalid); atom->SetAtomicNum(atomData(v).getAtomId()); @@ -239,13 +224,13 @@ OBMolHandle makeOBMolImpl(const Graph &g, } // add all edges - for(const auto e : asRange(edges(g))) { + for(const auto e: asRange(edges(g))) { const auto vSrc = source(e, g); const auto vTar = target(e, g); const auto srcId = get(boost::vertex_index_t(), g, vSrc); const auto tarId = get(boost::vertex_index_t(), g, vTar); - if(mayCollapse(vSrc, g, atomData, bondData)) continue; - if(mayCollapse(vTar, g, atomData, bondData)) continue; + if(mayCollapse(vSrc)) continue; + if(mayCollapse(vTar)) continue; OpenBabel::OBAtom *src = mol->GetAtomById(srcId), *tar = mol->GetAtomById(tarId); assert(src); assert(tar); @@ -268,7 +253,7 @@ OBMolHandle makeOBMolImpl(const Graph &g, std::abort(); } OpenBabel::OBBond *bond = mol->GetBond(src, tar); - if(!ignoreDuplicateBonds && bond) MOD_ABORT; + if(!ignoreDuplicateBonds && bond) std::abort(); if(!bond) { bond = mol->NewBond(); bond->SetBegin(src); @@ -297,7 +282,7 @@ void convertStereo(const Graph &g, std::function::vertex_descriptor)> hasImportantStereo, const bool withHydrogen, const Stereo &pStereo, OpenBabel::OBMol &mol) { using Emb = lib::Stereo::EmbeddingEdge; - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vId = get(boost::vertex_index_t(), g, v); auto *atom = mol.GetAtomById(vId); if(!atom) continue; @@ -318,7 +303,7 @@ void convertStereo(const Graph &g, const auto e = emb.getEdge(v, g); const auto vAdj = target(e, g); const auto vIdAdj = get(boost::vertex_index_t(), g, vAdj); - if(!withHydrogen && isCollapsible(vAdj, g, atomData, bondData, hasImportantStereo)) { + if(!withHydrogen && isCollapsibleHydrogen(vAdj, g, atomData, bondData, hasImportantStereo)) { return OpenBabel::OBStereo::ImplicitRef; } else { return vIdAdj; @@ -338,9 +323,9 @@ void convertStereo(const Graph &g, case 1: break; // hmm, does OpenBabel like this? case 2: - continue; // otherwise OpenBabel seg. faults (GetVector on a nullptr Atom) + continue; // otherwise, OpenBabel seg. faults (GetVector on a nullptr Atom) case 3: - continue; // otherwise OpenBabel loops infinitely + continue; // otherwise, OpenBabel loops infinitely } // { // debug // std::cout << "OBabelTetra(" << config.center << "): " << config.from; @@ -367,9 +352,9 @@ OBMolHandle makeOBMol(const lib::Graph::GraphType &g, std::function bondData, std::function hasImportantStereo, const bool withHydrogen, const lib::Graph::PropStereo *pStereo) { - const auto mayCollapse = [&](const auto v, const auto &g, const auto &atomData, const auto &bondData) { + const auto mayCollapse = [&](const auto v) { if(withHydrogen) return false; - return isCollapsible(v, g, atomData, bondData, hasImportantStereo); + return isCollapsibleHydrogen(v, g, atomData, bondData, hasImportantStereo); }; auto res = makeOBMolImpl(g, atomData, bondData, mayCollapse, false, true, [&](OpenBabel::OBMol &mol) { if(pStereo) convertStereo(g, atomData, bondData, hasImportantStereo, withHydrogen, *pStereo, mol); @@ -379,54 +364,42 @@ OBMolHandle makeOBMol(const lib::Graph::GraphType &g, std::tuple makeOBMol( const lib::Rules::LabelledRule &lr, - std::function atomData, - std::function bondData, - std::function atomDataLeft, - std::function bondDataLeft, - std::function atomDataRight, - std::function bondDataRight, + std::function cgAtom, + std::function cgBond, + std::function leftAtom, + std::function leftBond, + std::function rightAtom, + std::function rightBond, + std::function mayCollapse, const bool withHydrogen) { - OBMolHandle obMol, obMolLeft, obMolRight; - const auto hasImportantStereo = [&](const auto v) { - if(!has_stereo(lr)) return false; - const auto &g = get_graph(lr); - const auto m = g[v].membership; - if(m != lib::Rules::Membership::Right && !get_stereo(get_labelled_left(lr))[v]->morphismDynamicOk()) return true; - if(m != lib::Rules::Membership::Left && !get_stereo(get_labelled_right(lr))[v]->morphismDynamicOk()) return true; - return false; - }; - const auto mayCollapse = [&](const auto v, const auto &g, const auto &atomData, const auto &bondData) { + const auto mayCollapseCG = [mayCollapse, withHydrogen](const auto v) { if(withHydrogen) return false; - if(membership(lr, v) != lib::Rules::Membership::Context) - return false; - for(const auto e : asRange(out_edges(v, get_graph(lr)))) { - if(membership(lr, e) != lib::Rules::Membership::Context) - return false; - } - return isCollapsible(v, g, atomData, bondData, hasImportantStereo); + return mayCollapse(v); }; - obMol = makeOBMolImpl(get_graph(lr), atomData, bondData, mayCollapse, true, true, - [](OpenBabel::OBMol &mol) {}); - const auto &lgLeft = get_labelled_left(lr); - const auto &lgRight = get_labelled_right(lr); - const auto hasImportantStereoLeft = [&](const auto v) { - const auto &lg = get_labelled_left(lr); - if(!has_stereo(lg)) return false; - return !get_stereo(lg)[v]->morphismDynamicOk(); + const auto mayCollapseL = [&mayCollapseCG](const auto vL) { + // TODO: map to CG + return mayCollapseCG(vL); }; - const auto hasImportantStereoRight = [&](const auto v) { - const auto &lg = get_labelled_right(lr); - if(!has_stereo(lg)) return false; - return !get_stereo(lg)[v]->morphismDynamicOk(); + const auto mayCollapseR = [&mayCollapseCG](const auto vR) { + // TODO: map to CG + return mayCollapseCG(vR); }; - obMolLeft = makeOBMolImpl( - get_graph(lgLeft), atomDataLeft, bondDataLeft, mayCollapse, false, false, - [&](OpenBabel::OBMol &mol) { + OBMolHandle obMol = makeOBMolImpl(get_graph(lr), cgAtom, cgBond, mayCollapseCG, true, true, + [](OpenBabel::OBMol &mol) {}); + const auto &lgLeft = get_labelled_left(lr); + OBMolHandle obMolLeft = makeOBMolImpl( + get_graph(lgLeft), leftAtom, leftBond, mayCollapseL, false, false, + [&lgLeft, &lr, leftAtom, leftBond, &obMol](OpenBabel::OBMol &mol) { + const auto hasImportantStereoLeft = [&lr](const auto v) { + const auto &lg = get_labelled_left(lr); + if(!has_stereo(lg)) return false; + return !get_stereo(lg)[v]->morphismDynamicOk(); + }; const auto &g = get_graph(lgLeft); if(has_stereo(lr)) - convertStereo(g, atomDataLeft, bondDataLeft, hasImportantStereoLeft, true, + convertStereo(g, leftAtom, leftBond, hasImportantStereoLeft, true, get_stereo(lgLeft), mol); - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vId = get(boost::vertex_index_t(), g, v); const auto *aBase = obMol.p->m->GetAtomById(vId); if(!aBase) continue; @@ -436,14 +409,20 @@ std::tuple makeOBMol( } mol.SetDimension(2); }); - obMolRight = makeOBMolImpl( - get_graph(lgRight), atomDataRight, bondDataRight, mayCollapse, false, false, - [&](OpenBabel::OBMol &mol) { + const auto &lgRight = get_labelled_right(lr); + OBMolHandle obMolRight = makeOBMolImpl( + get_graph(lgRight), rightAtom, rightBond, mayCollapseR, false, false, + [&lr, &lgRight, rightAtom, rightBond, &obMol](OpenBabel::OBMol &mol) { + const auto hasImportantStereoRight = [&lr](const auto v) { + const auto &lg = get_labelled_right(lr); + if(!has_stereo(lg)) return false; + return !get_stereo(lg)[v]->morphismDynamicOk(); + }; const auto &g = get_graph(lgRight); if(has_stereo(lr)) - convertStereo(g, atomDataRight, bondDataRight, hasImportantStereoRight, true, + convertStereo(g, rightAtom, rightBond, hasImportantStereoRight, true, get_stereo(lgRight), mol); - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vId = get(boost::vertex_index_t(), g, v); const auto *aBase = obMol.p->m->GetAtomById(vId); if(!aBase) continue; diff --git a/libs/libmod/src/mod/lib/Chem/OBabel.hpp b/libs/libmod/src/mod/lib/Chem/OBabel.hpp index 80a0a61..79456c1 100644 --- a/libs/libmod/src/mod/lib/Chem/OBabel.hpp +++ b/libs/libmod/src/mod/lib/Chem/OBabel.hpp @@ -3,12 +3,15 @@ #include #include + #ifndef MOD_HAVE_OPENBABEL #include #else + #include #include + #endif namespace mod { @@ -16,60 +19,61 @@ struct AtomData; enum class BondType; } // namespace mod namespace mod::lib::IO::Graph::Write { -enum class EdgeFake3DType; +enum struct EdgeFake3DType; } // namespace mod::lib::IO::Graph::Write namespace mod::lib::Graph { struct PropStereo; struct Single; } // namespace mod::lib::Graph namespace mod::lib::Rules { -struct PropStereoCore; +struct PropStereo; } // namespace mod::lib::Rules namespace mod::lib::Chem { #ifndef MOD_HAVE_OPENBABEL -#define MOD_NO_OPENBABEL_ERROR \ - std::cout << "Call to '" << __FUNCTION__ << "' failed." << std::endl; \ - std::cout << "Open Babel features are not available. Rebuild with Open Babel enabled." << std::endl; +#define MOD_NO_OPENBABEL_ERROR_STR \ + std::string("Call to '") + __func__ + "' failed.\n" \ + + "Open Babel features are not available. Rebuild with Open Babel enabled." #else struct OBMolHandle { struct Pimpl; public: OBMolHandle(); - OBMolHandle(OBMolHandle&&); - OBMolHandle &operator=(OBMolHandle&&); + OBMolHandle(OBMolHandle &&); + OBMolHandle &operator=(OBMolHandle &&); OBMolHandle(std::unique_ptr p); ~OBMolHandle(); explicit operator bool() const; void setCoordinates(const std::vector &x, const std::vector &y); public: - double getMolarMass() const; // will truncate by *1024, cast to unsigned int, /1024 - double getEnergy() const; + double getEnergy(bool verbose) const; void print2Dsvg(std::ostream &s) const; public: double getCoordScaling() const; - bool hasAtom(unsigned int id) const; - double getAtomX(unsigned int id) const; - double getAtomY(unsigned int id) const; - lib::IO::Graph::Write::EdgeFake3DType getBondFake3D(unsigned int idSrc, unsigned int idTar) const; + bool hasAtom(int id) const; + double getAtomX(int id) const; + double getAtomY(int id) const; + lib::IO::Graph::Write::EdgeFake3DType getBondFake3D(int idSrc, int idTar) const; public: std::unique_ptr p; }; OBMolHandle copyOBMol(const OBMolHandle &mol); OBMolHandle makeOBMol(const lib::Graph::GraphType &g, - std::function atomData, - std::function bondData, - std::function hasImportantStereo, - bool withHydrogen, const lib::Graph::PropStereo *pStereo); -std::tuple makeOBMol(const lib::Rules::LabelledRule &lr, - std::function atomData, - std::function bondData, - std::function atomDataLeft, - std::function bondDataLeft, - std::function atomDataRight, - std::function bondDataRight, + std::function atomData, + std::function bondData, + std::function hasImportantStereo, + bool withHydrogen, const lib::Graph::PropStereo *pStereo); +std::tuple makeOBMol( + const lib::Rules::LabelledRule &lr, + std::function cgAtom, + std::function cgBond, + std::function leftAtom, + std::function leftBond, + std::function rightAtom, + std::function rightBond, + std::function mayCollapse, const bool withHydrogen); #endif diff --git a/libs/libmod/src/mod/lib/Chem/Smiles.hpp b/libs/libmod/src/mod/lib/Chem/Smiles.hpp index 446230e..a314fa2 100644 --- a/libs/libmod/src/mod/lib/Chem/Smiles.hpp +++ b/libs/libmod/src/mod/lib/Chem/Smiles.hpp @@ -1,12 +1,15 @@ #ifndef MOD_LIB_CHEM_SMILES_HPP #define MOD_LIB_CHEM_SMILES_HPP -#include +#include +#include #include +#include namespace mod { struct AtomId; +enum class SmilesClassPolicy; } // namespace mod namespace mod::lib::Graph { struct PropMolecule; @@ -16,8 +19,8 @@ namespace mod::lib::Chem { std::string getSmiles(const lib::Graph::GraphType &g, const lib::Graph::PropMolecule &molState, const std::vector *ranks, bool withIds); -lib::IO::Result> -readSmiles(lib::IO::Warnings &warnings, const std::string &smiles, bool allowAbstract, SmilesClassPolicy classPolicy); +lib::IO::Result> +readSmiles(lib::IO::Warnings &warnings, std::string_view smiles, bool allowAbstract, SmilesClassPolicy classPolicy); const std::vector &getSmilesOrganicSubset(); bool isInSmilesOrganicSubset(AtomId atomId); void addImplicitHydrogens(lib::Graph::GraphType &g, lib::Graph::PropString &pString, lib::Graph::Vertex v, diff --git a/libs/libmod/src/mod/lib/Chem/SmilesRead.cpp b/libs/libmod/src/mod/lib/Chem/SmilesRead.cpp index 8fc1a75..08f8486 100644 --- a/libs/libmod/src/mod/lib/Chem/SmilesRead.cpp +++ b/libs/libmod/src/mod/lib/Chem/SmilesRead.cpp @@ -6,12 +6,13 @@ #include #include +#include #include #include #include #include #include -#include +#include #include #include @@ -264,7 +265,7 @@ struct ChiralSymbl : x3::symbols { const auto abstractSymbolChar = x3::char_ - x3::char_("[]:"); const auto nestedAbstractSymbol = x3::rule("nestedAbstractSymbol"); const auto nestedAbstractSymbol_def = - *abstractSymbolChar > -(x3::char_('[') > nestedAbstractSymbol > x3::char_(']')) > *abstractSymbolChar; + *abstractSymbolChar > *(x3::char_('[') > nestedAbstractSymbol > x3::char_(']') > *abstractSymbolChar); // no recursion const auto singleDigit = x3::uint_parser(); const auto singleDoubleDigit = x3::rule{"singleDoubleDigit"} @@ -474,9 +475,8 @@ struct AssignConnectedComponentID { int nextID = 0; }; - struct JoinConnected { - JoinConnected(lib::IO::Graph::Read::ConnectedComponents &components) : components(components) {} + JoinConnected(ConnectedComponents &components) : components(components) {} void operator()(const SmilesChain &c) { (*this)(c.branchedAtom); @@ -512,13 +512,13 @@ struct JoinConnected { components.join(a.connectedComponentID, b.connectedComponentID); } public: - lib::IO::Graph::Read::ConnectedComponents &components; + ConnectedComponents &components; }; struct Converter { Converter(std::vector> &gPtrs, std::vector> &pStringPtrs, - const lib::IO::Graph::Read::ConnectedComponents &components, + const ConnectedComponents &components, lib::IO::Warnings &warnings, bool allowAbstract) : gPtrs(gPtrs), pStringPtrs(pStringPtrs), components(components), warnings(warnings), allowAbstract(allowAbstract) {} @@ -659,7 +659,7 @@ struct Converter { private: std::vector> &gPtrs; std::vector> &pStringPtrs; - const lib::IO::Graph::Read::ConnectedComponents &components; + const ConnectedComponents &components; lib::IO::Warnings &warnings; public: std::multimap> classToVertexId; @@ -671,7 +671,7 @@ struct Converter { struct ExplicitHydrogenAdder { ExplicitHydrogenAdder(std::vector> &gPtrs, std::vector> &pStringPtrs, - const lib::IO::Graph::Read::ConnectedComponents &components) + const ConnectedComponents &components) : gPtrs(gPtrs), pStringPtrs(pStringPtrs), components(components) {} void operator()(SmilesChain &c) { @@ -701,13 +701,13 @@ struct ExplicitHydrogenAdder { private: std::vector> &gPtrs; std::vector> &pStringPtrs; - const lib::IO::Graph::Read::ConnectedComponents &components; + const ConnectedComponents &components; }; struct ImplicitHydrogenAdder { ImplicitHydrogenAdder(std::vector> &gPtrs, std::vector> &pStringPtrs, - const lib::IO::Graph::Read::ConnectedComponents &components) + const ConnectedComponents &components) : gPtrs(gPtrs), pStringPtrs(pStringPtrs), components(components) {} void operator()(SmilesChain &c) { @@ -735,13 +735,13 @@ struct ImplicitHydrogenAdder { private: std::vector> &gPtrs; std::vector> &pStringPtrs; - const lib::IO::Graph::Read::ConnectedComponents &components; + const ConnectedComponents &components; }; struct StereoConverter { StereoConverter(std::vector> &gPtrs, const std::vector &pMols, - const lib::IO::Graph::Read::ConnectedComponents &components, + const ConnectedComponents &components, lib::IO::Warnings &warnings) : gPtrs(gPtrs), pMols(pMols), components(components), warnings(warnings), hasAssigned(gPtrs.size(), false) { @@ -856,21 +856,19 @@ struct StereoConverter { public: std::vector> &gPtrs; const std::vector &pMols; - const lib::IO::Graph::Read::ConnectedComponents &components; + const ConnectedComponents &components; lib::IO::Warnings &warnings; public: std::vector> infs; std::vector hasAssigned; }; -lib::IO::Result> -parseSmiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool allowAbstract, +lib::IO::Result> +parseSmiles(lib::IO::Warnings &warnings, std::string_view smiles, const bool allowAbstract, SmilesClassPolicy classPolicy) { - using IteratorType = std::string::const_iterator; - IteratorType iterStart = begin(smiles), iterEnd = end(smiles); SmilesChain ast; try { - lib::IO::parse(iterStart, iterEnd, parser::smiles, ast); + lib::IO::parse(smiles.begin(), smiles.end(), parser::smiles, ast, true); } catch(const lib::IO::ParsingError &e) { return lib::IO::Result<>::Error(e.msg); } @@ -911,7 +909,7 @@ parseSmiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool a } const int numAtoms = AssignConnectedComponentID()(ast); - lib::IO::Graph::Read::ConnectedComponents components(numAtoms); + ConnectedComponents components(numAtoms); (JoinConnected(components)(ast)); const int numComponents = components.finalize(); for(int i = 0; i != numAtoms; ++i) { @@ -930,7 +928,7 @@ parseSmiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool a (ExplicitHydrogenAdder(gPtrs, pStringPtrs, components))(ast); (ImplicitHydrogenAdder(gPtrs, pStringPtrs, components)(ast)); - std::vector datas(numComponents); + std::vector datas(numComponents); const auto iter = std::find_if(conv.classToVertexId.begin(), conv.classToVertexId.end(), [&conv](auto &vp) { return conv.classToVertexId.count(vp.first) > 1; @@ -991,17 +989,17 @@ parseSmiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool a datas[i].g = std::move(gPtrs[i]); datas[i].pString = std::move(pStringPtrs[i]); } - return lib::IO::Result>(std::move(datas)); + return lib::IO::Result>(std::move(datas)); } } // namespace } // namespace mod::lib::Chem::Smiles namespace mod::lib::Chem { -lib::IO::Result> -readSmiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool allowAbstract, +lib::IO::Result> +readSmiles(lib::IO::Warnings &warnings, std::string_view src, const bool allowAbstract, SmilesClassPolicy classPolicy) { - return Smiles::parseSmiles(warnings, smiles, allowAbstract, classPolicy); + return Smiles::parseSmiles(warnings, src, allowAbstract, classPolicy); } } // namespace mod::lib::Chem diff --git a/libs/libmod/src/mod/lib/DG/Dump.cpp b/libs/libmod/src/mod/lib/DG/Dump.cpp index 3e3b447..52145b7 100644 --- a/libs/libmod/src/mod/lib/DG/Dump.cpp +++ b/libs/libmod/src/mod/lib/DG/Dump.cpp @@ -7,9 +7,9 @@ #include #include #include -#include +#include #include -#include +#include #include #include @@ -148,14 +148,14 @@ bool parse(Iter &textFirst, const bool res = IO::detail::ParseDispatch::parse(first, last, p, attr, x3::ascii::space); if(!res) { err << "Error while parsing DG dump.\n" - << IO::detail::makeParserError(textFirst, first, last) + << IO::detail::makeParserError(textFirst, first, last, true) << '\n'; return false; } return true; } catch(const x3::expectation_failure> &e) { err << "Error while parsing DG dump.\n" - << IO::detail::makeParserExpectationError(e, textFirst, last) + << IO::detail::makeParserExpectationError(e, textFirst, last, true) << '\n'; return false; } @@ -206,13 +206,17 @@ std::unique_ptr load(const std::vector > PARSE("vertex:" >> x3::uint_, id); PARSE('"' >> x3::lexeme[*(x3::char_ - '"') >> '"'], name); PARSE('"' >> x3::lexeme[*(x3::char_ - '"') >> '"'], dfs); - auto gDataRes = IO::Graph::Read::dfs(dfs); + IO::Warnings warnings; + auto gDataRes = Graph::Read::dfs(warnings, dfs); + assert(warnings.empty()); if(!gDataRes) { err << gDataRes.extractError() << '\n'; err << "GraphDFS \"" << dfs << "\" could not be parsed for vertex " << id << '\n'; return nullptr; } - auto gData = std::move(*gDataRes); + auto gDatas = std::move(*gDataRes); + if(gDatas.size() != 1) MOD_ABORT; + auto gData = std::move(gDatas[0]); if(gData.pStereo) MOD_ABORT; vertices.emplace_back(id, std::move(name), std::move(gData.g), std::move(gData.pString)); validVertices.insert(id); diff --git a/libs/libmod/src/mod/lib/DG/Hyper.cpp b/libs/libmod/src/mod/lib/DG/Hyper.cpp index 6e2f251..0300827 100644 --- a/libs/libmod/src/mod/lib/DG/Hyper.cpp +++ b/libs/libmod/src/mod/lib/DG/Hyper.cpp @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include #include @@ -15,9 +13,7 @@ #include -namespace mod { -namespace lib { -namespace DG { +namespace mod::lib::DG { //------------------------------------------------------------------------------ // HyperCreator @@ -366,6 +362,4 @@ mod::Derivation Hyper::getDerivation(Vertex v) const { return d; } -} // namespace DG -} // namespace lib -} // namespace mod +} // namespace mod::lib::DG \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DG/Hyper.hpp b/libs/libmod/src/mod/lib/DG/Hyper.hpp index 29e1df6..919a6e4 100644 --- a/libs/libmod/src/mod/lib/DG/Hyper.hpp +++ b/libs/libmod/src/mod/lib/DG/Hyper.hpp @@ -1,26 +1,25 @@ -#ifndef MOD_LIB_DG_HYPER_H -#define MOD_LIB_DG_HYPER_H +#ifndef MOD_LIB_DG_HYPER_HPP +#define MOD_LIB_DG_HYPER_HPP -#include #include +#include #include namespace mod { template -class Function; -namespace lib { -namespace Graph { -class Single; -} // namespace Graph -namespace DG { +struct Function; +} // namespace mod +namespace mod::lib::Graph { +struct Single; +} // namespace mod::lib::Graph +namespace mod::lib::DG { struct Expanded; -class HyperCreator { +struct HyperCreator { HyperCreator(const HyperCreator &) = delete; HyperCreator &operator=(const HyperCreator &) = delete; public: - friend class Hyper; - + friend struct Hyper; private: HyperCreator() = default; explicit HyperCreator(Hyper &hyper); @@ -34,18 +33,16 @@ class HyperCreator { Hyper *owner = nullptr; }; -class Hyper { +struct Hyper { Hyper(const Hyper &) = delete; - Hyper(Hyper &&) = delete; Hyper &operator=(const Hyper &) = delete; - Hyper &operator=(Hyper &&) = delete; public: // bipartite representation using GraphType = HyperGraphType; using Vertex = HyperVertex; using Edge = HyperEdge; private: - friend class HyperCreator; + friend struct HyperCreator; Hyper(const NonHyper &dg); public: static std::pair, HyperCreator> makeHyper(const NonHyper &dg); @@ -81,8 +78,6 @@ class Hyper { std::map graphToHyperVertex; }; -} // namespace DG -} // namespace lib -} // namespace mod +} // namespace mod::lib::DG -#endif /* MOD_LIB_DG_HYPER_H */ +#endif // MOD_LIB_DG_HYPER_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/DGRead.cpp b/libs/libmod/src/mod/lib/DG/IO/Read.cpp similarity index 52% rename from libs/libmod/src/mod/lib/IO/DGRead.cpp rename to libs/libmod/src/mod/lib/DG/IO/Read.cpp index 5600709..b6534e5 100644 --- a/libs/libmod/src/mod/lib/IO/DGRead.cpp +++ b/libs/libmod/src/mod/lib/DG/IO/Read.cpp @@ -1,4 +1,4 @@ -#include "DG.hpp" +#include "Read.hpp" #include //#define BOOST_SPIRIT_X3_DEBUG @@ -11,9 +11,10 @@ #include #include #include -#include -#include +#include +#include +#include #include #include #include @@ -31,13 +32,13 @@ #include -BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DG::Read::AbstractDerivation, - (mod::lib::IO::DG::Read::AbstractDerivation::List, left) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::DG::Read::AbstractDerivation, + (mod::lib::DG::Read::AbstractDerivation::List, left) (bool, reversible) - (mod::lib::IO::DG::Read::AbstractDerivation::List, right) + (mod::lib::DG::Read::AbstractDerivation::List, right) ) -namespace mod::lib::IO::DG::Read { +namespace mod::lib::DG::Read { namespace { namespace parser { @@ -72,7 +73,7 @@ std::optional loadDump(const std::string &file, std::ostream &er "vertices": {"type": "array", "items": { "type": "array", "items": [ - {"type": "integer", "descirption": "id"}, + {"type": "integer", "description": "id"}, {"type": "string", "description": "graphName"}, {"type": "string", "description": "graphGML"} ] @@ -103,11 +104,11 @@ std::optional loadDump(const std::string &file, std::ostream &er return jOpt; } -std::unique_ptr dump(const std::vector> &graphDatabase, - const std::vector> &ruleDatabase, - const std::string &file, - IsomorphismPolicy graphPolicy, - std::ostream &err, int verbosity) { +std::unique_ptr dump(const std::vector> &graphDatabase, + const std::vector> &ruleDatabase, + const std::string &file, + IsomorphismPolicy graphPolicy, + std::ostream &err, int verbosity) { boost::iostreams::mapped_file_source ifs; try { ifs.open(file); @@ -115,9 +116,15 @@ std::unique_ptr dump(const std::vector 8 && std::string(ifs.begin(), ifs.begin() + 8) == "version:") { ifs.close(); - return lib::DG::Dump::load(graphDatabase, ruleDatabase, file, err); + return Dump::load(graphDatabase, ruleDatabase, file, err); + } + // but the older old versions do not + if(ifs.size() > 12 && std::string(ifs.begin(), ifs.begin() + 12) == "numVertices:") { + ifs.close(); + return Dump::load(graphDatabase, ruleDatabase, file, err); } ifs.close(); auto jOpt = loadDump(file, err); @@ -125,20 +132,20 @@ std::unique_ptr dump(const std::vector(labelSettings, graphDatabase, graphPolicy); + auto dgInternal = std::make_unique(labelSettings, graphDatabase, graphPolicy); { // construction auto b = dgInternal->build(); auto res = b.trustLoadDump(std::move(j), ruleDatabase, err, verbosity); if(!res) return {}; } - return std::unique_ptr(dgInternal.release()); + return std::unique_ptr(dgInternal.release()); } std::optional> abstract(const std::string &s, std::ostream &err) { auto iterStart = s.begin(), iterEnd = s.end(); std::vector derivations; try { - lib::IO::parse(iterStart, iterEnd, parser::derivations, derivations, x3::ascii::space); + lib::IO::parse(iterStart, iterEnd, parser::derivations, derivations, true, x3::ascii::space); } catch(const lib::IO::ParsingError &e) { err << e.msg; return {}; @@ -146,4 +153,81 @@ std::optional> abstract(const std::string &s, st return derivations; } -} // namespace mod::lib::IO::DG::Read \ No newline at end of file +// ----------------------------------------------------------------------------------- + +bool dumpDigest(const HyperGraphType &dg, const nlohmann::json &j, std::ostream &err, const std::string &errType) { + static const nlohmann::json schema = R"({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "numUDGVertices": {"type": "integer"}, + "isVertex": {"type": "array", "items": {"type": "integer"}} + }, + "required": ["numUDGVertices", "isVertex"] + })"_json; + static const nlohmann::json_schema::json_validator validator(schema); + if(!IO::validateJson(j, validator, err, "Data does not conform to schema:")) + return false; + const int numUDGVertices = j["numUDGVertices"]; + if(num_vertices(dg) != numUDGVertices) { + err << "DG size mismatch. Given DG has " << num_vertices(dg) + << " vertices and edges, but the " << errType << " is based on one with " + << numUDGVertices << "."; + return false; + } + const auto &bitmap = j["isVertex"]; + std::vector blocks; + blocks.reserve(bitmap.size()); + for(const auto &b: bitmap) + blocks.push_back(b.get()); + boost::dynamic_bitset isVertex(num_vertices(dg)); + boost::from_block_range(blocks.begin(), blocks.end(), isVertex); + assert(isVertex.size() >= num_vertices(dg)); + isVertex.resize(num_vertices(dg)); + for(const auto v: asRange(vertices(dg))) { + const auto isVertexActual = dg[v].kind == DG::HyperVertexKind::Vertex; + const auto vId = get(boost::vertex_index_t(), dg, v); + if(isVertex[vId] != isVertexActual) { + err << "DG mismatch at "; + if(isVertexActual) err << "vertex"; + else err << "edge"; + err << " " << vId << "."; + return false; + } + } + return true; +} + +std::optional +vertexOrEdge(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix) { + if(id >= num_vertices(dg)) { + err << errPrefix << " ID for vertex or edge is out of range (id=" << id << ", last=" << num_vertices(dg) << ")."; + return {}; + } + const auto vs = vertices(dg); + const auto v = *std::next(vs.first, id); + assert(get(boost::vertex_index_t(), dg, v) == id); + return v; +} + +std::optional +vertex(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix) { + auto vOpt = vertexOrEdge(dg, id, err, errPrefix); + if(vOpt && dg[*vOpt].kind != HyperVertexKind::Vertex) { + err << errPrefix << " ID does not represent a vertex (id=" << id << ")."; + return {}; + } + return vOpt; +} + +std::optional +edge(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix) { + auto vOpt = vertexOrEdge(dg, id, err, errPrefix); + if(vOpt && dg[*vOpt].kind != HyperVertexKind::Edge) { + err << errPrefix << " ID does not represent a edge (id=" << id << ")."; + return {}; + } + return vOpt; +} + +} // namespace mod::lib::DG::Read \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DG/IO/Read.hpp b/libs/libmod/src/mod/lib/DG/IO/Read.hpp new file mode 100644 index 0000000..200ebc7 --- /dev/null +++ b/libs/libmod/src/mod/lib/DG/IO/Read.hpp @@ -0,0 +1,52 @@ +#ifndef MOD_LIB_DG_IO_READ_HPP +#define MOD_LIB_DG_IO_READ_HPP + +#include +#include + +#include +#include + +#include +#include +#include + +namespace mod::lib::DG { +struct Hyper; +struct NonHyper; +} // namespace mod::lib::DG +namespace mod::lib::DG::Read { + +struct AbstractDerivation { + using List = std::vector>; +public: + List left; + bool reversible; + List right; +public: + friend bool operator==(const AbstractDerivation &l, const AbstractDerivation &r) { + return std::tie(l.left, l.reversible, l.right) == std::tie(r.left, r.reversible, r.right); + } +}; + +std::optional loadDump(const std::string &file, std::ostream &err); + +std::unique_ptr dump(const std::vector > &graphDatabase, + const std::vector > &ruleDatabase, + const std::string &file, + IsomorphismPolicy graphPolicy, + std::ostream &err, int verbosity); +std::optional> abstract(const std::string &s, std::ostream &err); + +// utilities for those referring to DG elements in their dumps +bool dumpDigest(const HyperGraphType &dg, const nlohmann::json &j, std::ostream &err, const std::string &errType); +std::optional +vertexOrEdge(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix); +std::optional +vertex(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix); +std::optional +edge(const HyperGraphType &dg, std::size_t id, std::ostream &err, const std::string &errPrefix); + +} // namespace mod::lib::DG::Read + +#endif // MOD_LIB_DG_IO_READ_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/DGWrite.cpp b/libs/libmod/src/mod/lib/DG/IO/Write.cpp similarity index 78% rename from libs/libmod/src/mod/lib/IO/DGWrite.cpp rename to libs/libmod/src/mod/lib/DG/IO/Write.cpp index 9ec4d1e..bc23486 100644 --- a/libs/libmod/src/mod/lib/IO/DGWrite.cpp +++ b/libs/libmod/src/mod/lib/DG/IO/Write.cpp @@ -1,33 +1,33 @@ -#include "DG.hpp" +#include "Write.hpp" #include #include #include -#include #include #include +#include #include #include +#include #include #include -#include -#include #include -#include -#include +#include #include +#include +#include #include #include -namespace mod::lib::IO::DG::Write { +namespace mod::lib::DG::Write { -nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { +nlohmann::json dumpToJson(const NonHyper &dgNonHyper) { if(dgNonHyper.getLabelSettings().withStereo) throw mod::LogicError("Can not yet dump DGs with stereo data."); - using VertexKind = lib::DG::HyperVertexKind; + using VertexKind = HyperVertexKind; const auto &dgHyper = dgNonHyper.getHyper(); const auto &dg = dgHyper.getGraph(); @@ -36,7 +36,7 @@ nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { j["labelSettings"] = dgNonHyper.getLabelSettings(); auto jVertices = nlohmann::json::array(); - for(const auto v : asRange(vertices(dg))) { + for(const auto v: asRange(vertices(dg))) { if(dg[v].kind != VertexKind::Vertex) continue; const auto id = get(boost::vertex_index_t(), dg, v); const lib::Graph::Single *g = dg[v].graph; @@ -45,7 +45,7 @@ nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { vertex.push_back(id); vertex.push_back(g->getName()); std::ostringstream ss; - lib::IO::Graph::Write::gml(*g, false, ss); + lib::Graph::Write::gml(*g, false, ss); vertex.push_back(ss.str()); jVertices.push_back(std::move(vertex)); } @@ -53,17 +53,17 @@ nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { std::set rules; - for(const auto v : asRange(vertices(dg))) { + for(const auto v: asRange(vertices(dg))) { if(dg[v].kind != VertexKind::Edge) continue; - for(const auto *r : dgHyper.getRulesFromEdge(v)) + for(const auto *r: dgHyper.getRulesFromEdge(v)) rules.insert(r); } auto jRules = nlohmann::json::array(); std::unordered_map idFromRule; int rId = 0; - for(const auto *r : rules) { + for(const auto *r: rules) { std::ostringstream ss; - lib::IO::Rules::Write::gml(*r, false, ss); + Rules::Write::gml(*r, false, ss); jRules.push_back(ss.str()); idFromRule.emplace(r, rId); ++rId; @@ -71,27 +71,27 @@ nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { j["rules"] = std::move(jRules); auto jEdges = nlohmann::json::array(); - for(const auto v : asRange(vertices(dg))) { + for(const auto v: asRange(vertices(dg))) { if(dg[v].kind != VertexKind::Edge) continue; const auto id = get(boost::vertex_index_t(), dg, v); auto jEdge = nlohmann::json::array(); jEdge.push_back(id); auto jSources = nlohmann::json::array(); - for(const auto e : asRange(in_edges(v, dg))) { + for(const auto e: asRange(in_edges(v, dg))) { const auto v = source(e, dg); const auto id = get(boost::vertex_index_t(), dg, v); jSources.push_back(id); } jEdge.push_back(std::move(jSources)); auto jTargets = nlohmann::json::array(); - for(const auto e : asRange(out_edges(v, dg))) { + for(const auto e: asRange(out_edges(v, dg))) { const auto v = target(e, dg); const auto id = get(boost::vertex_index_t(), dg, v); jTargets.push_back(id); } jEdge.push_back(std::move(jTargets)); auto jRules = nlohmann::json::array(); - for(const auto *r : dgHyper.getRulesFromEdge(v)) { + for(const auto *r: dgHyper.getRulesFromEdge(v)) { const auto iter = idFromRule.find(r); assert(iter != end(idFromRule)); jRules.push_back(iter->second); @@ -104,26 +104,26 @@ nlohmann::json dumpToJson(const lib::DG::NonHyper &dgNonHyper) { return j; } -std::string dotNonHyper(const lib::DG::NonHyper &nonHyper) { +std::string dotNonHyper(const NonHyper &nonHyper) { post::FileHandle s( - getUniqueFilePrefix() + "dgNonHyper_" + boost::lexical_cast(nonHyper.getId()) + ".dot"); + IO::makeUniqueFilePrefix() + "dgNonHyper_" + boost::lexical_cast(nonHyper.getId()) + ".dot"); { // printing - using Vertex = lib::DG::NonHyperVertex; - using Edge = lib::DG::NonHyperEdge; - const lib::DG::NonHyperGraphType &g = nonHyper.getGraph(); + using Vertex = NonHyperVertex; + using Edge = NonHyperEdge; + const NonHyperGraphType &g = nonHyper.getGraph(); s << "digraph g {" << std::endl; - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto id = get(boost::vertex_index_t(), g, v); s << id << " [ label=\"" << g[v].graphs << "\" ];\n"; } - for(const auto e : asRange(edges(g))) { + for(const auto e: asRange(edges(g))) { const auto srcId = get(boost::vertex_index_t(), g, source(e, g)); const auto tarId = get(boost::vertex_index_t(), g, target(e, g)); s << srcId << " -> " << tarId << " [ label=\""; bool first = true; - for(auto *r : g[e].rules) { + for(auto *r: g[e].rules) { if(!first) s << ", "; first = false; s << "r_" << r->getId(); @@ -137,7 +137,7 @@ std::string dotNonHyper(const lib::DG::NonHyper &nonHyper) { return fileNoExt; } -std::string pdfNonHyper(const lib::DG::NonHyper &nonHyper) { +std::string pdfNonHyper(const NonHyper &nonHyper) { std::string fileNoExt = dotNonHyper(nonHyper); IO::post() << "gv dgNonHyper \"" << fileNoExt << "\" pdf" << std::endl; return fileNoExt; @@ -183,8 +183,8 @@ void TikzPrinter::vertex(const std::string &id, if(haveImage && haveLabel) s << "\\\\"; if(haveLabel) { s << "{"; - if(options.labelsAsLatexMath) s << asLatexMath(label); - else s << escapeForLatex(label); + if(options.labelsAsLatexMath) s << IO::asLatexMath(label); + else s << IO::escapeForLatex(label); s << "}"; } s << "};" << std::endl; @@ -210,8 +210,8 @@ void TikzPrinter::hyperEdge(const std::string &id, const std::string &label, con if(!colour.empty()) s << ", draw=" << colour; s << "] (v-" << id << ") {"; if(label != "") { - if(options.labelsAsLatexMath) s << asLatexMath(label); - else s << escapeForLatex(label); + if(options.labelsAsLatexMath) s << IO::asLatexMath(label); + else s << IO::escapeForLatex(label); } s << "};" << std::endl; } @@ -259,8 +259,8 @@ void TikzPrinter::shortcutEdge(const std::string &idTail, if(hasReverse) s << "[modStyleDGHyperHasReverseShortcut]"; s << " node[auto, swap] {"; if(label != "") { - if(options.labelsAsLatexMath) s << asLatexMath(label); - else s << escapeForLatex(label); + if(options.labelsAsLatexMath) s << IO::asLatexMath(label); + else s << IO::escapeForLatex(label); } s << "} (v-" << idHead << ");" << std::endl; } @@ -274,21 +274,28 @@ void TikzPrinter::transitEdge(const std::string &idTail, s << "] (v-" << idTail << ") to"; s << " node[auto, swap] {"; if(label != "") { - if(options.labelsAsLatexMath) s << asLatexMath(label); - else s << escapeForLatex(label); + if(options.labelsAsLatexMath) s << IO::asLatexMath(label); + else s << IO::escapeForLatex(label); } s << "} (v-" << idHead << ");" << std::endl; } -std::function +std::function TikzPrinter::getImageCreator() { - return [this](const lib::DG::Hyper &dg, lib::DG::HyperVertex v, const std::string &id) -> std::string { + return [this](const Hyper &dg, HyperVertex v, Options::DupVertex vDup, const std::string &id) -> std::string { + if(this->options.imageOverwrite) { + auto[file, cmd] = this->options.imageOverwrite(v, options.dupGraph[vDup].dupNum, dg); + if(!file.empty()) { + if(!cmd.empty()) lib::IO::post() << cmd << '\n'; + return file; + } + } const auto &g = *dg.getGraph()[v].graph; const auto doIt = [&](const auto &gOpts) { if(this->options.withInlineGraphs) { - return IO::Graph::Write::tikz(g, gOpts, true, "v-" + id + "-"); + return lib::Graph::Write::tikz(g, gOpts, true, "v-" + id + "-"); } else { - return IO::Graph::Write::pdf(g, gOpts); + return lib::Graph::Write::pdf(g, gOpts); } }; if(!options.rotationOverwrite && !options.mirrorOverwrite) { @@ -309,11 +316,11 @@ TikzPrinter::getImageCreator() { // Options //------------------------------------------------------------------------------ -std::pair Options::inDegreeVisible(DupVertex e, const lib::DG::Hyper &dg) const { +std::pair Options::inDegreeVisible(DupVertex e, const Hyper &dg) const { const auto &g = dg.getGraph(); DupVertex vDupFirst = g.null_vertex(); int numVisible = 0; - for(DupVertex vDupIn : asRange(inv_adjacent_vertices(e, dupGraph))) { + for(DupVertex vDupIn: asRange(inv_adjacent_vertices(e, dupGraph))) { Vertex vIn = dupGraph[vDupIn].v; bool isVisible = isVertexVisible(vIn, dg); if(!isVisible) continue; @@ -323,11 +330,11 @@ std::pair Options::inDegreeVisible(DupVertex e, const l return std::make_pair(numVisible, vDupFirst); } -std::pair Options::outDegreeVisible(DupVertex e, const lib::DG::Hyper &dg) const { +std::pair Options::outDegreeVisible(DupVertex e, const Hyper &dg) const { const auto &g = dg.getGraph(); DupVertex vDupFirst = g.null_vertex(); int numVisible = 0; - for(DupVertex vDupOut : asRange(adjacent_vertices(e, dupGraph))) { + for(DupVertex vDupOut: asRange(adjacent_vertices(e, dupGraph))) { Vertex vOut = dupGraph[vDupOut].v; bool isVisible = isVertexVisible(vOut, dg); if(!isVisible) continue; @@ -338,7 +345,7 @@ std::pair Options::outDegreeVisible(DupVertex e, const } bool Options::isShortcutEdge(DupVertex e, - const lib::DG::Hyper &dg, + const Hyper &dg, int inDegreeVisible, int outDegreeVisible) const { const auto &g = dg.getGraph(); @@ -348,7 +355,7 @@ bool Options::isShortcutEdge(DupVertex e, || (withShortcutEdgesAfterVisibility && inDegreeVisible == 1 && outDegreeVisible == 1); } -std::string Options::vDupToId(DupVertex vDup, const lib::DG::Hyper &dg) const { +std::string Options::vDupToId(DupVertex vDup, const Hyper &dg) const { const auto dupNum = dupGraph[vDup].dupNum; const auto v = dupGraph[vDup].v; const auto vId = get(boost::vertex_index_t(), dg.getGraph(), v); @@ -359,9 +366,9 @@ std::string Options::vDupToId(DupVertex vDup, const lib::DG::Hyper &dg) const { // Data //------------------------------------------------------------------------------ -Data::Data(const lib::DG::Hyper &dg) : dg(dg) { - for(Vertex v : asRange(vertices(dg.getGraph()))) { - if(dg.getGraph()[v].kind != lib::DG::HyperVertexKind::Edge) continue; +Data::Data(const Hyper &dg) : dg(dg) { + for(Vertex v: asRange(vertices(dg.getGraph()))) { + if(dg.getGraph()[v].kind != HyperVertexKind::Edge) continue; std::unordered_map val; Connections c(in_degree(v, dg.getGraph()), out_degree(v, dg.getGraph())); val.emplace(0, std::move(c)); @@ -370,7 +377,7 @@ Data::Data(const lib::DG::Hyper &dg) : dg(dg) { } bool Data::makeDuplicate(Vertex e, int eDup) { - assert(dg.getGraph()[e].kind == lib::DG::HyperVertexKind::Edge); + assert(dg.getGraph()[e].kind == HyperVertexKind::Edge); const auto iterEdgeDups = connections.find(e); assert(iterEdgeDups != end(connections)); const auto iterEdgeCons = iterEdgeDups->second.find(eDup); @@ -382,7 +389,7 @@ bool Data::makeDuplicate(Vertex e, int eDup) { } bool Data::removeDuplicate(Vertex e, int eDup) { - assert(dg.getGraph()[e].kind == lib::DG::HyperVertexKind::Edge); + assert(dg.getGraph()[e].kind == HyperVertexKind::Edge); auto iterEdgeDups = connections.find(e); assert(iterEdgeDups != end(connections)); const auto count = iterEdgeDups->second.erase(eDup); @@ -391,15 +398,15 @@ bool Data::removeDuplicate(Vertex e, int eDup) { namespace { -void reconnectCommon(const lib::DG::Hyper &dgHyper, +void reconnectCommon(const Hyper &dgHyper, Data::ConnectionsStore &connections, Vertex v, int eDup, Vertex headOrTail, int vDupTar, int vDupSrc, bool isTail) { // std::cout << "reconnect(" << std::boolalpha << isTail << ", " << v << ", " << eDup << ", " << headOrTail << ", " << vDupTar << ", " << vDupSrc << ")" << std::endl; const auto &dg = dgHyper.getGraph(); - assert(dg[v].kind == lib::DG::HyperVertexKind::Edge); - assert(dg[headOrTail].kind == lib::DG::HyperVertexKind::Vertex); + assert(dg[v].kind == HyperVertexKind::Edge); + assert(dg[headOrTail].kind == HyperVertexKind::Vertex); const auto iterEdgeDups = connections.find(v); assert(iterEdgeDups != end(connections)); const auto iterEdgeCons = iterEdgeDups->second.find(eDup); @@ -423,7 +430,6 @@ void reconnectCommon(const lib::DG::Hyper &dgHyper, if(isTail) ss << "Tail"; else ss << "Head"; ss << " duplicate " << vDupSrc << " does not exist. Duplicates are:"; - ss << std::endl; throw FatalError(ss.str()); } dupNums[offset] = vDupTar; @@ -440,7 +446,7 @@ void Data::reconnectTarget(Vertex v, int eDup, Vertex head, int vDupTar, int vDu } void Data::removeVertexIfDegreeZero(Vertex v) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Vertex); + assert(dg.getGraph()[v].kind == HyperVertexKind::Vertex); removedIfDegreeZero.insert(v); } @@ -452,9 +458,9 @@ void Data::compile(Options &options) const { std::vector> duplicates(num_vertices(dg)); auto idx = get(boost::vertex_index_t(), dg); // collect duplicates, set all to null_vertex - for(const auto &eDupsCons : connections) { + for(const auto &eDupsCons: connections) { Vertex e = eDupsCons.first; - for(const auto &eDupCons : eDupsCons.second) { + for(const auto &eDupCons: eDupsCons.second) { duplicates[idx[e]][eDupCons.first] = dupGraph.null_vertex(); for(int i = 0; i < in_degree(e, dg); i++) { Vertex vIn = *(inv_adjacent_vertices(e, dg).first + i); @@ -469,8 +475,8 @@ void Data::compile(Options &options) const { } } // create vertices - for(Vertex v : asRange(vertices(dg))) { - if(dg[v].kind != lib::DG::HyperVertexKind::Vertex) continue; + for(Vertex v: asRange(vertices(dg))) { + if(dg[v].kind != HyperVertexKind::Vertex) continue; int vId = idx[v]; const auto &dups = duplicates[vId]; if(dups.empty() && @@ -479,7 +485,7 @@ void Data::compile(Options &options) const { dupGraph[vDup].v = v; dupGraph[vDup].dupNum = 0; } else { - for(auto &pDup : duplicates[vId]) { + for(auto &pDup: duplicates[vId]) { DupVertex vDup = add_vertex(dupGraph); pDup.second = vDup; dupGraph[vDup].v = v; @@ -488,10 +494,10 @@ void Data::compile(Options &options) const { } } // create edges - for(Vertex v : asRange(vertices(dg))) { - if(dg[v].kind != lib::DG::HyperVertexKind::Edge) continue; + for(Vertex v: asRange(vertices(dg))) { + if(dg[v].kind != HyperVertexKind::Edge) continue; int vId = idx[v]; - for(auto &pDup : duplicates[vId]) { + for(auto &pDup: duplicates[vId]) { DupVertex vDup = add_vertex(dupGraph); pDup.second = vDup; dupGraph[vDup].v = v; @@ -499,9 +505,9 @@ void Data::compile(Options &options) const { } } // create connectors - for(const auto &eDupsCons : connections) { + for(const auto &eDupsCons: connections) { Vertex e = eDupsCons.first; - for(const auto &eDupCons : eDupsCons.second) { + for(const auto &eDupCons: eDupsCons.second) { DupVertex eDup = duplicates[idx[e]][eDupCons.first]; for(int i = 0; i < in_degree(e, dg); i++) { Vertex vIn = *(inv_adjacent_vertices(e, dg).first + i); @@ -531,7 +537,7 @@ void Data::compile(Options &options) const { std::pair Printer::printHyper( const Data &data, const IO::Graph::Write::Options &graphOptions) { Options options = prePrint(data); - const auto files = IO::DG::Write::pdf(data.dg, options, graphOptions); + const auto files = pdf(data.dg, options, graphOptions); postPrint(); return files; } @@ -545,10 +551,8 @@ void Printer::popSuffix() { suffixes.pop_back(); } -void Printer::pushVertexVisible(std::function f) { - vertexVisibles. - push_back(f); +void Printer::pushVertexVisible(std::function f) { + vertexVisibles.push_back(f); } void Printer::popVertexVisible() { @@ -560,10 +564,8 @@ bool Printer::hasVertexVisible() const { return !vertexVisibles.empty(); } -void Printer::pushEdgeVisible(std::function f) { - edgeVisibles. - push_back(f); +void Printer::pushEdgeVisible(std::function f) { + edgeVisibles.push_back(f); } void Printer::popEdgeVisible() { @@ -575,10 +577,8 @@ bool Printer::hasEdgeVisible() const { return !edgeVisibles.empty(); } -void Printer::pushVertexLabel(std::function f) { - vertexLabels. - push_back(f); +void Printer::pushVertexLabel(std::function f) { + vertexLabels.push_back(f); } void Printer::popVertexLabel() { @@ -590,10 +590,8 @@ bool Printer::hasVertexLabel() const { return !vertexLabels.empty(); } -void Printer::pushEdgeLabel(std::function f) { - edgeLabels. - push_back(f); +void Printer::pushEdgeLabel(std::function f) { + edgeLabels.push_back(f); } void Printer::popEdgeLabel() { @@ -605,13 +603,8 @@ bool Printer::hasEdgeLabel() const { return !edgeLabels.empty(); } -void Printer::pushVertexColour(std::function f, - bool extendToEdges -) { - vertexColour. - emplace_back(f, extendToEdges - ); +void Printer::pushVertexColour(std::function f, bool extendToEdges) { + vertexColour.emplace_back(f, extendToEdges); } void Printer::popVertexColour() { @@ -623,10 +616,8 @@ bool Printer::hasVertexColour() const { return !vertexColour.empty(); } -void Printer::pushEdgeColour(std::function f) { - edgeColour. - push_back(f); +void Printer::pushEdgeColour(std::function f) { + edgeColour.push_back(f); } void Printer::popEdgeColour() { @@ -646,6 +637,11 @@ void Printer::setMirrorOverwrite(std::function( + Vertex v, int dupNum, const Hyper &)> f) { + baseOptions.imageOverwrite = f; +} + void Printer::setGraphvizPrefix(const std::string &prefix) { baseOptions.graphvizPrefix = prefix; } @@ -666,18 +662,18 @@ Options Printer::prePrint(const Data &data) { Options options = baseOptions; data.compile(options); if(withGraphName) { - pushVertexLabel([](Vertex v, const lib::DG::Hyper &dg) -> std::string { + pushVertexLabel([](Vertex v, const Hyper &dg) -> std::string { const auto &g = dg.getGraph(); - assert(g[v].kind == lib::DG::HyperVertexKind::Vertex); + assert(g[v].kind == HyperVertexKind::Vertex); return g[v].graph->getName(); }); } if(withRuleId) { // we want the rule id before its name - pushEdgeLabel([this](Vertex v, const lib::DG::Hyper &dg) -> std::string { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Edge); + pushEdgeLabel([this](Vertex v, const Hyper &dg) -> std::string { + assert(dg.getGraph()[v].kind == HyperVertexKind::Edge); std::string res; bool first = true; - for(auto *r : dg.getRulesFromEdge(v)) { + for(auto *r: dg.getRulesFromEdge(v)) { if(!first) res += this->edgeLabelSep; first = false; res += "r_{"; @@ -688,11 +684,11 @@ Options Printer::prePrint(const Data &data) { }); } if(withRuleName) { - pushEdgeLabel([this](Vertex v, const lib::DG::Hyper &dg) -> std::string { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Edge); + pushEdgeLabel([this](Vertex v, const Hyper &dg) -> std::string { + assert(dg.getGraph()[v].kind == HyperVertexKind::Edge); std::string res; bool first = true; - for(auto *r : dg.getRulesFromEdge(v)) { + for(auto *r: dg.getRulesFromEdge(v)) { if(!first) res += this->edgeLabelSep; first = false; res += r->getName(); @@ -712,23 +708,23 @@ void Printer::postPrint() { } void Printer::setup(Options &options) { - for(const auto &s : suffixes) options.suffix += s; + for(const auto &s: suffixes) options.suffix += s; - options.vertexVisible = [this](Vertex v, const lib::DG::Hyper &dg) { - for(const auto &f : vertexVisibles) { + options.vertexVisible = [this](Vertex v, const Hyper &dg) { + for(const auto &f: vertexVisibles) { if(!f(v, dg)) return false; } return true; }; - options.hyperedgeVisible = [this](Vertex v, const lib::DG::Hyper &dg) { - for(const auto &f : edgeVisibles) { + options.hyperedgeVisible = [this](Vertex v, const Hyper &dg) { + for(const auto &f: edgeVisibles) { if(!f(v, dg)) return false; } return true; }; - options.vertexLabel = [this](Vertex v, const lib::DG::Hyper &dg) { + options.vertexLabel = [this](Vertex v, const Hyper &dg) { std::string label; if(!vertexLabels.empty()) label += vertexLabels.front()(v, dg); for(int i = 1; i < vertexLabels.size(); i++) { @@ -737,7 +733,7 @@ void Printer::setup(Options &options) { } return label; }; - options.hyperedgeLabel = [this](Vertex v, const lib::DG::Hyper &dg) { + options.hyperedgeLabel = [this](Vertex v, const Hyper &dg) { std::string label; if(!edgeLabels.empty()) label += edgeLabels.front()(v, dg); for(int i = 1; i < edgeLabels.size(); i++) { @@ -747,26 +743,26 @@ void Printer::setup(Options &options) { return label; }; - options.vertexColour = [this](Vertex v, const lib::DG::Hyper &dg) { - for(const auto &fp : vertexColour) { + options.vertexColour = [this](Vertex v, const Hyper &dg) { + for(const auto &fp: vertexColour) { std::string colour = fp.first(v, dg); if(!colour.empty()) return colour; } return std::string(); }; - auto getRawHyperEdgeColour = [this](Vertex v, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Edge); - for(const auto &f : edgeColour) { + auto getRawHyperEdgeColour = [this](Vertex v, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Edge); + for(const auto &f: edgeColour) { std::string colour = f(v, dg); if(!colour.empty()) return colour; } return std::string(); }; - auto getExtendColour = [this](Vertex v, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Vertex); - for(const auto &fp : vertexColour) { + auto getExtendColour = [this](Vertex v, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Vertex); + for(const auto &fp: vertexColour) { if(!fp.second) continue; std::string colour = fp.first(v, dg); if(!colour.empty()) return colour; @@ -774,14 +770,14 @@ void Printer::setup(Options &options) { return std::string(); }; - auto getHyperedgeExtendColour = [getExtendColour](Vertex v, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Edge); + auto getHyperedgeExtendColour = [getExtendColour](Vertex v, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Edge); std::string tailColour, headColour; - for(Vertex vAdj : asRange(inv_adjacent_vertices(v, dg.getGraph()))) { + for(Vertex vAdj: asRange(inv_adjacent_vertices(v, dg.getGraph()))) { tailColour = getExtendColour(vAdj, dg); if(!tailColour.empty()) break; } - for(Vertex vAdj : asRange(adjacent_vertices(v, dg.getGraph()))) { + for(Vertex vAdj: asRange(adjacent_vertices(v, dg.getGraph()))) { headColour = getExtendColour(vAdj, dg); if(!headColour.empty()) break; } @@ -789,16 +785,16 @@ void Printer::setup(Options &options) { else return std::string(); }; - options.hyperedgeColour = [getRawHyperEdgeColour, getHyperedgeExtendColour](Vertex v, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Edge); + options.hyperedgeColour = [getRawHyperEdgeColour, getHyperedgeExtendColour](Vertex v, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Edge); std::string colour = getRawHyperEdgeColour(v, dg); if(!colour.empty()) return colour; return getHyperedgeExtendColour(v, dg); }; options.tailColour = [getRawHyperEdgeColour, getExtendColour, - getHyperedgeExtendColour](Vertex v, Vertex e, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Vertex); - assert(dg.getGraph()[e].kind == lib::DG::HyperVertexKind::Edge); + getHyperedgeExtendColour](Vertex v, Vertex e, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Vertex); + assert(dg.getGraph()[e].kind == HyperVertexKind::Edge); std::string colour = getRawHyperEdgeColour(e, dg); if(!colour.empty()) return colour; colour = getHyperedgeExtendColour(e, dg); @@ -806,9 +802,9 @@ void Printer::setup(Options &options) { else return getExtendColour(v, dg); }; options.headColour = [getRawHyperEdgeColour, getExtendColour, - getHyperedgeExtendColour](Vertex e, Vertex v, const lib::DG::Hyper &dg) { - assert(dg.getGraph()[v].kind == lib::DG::HyperVertexKind::Vertex); - assert(dg.getGraph()[e].kind == lib::DG::HyperVertexKind::Edge); + getHyperedgeExtendColour](Vertex e, Vertex v, const Hyper &dg) { + assert(dg.getGraph()[v].kind == HyperVertexKind::Vertex); + assert(dg.getGraph()[e].kind == HyperVertexKind::Edge); std::string colour = getRawHyperEdgeColour(e, dg); if(!colour.empty()) return colour; colour = getHyperedgeExtendColour(e, dg); @@ -821,7 +817,7 @@ void Printer::setup(Options &options) { // Algorithms //------------------------------------------------------------------------------ -void generic(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &print) { +void generic(const Hyper &dg, const Options &options, SyntaxPrinter &print) { using DupVertex = Options::DupVertex; using DupEdge = Options::DupEdge; const auto imageCreator = print.getImageCreator(); @@ -833,7 +829,7 @@ void generic(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &pr std::string id = options.vDupToId(vDup, dg); std::string label = options.getVertexLabel(dg, vDup); std::string image; - if(options.withGraphImages) image = imageCreator(dg, v, id); + if(options.withGraphImages) image = imageCreator(dg, v, vDup, id); std::string colour = options.getVertexColour(vDup, dg); print.vertex(id, label, image, colour); }); @@ -881,7 +877,7 @@ void generic(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &pr print.end(); } -std::string dot(const lib::DG::Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { +std::string dot(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { struct DotPrinter : SyntaxPrinter { DotPrinter(std::string file, std::string prefix, const IO::Graph::Write::Options &graphOptions) : SyntaxPrinter(file), prefix(prefix), graphOptions(graphOptions) {} @@ -957,12 +953,12 @@ std::string dot(const lib::DG::Hyper &dg, const Options &options, const IO::Grap s << " ];\n"; } - virtual std::function getImageCreator() override { - return [this](const lib::DG::Hyper &dg, lib::DG::HyperVertex v, const std::string &id) -> std::string { + return [this](const Hyper &dg, HyperVertex v, Options::DupVertex vDup, const std::string &id) -> std::string { const auto &g = *dg.getGraph()[v].graph; - return IO::Graph::Write::svg(g, graphOptions); + return lib::Graph::Write::svg(g, graphOptions); }; } @@ -970,7 +966,7 @@ std::string dot(const lib::DG::Hyper &dg, const Options &options, const IO::Grap std::string prefix; const IO::Graph::Write::Options &graphOptions; }; - std::string file = getUniqueFilePrefix(); + std::string file = IO::makeUniqueFilePrefix(); file += "dg_" + boost::lexical_cast(dg.getNonHyper().getId()) + "_"; file += options; file += ".dot"; @@ -979,17 +975,17 @@ std::string dot(const lib::DG::Hyper &dg, const Options &options, const IO::Grap return file; } -std::string coords(const lib::DG::Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { +std::string coords(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { std::string fileNoExt = dot(dg, options, graphOptions); fileNoExt.erase(end(fileNoExt) - 4, end(fileNoExt)); IO::post() << "coordsFromGV dgHyper \"" << fileNoExt << "\"" << std::endl; return fileNoExt + "_coord.tex"; } -std::pair tikz(const lib::DG::Hyper &dg, const Options &options, +std::pair tikz(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { std::string fileCoordsExt = coords(dg, options, graphOptions); - std::string file = getUniqueFilePrefix(); + std::string file = IO::makeUniqueFilePrefix(); file += "dg_" + boost::lexical_cast(dg.getNonHyper().getId()) + "_"; file += options; file += ".tex"; @@ -998,7 +994,7 @@ std::pair tikz(const lib::DG::Hyper &dg, const Options return std::make_pair(file, fileCoordsExt); } -std::string pdfFromDot(const lib::DG::Hyper &dg, const Options &options, +std::string pdfFromDot(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { std::string fileNoExt = dot(dg, options, graphOptions); fileNoExt.erase(end(fileNoExt) - 4, end(fileNoExt)); @@ -1006,7 +1002,7 @@ std::string pdfFromDot(const lib::DG::Hyper &dg, const Options &options, return fileNoExt + ".pdf"; } -std::pair pdf(const lib::DG::Hyper &dg, const Options &options, +std::pair pdf(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions) { auto tikzFiles = tikz(dg, options, graphOptions); std::string fileNoExt = tikzFiles.first.substr(0, tikzFiles.first.length() - 4); @@ -1027,10 +1023,26 @@ std::pair summary(const Data &data, Printer &printer, return files; } -std::string summaryNonHyper(const lib::DG::NonHyper &dg) { +std::string summaryNonHyper(const NonHyper &dg) { std::string file = pdfNonHyper(dg); IO::post() << "summaryDGNonHyper \"dg_" << dg.getId() << "\" \"" << file << "\"\n"; return file; } -} // namespace mod::lib::IO::DG::Write \ No newline at end of file +// ----------------------------------------------------------------------------------- + +nlohmann::json dumpDigest(const HyperGraphType &dg) { + nlohmann::json res; + // apparently it 53 bits is the maximum that is generally supported in some JSON implementations, + // so 32 is the maximum block size we can use + boost::dynamic_bitset isVertex(num_vertices(dg)); + for(const auto v: asRange(vertices(dg))) + isVertex[get(boost::vertex_index_t(), dg, v)] = dg[v].kind == HyperVertexKind::Vertex; + auto bitmap = nlohmann::json::array(); + boost::to_block_range(isVertex, std::back_inserter(bitmap)); + res["numUDGVertices"] = num_vertices(dg); + res["isVertex"] = std::move(bitmap); + return res; +} + +} // namespace mod::lib::DG::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/DG.hpp b/libs/libmod/src/mod/lib/DG/IO/Write.hpp similarity index 64% rename from libs/libmod/src/mod/lib/IO/DG.hpp rename to libs/libmod/src/mod/lib/DG/IO/Write.hpp index 1622548..fa65348 100644 --- a/libs/libmod/src/mod/lib/IO/DG.hpp +++ b/libs/libmod/src/mod/lib/DG/IO/Write.hpp @@ -1,144 +1,34 @@ -#ifndef MOD_LIB_IO_DG_HPP -#define MOD_LIB_IO_DG_HPP +#ifndef MOD_LIB_DG_WRITE_HPP +#define MOD_LIB_DG_WRITE_HPP #include #include -#include + #include -#include +#include #include #include #include namespace mod::lib::DG { -class Hyper; -class NonHyper; +struct Hyper; +struct NonHyper; } // namespace mod::lib::DG -namespace mod::lib::IO::DG::Read { - -struct AbstractDerivation { - using List = std::vector>; -public: - List left; - bool reversible; - List right; -public: - friend bool operator==(const AbstractDerivation &l, const AbstractDerivation &r) { - return std::tie(l.left, l.reversible, l.right) == std::tie(r.left, r.reversible, r.right); - } -}; +namespace mod::lib::DG::Write { +using Vertex = HyperVertex; +using Edge = HyperEdge; -std::optional loadDump(const std::string &file, std::ostream &err); +nlohmann::json dumpToJson(const NonHyper &dg); -std::unique_ptr dump(const std::vector > &graphDatabase, - const std::vector > &ruleDatabase, - const std::string &file, - IsomorphismPolicy graphPolicy, - std::ostream &err, int verbosity); -std::optional> abstract(const std::string &s, std::ostream &err); -} // namespace mod::lib::IO::DG::Read -namespace mod::lib::IO::DG::Write { -using Vertex = lib::DG::HyperVertex; -using Edge = lib::DG::HyperEdge; - -nlohmann::json dumpToJson(const lib::DG::NonHyper &dg); - -std::string dotNonHyper(const lib::DG::NonHyper &nonHyper); -std::string pdfNonHyper(const lib::DG::NonHyper &nonHyper); +std::string dotNonHyper(const NonHyper &nonHyper); +std::string pdfNonHyper(const NonHyper &nonHyper); //------------------------------------------------------------------------------ // Old/New delimiter //------------------------------------------------------------------------------ -struct Options; - -struct SyntaxPrinter { - SyntaxPrinter(std::string file) : s(file) {} - - virtual std::string getName() const = 0; - virtual void begin() = 0; - virtual void end() = 0; - virtual void comment(const std::string &str) = 0; - virtual void vertex(const std::string &id, - const std::string &label, - const std::string &image, - const std::string &colour) = 0; - virtual void vertexHidden(const std::string &id, bool large) = 0; - virtual void hyperEdge(const std::string &id, const std::string &label, const std::string &colour) = 0; - virtual void tailConnector(const std::string &idVertex, - const std::string &idHyperEdge, - const std::string &colour, - int num, int maxNum) = 0; - virtual void headConnector(const std::string &idHyperEdge, - const std::string &idVertex, - const std::string &colour, - int num, int maxNum) = 0; - virtual void shortcutEdge(const std::string &idTail, - const std::string &idHead, - const std::string &label, - const std::string &colour, - bool hasReverse) = 0; - virtual std::function getImageCreator() = 0; -public: - post::FileHandle s; -}; - -struct TikzPrinter : SyntaxPrinter { - TikzPrinter(std::string file, - std::string coords, - const Options &options, - const IO::Graph::Write::Options &graphOptions) - : SyntaxPrinter(file), coords(coords), options(options), graphOptions(graphOptions) {} - - std::string getName() const override { - return "tikz"; - } - - void begin() override; - void end() override; - void comment(const std::string &str) override; - void vertex(const std::string &id, - const std::string &label, - const std::string &image, - const std::string &colour) override; - void vertexHidden(const std::string &id, bool large) override; - void transitVertex(const std::string &idHost, - const std::string &idTransit, - const std::string &angle, - const std::string &label); - void hyperEdge(const std::string &id, const std::string &label, const std::string &colour) override; - void connector(const std::string &idTail, - const std::string &idHead, - const std::string &colour, - int num, int maxNum); - void tailConnector(const std::string &idVertex, - const std::string &idHyperEdge, - const std::string &colour, - int num, int maxNum) override; - void headConnector(const std::string &idHyperEdge, - const std::string &idVertex, - const std::string &colour, - int num, int maxNum) override; - void shortcutEdge(const std::string &idTail, - const std::string &idHead, - const std::string &label, - const std::string &colour, - bool hasReverse) override; - void transitEdge(const std::string &idTail, - const std::string &idHead, - const std::string &label, - const std::string &colour); - std::function getImageCreator() override; -public: - std::string coords; - const Options &options; - const IO::Graph::Write::Options &graphOptions; -}; +struct SyntaxPrinter; struct Options { struct DupVProp { @@ -203,11 +93,11 @@ struct Options { // stuff not giving state - bool isVertexVisible(Vertex v, const lib::DG::Hyper &dg) const { + bool isVertexVisible(Vertex v, const Hyper &dg) const { return vertexVisible ? vertexVisible(v, dg) : true; } - std::string getVertexLabel(const lib::DG::Hyper &dg, DupVertex vDup) const { + std::string getVertexLabel(const Hyper &dg, DupVertex vDup) const { std::string label; const auto v = dupGraph[vDup].v; const auto dupNum = dupGraph[vDup].dupNum; @@ -217,15 +107,15 @@ struct Options { return label; } - bool isHyperedgeVisible(Vertex v, const lib::DG::Hyper &dg) const { + bool isHyperedgeVisible(Vertex v, const Hyper &dg) const { return hyperedgeVisible ? hyperedgeVisible(v, dg) : true; } - std::string getHyperedgeLabel(Vertex v, const lib::DG::Hyper &dg) const { + std::string getHyperedgeLabel(Vertex v, const Hyper &dg) const { return hyperedgeLabel ? hyperedgeLabel(v, dg) : ""; } - std::string getVertexColour(DupVertex vDup, const lib::DG::Hyper &dg) const { + std::string getVertexColour(DupVertex vDup, const Hyper &dg) const { const auto v = dupGraph[vDup].v; const auto dupNum = dupGraph[vDup].dupNum; if(dupVertexColour) { @@ -237,24 +127,24 @@ struct Options { } } - std::string getHyperedgeColour(Vertex v, const lib::DG::Hyper &dg) const { + std::string getHyperedgeColour(Vertex v, const Hyper &dg) const { return hyperedgeColour ? hyperedgeColour(v, dg) : ""; } - std::string getTailColour(Vertex v, Vertex e, const lib::DG::Hyper &dg) const { + std::string getTailColour(Vertex v, Vertex e, const Hyper &dg) const { return tailColour ? tailColour(v, e, dg) : ""; } - std::string getHeadColour(Vertex e, Vertex v, const lib::DG::Hyper &dg) const { + std::string getHeadColour(Vertex e, Vertex v, const Hyper &dg) const { return headColour ? headColour(e, v, dg) : ""; } - std::pair inDegreeVisible(DupVertex e, const lib::DG::Hyper &dg) const; - std::pair outDegreeVisible(DupVertex e, const lib::DG::Hyper &dg) const; + std::pair inDegreeVisible(DupVertex e, const Hyper &dg) const; + std::pair outDegreeVisible(DupVertex e, const Hyper &dg) const; bool isShortcutEdge(DupVertex e, - const lib::DG::Hyper &dg, + const Hyper &dg, int inDegreeVisible, int outDegreeVisible) const; - std::string vDupToId(DupVertex vDup, const lib::DG::Hyper &dg) const; + std::string vDupToId(DupVertex vDup, const Hyper &dg) const; public: bool withShortcutEdges = true; bool withGraphImages = true; @@ -263,19 +153,21 @@ struct Options { bool withInlineGraphs = false; std::string suffix; // appearance (not giving state) - std::function vertexVisible; - std::function vertexLabel; - std::function dupVertexLabel; - std::function hyperedgeVisible; - std::function hyperedgeLabel; - std::function vertexColour; - std::function dupVertexColour; - std::function hyperedgeColour; - std::function tailColour, headColour; - std::function auxPrinter; - // + std::function vertexVisible; + std::function vertexLabel; + std::function dupVertexLabel; + std::function hyperedgeVisible; + std::function hyperedgeLabel; + std::function vertexColour; + std::function dupVertexColour; + std::function hyperedgeColour; + std::function tailColour, headColour; + std::function auxPrinter; + // GraphPrinter overrides std::function)> rotationOverwrite; std::function)> mirrorOverwrite; + // Graph overrides + std::function(Vertex v, int dupNum, const Hyper &)> imageOverwrite; // rendering engine things std::string graphvizPrefix; std::string tikzpictureOption = "scale=\\modDGHyperScale"; @@ -287,9 +179,99 @@ struct Options { DupGraphType dupGraph; }; +struct SyntaxPrinter { + SyntaxPrinter(std::string file) : s(file) {} + + virtual std::string getName() const = 0; + virtual void begin() = 0; + virtual void end() = 0; + virtual void comment(const std::string &str) = 0; + virtual void vertex(const std::string &id, + const std::string &label, + const std::string &image, + const std::string &colour) = 0; + virtual void vertexHidden(const std::string &id, bool large) = 0; + virtual void hyperEdge(const std::string &id, const std::string &label, const std::string &colour) = 0; + virtual void tailConnector(const std::string &idVertex, + const std::string &idHyperEdge, + const std::string &colour, + int num, int maxNum) = 0; + virtual void headConnector(const std::string &idHyperEdge, + const std::string &idVertex, + const std::string &colour, + int num, int maxNum) = 0; + virtual void shortcutEdge(const std::string &idTail, + const std::string &idHead, + const std::string &label, + const std::string &colour, + bool hasReverse) = 0; + virtual std::function getImageCreator() = 0; +public: + post::FileHandle s; +}; + +struct TikzPrinter : SyntaxPrinter { + TikzPrinter(std::string file, + std::string coords, + const Options &options, + const IO::Graph::Write::Options &graphOptions) + : SyntaxPrinter(file), coords(coords), options(options), graphOptions(graphOptions) {} + + std::string getName() const override { + return "tikz"; + } + + void begin() override; + void end() override; + void comment(const std::string &str) override; + void vertex(const std::string &id, + const std::string &label, + const std::string &image, + const std::string &colour) override; + void vertexHidden(const std::string &id, bool large) override; + void transitVertex(const std::string &idHost, + const std::string &idTransit, + const std::string &angle, + const std::string &label); + void hyperEdge(const std::string &id, const std::string &label, const std::string &colour) override; + void connector(const std::string &idTail, + const std::string &idHead, + const std::string &colour, + int num, int maxNum); + void tailConnector(const std::string &idVertex, + const std::string &idHyperEdge, + const std::string &colour, + int num, int maxNum) override; + void headConnector(const std::string &idHyperEdge, + const std::string &idVertex, + const std::string &colour, + int num, int maxNum) override; + void shortcutEdge(const std::string &idTail, + const std::string &idHead, + const std::string &label, + const std::string &colour, + bool hasReverse) override; + void transitEdge(const std::string &idTail, + const std::string &idHead, + const std::string &label, + const std::string &colour); + std::function getImageCreator() override; +public: + std::string coords; + const Options &options; + const IO::Graph::Write::Options &graphOptions; +}; + struct Data { struct Connections { Connections(int numTails, int numHeads) : tail(numTails, 0), head(numHeads, 0) {} + public: // indices are offsets on the in-/out-edge iterators // values are the duplicate numbers @@ -297,7 +279,7 @@ struct Data { }; using ConnectionsStore = std::unordered_map>; public: - explicit Data(const lib::DG::Hyper &dg); + explicit Data(const Hyper &dg); void compile(Options &options) const; // returns: was newly created bool makeDuplicate(Vertex e, int eDup); @@ -307,41 +289,44 @@ struct Data { void reconnectTarget(Vertex e, int eDup, Vertex head, int vDupTar, int vDupSrc); void removeVertexIfDegreeZero(Vertex v); public: - const lib::DG::Hyper &dg; + const Hyper &dg; private: ConnectionsStore connections; // hyper-edge -> dupNum -> Connections std::set removedIfDegreeZero; public: - std::function dupVertexLabel; - std::function dupVertexColour; + std::function dupVertexLabel; + std::function dupVertexColour; }; struct Printer { std::pair printHyper(const Data &data, const IO::Graph::Write::Options &graphOptions); void pushSuffix(const std::string suffix); void popSuffix(); - void pushVertexVisible(std::function f); // visible(v) <=> all of pushed f(v)) + void pushVertexVisible(std::function f); // visible(v) <=> all of pushed f(v)) void popVertexVisible(); bool hasVertexVisible() const; - void pushEdgeVisible(std::function f); // visible(v) <=> all of pushed f(v)) + void pushEdgeVisible(std::function f); // visible(v) <=> all of pushed f(v)) void popEdgeVisible(); bool hasEdgeVisible() const; - void pushVertexLabel(std::function f); + void pushVertexLabel(std::function f); void popVertexLabel(); bool hasVertexLabel() const; - void pushEdgeLabel(std::function f); + void pushEdgeLabel(std::function f); void popEdgeLabel(); bool hasEdgeLabel() const; - void pushVertexColour(std::function f, + void pushVertexColour(std::function f, bool extendToEdges); // colour(v) == first f(v) != "" void popVertexColour(); bool hasVertexColour() const; - void pushEdgeColour(std::function f); // colour(v) == first f(v) != "" + void pushEdgeColour(std::function f); // colour(v) == first f(v) != "" void popEdgeColour(); bool hasEdgeColour() const; -public: +public: // GraphPrinter overrides void setRotationOverwrite(std::function)> f); void setMirrorOverwrite(std::function)> f); +public: // Graph overrides + void setImageOverwrite(std::function( + Vertex v, int dupNum, const Hyper &)> f); public: // rendering engine things void setGraphvizPrefix(const std::string &prefix); const std::string &getGraphvizPrefix() const; @@ -357,27 +342,34 @@ struct Printer { private: std::vector suffixes; // not giving state - std::vector > vertexVisibles, edgeVisibles; - std::vector > vertexLabels, edgeLabels; - std::vector, bool> > vertexColour; - std::vector > edgeColour; + std::vector > vertexVisibles, edgeVisibles; + std::vector > vertexLabels, edgeLabels; + std::vector, bool> > vertexColour; + std::vector > edgeColour; public: std::string vertexLabelSep = ", ", edgeLabelSep = ", "; bool withGraphName = true, withRuleName = false, withRuleId = true; }; -void generic(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &print); -std::string dot(const lib::DG::Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); -std::string coords(const lib::DG::Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); -std::pair tikz(const lib::DG::Hyper &dg, const Options &options, +void generic(const Hyper &dg, const Options &options, SyntaxPrinter &print); +std::string dot(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); +std::string coords(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); +std::pair tikz(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); -std::string pdfFromDot(const lib::DG::Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); -std::pair pdf(const lib::DG::Hyper &dg, const Options &options, +std::string pdfFromDot(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); +std::pair pdf(const Hyper &dg, const Options &options, const IO::Graph::Write::Options &graphOptions); std::pair summary(const Data &data, Printer &printer, const IO::Graph::Write::Options &graphOptions); -std::string summaryNonHyper(const lib::DG::NonHyper &dg); +std::string summaryNonHyper(const NonHyper &dg); + +std::vector> +summaryDerivation(const NonHyper &dg, HyperVertex v, const IO::Graph::Write::Options &options, + const std::string &nomatchColour, const std::string &matchColour); + +// utilities for those referring to DG elements in their dumps +nlohmann::json dumpDigest(const HyperGraphType &dg); -} // namespace mod::lib::IO::DG::Write +} // namespace mod::lib::DG::Write -#endif // MOD_LIB_IO_DG_HPP \ No newline at end of file +#endif // MOD_LIB_DG_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Derivation.cpp b/libs/libmod/src/mod/lib/DG/IO/WriteDerivation.cpp similarity index 80% rename from libs/libmod/src/mod/lib/IO/Derivation.cpp rename to libs/libmod/src/mod/lib/DG/IO/WriteDerivation.cpp index e3a524b..61d342b 100644 --- a/libs/libmod/src/mod/lib/IO/Derivation.cpp +++ b/libs/libmod/src/mod/lib/DG/IO/WriteDerivation.cpp @@ -1,47 +1,46 @@ -#include "Derivation.hpp" +#include "Write.hpp" #include #include #include -#include +#include #include #include #include #include #include #include +#include #include -#include -#include #include #include #include #include #include #include -#include +#include #include #include -namespace mod::lib::IO::Derivation::Write { +namespace mod::lib::DG::Write { namespace { namespace GM = jla_boost::GraphMorphism; -std::vector findCompleteRules(const lib::DG::NonHyper &dg, - lib::DG::HyperVertex v, +std::vector findCompleteRules(const NonHyper &dg, + HyperVertex v, const lib::Rules::Real &rReal) { - const lib::DG::HyperGraphType &dgGraph = dg.getHyper().getGraph(); + const HyperGraphType &dgGraph = dg.getHyper().getGraph(); assert(v != dgGraph.null_vertex()); - using Vertex = lib::DG::HyperVertex; - assert(dgGraph[v].kind == lib::DG::HyperVertexKind::Edge); + using Vertex = HyperVertex; + assert(dgGraph[v].kind == HyperVertexKind::Edge); LabelledUnionGraph eductUnion, productUnion; - for(const auto vAdj : asRange(inv_adjacent_vertices(v, dgGraph))) + for(const auto vAdj: asRange(inv_adjacent_vertices(v, dgGraph))) eductUnion.push_back(&dgGraph[vAdj].graph->getLabelledGraph()); - for(const auto vAdj : asRange(adjacent_vertices(v, dgGraph))) + for(const auto vAdj: asRange(adjacent_vertices(v, dgGraph))) productUnion.push_back(&dgGraph[vAdj].graph->getLabelledGraph()); - const auto identifyL = lib::Rules::graphToRule(eductUnion, lib::Rules::Membership::Context, "G"); - const auto identifyR = lib::Rules::graphToRule(productUnion, lib::Rules::Membership::Context, "H"); + const auto identifyL = lib::Rules::graphToRule(eductUnion, lib::Rules::Membership::K, "G"); + const auto identifyR = lib::Rules::graphToRule(productUnion, lib::Rules::Membership::K, "H"); std::vector matchingLR; { std::vector matchingL; @@ -49,7 +48,7 @@ std::vector findCompleteRules(const lib::DG::NonHyper &dg, if(getConfig().dg.derivationDebugOutput.get()) { std::cout << "Derivation: compose identifyL -> rReal" << std::endl; std::cout << "Derivation: eductUnion:" << std::endl; - for(const auto vAdj : asRange(inv_adjacent_vertices(v, dgGraph))) + for(const auto vAdj: asRange(inv_adjacent_vertices(v, dgGraph))) std::cout << "Derivation: " << dgGraph[vAdj].graph->getName() << std::endl; } auto reporter = [&matchingL, &dg](std::unique_ptr r) { @@ -61,13 +60,13 @@ std::vector findCompleteRules(const lib::DG::NonHyper &dg, return true; }; if(getConfig().dg.derivationDebugOutput.get()) - lib::IO::Rules::Write::termState(rReal); + Rules::Write::termState(rReal); lib::RC::Super mm( getConfig().dg.derivationVerbosity.get(), IO::Logger(std::cout), false, true); lib::RC::composeRuleRealByMatchMaker(*identifyL, rReal, mm, reporter, dg.getLabelSettings()); } - for(auto *r : matchingL) { + for(auto *r: matchingL) { if(getConfig().dg.derivationDebugOutput.get()) std::cout << "Derivation: compose matchingL -> identifyR" << std::endl; auto reporter = [&matchingLR, &dg](std::unique_ptr r) { @@ -95,10 +94,10 @@ std::vector findCompleteRules(const lib::DG::NonHyper &dg, } template -void forEachMatch(const lib::DG::NonHyper &dg, lib::DG::HyperVertex v, const lib::Rules::Real &rReal, F f) { +void forEachMatch(const NonHyper &dg, HyperVertex v, const lib::Rules::Real &rReal, F f) { bool derivationFound = false; std::vector matchingLR = findCompleteRules(dg, v, rReal); - for(const lib::Rules::Real *rLower : matchingLR) { + for(const lib::Rules::Real *rLower: matchingLR) { auto mr = [&f, &derivationFound, rLower, matchCount = 0](auto &&m, const lib::Rules::GraphType &gUpper, const lib::Rules::GraphType &gLower) mutable { @@ -132,43 +131,41 @@ void forEachMatch(const lib::DG::NonHyper &dg, lib::DG::HyperVertex v, const lib } // namespace std::vector> -summary(const lib::DG::NonHyper &dg, - lib::DG::HyperVertex v, - const IO::Graph::Write::Options &options, - const std::string &nomatchColour, - const std::string &matchColour) { +summaryDerivation(const NonHyper &dg, + HyperVertex v, + const IO::Graph::Write::Options &options, + const std::string &nomatchColour, + const std::string &matchColour) { const auto &dgHyper = dg.getHyper(); const auto &dgGraph = dgHyper.getGraph(); const auto &rules = dgHyper.getRulesFromEdge(v); assert(!rules.empty()); std::vector> res; - for(const lib::Rules::Real *rPtr : rules) { + for(const lib::Rules::Real *rPtr: rules) { const lib::Rules::Real &rReal = *rPtr; const auto printHeader = [v, &dgGraph, &rReal]() { IO::post() << "summarySectionNoEscape \"Derivation " << get(boost::vertex_index_t(), dgGraph, v); IO::post() << ", $r_{" << rReal.getId() << "}$"; IO::post() << ", \\{"; bool first = true; - for(auto vIn : asRange(inv_adjacent_vertices(v, dgGraph))) { + for(auto vIn: asRange(inv_adjacent_vertices(v, dgGraph))) { if(!first) IO::post() << ", "; else first = false; IO::post() << "\\texttt{\\ensuremath{\\mathrm{" << dgGraph[vIn].graph->getName() << "}}}"; } IO::post() << "\\} \\texttt{->} \\{"; first = true; - for(auto vOut : asRange(adjacent_vertices(v, dgGraph))) { + for(auto vOut: asRange(adjacent_vertices(v, dgGraph))) { if(!first) IO::post() << ", "; else first = false; IO::post() << "\\texttt{\\ensuremath{\\mathrm{" << dgGraph[vOut].graph->getName() << "}}}"; } IO::post() << "\\}\"" << std::endl; { - std::string file = getUniqueFilePrefix() + "der_constraints.tex"; + std::string file = IO::makeUniqueFilePrefix() + "der_constraints.tex"; post::FileHandle s(file); - auto vis = lib::IO::MatchConstraint::Write::makeTexPrintVisitor(s, get_left(rReal.getDPORule())); - for(const auto &c : rReal.getDPORule().leftMatchConstraints) { - c->accept(vis); - } + for(const auto &c: get_match_constraints(get_labelled_left(rReal.getDPORule()))) + lib::GraphMorphism::Write::texConstraint(s, getL(rReal.getDPORule().getRule()), *c); IO::post() << "summaryInput \"" << file << "\"" << std::endl; } }; @@ -207,26 +204,26 @@ summary(const lib::DG::NonHyper &dg, }; using Vertex = boost::graph_traits::vertex_descriptor; std::map map; - for(const auto vUpper : asRange(vertices(gUpper))) + for(const auto vUpper: asRange(vertices(gUpper))) map.emplace(vUpper, get(m, gUpper, gLower, vUpper)); rUpperCopy.getDepictionData().copyCoords(rLower.getDepictionData(), map); - std::string fileNoExt1 = IO::Rules::Write::pdf( + std::string fileNoExt1 = Rules::Write::pdf( rUpperCopy, options, strMatch + "_derL", strMatch + "_derK", strMatch + "_derR", - IO::Rules::Write::BaseArgs{visibleRule, vColourRule, eColourRule}); - std::string fileNoExt2 = IO::Rules::Write::pdf( + Rules::Write::BaseArgs{visibleRule, vColourRule, eColourRule}); + std::string fileNoExt2 = Rules::Write::pdf( rLower, options, strMatch + "_derG", strMatch + "_derD", strMatch + "_derH", - IO::Rules::Write::BaseArgs{visibleInstantiation, vColourInstantiation, eColourInstantiation}); + Rules::Write::BaseArgs{visibleInstantiation, vColourInstantiation, eColourInstantiation}); IO::post() << "summaryDerivation \"" << fileNoExt1 << "_" << strMatch << "\" \"" << fileNoExt2 << "_" << strMatch << "\"" << std::endl; res.emplace_back(fileNoExt1 + "_" + strMatch, fileNoExt2 + "_" + strMatch); } - IO::Rules::Write::gml(rLower, false); + Rules::Write::gml(rLower, false); }; forEachMatch(dg, v, rReal, f); } return res; } -} // namespace mod::lib::IO::Derivation::Write +} // namespace mod::lib::DG::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/DGWriteDetail.hpp b/libs/libmod/src/mod/lib/DG/IO/WriteDetail.hpp similarity index 85% rename from libs/libmod/src/mod/lib/IO/DGWriteDetail.hpp rename to libs/libmod/src/mod/lib/DG/IO/WriteDetail.hpp index 9e0b9be..5a42974 100644 --- a/libs/libmod/src/mod/lib/IO/DGWriteDetail.hpp +++ b/libs/libmod/src/mod/lib/DG/IO/WriteDetail.hpp @@ -1,14 +1,14 @@ -#ifndef MOD_LIB_IO_DGWRITEDETAIL_HPP -#define MOD_LIB_IO_DGWRITEDETAIL_HPP +#ifndef MOD_LIB_DG_IO_WRITEDETAIL_HPP +#define MOD_LIB_DG_IO_WRITEDETAIL_HPP #include -#include +#include #include #include -namespace mod::lib::IO::DG::Write { +namespace mod::lib::DG::Write { template std::string toStr(const T &t) { @@ -17,21 +17,21 @@ std::string toStr(const T &t) { namespace detail { -inline std::string hyperEdgeComment(const lib::DG::Hyper &dg, Vertex v) { +inline std::string hyperEdgeComment(const Hyper &dg, Vertex v) { unsigned int vId = get(boost::vertex_index_t(), dg.getGraph(), v); return "id = " + toStr(vId) + toStr(dg.getDerivation(v)); } template void -forEachVertex(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &print, bool printHeader, Body body) { +forEachVertex(const Hyper &dg, const Options &options, SyntaxPrinter &print, bool printHeader, Body body) { using DupVertex = Options::DupVertex; const auto &g = dg.getGraph(); const auto &dupGraph = options.dupGraph; DupVertex prevVertex = dupGraph.null_vertex(); for(const DupVertex vDup : asRange(vertices(dupGraph))) { const Vertex v = dupGraph[vDup].v; - if(g[v].kind != lib::DG::HyperVertexKind::Vertex) continue; + if(g[v].kind != HyperVertexKind::Vertex) continue; unsigned int vId = get(boost::vertex_index_t(), g, v); const auto &graph = *g[v].graph; if(printHeader && (prevVertex == dupGraph.null_vertex() || dupGraph[prevVertex].v != dupGraph[vDup].v)) { @@ -48,14 +48,14 @@ forEachVertex(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &p } template -void forEachExplicitHyperEdge(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &print, Body body) { +void forEachExplicitHyperEdge(const Hyper &dg, const Options &options, SyntaxPrinter &print, Body body) { using DupVertex = Options::DupVertex; const auto &g = dg.getGraph(); const auto &dupGraph = options.dupGraph; DupVertex prevVertex = dupGraph.null_vertex(); for(const DupVertex vDup : asRange(vertices(dupGraph))) { const Vertex v = dupGraph[vDup].v; - if(g[v].kind != lib::DG::HyperVertexKind::Edge) continue; + if(g[v].kind != HyperVertexKind::Edge) continue; if(!options.isHyperedgeVisible(v, dg)) continue; unsigned int inDegreeVisible = options.inDegreeVisible(vDup, dg).first; unsigned int outDegreeVisible = options.outDegreeVisible(vDup, dg).first; @@ -75,7 +75,7 @@ void forEachExplicitHyperEdge(const lib::DG::Hyper &dg, const Options &options, } template -void forEachConnector(const lib::DG::Hyper &dg, const Options &options, SyntaxPrinter &print, +void forEachConnector(const Hyper &dg, const Options &options, SyntaxPrinter &print, TailBody tailBody, HeadBody headBody, ShortcutBody shortcutBody) { using DupVertex = Options::DupVertex; const auto &g = dg.getGraph(); @@ -83,7 +83,7 @@ void forEachConnector(const lib::DG::Hyper &dg, const Options &options, SyntaxPr DupVertex prevVertex = dupGraph.null_vertex(); for(const DupVertex vDup : asRange(vertices(dupGraph))) { const Vertex v = dupGraph[vDup].v; - if(g[v].kind != lib::DG::HyperVertexKind::Edge) continue; + if(g[v].kind != HyperVertexKind::Edge) continue; if(prevVertex == dupGraph.null_vertex() || dupGraph[prevVertex].v != dupGraph[vDup].v) { print.comment(detail::hyperEdgeComment(dg, v)); } else { @@ -146,6 +146,6 @@ void forEachConnector(const lib::DG::Hyper &dg, const Options &options, SyntaxPr } } // namespace detail -} // namespace namespace mod::lib::IO::DG::Write +} // namespace namespace mod::lib::DG::Write -#endif // MOD_LIB_IO_DGWRITEDETAIL_HPP \ No newline at end of file +#endif // MOD_LIB_DG_IO_WRITEDETAIL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DG/NonHyper.cpp b/libs/libmod/src/mod/lib/DG/NonHyper.cpp index bd8d57d..7a2ea6a 100644 --- a/libs/libmod/src/mod/lib/DG/NonHyper.cpp +++ b/libs/libmod/src/mod/lib/DG/NonHyper.cpp @@ -13,12 +13,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include @@ -190,6 +190,8 @@ std::pair NonHyper::isDerivation(const GraphMultiset &gmsS std::pair NonHyper::suggestDerivation( const GraphMultiset &gmsSrc, const GraphMultiset &gmsTar, const lib::Rules::Real *r) { + assert(!gmsSrc.empty()); + assert(!gmsTar.empty()); // make vertices for to and from Vertex vSrc = getVertex(gmsSrc), vTar = getVertex(gmsTar); std::pair e = edge(vSrc, vTar, dg); @@ -255,7 +257,7 @@ const std::vector > &NonHyper::getProducts() const void NonHyper::print() const { if(!getHasCalculated()) std::abort(); - std::string fileNoExt = IO::DG::Write::pdfNonHyper(*this); + std::string fileNoExt = Write::pdfNonHyper(*this); IO::post() << "summaryDGNonHyper \"dg_" << getId() << "\" \"" << fileNoExt << "\"" << std::endl; } diff --git a/libs/libmod/src/mod/lib/DG/NonHyper.hpp b/libs/libmod/src/mod/lib/DG/NonHyper.hpp index b086a53..0f50c2a 100644 --- a/libs/libmod/src/mod/lib/DG/NonHyper.hpp +++ b/libs/libmod/src/mod/lib/DG/NonHyper.hpp @@ -22,10 +22,10 @@ namespace mod::lib::Graph { struct PropString; } // namespace mod::lib::Graph namespace mod::lib::DG { -class HyperCreator; +struct HyperCreator; -class NonHyper { - friend class HyperCreator; +struct NonHyper { + friend struct HyperCreator; public: using GraphType = NonHyperGraphType; using Vertex = NonHyperVertex; diff --git a/libs/libmod/src/mod/lib/DG/NonHyperBuilder.cpp b/libs/libmod/src/mod/lib/DG/NonHyperBuilder.cpp index 48e002f..5392bc5 100644 --- a/libs/libmod/src/mod/lib/DG/NonHyperBuilder.cpp +++ b/libs/libmod/src/mod/lib/DG/NonHyperBuilder.cpp @@ -7,11 +7,12 @@ #include #include #include +#include +#include #include #include -#include #include -#include +#include #include #include @@ -64,16 +65,16 @@ std::pair Builder::addDerivation(const Derivations &d, Iso // add graphs switch(graphPolicy) { case IsomorphismPolicy::Check: { - for(const auto &g : d.left) + for(const auto &g: d.left) dg->tryAddGraph(g); - for(const auto &g : d.right) + for(const auto &g: d.right) dg->tryAddGraph(g); break; } case IsomorphismPolicy::TrustMe: - for(const auto &g : d.left) + for(const auto &g: d.left) dg->trustAddGraph(g); - for(const auto &g : d.right) + for(const auto &g: d.right) dg->trustAddGraph(g); break; } @@ -81,7 +82,7 @@ std::pair Builder::addDerivation(const Derivations &d, Iso const auto makeSide = [](const mod::Derivation::GraphList &graphs) -> GraphMultiset { std::vector gPtrs; gPtrs.reserve(graphs.size()); - for(const auto &g : graphs) gPtrs.push_back(&g->getGraph()); + for(const auto &g: graphs) gPtrs.push_back(&g->getGraph()); return GraphMultiset(std::move(gPtrs)); }; auto gmsLeft = makeSide(d.left); @@ -93,7 +94,7 @@ std::pair Builder::addDerivation(const Derivations &d, Iso return dg->suggestDerivation(std::move(gmsLeft), std::move(gmsRight), rule); } else { auto res = dg->suggestDerivation(gmsLeft, gmsRight, &d.rules.front()->getRule()); - for(const auto &r : asRange(d.rules.begin() + 1, d.rules.end())) + for(const auto &r: asRange(d.rules.begin() + 1, d.rules.end())) dg->suggestDerivation(gmsLeft, gmsRight, &r->getRule()); return res; } @@ -120,7 +121,7 @@ struct NonHyperBuilder::ExecutionEnv final : public Strategies::ExecutionEnv { } bool checkLeftPredicate(const mod::Derivation &d) const override { - for(const auto &pred : asRange(leftPredicates.rbegin(), leftPredicates.rend())) { + for(const auto &pred: asRange(leftPredicates.rbegin(), leftPredicates.rend())) { bool result = (*pred)(d); if(!result) return false; } @@ -128,7 +129,7 @@ struct NonHyperBuilder::ExecutionEnv final : public Strategies::ExecutionEnv { } bool checkRightPredicate(const mod::Derivation &d) const override { - for(const auto &pred : asRange(rightPredicates.rbegin(), rightPredicates.rend())) { + for(const auto &pred: asRange(rightPredicates.rbegin(), rightPredicates.rend())) { bool result = (*pred)(d); if(!result) return false; } @@ -233,11 +234,11 @@ Builder::apply(const std::vector> &graphs, dg->rules.insert(rOrig); switch(graphPolicy) { case IsomorphismPolicy::Check: - for(const auto &g : graphs) + for(const auto &g: graphs) dg->tryAddGraph(g); break; case IsomorphismPolicy::TrustMe: - for(const auto &g : graphs) + for(const auto &g: graphs) dg->trustAddGraph(g); break; } @@ -251,7 +252,7 @@ Builder::apply(const std::vector> &graphs, if(graphs.empty()) return {}; std::vector libGraphs; libGraphs.reserve(graphs.size()); - for(const auto &g : graphs) + for(const auto &g: graphs) libGraphs.push_back(&g->getGraph()); std::vector resultRules; @@ -287,13 +288,13 @@ Builder::apply(const std::vector> &graphs, firstGraph, firstGraph + round + 1, inputRules, dg->graphAsRuleCache, ls, onOutput); - for(BoundRule &br : outputRules) { + for(BoundRule &br: outputRules) { // always go to the next graph ++br.nextGraphOffset; } if(round != 0) { // in round 0 the inputRules is the actual original input rule, so don't delete it - for(auto &br : inputRules) + for(auto &br: inputRules) delete br.rule; } std::swap(inputRules, outputRules); @@ -303,10 +304,14 @@ Builder::apply(const std::vector> &graphs, --logger.indentLevel; } } // for each round + // after the last round we may still have rules with connected components in L + // which go unused, so delete them + for(auto &br: inputRules) + delete br.rule; } // end of binding std::vector> res; - for(const BoundRule &br : resultRules) { + for(const BoundRule &br: resultRules) { if(getConfig().dg.applyLimit.get() == res.size()) break; const auto &r = *br.rule; @@ -326,18 +331,26 @@ Builder::apply(const std::vector> &graphs, } } ); - for(const auto &p : products) + if(products.empty()) { + if(verbosity >= V_RuleApplication) { + ++logger.indentLevel; + logger.indent() << "Discarding derivation, empty result." << std::endl; + --logger.indentLevel; + } + continue; + } + for(const auto &p: products) dg->addProduct(p); std::vector rightGraphs; rightGraphs.reserve(products.size()); - for(const auto &p : products) + for(const auto &p: products) rightGraphs.push_back(&p->getGraph()); lib::DG::GraphMultiset gmsLeft(br.boundGraphs), gmsRight(std::move(rightGraphs)); const auto derivationRes = dg->suggestDerivation(gmsLeft, gmsRight, &rOrig->getRule()); res.push_back(derivationRes); } - for(const auto &br : resultRules) + for(const auto &br: resultRules) delete br.rule; return res; @@ -347,16 +360,15 @@ std::vector> Builder::applyRelaxed(const std::vector> &graphs, std::shared_ptr rOrig, int verbosity, IsomorphismPolicy graphPolicy) { - IO::Logger logger(std::cout); dg->rules.insert(rOrig); switch(graphPolicy) { case IsomorphismPolicy::Check: - for(const auto &g : graphs) + for(const auto &g: graphs) dg->tryAddGraph(g); break; case IsomorphismPolicy::TrustMe: - for(const auto &g : graphs) + for(const auto &g: graphs) dg->trustAddGraph(g); break; } @@ -370,7 +382,7 @@ Builder::applyRelaxed(const std::vector> &graphs, if(graphs.empty()) return {}; std::vector libGraphs; libGraphs.reserve(graphs.size()); - for(const auto &g : graphs) + for(const auto &g: graphs) libGraphs.push_back(&g->getGraph()); const auto ls = dg->getLabelSettings(); @@ -390,7 +402,8 @@ Builder::applyRelaxed(const std::vector> &graphs, const auto &r = *br.rule; if(verbosity >= V_RuleApplication_Binding) { logger.indent() << "Splitting " << r.getName() << " into " - << r.getDPORule().numRightComponents << " graphs" << std::endl; + << get_num_connected_components(get_labelled_right(r.getDPORule())) + << " graphs" << std::endl; ++logger.indentLevel; } @@ -410,11 +423,20 @@ Builder::applyRelaxed(const std::vector> &graphs, } } ); - for(const auto &p : products) + if(products.empty()) { + if(verbosity >= V_RuleApplication) { + ++logger.indentLevel; + logger.indent() << "Discarding derivation, empty result." << std::endl; + --logger.indentLevel; + } + delete br.rule; + return true; + } + for(const auto &p: products) dg->addProduct(p); std::vector rightGraphs; rightGraphs.reserve(products.size()); - for(const auto &p : products) + for(const auto &p: products) rightGraphs.push_back(&p->getGraph()); lib::DG::GraphMultiset gmsLeft(br.boundGraphs), gmsRight(std::move(rightGraphs)); const auto derivationRes = dg->suggestDerivation(gmsLeft, gmsRight, &rOrig->getRule()); @@ -432,28 +454,32 @@ Builder::applyRelaxed(const std::vector> &graphs, firstGraph, lastGraph, inputRules, dg->graphAsRuleCache, ls, onOutput); - for(BoundRule &br : outputRules) { + for(BoundRule &br: outputRules) { // always go to the next graph ++br.nextGraphOffset; } if(round != 0) { // in round 0 the inputRules is the actual original input rule, so don't delete it - for(auto &br : inputRules) + for(auto &br: inputRules) delete br.rule; } std::swap(inputRules, outputRules); } // for each round based on numComponents + // the last round should not produce any results with non-empty L, + // as we do exactly |CC(L)| number of rounds. + if(rOrig->getNumLeftComponents() != 0) + assert(inputRules.empty()); return res; } void Builder::addAbstract(const std::string &description) { std::ostringstream err; - auto res = lib::IO::DG::Read::abstract(description, err); + auto res = lib::DG::Read::abstract(description, err); if(!res) throw InputError("Could not parse description of abstract derivations.\n" + err.str()); const auto &derivations = *res; std::unordered_map > strToGraph; - const auto handleSide = [this, &strToGraph](const lib::IO::DG::Read::AbstractDerivation::List &side) { - for(const auto &e : side) { + const auto handleSide = [this, &strToGraph](const lib::DG::Read::AbstractDerivation::List &side) { + for(const auto &e: side) { const auto iter = strToGraph.find(e.second); if(iter != end(strToGraph)) continue; auto gBoost = std::make_unique(); @@ -465,15 +491,15 @@ void Builder::addAbstract(const std::string &description) { strToGraph[e.second] = g; } }; - for(const auto &der : derivations) { + for(const auto &der: derivations) { handleSide(der.left); handleSide(der.right); } using Side = std::unordered_map, unsigned int>; - const auto makeSide = [&strToGraph](const lib::IO::DG::Read::AbstractDerivation::List &side) { + const auto makeSide = [&strToGraph](const lib::DG::Read::AbstractDerivation::List &side) { Side result; - for(const auto &e : side) { + for(const auto &e: side) { const auto g = strToGraph[e.second]; assert(g); auto iter = result.find(g); @@ -482,15 +508,15 @@ void Builder::addAbstract(const std::string &description) { } return result; }; - for(const auto &der : derivations) { + for(const auto &der: derivations) { const Side left = makeSide(der.left); const Side right = makeSide(der.right); std::vector leftGraphs, rightGraphs; - for(const auto &e : left) { + for(const auto &e: left) { for(unsigned int i = 0; i < e.second; i++) leftGraphs.push_back(&e.first->getGraph()); } - for(const auto &e : right) { + for(const auto &e: right) { for(unsigned int i = 0; i < e.second; i++) rightGraphs.push_back(&e.first->getGraph()); } @@ -517,7 +543,7 @@ bool Builder::load(const std::vector> &ruleDatabase, return false; } ifs.close(); - auto jOpt = lib::IO::DG::Read::loadDump(file, err); + auto jOpt = lib::DG::Read::loadDump(file, err); if(!jOpt) return {}; auto &j = *jOpt; @@ -551,12 +577,12 @@ bool Builder::trustLoadDump(nlohmann::json &&j, auto &jVertices = j["vertices"]; std::vector vertices; vertices.reserve(jVertices.size()); - for(auto &jv : jVertices) { + for(auto &jv: jVertices) { Vertex v; v.id = jv[0].get(); const std::string &gml = jv[2].get(); lib::IO::Warnings warnings; - auto gDatasRes = lib::IO::Graph::Read::gml(warnings, gml); + auto gDatasRes = lib::Graph::Read::gml(warnings, gml); err << warnings; if(!gDatasRes) { err << gDatasRes.extractError() << '\n'; @@ -586,7 +612,7 @@ bool Builder::trustLoadDump(nlohmann::json &&j, std::vector> rules; rules.reserve(jRules.size()); const auto ls = dg->getLabelSettings(); - for(const auto &j : jRules) { + for(const auto &j: jRules) { const std::string &jr = j.get(); auto rCand = rule::Rule::fromGMLString(jr, false); const auto iter = std::find_if(ruleDatabase.begin(), ruleDatabase.end(), [rCand, ls](const auto &r) { @@ -628,7 +654,7 @@ bool Builder::trustLoadDump(nlohmann::json &&j, std::vector srcGraphs, tarGraphs; srcGraphs.reserve(e[1].size()); tarGraphs.reserve(e[2].size()); - for(int src : e[1]) { + for(int src: e[1]) { auto gIter = graphFromId.find(src); if(gIter == end(graphFromId)) { err << "Corrupt data for edge " << e[0].get() << ". Source " << src << " is not a yet a vertex."; @@ -636,7 +662,7 @@ bool Builder::trustLoadDump(nlohmann::json &&j, } srcGraphs.push_back(gIter->second); } - for(int tar : e[2]) { + for(int tar: e[2]) { auto gIter = graphFromId.find(tar); if(gIter == end(graphFromId)) { err << "Corrupt data for edge " << e[0].get() << ". Target " << tar << " is not a yet a vertex."; @@ -649,7 +675,7 @@ bool Builder::trustLoadDump(nlohmann::json &&j, if(ruleIds.empty()) { dg->suggestDerivation(std::move(gmsSrc), std::move(gmsTar), nullptr); } else { - for(const int rId : ruleIds) { + for(const int rId: ruleIds) { if(rId < 0 || rId >= rules.size()) { err << "Corrupt data for edge " << e[0].get() << ". Rule offset " << rId << " is not in range."; return false; diff --git a/libs/libmod/src/mod/lib/DG/NonHyperBuilder.hpp b/libs/libmod/src/mod/lib/DG/NonHyperBuilder.hpp index 98ed712..f8f00bf 100644 --- a/libs/libmod/src/mod/lib/DG/NonHyperBuilder.hpp +++ b/libs/libmod/src/mod/lib/DG/NonHyperBuilder.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include namespace mod::lib::DG { diff --git a/libs/libmod/src/mod/lib/DG/RuleApplicationUtils.hpp b/libs/libmod/src/mod/lib/DG/RuleApplicationUtils.hpp index 4f6345f..c2dfd4c 100644 --- a/libs/libmod/src/mod/lib/DG/RuleApplicationUtils.hpp +++ b/libs/libmod/src/mod/lib/DG/RuleApplicationUtils.hpp @@ -185,7 +185,7 @@ struct BoundRuleStorage { logger.indent() << "BoundRules: added <" << r->getName() << ", {"; for(const auto *g : p.boundGraphs) logger.s << " " << g->getName(); - logger.s << " }> onlyRight: " << std::boolalpha << r->isOnlySide(lib::Rules::Membership::Right) + logger.s << " }> onlyRight: " << std::boolalpha << r->isOnlySide(lib::Rules::Membership::R) << std::endl; } } @@ -207,10 +207,9 @@ struct BoundRuleStorage { // =========================================================================== struct GraphData { - using SideVertex = boost::graph_traits::vertex_descriptor; + using SideVertex = boost::graph_traits::vertex_descriptor; public: GraphData() : gPtr(new lib::Graph::GraphType()), pStringPtr(new lib::Graph::PropString(*gPtr)) {} - public: std::unique_ptr gPtr; std::unique_ptr pStringPtr; @@ -224,15 +223,15 @@ std::vector> splitRule(const lib::Rules::LabelledR const bool withStereo, CheckIfNew checkIfNew, OnDup onDup) { - if(rDPO.numRightComponents == 0) MOD_ABORT; // continue; + if(get_num_connected_components(get_labelled_right(rDPO)) == 0) return {}; using Vertex = lib::Graph::Vertex; using Edge = lib::Graph::Edge; - using SideVertex = boost::graph_traits::vertex_descriptor; - using SideEdge = boost::graph_traits::edge_descriptor; + using SideVertex = boost::graph_traits::vertex_descriptor; + using SideEdge = boost::graph_traits::edge_descriptor; - std::vector products(rDPO.numRightComponents); - const auto &compMap = rDPO.rightComponents; - const auto &gRight = get_right(rDPO); + std::vector products(get_num_connected_components(get_labelled_right(rDPO))); + const auto &compMap = get_component(get_labelled_right(rDPO)); + const auto &gRight = get_R_projected(rDPO); auto rpString = get_string(get_labelled_right(rDPO)); assert(num_vertices(gRight) == num_vertices(get_graph(rDPO))); std::vector vertexMap(num_vertices(gRight)); diff --git a/libs/libmod/src/mod/lib/DG/Strategies/Rule.cpp b/libs/libmod/src/mod/lib/DG/Strategies/Rule.cpp index b4e057f..b9915c6 100644 --- a/libs/libmod/src/mod/lib/DG/Strategies/Rule.cpp +++ b/libs/libmod/src/mod/lib/DG/Strategies/Rule.cpp @@ -13,14 +13,17 @@ namespace mod::lib::DG::Strategies { Rule::Rule(std::shared_ptr r) - : Strategy(std::max(r->getRule().getDPORule().numLeftComponents, r->getRule().getDPORule().numRightComponents)), + : Strategy(std::max(get_num_connected_components(get_labelled_left(r->getRule().getDPORule())), + get_num_connected_components(get_labelled_right(r->getRule().getDPORule())))), r(r), rRaw(&r->getRule()) { - assert(rRaw->getDPORule().numLeftComponents > 0); + assert(get_num_connected_components(get_labelled_left(rRaw->getDPORule())) > 0); } Rule::Rule(const lib::Rules::Real *r) - : Strategy(std::max(r->getDPORule().numLeftComponents, r->getDPORule().numRightComponents)), rRaw(r) { - assert(r->getDPORule().numLeftComponents > 0); + : Strategy(std::max(get_num_connected_components(get_labelled_left(r->getDPORule())), + get_num_connected_components(get_labelled_right(r->getDPORule())))), + rRaw(r) { + assert(get_num_connected_components(get_labelled_left(rRaw->getDPORule())) > 0); } std::unique_ptr Rule::clone() const { @@ -41,7 +44,7 @@ void Rule::printInfo(PrintSettings settings) const { settings.indent() << "consumed ="; std::vector temp(begin(consumedGraphs), end(consumedGraphs)); std::sort(begin(temp), end(temp), lib::Graph::Single::nameLess); - for(const auto *g : temp) + for(const auto *g: temp) settings.s << " " << g->getName(); settings.s << '\n'; } @@ -68,7 +71,7 @@ void handleBoundRulePair(int verbosity, IO::Logger logger, Context context, cons // All max component results should be only right side mod::Derivation d; d.r = context.r; - for(const lib::Graph::Single *g : brp.boundGraphs) d.left.push_back(g->getAPIReference()); + for(const lib::Graph::Single *g: brp.boundGraphs) d.left.push_back(g->getAPIReference()); { // left predicate bool result = context.executionEnv.checkLeftPredicate(d); if(!result) { @@ -79,7 +82,8 @@ void handleBoundRulePair(int verbosity, IO::Logger logger, Context context, cons } if(verbosity >= PrintSettings::V_RuleApplication) { logger.indent() << "Splitting " << r.getName() << " into " - << rDPO.numRightComponents << " graphs" << std::endl; + << get_num_connected_components(get_labelled_right(rDPO)) + << " graphs" << std::endl; ++logger.indentLevel; } const std::vector &educts = brp.boundGraphs; @@ -97,6 +101,14 @@ void handleBoundRulePair(int verbosity, IO::Logger logger, Context context, cons ); if(verbosity >= PrintSettings::V_RuleApplication) --logger.indentLevel; + if(d.right.empty()) { + if(verbosity >= V_RuleApplication) { + ++logger.indentLevel; + logger.indent() << "Discarding derivation, empty result." << std::endl; + --logger.indentLevel; + } + return; + } { // right predicates bool result = context.executionEnv.checkRightPredicate(d); @@ -108,24 +120,24 @@ void handleBoundRulePair(int verbosity, IO::Logger logger, Context context, cons } { // now the derivation is good, so add the products to output if(getConfig().dg.putAllProductsInSubset.get()) { - for(const auto &g : d.right) + for(const auto &g: d.right) context.output->addToSubset(&g->getGraph()); } else { - for(const auto &g : d.right) + for(const auto &g: d.right) if(!context.output->isInUniverse(&g->getGraph())) context.output->addToSubset(&g->getGraph()); } - for(const auto &g : d.right) + for(const auto &g: d.right) context.executionEnv.addProduct(g); } std::vector rightGraphs; rightGraphs.reserve(d.right.size()); - for(const std::shared_ptr &g : d.right) + for(const std::shared_ptr &g: d.right) rightGraphs.push_back(&g->getGraph()); lib::DG::GraphMultiset gmsLeft(educts), gmsRight(std::move(rightGraphs)); bool inserted = context.executionEnv.suggestDerivation(gmsLeft, gmsRight, &context.r->getRule()); if(inserted) { - for(const lib::Graph::Single *g : educts) + for(const lib::Graph::Single *g: educts) context.consumedGraphs.insert(g); } } @@ -138,9 +150,9 @@ unsigned int bindGraphs(PrintSettings settings, Context context, Rules::GraphAsRuleCache &graphAsRuleCache) { unsigned int processedRules = 0; - for(const lib::Graph::Single *g : graphRange) { + for(const lib::Graph::Single *g: graphRange) { if(context.executionEnv.doExit()) break; - for(const BoundRule &p : rules) { + for(const BoundRule &p: rules) { if(context.executionEnv.doExit()) break; if(settings.verbosity >= PrintSettings::V_RuleApplication) { settings.indent() << "Trying to bind " << g->getName() << " to " << p.rule->getName() << ":" << std::endl; @@ -163,7 +175,7 @@ unsigned int bindGraphs(PrintSettings settings, Context context, settings, true, true); lib::RC::composeRuleRealByMatchMaker(rFirst, rSecond, mm, reporter, context.executionEnv.labelSettings); - for(const BoundRule &brp : resultRules) { + for(const BoundRule &brp: resultRules) { processedRules++; if(context.executionEnv.doExit()) delete brp.rule; else if(brp.rule->isOnlyRightSide()) { @@ -213,7 +225,8 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { } if(getConfig().dg.useOldRuleApplication.get()) { - std::vector> intermediaryRules(rRaw->getDPORule().numLeftComponents + 1); + std::vector> + intermediaryRules(get_num_connected_components(get_labelled_left(rRaw->getDPORule())) + 1); { BoundRule p; p.rule = rRaw; @@ -222,7 +235,7 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { Context context{r, getExecutionEnv(), output, consumedGraphs}; const auto &subset = input.getSubset(); const auto &universe = input.getUniverse(); - for(unsigned int i = 1; i <= rRaw->getDPORule().numLeftComponents; i++) { + for(unsigned int i = 1; i <= get_num_connected_components(get_labelled_left(rRaw->getDPORule())); i++) { if(settings.verbosity >= PrintSettings::V_RuleBinding) { settings.indent() << "Component bind round " << i << " with "; ++settings.indentLevel; @@ -241,7 +254,7 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { } else { processedRules = bindGraphs(settings, context, universe, intermediaryRules[i - 1], intermediaryRules[i], getExecutionEnv().graphAsRuleCache); - for(BoundRule &p : intermediaryRules[i - 1]) { + for(BoundRule &p: intermediaryRules[i - 1]) { delete p.rule; p.rule = nullptr; } @@ -262,7 +275,7 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { assert(subset.begin()[i] == universe[subset.getIndices()[i]]); std::vector inSubset(universe.size(), false); - for(int idx : subset.getIndices()) + for(int idx: subset.getIndices()) inSubset[idx] = true; std::vector graphs = universe; @@ -278,7 +291,7 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { Context context{r, getExecutionEnv(), output, consumedGraphs}; std::vector inputRules{{rRaw, {}, 0}}; - for(int round = 0; round != rRaw->getDPORule().numLeftComponents; ++round) { + for(int round = 0; round != get_num_connected_components(get_labelled_left(rRaw->getDPORule())); ++round) { const auto firstGraph = graphs.begin(); const auto lastGraph = round == 0 ? subsetEnd : graphs.end(); @@ -299,7 +312,7 @@ void Rule::executeImpl(PrintSettings settings, const GraphState &input) { onOutput); if(round != 0) { // in round 0 the inputRules is the actual original input rule, so don't delete it - for(auto &br : inputRules) + for(auto &br: inputRules) delete br.rule; } std::swap(inputRules, outputRules); diff --git a/libs/libmod/src/mod/lib/DPO/CombinedRule.cpp b/libs/libmod/src/mod/lib/DPO/CombinedRule.cpp new file mode 100644 index 0000000..90af537 --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/CombinedRule.cpp @@ -0,0 +1,129 @@ +#include "CombinedRule.hpp" + +#include + +namespace mod::lib::DPO { + +BOOST_CONCEPT_ASSERT((RuleConcept)); + +CombinedRule::CombinedRule() : gProjectedK(gCombined, Membership::K), + gProjectedL(gCombined, Membership::L), + gProjectedR(gCombined, Membership::R), + mL(getK(*this), getL(*this)), + mR(getK(*this), getR(*this)), + mKtoCG(getK(*this), gCombined), + mLtoCG(getL(*this), gCombined), + mRtoCG(getR(*this), gCombined) {} + +const CombinedRule::SideGraphType &getL(const CombinedRule &r) { + return r.gProjectedL; +} + +const CombinedRule::KGraphType &getK(const CombinedRule &r) { return r.gProjectedK; } + +const CombinedRule::SideGraphType &getR(const CombinedRule &r) { + return r.gProjectedR; +} + +const CombinedRule::MorphismType &getMorL(const CombinedRule &r) { return r.mL; } +const CombinedRule::MorphismType &getMorR(const CombinedRule &r) { return r.mR; } + +// ====================================================================================== + +void invert(CombinedRule &r) { + auto &g = r.gCombined; + for(const auto v: asRange(vertices(g))) + g[v].membership = lib::DPO::invert(g[v].membership); + for(const auto e: asRange(edges(g))) + g[e].membership = lib::DPO::invert(g[e].membership); + using std::swap; + // don't swap projected, it acts as a reference type + swap(r.mL, r.mR); + swap(r.mLtoCG, r.mRtoCG); +} + +CombinedRule::SideVertex addVertexL(CombinedRule &r) { + const auto vCG = add_vertex(r.gCombined); + r.gCombined[vCG].membership = Membership::L; + return vCG; +} + +CombinedRule::KVertex addVertexK(CombinedRule &r) { + const auto vCG = add_vertex(r.gCombined); + r.gCombined[vCG].membership = Membership::K; + return vCG; +} + +CombinedRule::SideVertex addVertexR(CombinedRule &r) { + const auto vCG = add_vertex(r.gCombined); + r.gCombined[vCG].membership = Membership::R; + return vCG; +} + +CombinedRule::SideVertex promoteVertexL(CombinedRule &r, CombinedRule::SideVertex vL) { + const auto vCG = get(r.getLtoCG(), getL(r), r.getCombinedGraph(), vL); + assert(r.gCombined[vCG].membership == Membership::L); + r.gCombined[vCG].membership = Membership::K; + return vCG; +} + +CombinedRule::SideEdge addEdgeL(CombinedRule &r, CombinedRule::SideVertex v1, CombinedRule::SideVertex v2) { + const auto vc1 = get(r.getLtoCG(), getL(r), r.getCombinedGraph(), v1); + const auto vc2 = get(r.getLtoCG(), getL(r), r.getCombinedGraph(), v2); + const auto[eCG, addedCG] = add_edge(vc1, vc2, r.gCombined); + assert(addedCG); + r.gCombined[eCG].membership = Membership::L; + return eCG; +} + +CombinedRule::KEdge addEdgeK(CombinedRule &r, CombinedRule::KVertex v1, CombinedRule::KVertex v2) { + const auto[eCG, added] = add_edge(v1, v2, r.gCombined); + assert(added); + r.gCombined[eCG].membership = Membership::K; + return eCG; +} + +CombinedRule::SideEdge addEdgeR(CombinedRule &r, CombinedRule::SideVertex v1, CombinedRule::SideVertex v2) { + const auto vc1 = get(r.getRtoCG(), getR(r), r.getCombinedGraph(), v1); + const auto vc2 = get(r.getRtoCG(), getR(r), r.getCombinedGraph(), v2); + const auto[eCG, addedCG] = add_edge(vc1, vc2, r.gCombined); + assert(addedCG); + r.gCombined[eCG].membership = Membership::R; + return eCG; +} + +// ====================================================================================== + +CombinedRule::CombinedGraphType &CombinedRule::getCombinedGraph() { + return gCombined; +} + +const CombinedRule::CombinedGraphType &CombinedRule::getCombinedGraph() const { + return gCombined; +} + +CombinedRule::MembershipPropertyMap CombinedRule::makeMembershipPropertyMap() const { + return {*this}; +} + +const CombinedRule::ToCombinedMorphismSide &CombinedRule::getLtoCG() const { return mLtoCG; } +const CombinedRule::ToCombinedMorphismK &CombinedRule::getKtoCG() const { return mKtoCG; } +const CombinedRule::ToCombinedMorphismSide &CombinedRule::getRtoCG() const { return mRtoCG; } + +// ========================================================================================= + +const CombinedRule::SideProjectedGraphType &CombinedRule::getLProjected() const { + return gProjectedL; +} + +const CombinedRule::KProjectedGraphType &CombinedRule::getKProjected() const { + return gProjectedK; +} + +const CombinedRule::SideProjectedGraphType &CombinedRule::getRProjected() const { + return gProjectedR; +} + +} // namespace mod::lib::DPO + +BOOST_CONCEPT_ASSERT((mod::lib::DPO::WritableRuleConcept)); \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DPO/CombinedRule.hpp b/libs/libmod/src/mod/lib/DPO/CombinedRule.hpp new file mode 100644 index 0000000..be0dc3e --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/CombinedRule.hpp @@ -0,0 +1,157 @@ +#ifndef MOD_LIB_DPO_COMBINEDRULE_HPP +#define MOD_LIB_DPO_COMBINEDRULE_HPP + +#include + +#include +#include +#include + +namespace mod::lib::DPO { + +// A DPO rule model which contains a combined graph of the L <- K -> R span. +// That is, besides getting views of L, K, and R, there is a view which represents +// the pushout object C of the rule span. +// There are two views of L and R: an ordinary one, and then a projected one. +// The projected views are adaptations of the combined view C, i.e., +// vertex/edge descriptors from LProjected and RProjected are valid in C. +// The K-view is currently also just a projected view. +struct CombinedRule { +public: // CombinedGraph + struct CombinedVProp { + Membership membership; + }; + + struct CombinedEProp { + Membership membership; + }; + + using CombinedGraphType = jla_boost::EdgeIndexedAdjacencyList; + using CombinedVertex = boost::graph_traits::vertex_descriptor; + using CombinedEdge = boost::graph_traits::edge_descriptor; + + struct MembershipPropertyMap { + MembershipPropertyMap(const CombinedRule &r) : g(r.getCombinedGraph()) {} + friend Membership get(MembershipPropertyMap m, CombinedVertex v) { + return get(&CombinedVProp::membership, m.g, v); + } + + friend Membership get(MembershipPropertyMap m, CombinedEdge e) { + return get(&CombinedEProp::membership, m.g, e); + } + public: + const CombinedGraphType &g; + }; +public: + using SideProjectedGraphType = lib::DPO::FilteredGraphProjection; + using KProjectedGraphType = lib::DPO::FilteredGraphProjection; +public: // RuleConcept + using SideGraphType = SideProjectedGraphType; + using SideVertex = boost::graph_traits::vertex_descriptor; + using SideEdge = boost::graph_traits::edge_descriptor; + using KGraphType = KProjectedGraphType; + using KVertex = boost::graph_traits::vertex_descriptor; + using KEdge = boost::graph_traits::edge_descriptor; + struct MorphismType { + // Both domain and codomain are projected so vertex descriptors mean the same in both. + // But a side-vertex should map to a null-vertex if it is not in K. + using GraphDom = KGraphType; + using GraphCodom = SideGraphType; + using Storable = std::false_type; + public: + MorphismType(const GraphDom &, const GraphCodom &) {} + public: + friend SideVertex get(const MorphismType &, const GraphDom &gDom, const GraphCodom &, KVertex v) { + assert(gDom.gInner[v].membership == Membership::K); + return v; + } + + friend KVertex get_inverse(const MorphismType &, const GraphDom &gDom, const GraphCodom &gCodom, SideVertex v) { + if(gCodom.gInner[v].membership == Membership::K) return v; + else return GraphDom::null_vertex(); + } + public: + friend SideEdge get(const MorphismType &, const GraphDom &gDom, const GraphCodom &, KEdge e) { + assert(gDom.gInner[e].membership == Membership::K); + return e; + } + + friend KEdge get_inverse(const MorphismType &, const GraphDom &gDom, const GraphCodom &gCodom, SideEdge e) { + if(gCodom.gInner[e].membership == Membership::K) return e; + else return {}; + } + }; +public: // Other, {L, K, R} -> Combined Graph + struct ToCombinedMorphismK { + // The identity morphism + using GraphDom = KGraphType; + using GraphCodom = CombinedGraphType; + using Storable = std::false_type; + public: + ToCombinedMorphismK(const GraphDom &, const GraphCodom &) {} + + friend CombinedVertex + get(const ToCombinedMorphismK &, const GraphDom &gDom, const GraphCodom &, CombinedVertex v) { + return v; + } + + friend CombinedVertex + get_inverse(const ToCombinedMorphismK &, const GraphDom &gDom, const GraphCodom &, CombinedVertex v) { + return v; + } + + friend CombinedEdge + get(const ToCombinedMorphismK &, const GraphDom &gDom, const GraphCodom &, CombinedEdge e) { + return e; + } + + friend CombinedEdge + get_inverse(const ToCombinedMorphismK &, const GraphDom &gDom, const GraphCodom &, CombinedEdge e) { + return e; + } + }; + using ToCombinedMorphismSide = ToCombinedMorphismK; +public: + CombinedRule(); + // internally pointers are used, so don't allow moving and copying + CombinedRule(CombinedRule &&) = delete; + CombinedRule &operator=(CombinedRule &&) = delete; +public: // RuleConcept + friend const SideGraphType &getL(const CombinedRule &r); + friend const KGraphType &getK(const CombinedRule &r); + friend const SideGraphType &getR(const CombinedRule &r); + friend const MorphismType &getMorL(const CombinedRule &r); + friend const MorphismType &getMorR(const CombinedRule &r); +public: // WriteableRuleConcept + friend void invert(CombinedRule &r); + friend SideVertex addVertexL(CombinedRule &r); + friend KVertex addVertexK(CombinedRule &r); + friend SideVertex addVertexR(CombinedRule &r); + friend SideVertex promoteVertexL(CombinedRule &r, SideVertex vL); + friend SideEdge addEdgeL(CombinedRule &r, SideVertex v1, SideVertex v2); + friend KEdge addEdgeK(CombinedRule &r, KVertex v1, KVertex v2); + friend SideEdge addEdgeR(CombinedRule &r, SideVertex v1, SideVertex v2); +public: // Other, Combined Graph + CombinedGraphType &getCombinedGraph(); // TODO: remove non-const version + const CombinedGraphType &getCombinedGraph() const; + MembershipPropertyMap makeMembershipPropertyMap() const; + const ToCombinedMorphismSide &getLtoCG() const; + const ToCombinedMorphismK &getKtoCG() const; + const ToCombinedMorphismSide &getRtoCG() const; +public: // Other, Projections of Combined Graph + const SideProjectedGraphType &getLProjected() const; + const KProjectedGraphType &getKProjected() const; + const SideProjectedGraphType &getRProjected() const; +private: + CombinedGraphType gCombined; + KProjectedGraphType gProjectedK; + SideProjectedGraphType gProjectedL, gProjectedR; + MorphismType mL, mR; +private: + ToCombinedMorphismK mKtoCG; + ToCombinedMorphismSide mLtoCG, mRtoCG; +}; + +} // namesapce mod::lib::DPO + +#endif // MOD_LIB_DPO_COMBINEDRULE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DPO/Concepts.hpp b/libs/libmod/src/mod/lib/DPO/Concepts.hpp new file mode 100644 index 0000000..cb2a186 --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/Concepts.hpp @@ -0,0 +1,66 @@ +#ifndef MOD_LIB_DPO_CONCEPTS_HPP +#define MOD_LIB_DPO_CONCEPTS_HPP + +#include + +#include + +#include + +namespace mod::lib::DPO { + +template +struct RuleConcept { + using Traits = RuleTraits; + using SideGraphType = typename Traits::SideGraphType; + using KGraphType = typename Traits::KGraphType; + using MorphismType = typename Traits::MorphismType; + + BOOST_CONCEPT_ASSERT((boost::GraphConcept)); + BOOST_CONCEPT_ASSERT((boost::GraphConcept)); + BOOST_CONCEPT_ASSERT((jla_boost::GraphMorphism::InvertibleGraphMapConcept)); + + BOOST_CONCEPT_USAGE(RuleConcept) { + const Rule &rConst = r; + [[maybe_unused]] const SideGraphType &gL = getL(rConst); + [[maybe_unused]] const KGraphType &gK = getK(rConst); + [[maybe_unused]] const SideGraphType &gR = getR(rConst); + [[maybe_unused]] const MorphismType &mL = getMorL(rConst); + [[maybe_unused]] const MorphismType &mR = getMorR(rConst); + } +private: + Rule r; +}; + +template +struct WritableRuleConcept : RuleConcept { + BOOST_CONCEPT_ASSERT((boost::DefaultConstructible)); + + using Traits = RuleTraits; + using SideGraphType = typename Traits::SideGraphType; + using SideVertex = typename boost::graph_traits::vertex_descriptor; + using SideEdge = typename boost::graph_traits::edge_descriptor; + using KGraphType = typename Traits::KGraphType; + using KVertex = typename boost::graph_traits::vertex_descriptor; + using KEdge = typename boost::graph_traits::edge_descriptor; + + BOOST_CONCEPT_USAGE(WritableRuleConcept) { + invert(r); // swap L and R + [[maybe_unused]] const SideVertex vL = addVertexL(r); + [[maybe_unused]] const KVertex vK = addVertexK(r); + [[maybe_unused]] const SideVertex vR = addVertexR(r); + [[maybe_unused]] const SideVertex vRfL = promoteVertexL(r, cvSide); + //[[maybe_unused]] const SideVertex vLfR = promoteVertexR(r, cvSide); + [[maybe_unused]] const SideEdge eL = addEdgeL(r, cvSide, cvSide); + [[maybe_unused]] const KEdge eK = addEdgeK(r, cvK, cvK); + [[maybe_unused]] const SideEdge eR = addEdgeR(r, cvSide, cvSide); + } +private: + Rule r; + static const SideVertex cvSide; + static const KVertex cvK; +}; + +} // namespace mod::lib::DPO + +#endif // MOD_LIB_DPO_CONCEPTS_HPP \ No newline at end of file diff --git a/libs/jla_boost/include/jla_boost/graph/dpo/FilteredGraphProjection.hpp b/libs/libmod/src/mod/lib/DPO/FilteredGraphProjection.hpp similarity index 82% rename from libs/jla_boost/include/jla_boost/graph/dpo/FilteredGraphProjection.hpp rename to libs/libmod/src/mod/lib/DPO/FilteredGraphProjection.hpp index a0a3647..d78744d 100644 --- a/libs/jla_boost/include/jla_boost/graph/dpo/FilteredGraphProjection.hpp +++ b/libs/libmod/src/mod/lib/DPO/FilteredGraphProjection.hpp @@ -1,35 +1,34 @@ -#ifndef JLA_BOOST_GRAPH_DPO_FILTERED_GRAPH_PROJECTION_HPP -#define JLA_BOOST_GRAPH_DPO_FILTERED_GRAPH_PROJECTION_HPP +#ifndef MOD_LIB_DPO_FILTERED_GRAPH_PROJECTION_HPP +#define MOD_LIB_DPO_FILTERED_GRAPH_PROJECTION_HPP + +#include #include -#include -#include +#include #include -namespace jla_boost { -namespace GraphDPO { +namespace mod::lib::DPO { template struct FilteredGraphProjection { using Self = FilteredGraphProjection; struct Filter { + Filter() : gInner(nullptr) {} - Filter() : gInner(nullptr) { } - - Filter(const GraphType &gInner, Membership membership) : gInner(&gInner), membership(membership) { } + Filter(const GraphType &gInner, Membership membership) : gInner(&gInner), membership(membership) {} bool operator()(typename boost::graph_traits::vertex_descriptor v) const { assert(gInner); auto m = (*gInner)[v].membership; - return m == membership || m == Membership::Context; + return m == membership || m == Membership::K; } bool operator()(typename boost::graph_traits::edge_descriptor e) const { assert(gInner); auto m = (*gInner)[e].membership; - return m == membership || m == Membership::Context; + return m == membership || m == Membership::K; } private: const GraphType *gInner; @@ -38,9 +37,8 @@ struct FilteredGraphProjection { using GraphTypeFiltered = boost::filtered_graph; public: - - FilteredGraphProjection(const GraphType &gInner, Membership membership) - : gInner(gInner), membership(membership), g(gInner, Filter(gInner, membership), Filter(gInner, membership)) { } + explicit FilteredGraphProjection(const GraphType &gInner, Membership membership) + : gInner(gInner), membership(membership), g(gInner, Filter(gInner, membership), Filter(gInner, membership)) {} public: // Graph using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; using edge_descriptor = typename boost::graph_traits::edge_descriptor; @@ -113,12 +111,10 @@ struct FilteredGraphProjection { return num_edges(g.g); } public: // "AdjacencyMatrix" (it's not constant time) - friend std::pair edge(vertex_descriptor u, vertex_descriptor v, const Self &g) { return edge(u, v, g.g); } public: // PropertyGraph - template friend decltype(auto) get(PropertyTag t, const Self &g) { return get(t, g.g); @@ -144,7 +140,6 @@ struct FilteredGraphProjection { return g[ve]; } public: // Other - friend vertex_descriptor vertex(vertices_size_type n, const Self &g) { return vertex(n, g.g); } @@ -154,13 +149,14 @@ struct FilteredGraphProjection { const GraphTypeFiltered g; }; -} // namespace GraphDPO +} // namespace mod::lib::DPO +namespace jla_boost { template -struct GraphAdaptorTraits > { +struct GraphAdaptorTraits > { using type = GraphType; - static const type &unwrap(const GraphDPO::FilteredGraphProjection &g) { + static const type &unwrap(const mod::lib::DPO::FilteredGraphProjection &g) { return g.gInner; } }; @@ -172,15 +168,15 @@ namespace boost { //------------------------------------------------------------------------------ template -struct property_map, Property> -: property_map { +struct property_map, Property> + : property_map { }; template -struct property_map, Property> -: property_map { +struct property_map, Property> + : property_map { }; } // namespace boost -#endif /* JLA_BOOST_GRAPH_DPO_FILTERED_GRAPH_PROJECTION_HPP */ \ No newline at end of file +#endif // MOD_LIB_DPO_FILTERED_GRAPH_PROJECTION_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DPO/Membership.cpp b/libs/libmod/src/mod/lib/DPO/Membership.cpp new file mode 100644 index 0000000..bf9b096 --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/Membership.cpp @@ -0,0 +1,19 @@ +#include "Membership.hpp" + +#include + +namespace mod::lib::DPO { + +std::ostream &operator<<(std::ostream &s, Membership m) { + switch(m) { + case Membership::L: + return s << "Left"; + case Membership::R: + return s << "Right"; + case Membership::K: + return s << "Context"; + } + __builtin_unreachable(); +} + +} // namespace mod::lib::DPO \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DPO/Membership.hpp b/libs/libmod/src/mod/lib/DPO/Membership.hpp new file mode 100644 index 0000000..74db80c --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/Membership.hpp @@ -0,0 +1,28 @@ +#ifndef MOD_LIB_DPO_MEMBERSHIP_HPP +#define MOD_LIB_DPO_MEMBERSHIP_HPP + +#include + +namespace mod::lib::DPO { + +enum class Membership { + L, K, R +}; + +inline Membership invert(Membership m) { + switch(m) { + case Membership::L: + return Membership::R; + case Membership::K: + return Membership::K; + case Membership::R: + return Membership::L; + } + __builtin_unreachable(); +} + +std::ostream &operator<<(std::ostream &s, Membership m); + +} // namespace mod::lib::DPO + +#endif // MOD_LIB_DPO_MEMBERSHIP_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/DPO/Traits.hpp b/libs/libmod/src/mod/lib/DPO/Traits.hpp new file mode 100644 index 0000000..2f6f8ff --- /dev/null +++ b/libs/libmod/src/mod/lib/DPO/Traits.hpp @@ -0,0 +1,15 @@ +#ifndef MOD_LIB_DPO_TRAITS_HPP +#define MOD_LIB_DPO_TRAITS_HPP + +namespace mod::lib::DPO { + +template +struct RuleTraits { + using SideGraphType = typename Rule::SideGraphType; + using KGraphType = typename Rule::KGraphType; + using MorphismType = typename Rule::MorphismType; +}; + +} // namespace mod::lib::DPO + +#endif // MOD_LIB_DPO_TRAITS_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/Collection.cpp b/libs/libmod/src/mod/lib/Graph/Collection.cpp index 5c812ad..351e979 100644 --- a/libs/libmod/src/mod/lib/Graph/Collection.cpp +++ b/libs/libmod/src/mod/lib/Graph/Collection.cpp @@ -40,7 +40,7 @@ struct Collection::Store { // opts.edgesAsBonds = opts.withIndex = true; // optsGraph.collapseHydrogens = optsGraph.edgesAsBonds = optsGraph.raiseCharges = true; // optsGraph.simpleCarbons = optsGraph.withColour = optsGraph.withIndex = true; - // mod::lib::IO::Graph::Write::summary(*gCand, optsGraph, optsGraph); + // mod::lib::Graph::IO::Write::summary(*gCand, optsGraph, optsGraph); // std::shared_ptr gCandWrapped = graph::Graph::makeGraph(std::move(gCand)); // for(auto v : gCandWrapped->vertices()) { // if(v.getStringLabel() == "C") diff --git a/libs/libmod/src/mod/lib/Graph/DFSEncoding.cpp b/libs/libmod/src/mod/lib/Graph/DFSEncoding.cpp deleted file mode 100644 index 1b5a91c..0000000 --- a/libs/libmod/src/mod/lib/Graph/DFSEncoding.cpp +++ /dev/null @@ -1,623 +0,0 @@ -#include "DFSEncoding.hpp" - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -namespace mod::lib::Graph::DFSEncoding { -namespace { - -void escapeLabel(std::ostream &s, const std::string &label, char escChar) { - for(int i = 0; i != label.size(); i++) { - char c = label[i]; - if(c == escChar) s << "\\" << escChar; - else if(c == '\t') s << "\\t"; - else if(c == '\\' && i + 1 != label.size()) { - char next = label[i + 1]; - if(next == '\\' || next == 't') s << "\\\\"; - else s << '\\'; - } else s << c; - } -} - -} // namespace -namespace detail { -class LabelVertex; -class RingClosure; -class Branch; - -// x3 does not yet fully support std::variant -// https://github.com/boostorg/spirit/issues/321 -using BaseVertex = boost::variant; - -using GVertex = lib::Graph::Vertex; -using GEdge = lib::Graph::Edge; - -struct LabelVertex { - std::string label; - bool implicit = false; - std::optional id; -public: - GVertex gVertex; -public: - friend std::ostream &operator<<(std::ostream &s, const LabelVertex &lv) { - if(lv.implicit) s << lv.label; - else { - s << '['; - escapeLabel(s, lv.label, ']'); - s << ']'; - } - if(lv.id) s << *lv.id; - return s; - } -}; - -struct RingClosure { - // must use unsigned so the parser understands how to store it - unsigned int id = std::numeric_limits::max(); -public: - friend std::ostream &operator<<(std::ostream &s, const RingClosure &rc) { - return s << rc.id; - } -}; - -struct Vertex { - BaseVertex vertex; - std::vector branches; -public: - friend std::ostream &operator<<(std::ostream &s, const Vertex &v); -}; - -struct Edge { - std::string label; -public: - friend std::ostream &operator<<(std::ostream &s, const Edge &e) { - if(e.label.size() > 1) { - s << '{'; - escapeLabel(s, e.label, '}'); - return s << '}'; - } - switch(e.label.front()) { - case '-': - return s; - case '=': - case '#': - case ':': - return s << e.label; - default: - return s << "{" << e.label << "}"; - } - } -}; - -using EVPair = std::pair; - -struct Chain { - Vertex head; - std::vector tail; - bool hasNonSmilesRingClosure; // only set when creating GraphDFS from a graph -public: - friend std::ostream &operator<<(std::ostream &s, const Chain &c) { - s << c.head; - for(const auto &ev : c.tail) s << ev.first << ev.second; - return s; - } -}; - -struct Branch { - std::vector tail; -public: - friend std::ostream &operator<<(std::ostream &s, const Branch &b) { - for(const auto &ev : b.tail) s << ev.first << ev.second; - return s; - } -}; - -std::ostream &operator<<(std::ostream &s, const Vertex &v) { - s << v.vertex; - for(const auto &b : v.branches) s << b; - return s; -} - -namespace { -namespace parser { - -struct SpecialVertexLabels : x3::symbols { - SpecialVertexLabels() { - name("specialVertexLabels"); - for(auto atomId : lib::Chem::getSmilesOrganicSubset()) - add(lib::Chem::symbolFromAtomId(atomId), lib::Chem::symbolFromAtomId(atomId)); - } -} specialVertexLabels; - -struct SpecialEdgeLabels : x3::symbols { - SpecialEdgeLabels() { - name("specialEdgeLabels"); - add("-", "-"); - add(":", ":"); - add("=", "="); - add("#", "#"); - } -} specialEdgeLabelSymbols; - -// no recursion -const auto implicitBackslash = x3::attr('\\'); -const auto explicitBackslash = '\\' >> x3::attr('\\'); -const auto tab = 't' >> x3::attr('\t'); -const auto plainBrace /* */ = (x3::char_ - x3::char_('}')); -const auto plainBracket /**/ = (x3::char_ - x3::char_(']')); -const auto escapedBrace /* */ = '\\' >> (x3::char_('}') | tab | explicitBackslash | implicitBackslash); -const auto escapedBracket /**/ = '\\' >> (x3::char_(']') | tab | explicitBackslash | implicitBackslash); -const auto escapedStringBrace /* */ = x3::lexeme[x3::lit('{') > *(escapedBrace /* */ | plainBrace /* */) > - x3::lit('}')]; -const auto escapedStringBracket /**/ = x3::lexeme[x3::lit('[') > *(escapedBracket /**/ | plainBracket /**/) > - x3::lit(']')]; - -const auto specialEdgeLabels = specialEdgeLabelSymbols | x3::attr(std::string(1, '-')); -const auto edge = x3::rule("edge") = escapedStringBrace | specialEdgeLabels; -const auto defRingId = x3::uint_; -const auto ringClosure = x3::rule("ringClosure") = x3::uint_; -const auto specialLabelVertex = x3::rule("specialLabelVertex") -/* */ = specialVertexLabels >> x3::attr(true) >> -defRingId; -const auto explicitLabelVertex = x3::rule("explicitLabelVertex") -/* */ = escapedStringBracket >> x3::attr(false) >> -defRingId; -const auto labelVertex = explicitLabelVertex | specialLabelVertex; -// part of recursion -const x3::rule vertex = "vertex"; -const auto evPair = x3::rule("edgeVertexPair") = edge >> vertex; -const auto branch = x3::rule("branch") = '(' > +evPair > ')'; -const auto branches = *branch; -const auto vertex_def = (labelVertex | ringClosure) >> branches; -BOOST_SPIRIT_DEFINE(vertex) -// no recursion -const auto chain = vertex > *evPair; -const auto graphDFS = x3::rule("graphDFS") -/* */ = chain; - -} // namespace parser -} // namespace - -class Converter { - struct ConvertRes { - GVertex next; - bool isRingClosure; - }; -public: - Converter(GraphType &g, PropString &pString) : g(g), pString(pString) {} - - lib::IO::Result<> convert(Chain &chain) { - auto subRes = convertTail(chain.head, g.null_vertex()); - if(!subRes) return std::move(subRes); - const auto sub = *subRes; - GVertex vPrev = sub.next; - assert(!sub.isRingClosure); - assert(vPrev != g.null_vertex()); - for(EVPair &ev : chain.tail) { - auto attachRes = attach(ev, vPrev); - if(!attachRes) return std::move(attachRes); - vPrev = *attachRes; - assert(vPrev != g.null_vertex()); - } - return {}; - } - - lib::IO::Result convertTail(Vertex &vertex, GVertex prev) { - auto subRes = boost::apply_visitor(*this, vertex.vertex); - if(!subRes) return subRes; - const auto sub = *subRes; - assert(!(sub.isRingClosure && prev == g.null_vertex())); - GVertex branchRoot = sub.isRingClosure ? prev : sub.next; - for(Branch &branch : vertex.branches) { - GVertex branchPrev = branchRoot; - for(EVPair &ev : branch.tail) { - auto branchPrevRes = attach(ev, branchPrev); - if(!branchPrevRes) return std::move(branchPrevRes); - branchPrev = *branchPrevRes; - assert(branchPrev != g.null_vertex()); - } - } - return sub; - } - - void makeRingClosureBond(GVertex vSrc, GVertex vTar, const std::string &label) { - std::pair e = add_edge(vSrc, vTar, g); - assert(e.second); - pString.addEdge(e.first, label); - } - - lib::IO::Result attach(EVPair &ev, GVertex prev) { - auto subRes = convertTail(ev.second, prev); - if(!subRes) return std::move(subRes); - const auto res = *subRes; - makeRingClosureBond(prev, res.next, ev.first.label); - if(res.isRingClosure) return prev; - else return res.next; - } - - ConvertRes operator()(LabelVertex &vertex) { - GVertex v = add_vertex(g); - pString.addVertex(v, vertex.label); - if(vertex.id) { - const auto iter = idVertexMap.find(*vertex.id); - if(iter == idVertexMap.end()) { - // use the id as definition - idVertexMap[*vertex.id] = v; - } else { - // use the id as ring closure - makeRingClosureBond(v, iter->second, "-"); - } - } - vertex.gVertex = v; - return ConvertRes{v, false}; - } - - lib::IO::Result operator()(const RingClosure &vertex) { - const auto iter = idVertexMap.find(vertex.id); - if(iter == idVertexMap.end()) - return lib::IO::Result<>::Error("Ring closure id " + std::to_string(vertex.id) + " not found."); - return ConvertRes{iter->second, true}; - } -private: - GraphType &g; - PropString &pString; -public: - std::map idVertexMap; -}; - -struct ImplicitHydrogenAdder : public boost::static_visitor { - ImplicitHydrogenAdder(GraphType &g, PropString &pString) : g(g), pString(pString) {} - - void operator()(const Chain &chain) { - (*this)(chain.head); - for(const auto &ev : chain.tail) (*this)(ev.second); - } - - void operator()(const Vertex &vertex) { - boost::apply_visitor(*this, vertex.vertex); - for(const auto &b : vertex.branches) - for(const auto &ev : b.tail) (*this)(ev.second); - } - - void operator()(const RingClosure &) {} - - void operator()(const LabelVertex &vertex) { - if(vertex.implicit) { - // we can only add hydrogens if all incident edges are valid bonds - for(auto eOut : asRange(out_edges(vertex.gVertex, g))) { - if(lib::Chem::decodeEdgeLabel(pString[eOut]) == BondType::Invalid) - return; - } - const auto atomId = lib::Chem::atomIdFromSymbol(vertex.label); - const auto iter = std::find(begin(lib::Chem::getSmilesOrganicSubset()), - end(lib::Chem::getSmilesOrganicSubset()), atomId); - if(iter == end(lib::Chem::getSmilesOrganicSubset())) - MOD_ABORT; - const auto hydrogenAdder = [](lib::Graph::GraphType &g, lib::Graph::PropString &pString, - lib::Graph::Vertex p) { - const GVertex v = add_vertex(g); - pString.addVertex(v, "H"); - const GEdge e = add_edge(v, p, g).first; - pString.addEdge(e, "-"); - }; - lib::Chem::addImplicitHydrogens(g, pString, vertex.gVertex, atomId, hydrogenAdder); - } - } -private: - GraphType &g; - PropString &pString; -}; - -} // namespace detail - -lib::IO::Result parse(const std::string &dfs) { - using IteratorType = std::string::const_iterator; - IteratorType first = dfs.begin(), last = dfs.end(); - detail::Chain chain; - try { - lib::IO::parse(first, last, detail::parser::graphDFS, chain); - } catch(const lib::IO::ParsingError &e) { - return lib::IO::Result<>::Error(e.msg); - } - - auto g = std::make_unique(); - auto pString = std::make_unique(*g); - detail::Converter conv(*g, *pString); - if(auto res = conv.convert(chain); res) { - detail::ImplicitHydrogenAdder adder(*g, *pString); - adder(chain); - // write(*g, *pString); - lib::IO::Graph::Read::Data data; - data.g = std::move(g); - data.pString = std::move(pString); - for(auto &&vp : conv.idVertexMap) - data.externalToInternalIds[vp.first] = get(boost::vertex_index_t(), *g, vp.second); - return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available - } else { - return res; - } -} - -} // namespace mod::lib::Graph::DFSEncoding - -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::LabelVertex, - (std::string, label) - (bool, implicit) - (std::optional, id)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::RingClosure, - (unsigned int, id)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::Vertex, - (mod::lib::Graph::DFSEncoding::detail::BaseVertex, vertex) - (std::vector, branches)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::Edge, - (std::string, label)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::Chain, - (mod::lib::Graph::DFSEncoding::detail::Vertex, head) - (std::vector, tail)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::Graph::DFSEncoding::detail::Branch, - (std::vector, tail)) - -#include -#include - -#include -#include -#include - -namespace mod::lib::Graph::DFSEncoding { -namespace detail { - -enum class Colour { - White, Grey, Black -}; - -struct Printer { - Printer(std::ostream &s, std::map idMap) : s(s), idMap(idMap) {} - - void operator()(const Chain &chain) { - (*this)(chain.head); - (*this)(chain.tail); - } - - void operator()(const Vertex &v) { - boost::apply_visitor(*this, v.vertex); - for(const Branch &b : v.branches) { - s << "("; - (*this)(b.tail); - s << ")"; - } - } - - void operator()(const LabelVertex &v) { - s << "["; - escapeLabel(s, v.label, ']'); - s << "]"; - if(v.id && idMap.find(*v.id) != idMap.end()) - s << idMap[*v.id]; - } - - void operator()(const RingClosure &v) { - assert(v.id != std::numeric_limits::max()); - s << idMap[v.id]; - } - - void operator()(const std::vector &evPairs) { - for(const EVPair &p : evPairs) { - (*this)(p.first); - (*this)(p.second); - } - } - - void operator()(const Edge &e) { - if(e.label.size() == 1) { - char c = e.label[0]; - switch(c) { - case '-': - return; - case ':': - case '=': - case '#': - s << c; - return; - } - } - s << "{"; - escapeLabel(s, e.label, '}'); - s << "}"; - } -private: - std::ostream &s; - std::map idMap; -}; - -struct Prettyfier { - Prettyfier(const std::vector &targetForRing) : targetForRing(targetForRing) {} - - void operator()(Chain &chain) { - (*this)(chain.head); - (*this)(chain.tail); - if(chain.head.branches.size() > 0 && chain.tail.size() == 0) { - chain.tail = chain.head.branches.back().tail; - chain.head.branches.pop_back(); - } - } - - void operator()(Vertex &v) { - boost::apply_visitor(*this, v.vertex); - for(Branch &b : v.branches) (*this)(b.tail); - } - - void operator()(LabelVertex &v) { - if(v.id) - if(!targetForRing[*v.id]) v.id.reset(); - } - - void operator()(RingClosure &v) { - assert(v.id != std::numeric_limits::max()); - assert(targetForRing[v.id]); - } - - void operator()(std::vector &evPairs) { - for(EVPair &p : evPairs) { - (*this)(p.first); - (*this)(p.second); - } - if(evPairs.size() > 0 && evPairs.back().second.branches.size() > 0) { - unsigned int index = evPairs.size() - 1; - std::vector tempCopy = evPairs.back().second.branches.back().tail; - std::vector newVec = evPairs; - newVec.insert(newVec.end(), tempCopy.begin(), tempCopy.end()); - newVec[index].second.branches.pop_back(); - evPairs = newVec; - } - } - - void operator()(Edge &e) {} -private: - const std::vector targetForRing; -}; - -std::pair> -write(const lib::Graph::GraphType &g, const PropString &pString, bool withIds) { - using namespace detail; - using GEdgeIter = lib::Graph::GraphType::out_edge_iterator; - using VertexInfo = std::pair>; - std::vector realVertices(num_vertices(g), nullptr); - Chain chain; - chain.hasNonSmilesRingClosure = false; - - std::vector colour(num_vertices(g), Colour::White); - // note: if withIds, pretend *all* vertices are targets for ring closures - std::vector targetForRing(num_vertices(g), withIds); - std::map edgeColour; - for(GEdge e : asRange(edges(g))) edgeColour[e] = Colour::White; - std::stack stack; - { // discover root - GVertex cur = *vertices(g).first; - unsigned int curId = get(boost::vertex_index_t(), g, cur); - assert(curId < colour.size()); - colour[curId] = Colour::Grey; - assert(!realVertices[curId]); - realVertices[curId] = &chain.head; - LabelVertex lv; - lv.id = curId; - lv.label = pString[cur]; - realVertices[curId]->vertex = lv; - stack.push(std::make_pair(cur, out_edges(cur, g))); - } - while(!stack.empty()) { - GVertex cur = stack.top().first; - GEdgeIter iter, iterEnd; - boost::tie(iter, iterEnd) = stack.top().second; - stack.pop(); - unsigned int curId = get(boost::vertex_index_t(), g, cur); - assert(realVertices[curId]); - Vertex *curVertex = realVertices[curId]; - // std::cout << "CurVertex: " << pString(cur) << std::endl; - while(iter != iterEnd) { - GVertex next = target(*iter, g); - GEdge test; - unsigned int nextId = get(boost::vertex_index_t(), g, next); - Edge edge; - edge.label = pString[*iter]; - // mark edge - Colour oldEdgeColour = edgeColour[*iter]; - edgeColour[*iter] = Colour::Black; - // std::cout << "\tEdge: " << pString(cur) - // << " ->(" << edge.label << ", " << (oldEdgeColour == Black ? "black" : "white") << ") " - // << pString(next) << "\t"; - if(colour[nextId] == Colour::White) { // tree edge, new vertex - // std::cout << "white" << std::endl; - // create the new vertex - assert(!realVertices[nextId]); - LabelVertex lv; - lv.id = nextId; - lv.label = pString[next]; - Vertex newVertex; - newVertex.vertex = lv; - curVertex->branches.push_back((Branch())); - curVertex->branches.back().tail.push_back(std::make_pair(edge, newVertex)); - Vertex *nextVertex = &curVertex->branches.back().tail.back().second; - realVertices[nextId] = nextVertex; - colour[nextId] = Colour::Grey; - // switch to the new vertex - iter++; - stack.push(std::make_pair(cur, std::make_pair(iter, iterEnd))); - cur = next; - curId = nextId; - curVertex = nextVertex; - boost::tie(iter, iterEnd) = out_edges(next, g); - // std::cout << "CurVertex: " << pString(cur)<< std::endl; - } else if(colour[nextId] == Colour::Grey) { // back edge, maybe an already traversed edge - // std::cout << "grey" << std::endl; - if(oldEdgeColour == Colour::Black) iter++; // already traversed - else { - RingClosure rc; - rc.id = nextId; - Vertex backVertex; - backVertex.vertex = rc; - curVertex->branches.push_back((Branch())); - curVertex->branches.back().tail.push_back(std::make_pair(edge, backVertex)); - if(targetForRing[nextId]) chain.hasNonSmilesRingClosure = true; - targetForRing[nextId] = true; - iter++; - } - } else { - // std::cout << "black" << std::endl; - iter++; - } - } - colour[curId] = Colour::Black; - } - - std::map idMap; - int nextMappedId = 1; - for(int id = 0; id != targetForRing.size(); id++) - if(targetForRing[id]) - idMap[id] = nextMappedId++; - Prettyfier pretty(targetForRing); - pretty(chain); - return std::make_pair(chain, idMap); -} - -} // namespace detail - -std::pair write(const lib::Graph::GraphType &g, const PropString &pString, bool withIds) { - if(num_vertices(g) == 0) return std::make_pair("", false); - using namespace detail; - auto[chain, idMap] = detail::write(g, pString, withIds); - - std::stringstream graphDFS; - Printer p(graphDFS, idMap); - p(chain); - return std::make_pair(graphDFS.str(), chain.hasNonSmilesRingClosure); -} - -} // namespace mod::lib::Graph::DFSEncoding \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/DFSEncoding.hpp b/libs/libmod/src/mod/lib/Graph/DFSEncoding.hpp deleted file mode 100644 index 8a57da6..0000000 --- a/libs/libmod/src/mod/lib/Graph/DFSEncoding.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MOD_LIB_GRAPH_DFSENCODING_HPP -#define MOD_LIB_GRAPH_DFSENCODING_HPP - -#include -#include - -#include - -namespace mod::lib::Graph { -struct PropString; -} // namespace mod::lib::Graph -namespace mod::lib::Graph::DFSEncoding { - -lib::IO::Result parse(const std::string &dfs); -std::pair write(const GraphType &g, const PropString &pString, bool withIds); - -} // namespace mod::lib::Graph::DFSEncoding - -#endif // MOD_LIB_GRAPH_DFSENCODING_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/Properties/Depiction.cpp b/libs/libmod/src/mod/lib/Graph/IO/DepictionData.cpp similarity index 84% rename from libs/libmod/src/mod/lib/Graph/Properties/Depiction.cpp rename to libs/libmod/src/mod/lib/Graph/IO/DepictionData.cpp index ff91c77..ce8d2a4 100644 --- a/libs/libmod/src/mod/lib/Graph/Properties/Depiction.cpp +++ b/libs/libmod/src/mod/lib/Graph/IO/DepictionData.cpp @@ -1,19 +1,19 @@ -#include "Depiction.hpp" +#include "DepictionData.hpp" #include #include #include #include +#include #include #include #include -#include #include #include -namespace mod::lib::Graph { +namespace mod::lib::Graph::Write { DepictionData::DepictionData(const LabelledGraph &lg) : lg(lg), hasMoleculeEncoding(true) { const auto &g = get_graph(lg); @@ -23,14 +23,14 @@ DepictionData::DepictionData(const LabelledGraph &lg) : lg(lg), hasMoleculeEncod std::vector atomUsed(AtomIds::Max + 1, false); Chem::markSpecialAtomsUsed(atomUsed); std::vector verticesToProcess; - for(Vertex v : asRange(vertices(g))) { + for(Vertex v: asRange(vertices(g))) { unsigned char atomId = pMol[v].getAtomId(); if(atomId != AtomIds::Invalid) atomUsed[atomId] = true; else verticesToProcess.push_back(v); } // map non-atom labels to atoms std::map labelNoStuff; - for(Vertex v : verticesToProcess) { + for(Vertex v: verticesToProcess) { std::string label = std::get<0>(Chem::extractIsotopeChargeRadical(pString[v])); auto iter = labelNoStuff.find(label); if(iter == end(labelNoStuff)) { @@ -47,14 +47,14 @@ DepictionData::DepictionData(const LabelledGraph &lg) : lg(lg), hasMoleculeEncod break; } } - auto atomId = iter->second; + const auto atomId = iter->second; nonAtomToPhonyAtom[v] = AtomData(atomId, pMol[v].getCharge(), pMol[v].getRadical()); } } { // edgeData - for(Edge e : asRange(edges(g))) { - if(pMol[e] == BondType::Invalid) nonBondEdges[e] = pString[e]; - } + for(Edge e: asRange(edges(g))) + if(pMol[e] == BondType::Invalid) + nonBondEdges[e] = pString[e]; } } @@ -91,42 +91,62 @@ BondType DepictionData::getBondData(Edge e) const { return get_molecule(lg)[e]; } -bool DepictionData::hasImportantStereo(Vertex v) const { - if(!has_stereo(lg)) return false; - return !get_stereo(lg)[v]->morphismDynamicOk(); -} - std::string DepictionData::getEdgeLabel(Edge e) const { if(!hasMoleculeEncoding) MOD_ABORT; - auto bt = getBondData(e); + const auto bt = getBondData(e); if(bt != BondType::Invalid) return std::string(1, Chem::bondToChar(bt)); - auto iter = nonBondEdges.find(e); + const auto iter = nonBondEdges.find(e); assert(iter != end(nonBondEdges)); return iter->second; } -const AtomData &DepictionData::operator()(Vertex v) const { - if(get_molecule(lg)[v].getAtomId() != AtomIds::Invalid) return get_molecule(lg)[v]; - else { - auto iter = nonAtomToPhonyAtom.find(v); - assert(iter != end(nonAtomToPhonyAtom)); - return iter->second; - } +bool DepictionData::hasImportantStereo(Vertex v) const { + if(!has_stereo(lg)) return false; + return !get_stereo(lg)[v]->morphismDynamicOk(); } -BondType DepictionData::operator()(Edge e) const { - auto eType = get_molecule(lg)[e]; - return eType != BondType::Invalid ? eType : BondType::Single; +lib::IO::Graph::Write::EdgeFake3DType DepictionData::getEdgeFake3DType(Edge e, bool withHydrogen) const { + if(!has_stereo(lg)) + return lib::IO::Graph::Write::EdgeFake3DType::None; +#ifndef MOD_HAVE_OPENBABEL + throw FatalError(MOD_NO_OPENBABEL_ERROR_STR); +#else + const auto idSrc = get(boost::vertex_index_t(), get_graph(lg), source(e, get_graph(lg))); + const auto idTar = get(boost::vertex_index_t(), get_graph(lg), target(e, get_graph(lg))); + const auto &mol = getOB(withHydrogen); + return mol.getBondFake3D(idSrc, idTar); +#endif +} + +std::string DepictionData::getRawStereoString(Vertex v) const { + const auto &conf = *get_stereo(lg)[v]; + const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { + const auto &g = get_graph(lg); + return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); + }; + return conf.asRawString(getNeighbourId); +} + +std::string DepictionData::getPrettyStereoString(Vertex v) const { + const auto &conf = *get_stereo(lg)[v]; + const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { + const auto &g = get_graph(lg); + return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); + }; + return conf.asPrettyString(getNeighbourId); +} + +std::string DepictionData::getStereoString(Edge e) const { + const auto cat = get_stereo(lg)[e]; + return boost::lexical_cast(cat); } bool DepictionData::getHasCoordinates() const { #ifdef MOD_HAVE_OPENBABEL - if(getConfig().io.useOpenBabelCoords.get()) - return hasMoleculeEncoding; - else return false; + return hasMoleculeEncoding; #else return false; -#endif +#endif } double DepictionData::getX(Vertex v, bool withHydrogen) const { @@ -143,7 +163,7 @@ double DepictionData::getX(Vertex v, bool withHydrogen) const { double DepictionData::getY(Vertex v, bool withHydrogen) const { if(!getHasCoordinates()) MOD_ABORT; #ifdef MOD_HAVE_OPENBABEL - unsigned int vId = get(boost::vertex_index_t(), get_graph(lg), v); + const auto vId = get(boost::vertex_index_t(), get_graph(lg), v); const auto &mol = getOB(withHydrogen); return mol.getAtomY(vId); #else @@ -151,40 +171,23 @@ double DepictionData::getY(Vertex v, bool withHydrogen) const { #endif } -lib::IO::Graph::Write::EdgeFake3DType DepictionData::getEdgeFake3DType(Edge e, bool withHydrogen) const { - if(!has_stereo(lg)) - return lib::IO::Graph::Write::EdgeFake3DType::None; -#ifdef MOD_HAVE_OPENBABEL - auto idSrc = get(boost::vertex_index_t(), get_graph(lg), source(e, get_graph(lg))); - auto idTar = get(boost::vertex_index_t(), get_graph(lg), target(e, get_graph(lg))); - const auto &mol = getOB(withHydrogen); - return mol.getBondFake3D(idSrc, idTar); -#else - MOD_ABORT; -#endif -} - -std::string DepictionData::getRawStereoString(Vertex v) const { - const auto &conf = *get_stereo(lg)[v]; - const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge & emb) { - const auto &g = get_graph(lg); - return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); - }; - return conf.asRawString(getNeighbourId); +int DepictionData::getOutputId(Vertex v) const { + return get(boost::vertex_index_t(), get_graph(lg), v); } -std::string DepictionData::getPrettyStereoString(Vertex v) const { - const auto &conf = *get_stereo(lg)[v]; - const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge & emb) { - const auto &g = get_graph(lg); - return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); - }; - return conf.asPrettyString(getNeighbourId); +const AtomData &DepictionData::operator()(Vertex v) const { + if(get_molecule(lg)[v].getAtomId() != AtomIds::Invalid) { + return get_molecule(lg)[v]; + } else { + auto iter = nonAtomToPhonyAtom.find(v); + assert(iter != end(nonAtomToPhonyAtom)); + return iter->second; + } } -std::string DepictionData::getStereoString(Edge e) const { - const auto cat = get_stereo(lg)[e]; - return boost::lexical_cast(cat); +BondType DepictionData::operator()(Edge e) const { + const auto eType = get_molecule(lg)[e]; + return eType != BondType::Invalid ? eType : BondType::Single; } void DepictionData::setImage(std::shared_ptr> image) { @@ -203,9 +206,9 @@ std::string DepictionData::getImageCommand() const { return imageCmd; } +#ifdef MOD_HAVE_OPENBABEL const lib::Chem::OBMolHandle &DepictionData::getOB(bool withHydrogen) const { if(!hasMoleculeEncoding) MOD_ABORT; -#ifdef MOD_HAVE_OPENBABEL if(!obMolAll) { const auto &g = get_graph(lg); const auto *pStereo = has_stereo(lg) ? &get_stereo(lg) : nullptr; @@ -217,7 +220,7 @@ const lib::Chem::OBMolHandle &DepictionData::getOB(bool withHydrogen) const { obMolNoHydrogen = Chem::makeOBMol(g, std::cref(*this), std::cref(*this), hasImportantStereo, false, pStereo); } return withHydrogen ? obMolAll : obMolNoHydrogen; -#endif } +#endif -} // namespace mod::lib::Graph \ No newline at end of file +} // namespace mod::lib::Graph::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/Properties/Depiction.hpp b/libs/libmod/src/mod/lib/Graph/IO/DepictionData.hpp similarity index 70% rename from libs/libmod/src/mod/lib/Graph/Properties/Depiction.hpp rename to libs/libmod/src/mod/lib/Graph/IO/DepictionData.hpp index c133808..caeb42f 100644 --- a/libs/libmod/src/mod/lib/Graph/Properties/Depiction.hpp +++ b/libs/libmod/src/mod/lib/Graph/IO/DepictionData.hpp @@ -1,7 +1,8 @@ -#ifndef MOD_LIB_GRAPH_DEPICTION_HPP -#define MOD_LIB_GRAPH_DEPICTION_HPP +#ifndef MOD_LIB_GRAPH_IO_DEPICTIONDATA_HPP +#define MOD_LIB_GRAPH_IO_DEPICTIONDATA_HPP #include +#include #include #include @@ -14,34 +15,42 @@ template class Function; } // namespace mod namespace mod::lib::IO::Graph::Write { -enum class EdgeFake3DType; +enum struct EdgeFake3DType; } // namespace mod::lib::IO::Graph::Write namespace mod::lib::Graph { struct PropMolecule; struct PropString; +} // namespace mod::lib::Grpah +namespace mod::lib::Graph::Write { -class DepictionData { +struct DepictionData { DepictionData(const DepictionData &) = delete; DepictionData &operator=(const DepictionData &) = delete; public: DepictionData(const LabelledGraph &lg); - AtomId getAtomId(Vertex v) const; // shortcut to moleculeState - Isotope getIsotope(Vertex v) const; // shortcut to moleculeState - Charge getCharge(Vertex v) const; // shortcut to moleculeState - bool getRadical(Vertex v) const; // shortcut to moleculeState +public: // used in GraphWriteGeneric + AtomId getAtomId(Vertex v) const; // shortcut to PropMolecule + Isotope getIsotope(Vertex v) const; // shortcut to PropMolecule + Charge getCharge(Vertex v) const; // shortcut to PropMolecule + bool getRadical(Vertex v) const; // shortcut to PropMolecule std::string getVertexLabelNoIsotopeChargeRadical(Vertex v) const; - BondType getBondData(Edge e) const; // shortcut to moleculeState - bool hasImportantStereo(Vertex v) const; + BondType getBondData(Edge e) const; // shortcut to PropMolecule std::string getEdgeLabel(Edge e) const; - const AtomData &operator()(Vertex v) const; // fake data - BondType operator()(Edge e) const; // fake data - bool getHasCoordinates() const; - double getX(Vertex v, bool withHydrogen) const; - double getY(Vertex v, bool withHydrogen) const; + bool hasImportantStereo(Vertex v) const; lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(Edge e, bool withHydrogen) const; std::string getRawStereoString(Vertex v) const; std::string getPrettyStereoString(Vertex v) const; std::string getStereoString(Edge e) const; + bool getHasCoordinates() const; + // pre: getHasCoordinates() + double getX(Vertex v, bool withHydrogen) const; + // pre: getHasCoordinates() + double getY(Vertex v, bool withHydrogen) const; +public: + int getOutputId(Vertex v) const; +public: // used for Coordinate handling + const AtomData &operator()(Vertex v) const; // fake data + BondType operator()(Edge e) const; // fake data public: // custom depiction void setImage(std::shared_ptr > image); std::shared_ptr > getImage() const; @@ -64,6 +73,6 @@ class DepictionData { std::string imageCmd; }; -} // namespace mod::lib::Graph +} // namespace mod::lib::Graph::Write -#endif // MOD_LIB_GRAPH_DEPICTION_HPP \ No newline at end of file +#endif // MOD_LIB_GRAPH_IO_DEPICTIONDATA_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/IO/Read.cpp b/libs/libmod/src/mod/lib/Graph/IO/Read.cpp new file mode 100644 index 0000000..5b29061 --- /dev/null +++ b/libs/libmod/src/mod/lib/Graph/IO/Read.cpp @@ -0,0 +1,524 @@ +#include "Read.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace mod::lib::Graph::Read { + +namespace GML = lib::IO::GML; +using lib::IO::Result; + +Data::Data() = default; + +Data::Data(Data &&other) : g(std::move(other.g)), pString(std::move(other.pString)), pStereo(std::move(other.pStereo)), + externalToInternalIds(std::move(other.externalToInternalIds)) {} + +Data::~Data() { + if(std::uncaught_exceptions() != 0) return; + if(g) MOD_ABORT; + if(pString) MOD_ABORT; + if(pStereo) MOD_ABORT; +} + +void Data::reset() { + pStereo.reset(); + pString.reset(); + g.reset(); +} + +Result> gml(lib::IO::Warnings &warnings, std::string_view src) { + GML::Graph gGML; + { + gml::ast::KeyValue ast; + try { + ast = gml::parser::parse(src); + } catch(const gml::parser::error &e) { + return lib::IO::Result<>::Error(e.what()); + } + using namespace gml::converter::edsl; + auto cVertex = GML::makeVertexConverter(1); + auto cEdge = GML::makeEdgeConverter(1); + auto cGraph = list("graph")(cVertex)(cEdge); + auto iterBegin = * + auto iterEnd = iterBegin + 1; + try { + gml::converter::convert(iterBegin, iterEnd, cGraph, gGML); + } catch(const gml::converter::error &e) { + return Result<>::Error(e.what()); + } + } + + std::sort(begin(gGML.vertices), end(gGML.vertices), [](const GML::Vertex &v1, const GML::Vertex &v2) -> bool { + return v1.id < v2.id; + }); + + // Check that is mathematically is a graph + + std::unordered_map globalIddFromExtID; + { + int i = 0; + for(const auto &vGML: gGML.vertices) { + if(globalIddFromExtID.find(vGML.id) != end(globalIddFromExtID)) + return Result<>::Error("Vertex id " + std::to_string(vGML.id) + " used multiple times."); + globalIddFromExtID.emplace(vGML.id, i++); + } + } + + for(const auto &eGML: gGML.edges) { + if(eGML.source == eGML.target) + return Result<>::Error("Loop edge (on " + std::to_string(eGML.source) + ") is not allowed."); + if(globalIddFromExtID.find(eGML.source) == end(globalIddFromExtID)) + return Result<>::Error("Source " + std::to_string(eGML.source) + " does not exist in '" + + boost::lexical_cast(eGML) + "'."); + if(globalIddFromExtID.find(eGML.target) == end(globalIddFromExtID)) + return Result<>::Error("Target " + std::to_string(eGML.target) + " does not exist in '" + + boost::lexical_cast(eGML) + "'."); + } + + // Now we can calculate connected components and start converting data + + ConnectedComponents components(gGML.vertices.size()); + for(const auto &e: gGML.edges) { + const auto iterSrc = globalIddFromExtID.find(e.source); + const auto iterTar = globalIddFromExtID.find(e.target); + components.join(iterSrc->second, iterTar->second); + } + const auto numComponents = components.finalize(); + + std::vector datas(numComponents); + std::vector> extIDFromVertex(numComponents); + for(auto &d: datas) { + d.g = std::make_unique(); + d.pString = std::make_unique(*d.g); + } + + const auto atError = [&datas](std::string msg) -> Result<> { + for(auto &d: datas) d.reset(); + return Result<>::Error(std::move(msg)); + }; + + for(const auto &vGML: gGML.vertices) { + const auto comp = components[globalIddFromExtID.find(vGML.id)->second]; + auto &g = *datas[comp].g; + const auto v = add_vertex(g); + assert(vGML.label); + datas[comp].pString->addVertex(v, *vGML.label); + datas[comp].externalToInternalIds.emplace(vGML.id, get(boost::vertex_index_t(), g, v)); + extIDFromVertex[comp].emplace(v, vGML.id); + } + const auto vFromVertexId = [&](int id) { + const auto globalIDIter = globalIddFromExtID.find(id); + assert(globalIDIter != end(globalIddFromExtID)); + const auto comp = components[globalIDIter->second]; + const auto vIdIter = datas[comp].externalToInternalIds.find(id); + assert(vIdIter != end(datas[comp].externalToInternalIds)); + return std::pair(comp, vertex(vIdIter->second, *datas[comp].g)); + }; + for(const auto &eGML: gGML.edges) { + const auto[comp, vSrc] = vFromVertexId(eGML.source); + const auto[compTar, vTar] = vFromVertexId(eGML.target); + assert(comp == compTar); + auto &g = *datas[comp].g; + const auto eQuery = edge(vSrc, vTar, g); + if(eQuery.second) + return atError("Duplicate edge with source " + std::to_string(eGML.source) + + " and target " + std::to_string(eGML.target) + "."); + const auto e = add_edge(vSrc, vTar, g); + assert(eGML.label); + datas[comp].pString->addEdge(e.first, *eGML.label); + } + + bool doStereo = false; + for(const auto &vGML: gGML.vertices) doStereo = doStereo || vGML.stereo; + for(const auto &eGML: gGML.edges) doStereo = doStereo || eGML.stereo; + if(!doStereo) return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available + // Stereo + //============================================================================ + std::vector molStates; + std::vector> stereoInferences; + molStates.reserve(numComponents); + stereoInferences.reserve(numComponents); + for(int i = 0; i != numComponents; ++i) { + const auto &g = *datas[i].g; + molStates.emplace_back(g, *datas[i].pString); + stereoInferences.push_back(lib::Stereo::Inference(g, molStates.back(), false)); + } + + const auto &gGeometry = lib::Stereo::getGeometryGraph(); + // Set the explicitly defined edge categories. + //---------------------------------------------------------------------------- + for(const auto &eGML: gGML.edges) { + const auto[comp, vSrc] = vFromVertexId(eGML.source); + const auto[compTar, vTar] = vFromVertexId(eGML.target); + assert(comp == compTar); + const auto &g = *datas[comp].g; + const auto ePair = edge(vSrc, vTar, g); + assert(ePair.second); + if(!eGML.stereo) continue; + const std::string &s = *eGML.stereo; + if(s.size() != 1) + return atError( + "Error in stereo data for edge (" + std::to_string(eGML.source) + + ", " + std::to_string(eGML.target) + "). Parsing error in stereo data '" + s + "'."); + lib::Stereo::EdgeCategory cat; + switch(s.front()) { + case '*': + cat = lib::Stereo::EdgeCategory::Any; + break; + default: + return atError("Error in stereo data for edge (" + std::to_string(eGML.source) + + ", " + std::to_string(eGML.target) + "). Parsing error in stereo data '" + s + "'."); + } + if(auto res = stereoInferences[comp].assignEdgeCategory(ePair.first, cat); !res) + return atError("Error in stereo data for edge (" + std::to_string(eGML.source) + + ", " + std::to_string(eGML.target) + "). " + + res.extractError()); + } + // Set the explicitly stereo data. + //---------------------------------------------------------------------------- + for(auto &vGML: gGML.vertices) { + if(!vGML.stereo) continue; + const auto[comp, v] = vFromVertexId(vGML.id); + if(auto res = lib::Stereo::Read::parseEmbedding(*vGML.stereo)) { + vGML.parsedEmbedding = std::move(*res); + } else { + return atError("Error in stereo data for vertex " + std::to_string(vGML.id) + ". " + + res.extractError()); + } + // Geometry + //.......................................................................... + const auto &embGML = *vGML.parsedEmbedding; + if(embGML.geometry) { + const auto vGeo = gGeometry.findGeometry(*embGML.geometry); + if(vGeo == gGeometry.nullGeometry()) + return atError("Error in stereo data for vertex " + std::to_string(vGML.id) + + ". Invalid gGeometry '" + *embGML.geometry + "'."); + if(auto res = stereoInferences[comp].assignGeometry(v, vGeo); !res) + return atError("Error in stereo data for vertex " + std::to_string(vGML.id) + ". " + + res.extractError()); + } + // Edges + //.......................................................................... + if(embGML.edges) { + stereoInferences[comp].initEmbedding(v); + for(const auto &e: *embGML.edges) { + if(const int *idPtr = std::get_if(&e)) { + const auto extIDNeighbour = *idPtr; + if(globalIddFromExtID.find(extIDNeighbour) == end(globalIddFromExtID)) + return atError("Neighbour vertex " + std::to_string(extIDNeighbour) + + " in stereo embedding for vertex " + std::to_string(vGML.id) + " does not exist."); + const auto[compNeighbour, vNeighbour] = vFromVertexId(extIDNeighbour); + const auto ePair = edge(v, vNeighbour, *datas[comp].g); + if(!ePair.second) + return atError("Error in graph GML. Vertex " + std::to_string(extIDNeighbour) + + " in stereo embedding for vertex " + std::to_string(vGML.id) + " is not a neighbour."); + assert(compNeighbour == comp); + stereoInferences[comp].addEdge(v, ePair.first); + } else if(const char *virtPtr = std::get_if(&e)) { + switch(*virtPtr) { + case 'e': + stereoInferences[comp].addLonePair(v); + break; + case 'r': + stereoInferences[comp].addRadical(v); + break; + default: + MOD_ABORT; // the parser should know what is allowed, nope, not any more +// return Result<>::Error( +// "Error in graph GML. Virtual neighbour in stereo embedding for vertex " + +// std::to_string(vId) + " in " + side + " has unknown type '" + *virtPtr + "'."); + } + } else { + MOD_ABORT; // the parser should know what is allowed + } + } + } + // Fixation + //.......................................................................... + if(embGML.fixation) { + // TODO: expand this when more complicated geometries are implemented + const bool isFixed = *embGML.fixation; + if(isFixed) stereoInferences[comp].fixSimpleGeometry(v); + } + } // end of explicit stereo data + + for(int comp = 0; comp != numComponents; ++comp) { + // TODO: the warning should only be printed once, instead of for each connected component + lib::IO::Warnings stereoWarnings; + auto stereoResult = stereoInferences[comp].finalize( + stereoWarnings, [comp, &extIDFromVertex](lib::Graph::Vertex v) { + const auto iter = extIDFromVertex[comp].find(v); + assert(iter != extIDFromVertex[comp].end()); + return std::to_string(iter->second); + }); + warnings.addFrom(std::move(stereoWarnings), !getConfig().stereo.silenceDeductionWarnings.get()); + if(!stereoResult) + return atError(stereoResult.extractError()); + datas[comp].pStereo = std::make_unique(*datas[comp].g, std::move(stereoInferences[comp])); + } + return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available +} + +namespace { +namespace dfsDetail { +using namespace IO::DFS; +using Vertex = IO::DFS::Vertex; +using Edge = IO::DFS::Edge; + +using GVertex = lib::Graph::Vertex; +using GEdge = lib::Graph::Edge; + +struct JoinConnected { + using ConvertRes = std::pair; +public: + JoinConnected(ConnectedComponents &components) : components(components) {} + + void operator()(const Chain &chain) { + auto[prev, isRingClosure] = (*this)(chain.head, nullptr); + assert(!isRingClosure); + assert(prev); + for(const EVPair &ev: chain.tail) { + prev = (*this)(ev, prev); + assert(prev); + } + } + + ConvertRes operator()(const Vertex &vertex, const LabelVertex *prev) { + const auto[subNext, subIsRingClosure] = boost::apply_visitor(*this, vertex.vertex); + assert(!(subIsRingClosure && prev == nullptr)); + const auto *branchRoot = subIsRingClosure ? prev : subNext; + for(const Branch &branch: vertex.branches) { + const auto *branchPrev = branchRoot; + for(const EVPair &ev: branch.tail) { + branchPrev = (*this)(ev, branchPrev); + assert(branchPrev); + } + } + return {subNext, subIsRingClosure}; + } + + const LabelVertex *operator()(const EVPair &ev, const LabelVertex *prev) { + const auto[next, isRingClosure] = (*this)(ev.second, prev); + join(prev, next, ev.first.label); + if(isRingClosure) return prev; + else return next; + } + + ConvertRes operator()(const LabelVertex &vertex) { + if(vertex.ringClosure) + join(&vertex, vertex.ringClosure, "-"); + return {&vertex, false}; + } + + ConvertRes operator()(const RingClosure &vertex) { + return {vertex.other, true}; + } +private: + void join(const LabelVertex *src, const LabelVertex *tar, const std::string &label) { + if(label.empty()) return; // a dot edge for no-edge + components.join(src->connectedComponentID, tar->connectedComponentID); + } +private: + ConnectedComponents &components; +}; + +struct Converter { + struct ConvertRes { + int component; + GVertex next; + bool isRingClosure; + }; +public: + Converter(std::vector> &gPtrs, + std::vector> &pStringPtrs, + const ConnectedComponents &components) + : gPtrs(gPtrs), pStringPtrs(pStringPtrs), components(components) {} + + void operator()(Chain &chain) { + const auto sub = (*this)(chain.head, -1, lib::Graph::GraphType::null_vertex()); + int component = sub.component; + GVertex vPrev = sub.next; + assert(!sub.isRingClosure); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + for(EVPair &ev: chain.tail) { + std::tie(component, vPrev) = (*this)(ev, component, vPrev); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + } + } + + ConvertRes operator()(Vertex &vertex, int component, GVertex prev) { + const auto sub = boost::apply_visitor(*this, vertex.vertex); + assert(!(sub.isRingClosure && prev == lib::Graph::GraphType::null_vertex())); + const GVertex branchRoot = sub.isRingClosure ? prev : sub.next; + const int branchRootComponent = sub.isRingClosure ? component : sub.component; + for(Branch &branch: vertex.branches) { + int componentPrev = branchRootComponent; + GVertex branchPrev = branchRoot; + for(EVPair &ev: branch.tail) { + std::tie(componentPrev, branchPrev) = (*this)(ev, componentPrev, branchPrev); + assert(branchPrev != lib::Graph::GraphType::null_vertex()); + } + } + return sub; + } + + std::pair operator()(EVPair &ev, int component, GVertex prev) { + const auto res = (*this)(ev.second, component, prev); + makeEdge(component, prev, res.component, res.next, ev.first.label); + if(res.isRingClosure) return {component, prev}; + else return {res.component, res.next}; + } + + ConvertRes operator()(LabelVertex &vDFS) { + const int component = components[vDFS.connectedComponentID]; + const GVertex v = add_vertex(*gPtrs[component]); + pStringPtrs[component]->addVertex(v, vDFS.label); + if(vDFS.ringClosure) { + const auto componentRing = components[vDFS.ringClosure->connectedComponentID]; + const auto vRing = vertex(vDFS.ringClosure->gVertexId, *gPtrs[componentRing]); + makeEdge(component, v, componentRing, vRing, "-"); + } + vDFS.gVertexId = get(boost::vertex_index_t(), *gPtrs[component], v); + return {component, v, false}; + } + + ConvertRes operator()(RingClosure &rc) { + const int component = components[rc.other->connectedComponentID]; + return {component, vertex(rc.other->gVertexId, *gPtrs[component]), true}; + } +private: + void makeEdge(int srcComponent, GVertex vSrc, int tarComponent, GVertex vTar, const std::string &label) { + if(label.empty()) return; // a dot edge for no-edge + assert(srcComponent == tarComponent); + assert(vSrc != vTar); + assert(!edge(vSrc, vTar, *gPtrs[srcComponent]).second); + std::pair e = add_edge(vSrc, vTar, *gPtrs[srcComponent]); + assert(e.second); + pStringPtrs[srcComponent]->addEdge(e.first, label); + } +private: + std::vector> &gPtrs; + std::vector> &pStringPtrs; + const ConnectedComponents &components; +}; + +struct ImplicitHydrogenAdder { + ImplicitHydrogenAdder(std::vector> &gPtrs, + std::vector> &pStringPtrs, + const ConnectedComponents &components) + : gPtrs(gPtrs), pStringPtrs(pStringPtrs), components(components) {} + + void operator()(const Chain &chain) { + (*this)(chain.head); + for(const auto &ev: chain.tail) (*this)(ev.second); + } + + void operator()(const Vertex &vertex) { + boost::apply_visitor(*this, vertex.vertex); + for(const auto &b: vertex.branches) + for(const auto &ev: b.tail) (*this)(ev.second); + } + + void operator()(const RingClosure &) {} + + void operator()(const LabelVertex &vDFS) { + if(!vDFS.implicit) return; + // we can only add hydrogens if all incident edges are valid bonds + const int component = components[vDFS.connectedComponentID]; + auto &g = *gPtrs[component]; + const auto gVertex = vertex(vDFS.gVertexId, g); + auto &pString = *pStringPtrs[component]; + for(auto eOut: asRange(out_edges(gVertex, g))) + if(lib::Chem::decodeEdgeLabel(pString[eOut]) == BondType::Invalid) + return; + const auto atomId = lib::Chem::atomIdFromSymbol(vDFS.label); + const auto iter = std::find(begin(lib::Chem::getSmilesOrganicSubset()), + end(lib::Chem::getSmilesOrganicSubset()), atomId); + if(iter == end(lib::Chem::getSmilesOrganicSubset())) + MOD_ABORT; + const auto hydrogenAdder = [](lib::Graph::GraphType &g, lib::Graph::PropString &pString, + lib::Graph::Vertex p) { + const GVertex v = add_vertex(g); + pString.addVertex(v, "H"); + const GEdge e = add_edge(v, p, g).first; + pString.addEdge(e, "-"); + }; + lib::Chem::addImplicitHydrogens(g, pString, gVertex, atomId, hydrogenAdder); + } +private: + std::vector> &gPtrs; + std::vector> &pStringPtrs; + const ConnectedComponents &components; +}; + +} // namespace dfsDetail +} // namespace + +Result> dfs(lib::IO::Warnings &warnings, std::string_view src) { + auto astRes = lib::IO::DFS::Read::graph(src); + if(!astRes) return lib::IO::Result<>::Error(astRes.extractError()); + auto &[astPtr, numVertices, vertexFromId] = *astRes; + auto &ast = *astPtr; + + ConnectedComponents components(numVertices); + (dfsDetail::JoinConnected(components)(ast)); + const int numComponents = components.finalize(); + for(int i = 0; i != numVertices; ++i) { + assert(components[i] >= 0); + assert(components[i] < numComponents); + } + + std::vector> gPtrs(numComponents); + std::vector> pStringPtrs(numComponents); + for(int i = 0; i != numComponents; ++i) { + gPtrs[i] = std::make_unique(); + pStringPtrs[i] = std::make_unique(*gPtrs[i]); + } + + dfsDetail::Converter(gPtrs, pStringPtrs, components)(ast); + dfsDetail::ImplicitHydrogenAdder(gPtrs, pStringPtrs, components)(ast); + std::vector datas(numComponents); + for(int i = 0; i != numComponents; ++i) { + assert(gPtrs[i]); + datas[i].g = std::move(gPtrs[i]); + datas[i].pString = std::move(pStringPtrs[i]); + } + for(auto &&vp: vertexFromId) { + const int component = components[vp.second->connectedComponentID]; + datas[component].externalToInternalIds[vp.first] = vp.second->gVertexId; + } + return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available +} + +Result> smiles(lib::IO::Warnings &warnings, std::string_view src, const bool allowAbstract, + SmilesClassPolicy classPolicy) { + return lib::Chem::readSmiles(warnings, src, allowAbstract, classPolicy); +} + +Result> MDLMOL(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options) { + return lib::Chem::readMDLMOL(warnings, src, options); +} + +Result>> +MDLSD(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options) { + return lib::Chem::readMDLSD(warnings, src, options); +} + +} // namespace mod::lib::Graph::Read \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/IO/Read.hpp b/libs/libmod/src/mod/lib/Graph/IO/Read.hpp new file mode 100644 index 0000000..6d42165 --- /dev/null +++ b/libs/libmod/src/mod/lib/Graph/IO/Read.hpp @@ -0,0 +1,52 @@ +#ifndef MOD_LIB_GRAPH_IO_READ_HPP +#define MOD_LIB_GRAPH_IO_READ_HPP + +#include +#include + +#include +#include +#include +#include + +namespace mod { +enum class SmilesClassPolicy; +struct MDLOptions; +} // namespace mod +namespace mod::lib::Graph { +struct PropStereo; +struct PropString; +} // namespace mod::lib::Graph +namespace mod::lib::Graph::Read { + +struct Data { + Data(); + Data(std::unique_ptr graph, std::unique_ptr label); + Data(Data &&other); + ~Data(); + void reset(); +public: + std::unique_ptr g; + std::unique_ptr pString; + std::unique_ptr pStereo; + std::map externalToInternalIds; +}; + +struct RXNFileData { + std::vector> reactants, products; + // maps external IDs to )productOffset, external ID) + // if no map, then (-1, -1) + // std::vector>> aamap; +}; + +lib::IO::Result> gml(lib::IO::Warnings &warnings, std::string_view src); +lib::IO::Result> dfs(lib::IO::Warnings &warnings, std::string_view src); +lib::IO::Result> smiles(lib::IO::Warnings &warnings, std::string_view smiles, bool allowAbstract, + SmilesClassPolicy classPolicy); +lib::IO::Result> MDLMOL(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options); +lib::IO::Result>> +MDLSD(lib::IO::Warnings &warnings, std::string_view src, const MDLOptions &options); + +} // namespace mod::lib::Graph::Read + +#endif // MOD_LIB_GRAPH_IO_READ_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/IO/Write.cpp b/libs/libmod/src/mod/lib/Graph/IO/Write.cpp new file mode 100644 index 0000000..56d2828 --- /dev/null +++ b/libs/libmod/src/mod/lib/Graph/IO/Write.cpp @@ -0,0 +1,665 @@ +#include "Write.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +namespace mod::lib::Graph::Write { +namespace { + +// returns the filename _without_ extension + +std::string getFilePrefix(const std::size_t gId) { + return lib::IO::makeUniqueFilePrefix() + "g_" + boost::lexical_cast(gId); +} + +void escapeLabelForDot(const std::string &label, std::ostream &s) { + for(char c: label) { + if(c == '"') s << "\\\""; + else if(c == '\\') s << "\\\\"; + else s << c; + } +} + +} // namespace + +void gml(const LabelledGraph &gLabelled, const DepictionData &depict, const std::size_t gId, + bool withCoords, std::ostream &s) { + if(!depict.getHasCoordinates() && withCoords) MOD_ABORT; + const auto &g = get_graph(gLabelled); + const auto &pString = get_string(gLabelled); + s << "graph [\n"; + for(auto v: asRange(vertices(g))) { + s << "\tnode [ id " << get(boost::vertex_index_t(), g, v) << " label \"" << pString[v] << "\""; + if(withCoords) + s << " vis2d [ x " << depict.getX(v, true) << " y " << depict.getY(v, true) << " ]"; + s << " ]\n"; + } + for(auto e: asRange(edges(g))) { + s << "\tedge [ source " << get(boost::vertex_index_t(), g, source(e, g)) + << " target " << get(boost::vertex_index_t(), g, target(e, g)) + << " label \"" << pString[e] << "\" ]\n"; + } + s << "]\n"; +} + +std::string gml(const Single &g, bool withCoords) { + static std::map, std::string> cache; + const auto iter = cache.find({g.getId(), withCoords}); + if(iter != end(cache)) return iter->second; + + std::string fileNoExt = getFilePrefix(g.getId()); + post::FileHandle s(fileNoExt + ".gml"); + gml(g.getLabelledGraph(), g.getDepictionData(), g.getId(), withCoords, s); + + cache[{g.getId(), withCoords}] = s; + return s; +} + +namespace { +namespace dfsDetail { +using namespace IO::DFS; +using Vertex = IO::DFS::Vertex; +using Edge = IO::DFS::Edge; + +using GVertex = lib::Graph::Vertex; +using GEdge = lib::Graph::Edge; + +enum class Colour { + White, Grey, Black +}; + +struct Printer { + Printer(std::ostream &s, const std::map &idMap) : s(s), idMap(idMap) {} + + void operator()(const Chain &chain) { + (*this)(chain.head); + (*this)(chain.tail); + } + + void operator()(const Vertex &v) { + boost::apply_visitor(*this, v.vertex); + for(const Branch &b: v.branches) { + s << "("; + (*this)(b.tail); + s << ")"; + } + } + + void operator()(const LabelVertex &v) { + s << "["; + escapeLabel(s, v.label, ']'); + s << "]"; + if(v.id) + if(const auto iter = idMap.find(*v.id); iter != idMap.end()) + s << iter->second; + } + + void operator()(const RingClosure &v) { + assert(v.id != std::numeric_limits::max()); + const auto iter = idMap.find(v.id); + assert(iter != idMap.end()); + s << iter->second; + } + + void operator()(const std::vector &evPairs) { + for(const EVPair &p: evPairs) { + (*this)(p.first); + (*this)(p.second); + } + } + + void operator()(const Edge &e) { + if(e.label.size() == 1) { + char c = e.label[0]; + switch(c) { + case '-': + return; + case ':': + case '=': + case '#': + s << c; + return; + } + } + s << "{"; + escapeLabel(s, e.label, '}'); + s << "}"; + } +private: + std::ostream &s; + const std::map &idMap; +}; + +struct Prettyfier { + struct EndsWithNumber { + bool operator()(const LabelVertex &v) const { + return v.id.has_value(); + } + + bool operator()(const RingClosure &v) const { + return true; + } + }; + + struct IsRingClorsure { + bool operator()(const LabelVertex &v) const { + return false; + } + + bool operator()(const RingClosure &v) const { + return true; + } + }; +public: + Prettyfier(const std::vector &targetForRing) : targetForRing(targetForRing) {} + + void operator()(Chain &chain) { + (*this)(chain.head); + (*this)(chain.tail); + if(chain.head.branches.size() > 0 && chain.tail.size() == 0) { + // V(B)..(B)(B) + chain.tail = std::move(chain.head.branches.back().tail); + chain.head.branches.pop_back(); + // V(B)..(B)B + } + } + + void operator()(Vertex &v) { + boost::apply_visitor(*this, v.vertex); + for(Branch &b: v.branches) (*this)(b.tail); + } + + void operator()(LabelVertex &v) { + if(v.id) + if(!targetForRing[*v.id]) v.id.reset(); + } + + void operator()(RingClosure &v) { + assert(v.id != std::numeric_limits::max()); + assert(targetForRing[v.id]); + } + + void operator()(std::vector &evPairs) { + for(EVPair &p: evPairs) { + (*this)(p.first); + (*this)(p.second); + } + if(evPairs.empty()) return; + // -EV...-EV + auto &lastVertex = evPairs.back().second; + if(lastVertex.branches.empty()) return; + // -EV...-EV(B)..(B)(B) + // pull up the last B to be more EV-pairs + if(lastVertex.branches.size() == 1 && boost::apply_visitor(EndsWithNumber(), lastVertex.vertex)) { + // -EV...-EVN(B) + // or + // -EV...-N(B) + assert(!lastVertex.branches.front().tail.empty()); + const auto &firstBranchEV = lastVertex.branches.front().tail.front(); + if(firstBranchEV.first.label == "-" && boost::apply_visitor(IsRingClorsure(), firstBranchEV.second.vertex)) { + // -EV...-EVN(M) + // or + // -EV...-N(M) + return; + } + } + std::vector lastTail = std::move(evPairs.back().second.branches.back().tail); + evPairs.back().second.branches.pop_back(); + evPairs.insert(end(evPairs), std::move_iterator(lastTail.begin()), std::move_iterator(lastTail.end())); + } + + void operator()(Edge &e) {} +private: + const std::vector &targetForRing; +}; + +std::pair> +write(const GraphType &g, const PropString &pString, bool withIds) { + using namespace detail; + using GEdgeIter = GraphType::out_edge_iterator; + using VertexInfo = std::pair>; + std::vector realVertices(num_vertices(g), nullptr); + Chain chain; + chain.hasNonSmilesRingClosure = false; + + std::vector colour(num_vertices(g), Colour::White); + // note: if withIds, pretend *all* vertices are targets for ring closures + std::vector targetForRing(num_vertices(g), withIds); + std::map edgeColour; + for(GEdge e: asRange(edges(g))) edgeColour[e] = Colour::White; + std::stack stack; + { // discover root + GVertex cur = *vertices(g).first; + unsigned int curId = get(boost::vertex_index_t(), g, cur); + assert(curId < colour.size()); + colour[curId] = Colour::Grey; + assert(!realVertices[curId]); + realVertices[curId] = &chain.head; + LabelVertex lv; + lv.id = curId; + lv.label = pString[cur]; + realVertices[curId]->vertex = lv; + stack.push(std::make_pair(cur, out_edges(cur, g))); + } + while(!stack.empty()) { + GVertex cur = stack.top().first; + GEdgeIter iter, iterEnd; + boost::tie(iter, iterEnd) = stack.top().second; + stack.pop(); + unsigned int curId = get(boost::vertex_index_t(), g, cur); + assert(realVertices[curId]); + Vertex *curVertex = realVertices[curId]; + // std::cout << "CurVertex: " << pString(cur) << std::endl; + while(iter != iterEnd) { + GVertex next = target(*iter, g); + GEdge test; + unsigned int nextId = get(boost::vertex_index_t(), g, next); + Edge edge; + edge.label = pString[*iter]; + // mark edge + Colour oldEdgeColour = edgeColour[*iter]; + edgeColour[*iter] = Colour::Black; + // std::cout << "\tEdge: " << pString(cur) + // << " ->(" << edge.label << ", " << (oldEdgeColour == Black ? "black" : "white") << ") " + // << pString(next) << "\t"; + if(colour[nextId] == Colour::White) { // tree edge, new vertex + // std::cout << "white" << std::endl; + // create the new vertex + assert(!realVertices[nextId]); + LabelVertex lv; + lv.id = nextId; + lv.label = pString[next]; + Vertex newVertex; + newVertex.vertex = lv; + curVertex->branches.push_back((Branch())); + curVertex->branches.back().tail.push_back(std::make_pair(edge, newVertex)); + Vertex *nextVertex = &curVertex->branches.back().tail.back().second; + realVertices[nextId] = nextVertex; + colour[nextId] = Colour::Grey; + // switch to the new vertex + iter++; + stack.push(std::make_pair(cur, std::make_pair(iter, iterEnd))); + cur = next; + curId = nextId; + curVertex = nextVertex; + boost::tie(iter, iterEnd) = out_edges(next, g); + // std::cout << "CurVertex: " << pString(cur)<< std::endl; + } else if(colour[nextId] == Colour::Grey) { // back edge, maybe an already traversed edge + // std::cout << "grey" << std::endl; + if(oldEdgeColour == Colour::Black) iter++; // already traversed + else { + RingClosure rc; + rc.id = nextId; + Vertex backVertex; + backVertex.vertex = rc; + curVertex->branches.push_back((Branch())); + curVertex->branches.back().tail.push_back(std::make_pair(edge, backVertex)); + if(targetForRing[nextId]) chain.hasNonSmilesRingClosure = true; + targetForRing[nextId] = true; + iter++; + } + } else { + // std::cout << "black" << std::endl; + iter++; + } + } + colour[curId] = Colour::Black; + } + + std::map idMap; + int nextMappedId = 1; + for(int id = 0; id != targetForRing.size(); id++) + if(targetForRing[id]) + idMap[id] = nextMappedId++; + Prettyfier pretty(targetForRing); + pretty(chain); + return std::make_pair(chain, idMap); +} + +} // namespace dfsDetail +} // namespace + +std::pair dfs(const LabelledGraph &gLabelled, bool withIds) { + const auto &g = get_graph(gLabelled); + const auto &pString = get_string(gLabelled); + if(num_vertices(g) == 0) return std::pair("", false); + auto[chain, idMap] = dfsDetail::write(g, pString, withIds); + + std::stringstream graphDFS; + dfsDetail::Printer p(graphDFS, idMap); + p(chain); + return std::pair(graphDFS.str(), chain.hasNonSmilesRingClosure); +} + +namespace { + +struct DotCacheEntry { + std::size_t id; + std::string prefix; +public: + friend bool operator<(const DotCacheEntry &a, const DotCacheEntry &b) { + return std::tie(a.id, a.prefix) < std::tie(b.id, b.prefix); + } +}; + +} // namespace + +std::string dot(const LabelledGraph &gLabelled, const std::size_t gId, const Options &options) { + static std::map cache; + const auto iter = cache.find({gId, options.graphvizPrefix}); + if(iter != end(cache)) return iter->second; + + // use _gv suffix so a coord file will not clash with non-gv coord files + post::FileHandle s(getFilePrefix(gId) + "_gv.dot"); + const auto &g = get_graph(gLabelled); + const auto &pString = get_string(gLabelled); + s << "graph G {\n"; + s << "\tnode [shape=plaintext]\n"; + if(!options.graphvizPrefix.empty()) + s << "\t" << options.graphvizPrefix << '\n'; + + for(const auto v: asRange(vertices(g))) { + s << '\t' << get(boost::vertex_index_t(), g, v) << " [ label=\""; + escapeLabelForDot(pString[v], s); + s << "\" ]\n"; + } + for(const auto e: asRange(edges(g))) { + const auto srcId = get(boost::vertex_index_t(), g, source(e, g)); + const auto tarId = get(boost::vertex_index_t(), g, target(e, g)); + const std::string &label = pString[e]; + if(label == "-" || label == "=" || label == "#") { + s << srcId << " -- " << tarId << '\n'; + if(label != "-") s << srcId << " -- " << tarId << '\n'; + if(label == "#") s << srcId << " -- " << tarId << '\n'; + } else { + s << srcId << " -- " << tarId << " [ label=\""; + escapeLabelForDot(label, s); + s << "\" ]\n"; + } + } + s << "}\n"; + cache[{gId, options.graphvizPrefix}] = s; + return s; +} + +namespace { + +struct OpenBabelCoordsCacheEntry { + std::size_t id; + bool collapseHydrogens; + int rotation; + bool mirror; +public: + friend bool operator<(const OpenBabelCoordsCacheEntry &a, const OpenBabelCoordsCacheEntry &b) { + return std::tie(a.id, a.collapseHydrogens, a.rotation, a.mirror) + < std::tie(b.id, b.collapseHydrogens, b.rotation, b.mirror); + } +}; + +} // namespace + +std::string coords(const LabelledGraph &gLabelled, const DepictionData &depict, + const std::size_t gId, const Options &options) { + if(options.withGraphvizCoords || !depict.getHasCoordinates()) { + // we map 1-to-1 a dot file to a coord file, so cache by the dot filename + static std::map cache; + + auto fileNoExt = dot(gLabelled, gId, options); + fileNoExt.erase(fileNoExt.end() - 4, fileNoExt.end()); + const auto iter = cache.find(fileNoExt); + if(iter != end(cache)) return iter->second; + + lib::IO::post() << "coordsFromGV graph \"" << fileNoExt << "\"\n"; + + // the coord file is still for the tex coord file which is just then created in post + std::string file = fileNoExt + "_coord.tex"; + cache[fileNoExt] = file; + return file; + } else { + static std::map cache; + const auto iter = cache.find({gId, options.collapseHydrogens, options.rotation, options.mirror}); + if(iter != end(cache)) return iter->second; + + const auto &g = get_graph(gLabelled); + std::string f = getFilePrefix(gId); + if(options.collapseHydrogens) f += "_mol"; + if(options.rotation != 0) f += "_r" + std::to_string(options.rotation); + if(options.mirror) f += "_m" + std::to_string(options.mirror); + post::FileHandle s(f + "_coord.tex"); + s << "% dummy\n"; + for(const auto v: asRange(vertices(g))) { + const auto vId = get(boost::vertex_index_t(), g, v); + if(options.collapseHydrogens && Chem::isCollapsibleHydrogen(v, g, depict, depict, [&depict](const auto v) { + return depict.hasImportantStereo(v); + })) + continue; + double x, y; + std::tie(x, y) = pointTransform( + depict.getX(v, !options.collapseHydrogens), + depict.getY(v, !options.collapseHydrogens), + options.rotation, options.mirror); + s << "\\coordinate[overlay] (\\modIdPrefix v-coord-" << vId << ") at (" + << std::fixed << x << ", " << y << ") {};\n"; + } + std::string file = s; + cache[{gId, options.collapseHydrogens, options.rotation, options.mirror}] = file; + return file; + } +} + +namespace { + +struct TikzCacheEntry { + std::size_t id; + std::string coordFile; + std::string options; + bool asInline; + std::string idPrefix; +public: + friend bool operator<(const TikzCacheEntry &a, const TikzCacheEntry &b) { + return std::tie(a.id, a.coordFile, a.options, a.asInline, a.idPrefix) + < std::tie(b.id, b.coordFile, b.options, b.asInline, b.idPrefix); + } +}; + +} // namespace + +std::pair +tikz(const LabelledGraph &gLabelled, const DepictionData &depict, const std::size_t gId, + const Options &options, + bool asInline, const std::string &idPrefix) { + static std::map cache; + + std::string strOptions = options.getStringEncoding(); + std::string fileCoordsExt = coords(gLabelled, depict, gId, options); + + const auto iter = cache.find({gId, fileCoordsExt, strOptions, asInline, idPrefix}); + if(iter != end(cache)) return std::pair(iter->second, fileCoordsExt); + + std::string file = getFilePrefix(gId) + "_" + strOptions; + if(asInline) file += "i"; + file += ".tex"; + post::FileHandle s(file); + tikz(s, options, get_graph(gLabelled), depict, fileCoordsExt, asInline, idPrefix); + + cache[{gId, fileCoordsExt, strOptions, asInline, idPrefix}] = file; + return std::pair(file, fileCoordsExt); +} + +std::string +pdf(const LabelledGraph &gLabelled, const DepictionData &depict, const std::size_t gId, + const Options &options) { + { // user-specified depiction + static std::map userCache; + auto iter = userCache.find(gId); + if(iter != end(userCache)) return iter->second; + auto fImage = depict.getImage(); + if(fImage) { + std::string imageNoExt = (*fImage)(); + if(imageNoExt.empty()) { + std::cout << "User-specified depiction file for graph with id " << gId << " can not be empty." << std::endl; + throw 0; + } + std::string cmd = depict.getImageCommand(); + if(!cmd.empty()) lib::IO::post() << cmd << '\n'; + std::string image = imageNoExt + ".pdf"; + userCache[gId] = image; + return image; + } + } + // auto-generated depiction + // maps 1-to-1 a (coord, tikz) pair to a PDF + static std::map, std::string> cache; + auto tikzFiles = tikz(gLabelled, depict, gId, options, false, ""); + + const auto iter = cache.find(tikzFiles); + if(iter != end(cache)) return iter->second; + + std::string fileNoExt = tikzFiles.first.substr(0, tikzFiles.first.size() - 4); + std::string fileCoordsNoExt = tikzFiles.second.substr(0, tikzFiles.second.size() - 4); + lib::IO::post() << "compileTikz \"" << fileNoExt << "\" \"" << fileCoordsNoExt << "\"\n"; + + fileNoExt += ".pdf"; + cache[tikzFiles] = fileNoExt; + return fileNoExt; +} + +std::string svg(const LabelledGraph &gLabelled, const DepictionData &depict, const std::size_t gId, + const Options &options) { + // maps 1-to-1 a PDF to an SVG + static std::map cache; + + std::string pdfFile = pdf(gLabelled, depict, gId, options); + + const auto iter = cache.find(pdfFile); + if(iter != end(cache)) return iter->second; + + std::string fileNoExt = pdfFile.substr(0, pdfFile.size() - 4); + lib::IO::post() << "pdfToSvg \"" << fileNoExt << "\" \"" << fileNoExt << "\"\n"; + + std::string file = fileNoExt + ".svg"; + cache[pdfFile] = file; + return file; +} + +std::pair summary(const Single &g, const Options &first, const Options &second) { + std::string graphLike = pdf(g, first); + std::string molLike = first == second ? "" : pdf(g, second); + lib::IO::post() << "summaryGraph \"" << g.getName() << "\" \"" + << std::string(begin(graphLike), end(graphLike) - 4) << "\" \""; + + if(!molLike.empty()) + lib::IO::post() << std::string(begin(molLike), end(molLike) - 4); + lib::IO::post() << "\"\n"; + + if(molLike.empty()) + return std::pair(graphLike, graphLike); + else + return std::pair(graphLike, molLike); +} + +void termState(const Single &g) { + using namespace lib::Term; + lib::IO::post() << "summarySubsection \"Term State for " << g.getName() << "\"\n"; + post::FileHandle s(lib::IO::makeUniqueFilePrefix() + "termState.tex"); + s << "\\begin{verbatim}\n"; + const auto &termState = get_term(g.getLabelledGraph()); + if(isValid(termState)) { + std::unordered_map > addrToVertex; + std::unordered_map > addrToEdge; + for(Vertex v: asRange(vertices(g.getGraph()))) { + Address a{AddressType::Heap, termState[v]}; + addrToVertex[a].insert(v); + } + for(Edge e: asRange(edges(g.getGraph()))) { + Address a{AddressType::Heap, termState[e]}; + addrToEdge[a].insert(e); + } + lib::Term::Write::wam(getMachine(termState), lib::Term::getStrings(), s, [&](Address addr, std::ostream &s) { + s << " "; + bool first = true; + for(auto v: addrToVertex[addr]) { + if(!first) s << ", "; + first = false; + s << "v" << get(boost::vertex_index_t(), g.getGraph(), v); + } + for(auto e: addrToEdge[addr]) { + if(!first) s << ", "; + first = false; + s << "e(" + << get(boost::vertex_index_t(), g.getGraph(), source(e, g.getGraph())) + << ", " + << get(boost::vertex_index_t(), g.getGraph(), target(e, g.getGraph())) + << ")"; + } + }); + } else { + std::string msg = "Parsing failed for graph '" + g.getName() + "'. " + termState.getParsingError(); + throw TermParsingError(std::move(msg)); + } + s << "\\end{verbatim}\n"; + lib::IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; +} + +std::string stereoSummary(const Single &gLib, Vertex v, const lib::Stereo::Configuration &conf, + const IO::Graph::Write::Options &options, int shownIdOffset, const std::string &nameSuffix) { + const auto &g = gLib.getGraph(); + const auto vId = get(boost::vertex_index_t(), g, v); + std::string name = "g_" + boost::lexical_cast(gLib.getId()) + "_stereo_" + + boost::lexical_cast(vId); + IO::post() << "summarySubsection \"Stereo, g " << gLib.getId() << ", v " << vId + << nameSuffix << "\"\n"; + std::string f = lib::Stereo::Write::pdf(g, v, conf, name, gLib.getDepictionData(), options, + [shownIdOffset](const auto &g, const auto v) { + return get(boost::vertex_index_t(), g, v) + shownIdOffset; + }); + post::FileHandle s(IO::makeUniqueFilePrefix() + "stereo.tex"); + s << "\\begin{center}\n"; + s << "\\includegraphics{" << f << "}\\\\\n"; + s << "File: \\texttt{" << IO::escapeForLatex(f) << "}\n"; + s << "\\end{center}\n"; + IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; + return f; +} + +//------------------------------------------------------------------------------ +// Simplified interface for Single +//------------------------------------------------------------------------------ + +void gml(const Single &g, bool withCoords, std::ostream &s) { + gml(g.getLabelledGraph(), g.getDepictionData(), g.getId(), withCoords, s); +} + +std::string tikz(const Single &g, const Options &options, bool asInline, const std::string &idPrefix) { + auto res = tikz(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options, asInline, idPrefix); + return res.first; +} + +std::string pdf(const Single &g, const Options &options) { + return pdf(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options); +} + +std::string svg(const Single &g, const Options &options) { + return svg(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options); +} + +} // namespace mod::lib::Graph::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/IO/Write.hpp b/libs/libmod/src/mod/lib/Graph/IO/Write.hpp new file mode 100644 index 0000000..7e6cd1f --- /dev/null +++ b/libs/libmod/src/mod/lib/Graph/IO/Write.hpp @@ -0,0 +1,52 @@ +#ifndef MOD_LIB_GRAPH_IO_WRITE_HPP +#define MOD_LIB_GRAPH_IO_WRITE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace mod::lib::Graph { +struct LabelledGraph; +struct Single; +} // namespace mod::lib::Graph +namespace mod::lib::Graph::Write { +struct DepictionData; +using Options = lib::IO::Graph::Write::Options; + +// all return the filename _with_ extension +void gml(const LabelledGraph &gLabelled, const DepictionData &depict, + const std::size_t gId, bool withCoords, std::ostream &s); +std::string gml(const Single &g, bool withCoords); +std::pair dfs(const LabelledGraph &gLabelled, bool withIds); + +std::string dot(const LabelledGraph &gLabelled, const std::size_t gId, const Options &options); +std::string coords(const LabelledGraph &gLabelled, const DepictionData &depict, + const std::size_t gId, const Options &options); +std::pair tikz(const LabelledGraph &gLabelled, + const DepictionData &depict, + const std::size_t gId, const Options &options, + bool asInline, const std::string &idPrefix); +std::string pdf(const LabelledGraph &gLabelled, const DepictionData &depict, + const std::size_t gId, const Options &options); +std::string svg(const LabelledGraph &gLabelled, const DepictionData &depict, + const std::size_t gId, const Options &options); +std::pair summary(const Single &g, const Options &first, const Options &second); +void termState(const Single &g); + +std::string stereoSummary(const Single &g, Vertex v, const lib::Stereo::Configuration &conf, + const IO::Graph::Write::Options &options, int shownIdOffset, const std::string &nameSuffix); + +// simplified interface for lib::Graph::Single +void gml(const Single &g, bool withCoords, std::ostream &s); +std::string tikz(const Single &g, const Options &options, bool asInline, const std::string &idPrefix); +std::string pdf(const Single &g, const Options &options); +std::string svg(const Single &g, const Options &options); + +} // namespace mod::lib::Graph::Write + +#endif // MOD_LIB_GRAPH_IO_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Graph/LabelledGraph.cpp b/libs/libmod/src/mod/lib/Graph/LabelledGraph.cpp index c229cb4..374093c 100644 --- a/libs/libmod/src/mod/lib/Graph/LabelledGraph.cpp +++ b/libs/libmod/src/mod/lib/Graph/LabelledGraph.cpp @@ -68,7 +68,7 @@ bool has_stereo(const LabelledGraph &g) { const LabelledGraph::PropStereoType &get_stereo(const LabelledGraph &g) { if(!has_stereo(g)) { - auto inference = lib::Stereo::makeInference(get_graph(g), get_molecule(g), false); + auto inference = lib::Stereo::Inference(get_graph(g), get_molecule(g), false); lib::IO::Warnings warnings; auto result = inference.finalize(warnings, [&g](Vertex v) { return std::to_string(get(boost::vertex_index_t(), get_graph(g), v)); diff --git a/libs/libmod/src/mod/lib/Graph/LabelledGraph.hpp b/libs/libmod/src/mod/lib/Graph/LabelledGraph.hpp index dc3a192..b4e51f9 100644 --- a/libs/libmod/src/mod/lib/Graph/LabelledGraph.hpp +++ b/libs/libmod/src/mod/lib/Graph/LabelledGraph.hpp @@ -21,6 +21,7 @@ struct LabelledGraph { // models a mod::lib::LabelledGraphConcept std::unique_ptr pStereo); LabelledGraph(const LabelledGraph &other); ~LabelledGraph(); +public: // LabelledGraphConcept friend GraphType &get_graph(LabelledGraph &g); friend const GraphType &get_graph(const LabelledGraph &g); friend PropStringType &get_string(LabelledGraph &g); diff --git a/libs/libmod/src/mod/lib/Graph/Properties/Molecule.cpp b/libs/libmod/src/mod/lib/Graph/Properties/Molecule.cpp index fb07437..e92e6b3 100644 --- a/libs/libmod/src/mod/lib/Graph/Properties/Molecule.cpp +++ b/libs/libmod/src/mod/lib/Graph/Properties/Molecule.cpp @@ -1,5 +1,6 @@ #include "Molecule.hpp" +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include namespace mod::lib::Graph { @@ -47,7 +49,7 @@ bool PropMolecule::getIsMolecule() const { const lib::Chem::OBMolHandle &PropMolecule::getOBMol() const { if(!isMolecule) { std::cout << "MoleculeState: Trying to create OpenBabel::OBMol from non-molecule." << std::endl - << "Should DepictionData be used instead?" << std::endl; + << "Should DepictionData be used instead?" << std::endl; MOD_ABORT; } if(!obMol) { @@ -77,12 +79,11 @@ double PropMolecule::getExactMass() const { double PropMolecule::getEnergy() const { if(!energy) { #ifndef MOD_HAVE_OPENBABEL - MOD_NO_OPENBABEL_ERROR - std::cout << "Energy calculation is not possible without Open Babel." << std::endl; - std::cout << "Energy values can be manually cached on graphs if calculation is not desired." << std::endl; - std::exit(1); + throw FatalError(MOD_NO_OPENBABEL_ERROR_STR + + "\nEnergy calculation is not possible without Open Babel.\n" + + "Energy values can be manually cached on graphs if calculation is not desired."); #else - energy = getOBMol().getEnergy(); + energy = getOBMol().getEnergy(false); #endif } return *energy; diff --git a/libs/libmod/src/mod/lib/Graph/Properties/Term.cpp b/libs/libmod/src/mod/lib/Graph/Properties/Term.cpp index c36cf5f..a2f4dd5 100644 --- a/libs/libmod/src/mod/lib/Graph/Properties/Term.cpp +++ b/libs/libmod/src/mod/lib/Graph/Properties/Term.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include @@ -23,7 +23,7 @@ PropTerm::PropTerm(const GraphType &g, const PropString &pString, const StringSt } lib::Term::RawTerm rawTerm; try { - rawTerm = lib::IO::Term::Read::rawTerm(label, stringStore); + rawTerm = Term::Read::rawTerm(label, stringStore); } catch(const lib::IO::ParsingError &e) { parsingError = e.msg; return std::numeric_limits::max(); diff --git a/libs/libmod/src/mod/lib/Graph/Single.cpp b/libs/libmod/src/mod/lib/Graph/Single.cpp index 0a6eb9b..fa97fb4 100644 --- a/libs/libmod/src/mod/lib/Graph/Single.cpp +++ b/libs/libmod/src/mod/lib/Graph/Single.cpp @@ -1,12 +1,15 @@ #include "Single.hpp" #include +#include #include +#include #include #include #include -#include -#include +#include +#include +#include #include #include #include @@ -14,12 +17,14 @@ #include #include #include -#include #include #include #include #include +#include +#include +#include #include #include @@ -38,7 +43,7 @@ const std::string getGraphName(unsigned int id) { bool sanityCheck(const GraphType &g, const PropString &pString, std::ostream &s) { std::vector > edgesSorted; edgesSorted.reserve(num_edges(g)); - for(Edge e : asRange(edges(g))) { + for(Edge e: asRange(edges(g))) { Vertex v1 = source(e, g), v2 = target(e, g); if(get(boost::vertex_index_t(), g, v2) > get(boost::vertex_index_t(), g, v1)) std::swap(v1, v2); edgesSorted.emplace_back(v1, v2); @@ -115,12 +120,12 @@ void Single::setName(std::string name) { } const std::pair Single::getGraphDFS() const { - if(!dfs) std::tie(dfs, dfsHasNonSmilesRingClosure) = DFSEncoding::write(getGraph(), getStringState(), false); + if(!dfs) std::tie(dfs, dfsHasNonSmilesRingClosure) = Write::dfs(getLabelledGraph(), false); return std::pair(*dfs, dfsHasNonSmilesRingClosure); } const std::string &Single::getGraphDFSWithIds() const { - if(!dfsWithIds) dfsWithIds = DFSEncoding::write(getGraph(), getStringState(), true).first; + if(!dfsWithIds) dfsWithIds = Write::dfs(getLabelledGraph(), true).first; return *dfsWithIds; } @@ -167,7 +172,7 @@ const std::string &Single::getSmilesWithIds() const { unsigned int Single::getVertexLabelCount(const std::string &label) const { unsigned int count = 0; - for(Vertex v : asRange(vertices(getGraph()))) { + for(Vertex v: asRange(vertices(getGraph()))) { const std::string &vLabel = getStringState()[v]; if(vLabel == label) count++; } @@ -177,20 +182,20 @@ unsigned int Single::getVertexLabelCount(const std::string &label) const { unsigned int Single::getEdgeLabelCount(const std::string &label) const { unsigned int count = 0; - for(Edge e : asRange(edges(getGraph()))) { + for(Edge e: asRange(edges(getGraph()))) { const std::string &eLabel = getStringState()[e]; if(eLabel == label) count++; } return count; } -DepictionData &Single::getDepictionData() { - if(!depictionData) depictionData.reset(new DepictionData(getLabelledGraph())); +Write::DepictionData &Single::getDepictionData() { + if(!depictionData) depictionData.reset(new Write::DepictionData(getLabelledGraph())); return *depictionData; } -const DepictionData &Single::getDepictionData() const { - if(!depictionData) depictionData.reset(new DepictionData(getLabelledGraph())); +const Write::DepictionData &Single::getDepictionData() const { + if(!depictionData) depictionData.reset(new Write::DepictionData(getLabelledGraph())); return *depictionData; } @@ -239,13 +244,24 @@ namespace { namespace GM = jla_boost::GraphMorphism; namespace GM_MOD = lib::GraphMorphism; +template +void morphism(const Single &gDomain, + const Single &gCodomain, + LabelSettings labelSettings, + Finder finder, + Callback callback) { + lib::GraphMorphism::morphismSelectByLabelSettings(gDomain.getLabelledGraph(), gCodomain.getLabelledGraph(), + labelSettings, finder, callback); +} + template -std::size_t -morphism(const Single &gDomain, const Single &gCodomain, std::size_t maxNumMatches, LabelSettings labelSettings, - Finder finder) { +std::size_t morphismMax(const Single &gDomain, + const Single &gCodomain, + std::size_t maxNumMatches, + LabelSettings labelSettings, + Finder finder) { auto mr = GM::makeLimit(maxNumMatches); - lib::GraphMorphism::morphismSelectByLabelSettings(gDomain.getLabelledGraph(), gCodomain.getLabelledGraph(), - labelSettings, finder, std::ref(mr)); + morphism(gDomain, gCodomain, labelSettings, finder, std::ref(mr)); return mr.getNumHits(); } @@ -270,7 +286,7 @@ std::size_t isomorphismSmilesOrCanonOrVF2(const Single &gDom, const Single &gCod std::size_t Single::isomorphismVF2(const Single &gDom, const Single &gCodom, std::size_t maxNumMatches, LabelSettings labelSettings) { - return morphism(gDom, gCodom, maxNumMatches, labelSettings, GM_MOD::VF2Isomorphism()); + return morphismMax(gDom, gCodom, maxNumMatches, labelSettings, GM_MOD::VF2Isomorphism()); } bool Single::isomorphic(const Single &gDom, const Single &gCodom, LabelSettings labelSettings) { @@ -284,10 +300,8 @@ bool Single::isomorphic(const Single &gDom, const Single &gCodom, LabelSettings return gDom.getName() == gCodom.getName(); if(&gDom == &gCodom) return true; switch(getConfig().graph.isomorphismAlg.get()) { - case Config::IsomorphismAlg::SmilesCanonVF2: - return isomorphismSmilesOrCanonOrVF2(gDom, gCodom, labelSettings); - case Config::IsomorphismAlg::VF2: - return isomorphismVF2(gDom, gCodom, 1, labelSettings); + case Config::IsomorphismAlg::SmilesCanonVF2: return isomorphismSmilesOrCanonOrVF2(gDom, gCodom, labelSettings); + case Config::IsomorphismAlg::VF2: return isomorphismVF2(gDom, gCodom, 1, labelSettings); case Config::IsomorphismAlg::Canon: if(labelSettings.relation != LabelRelation::Isomorphism) throw LogicError("Can only do isomorphism via canonicalisation with the isomorphism relation."); @@ -315,7 +329,44 @@ Single::isomorphism(const Single &gDom, const Single &gCodom, std::size_t maxNum std::size_t Single::monomorphism(const Single &gDom, const Single &gCodom, std::size_t maxNumMatches, LabelSettings labelSettings) { - return morphism(gDom, gCodom, maxNumMatches, labelSettings, GM_MOD::VF2Monomorphism()); + return morphismMax(gDom, gCodom, maxNumMatches, labelSettings, GM_MOD::VF2Monomorphism()); +} + +void Single::enumerateMonomorphisms(const Single &gDom, const Single &gCodom, + std::function)> callback, + LabelSettings labelSettings) { + if(labelSettings.type != LabelType::String) MOD_ABORT; + if(labelSettings.withStereo) MOD_ABORT; + morphism(gDom, gCodom, labelSettings, GM_MOD::VF2Monomorphism(), + GM::makeSliceProps( // Slice away the properties for now + GM::makeTransform( + GM::ToInvertibleVectorVertexMap(), + [&gDom, &gCodom, callback](auto &&mVal, const auto &dom, const auto &codom) -> bool { + auto mPtr = std::make_shared>( + std::move(mVal)); + auto gDomAPI = gDom.getAPIReference(); + auto gCodomAPI = gCodom.getAPIReference(); + auto m = VertexMap( + gDomAPI, gCodomAPI, + [gDomAPI, gCodomAPI, mPtr](graph::Graph::Vertex vDom) -> graph::Graph::Vertex { + const auto &gDom = gDomAPI->getGraph().getGraph(); + const auto &gCodom = gCodomAPI->getGraph().getGraph(); + assert(vDom.getId() < num_vertices(gDom)); + const auto v = vertices(gDom).first[vDom.getId()]; + const auto vRes = get(*mPtr, gDom, gCodom, v); + return gCodomAPI->vertices()[get(boost::vertex_index_t(), gCodom, vRes)]; + }, + [gDomAPI, gCodomAPI, mPtr](graph::Graph::Vertex vCodom) -> graph::Graph::Vertex { + const auto &gDom = gDomAPI->getGraph().getGraph(); + const auto &gCodom = gCodomAPI->getGraph().getGraph(); + assert(vCodom.getId() < num_vertices(gCodom)); + const auto v = vertices(gCodom).first[vCodom.getId()]; + const auto vRes = get_inverse(*mPtr, gDom, gCodom, v); + return gDomAPI->vertices()[get(boost::vertex_index_t(), gDom, vRes)]; + } + ); + return callback(std::move(m)); + }))); } bool Single::nameLess(const Single *g1, const Single *g2) { @@ -351,14 +402,14 @@ Single makePermutation(const Single &g) { {LabelType::String, LabelRelation::Isomorphism, false, LabelRelation::Isomorphism}); if(!iso) { - IO::Graph::Write::Options graphLike, molLike; + Write::Options graphLike, molLike; graphLike.EdgesAsBonds(true).RaiseCharges(true).CollapseHydrogens(true).WithIndex(true); molLike.CollapseHydrogens(true).EdgesAsBonds(true).RaiseCharges(true).SimpleCarbons(true).WithColour( true).WithIndex(true); - IO::Graph::Write::summary(g, graphLike, molLike); - IO::Graph::Write::summary(gPerm, graphLike, molLike); - IO::Graph::Write::gml(g, false); - IO::Graph::Write::gml(gPerm, false); + Write::summary(g, graphLike, molLike); + Write::summary(gPerm, graphLike, molLike); + Write::gml(g, false); + Write::gml(gPerm, false); std::cout << "g: " << g.getSmiles() << std::endl; std::cout << "gPerm: " << gPerm.getSmiles() << std::endl; MOD_ABORT; diff --git a/libs/libmod/src/mod/lib/Graph/Single.hpp b/libs/libmod/src/mod/lib/Graph/Single.hpp index 18ca335..86e78e6 100644 --- a/libs/libmod/src/mod/lib/Graph/Single.hpp +++ b/libs/libmod/src/mod/lib/Graph/Single.hpp @@ -16,9 +16,15 @@ #include #include +namespace mod { +template +struct VertexMap; +} // namespace mod namespace mod::lib::Graph { struct PropMolecule; +namespace Write { struct DepictionData; +} // namespace Write struct Single { using CanonIdxMap = boost::iterator_property_map::const_iterator, @@ -44,8 +50,8 @@ struct Single { const std::string &getSmilesWithIds() const; unsigned int getVertexLabelCount(const std::string &label) const; unsigned int getEdgeLabelCount(const std::string &label) const; - DepictionData &getDepictionData(); - const DepictionData &getDepictionData() const; + Write::DepictionData &getDepictionData(); + const Write::DepictionData &getDepictionData() const; public: // deprecated interface const GraphType &getGraph() const; const PropString &getStringState() const; @@ -65,7 +71,7 @@ struct Single { mutable std::vector canon_perm_string; mutable std::unique_ptr canon_form_string; mutable std::unique_ptr aut_group_string; - mutable std::unique_ptr depictionData; + mutable std::unique_ptr depictionData; public: static std::size_t isomorphismVF2(const Single &gDom, const Single &gCodom, std::size_t maxNumMatches, LabelSettings labelSettings); @@ -74,6 +80,9 @@ struct Single { isomorphism(const Single &gDom, const Single &gCodom, std::size_t maxNumMatches, LabelSettings labelSettings); static std::size_t monomorphism(const Single &gDom, const Single &gCodom, std::size_t maxNumMatches, LabelSettings labelSettings); + static void enumerateMonomorphisms(const Single &gDom, const Single &gCodom, + std::function)> callback, + LabelSettings labelSettings); static bool nameLess(const Single *g1, const Single *g2); static bool canonicalCompare(const Single &g1, const Single &g2, LabelType labelType, bool withStereo); public: diff --git a/libs/libmod/src/mod/lib/GraphMorphism/CommonSubgraphFinder.hpp b/libs/libmod/src/mod/lib/GraphMorphism/CommonSubgraphFinder.hpp index 1673d82..0ccb175 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/CommonSubgraphFinder.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/CommonSubgraphFinder.hpp @@ -2,10 +2,9 @@ #define JLA_BOOST_GRAPH_MORPHISM_MCGREGORCOMMONFINDER_HPP #include +#include #include -#include - namespace mod::lib::GraphMorphism { namespace detail { diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/AllVisitor.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/AllVisitor.hpp index 344b3a3..2c24a22 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/AllVisitor.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/AllVisitor.hpp @@ -1,22 +1,19 @@ -#ifndef MOD_LIB_GRAPHMORPHISM_ALLVISITOR_H -#define MOD_LIB_GRAPHMORPHISM_ALLVISITOR_H +#ifndef MOD_LIB_GRAPHMORPHISM_ALLVISITOR_HPP +#define MOD_LIB_GRAPHMORPHISM_ALLVISITOR_HPP #include #include #include -namespace mod { -namespace lib { -namespace GraphMorphism { -namespace Constraints { +namespace mod::lib::GraphMorphism::Constraints { namespace detail { template class ...Cs> -struct AllVisitor : BaseVisitor, Visitor >... { +struct AllVisitor : BaseVisitor, Visitor > ... { }; template class ...Cs> -struct AllVisitorNonConst : BaseVisitorNonConst, Visitor >... { +struct AllVisitorNonConst : BaseVisitorNonConst, Visitor > ... { }; } // namespace detail @@ -35,9 +32,6 @@ struct AllVisitorNonConst : detail::AllVisitorNonConst { }; -} // namespace Constraints -} // namespace GraphMorphism -} // namespace lib -} // namespace mod +} // namespace mod::lib::GraphMorphism::Constraints -#endif /* MOD_LIB_GRAPHMORPHISM_ALLVISITOR_H */ \ No newline at end of file +#endif // MOD_LIB_GRAPHMORPHISM_ALLVISITOR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/CheckVisitor.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/CheckVisitor.hpp index 16ce4c7..b085908 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/CheckVisitor.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/CheckVisitor.hpp @@ -1,24 +1,20 @@ -#ifndef MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_H -#define MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_H +#ifndef MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_HPP +#define MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_HPP #include -namespace mod { -namespace lib { -namespace GraphMorphism { -namespace Constraints { +namespace mod::lib::GraphMorphism::Constraints { template struct CheckVisitor : AllVisitor { - CheckVisitor(const GraphDom &gDom, const LabelledGraphCodom &lgCodom, Morphism &m, const LabelSettings ls) - : gDom(gDom), lgCodom(lgCodom), m(m), ls(ls) { } + : gDom(gDom), lgCodom(lgCodom), m(m), ls(ls) {} - virtual void operator()(const VertexAdjacency &c) override { + virtual void operator()(const VertexAdjacency &c) override { result = c.matches(*this, gDom, lgCodom, m, ls); } - virtual void operator()(const ShortestPath &c) override { + virtual void operator()(const ShortestPath &c) override { result = c.matches(*this, gDom, lgCodom, m, ls); } public: @@ -31,15 +27,14 @@ struct CheckVisitor : AllVisitor { template struct Checker { - Checker(ConstraintRange constraints, const LabelledGraphCodom &lgCodom, LabelSettings ls, Next next) - : constraints(constraints), lgCodom(lgCodom), ls(ls), next(next) { } + : constraints(constraints), lgCodom(lgCodom), ls(ls), next(next) {} template bool operator()(Morphism &&m, const GraphDom &gDom, const GraphCodom &gCodom) const { assert(&gCodom == &get_graph(lgCodom)); CheckVisitor visitor(gDom, lgCodom, m, ls); - for(const auto &c : constraints) { + for(const auto &c: constraints) { c->accept(visitor); if(!visitor.result) return true; } @@ -54,14 +49,11 @@ struct Checker { template Checker -makeChecker(ConstraintRange constraints, const LabelledGraphCodom &lgCodom, LabelSettings ls, Next next = jla_boost::AlwaysTrue()) { +makeChecker(ConstraintRange constraints, const LabelledGraphCodom &lgCodom, LabelSettings ls, + Next next = jla_boost::AlwaysTrue()) { return Checker(constraints, lgCodom, ls, next); } -} // namespace Constraints -} // namespace GraphMorphism -} // namespace lib -} // namespace mod - -#endif /* MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_H */ +} // namespace mod::lib::GraphMorphism::Constraints +#endif // MOD_LIB_GRAPHMORPHISM_MATCHESVISITOR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Constraint.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Constraint.hpp index e512771..a0a328a 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Constraint.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Constraint.hpp @@ -14,18 +14,17 @@ struct Constraint { virtual ~Constraint() = default; virtual void accept(BaseVisitorNonConst &v) = 0; virtual void accept(BaseVisitor &v) const = 0; - virtual std::unique_ptr > clone() const = 0; + virtual std::unique_ptr> clone() const = 0; virtual std::string name() const = 0; - virtual bool supportsTerm() const = 0; protected: template static void acceptDispatch(C &c, BaseVisitorNonConst &visitor) { - dynamic_cast &> (visitor)(c); + dynamic_cast &>(visitor)(c); } template static void acceptDispatch(C &c, BaseVisitor &visitor) { - dynamic_cast &> (visitor)(c); + dynamic_cast &>(visitor)(c); } #define MOD_VISITABLE() \ diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/ShortestPath.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/ShortestPath.hpp index 5384c7b..86ca796 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/ShortestPath.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/ShortestPath.hpp @@ -1,29 +1,26 @@ -#ifndef MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_H -#define MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_H +#ifndef MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_HPP +#define MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_HPP #include #include #include -#include +#include #include -namespace mod { -namespace lib { -namespace GraphMorphism { -namespace Constraints { +namespace mod::lib::GraphMorphism::Constraints { template struct ShortestPath : Constraint { MOD_VISITABLE(); using Vertex = typename boost::graph_traits::vertex_descriptor; public: - ShortestPath(Vertex vSrc, Vertex vTar, Operator op, int length) - : vSrc(vSrc), vTar(vTar), op(op), length(length) { } + : vSrc(vSrc), vTar(vTar), op(op), length(length) {} - virtual std::unique_ptr > clone() const override { + virtual std::unique_ptr > + clone() const override { return std::make_unique(vSrc, vTar, op, length); } @@ -31,15 +28,15 @@ struct ShortestPath : Constraint { return "ShortestPath"; } - virtual bool supportsTerm() const override { - return true; - } - template - bool matches(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, const VertexMap &m, const LabelSettings ls) const { + bool matches(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, const VertexMap &m, + const LabelSettings ls) const { using GraphCodom = typename LabelledGraphTraits::GraphType; - static_assert(std::is_same::GraphDom>::value, ""); - static_assert(std::is_same::GraphCodom>::value, ""); + static_assert(std::is_same::GraphDom>::value, + ""); + static_assert( + std::is_same::GraphCodom>::value, + ""); const GraphCodom &gCodom = get_graph(lgCodom); { // verify #ifndef NDEBUG @@ -50,11 +47,16 @@ struct ShortestPath : Constraint { } const auto check = [this](int length) -> bool { switch(op) { - case Operator::EQ: return length == this->length; - case Operator::LT: return length < this->length; - case Operator::GT: return length > this->length; - case Operator::LEQ: return length <= this->length; - case Operator::GEQ: return length >= this->length; + case Operator::EQ: + return length == this->length; + case Operator::LT: + return length < this->length; + case Operator::GT: + return length > this->length; + case Operator::LEQ: + return length <= this->length; + case Operator::GEQ: + return length >= this->length; } assert(false); std::abort(); @@ -84,9 +86,6 @@ struct ShortestPath : Constraint { int length; }; -} // namespace Constraints -} // namespace GraphMorphism -} // namespace lib -} // namespace mod +} // namespace mod::lib::GraphMorphism::Constraints -#endif /* MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_H */ \ No newline at end of file +#endif // MOD_LIB_GRAPHMORPHISM_SHORTESTPATH_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/VertexAdjacency.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/VertexAdjacency.hpp index a01fcc3..1900d68 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/VertexAdjacency.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/VertexAdjacency.hpp @@ -1,29 +1,25 @@ -#ifndef MOD_LIB_GRAPHMORPHISM_ADJACENCY_H -#define MOD_LIB_GRAPHMORPHISM_ADJACENCY_H +#ifndef MOD_LIB_GRAPHMORPHISM_ADJACENCY_HPP +#define MOD_LIB_GRAPHMORPHISM_ADJACENCY_HPP #include #include #include #include -#include +#include #include -namespace mod { -namespace lib { -namespace GraphMorphism { -namespace Constraints { +namespace mod::lib::GraphMorphism::Constraints { template struct VertexAdjacency : Constraint { MOD_VISITABLE(); using Vertex = typename boost::graph_traits::vertex_descriptor; public: - VertexAdjacency(Vertex vConstrained, Operator op, int count) - : vConstrained(vConstrained), op(op), count(count), vertexLabels(1), edgeLabels(1) { } + : vConstrained(vConstrained), op(op), count(count), vertexLabels(1), edgeLabels(1) {} - virtual std::unique_ptr > clone() const override { + virtual std::unique_ptr> clone() const override { auto c = std::make_unique(vConstrained, op, count); c->vertexLabels = vertexLabels; c->edgeLabels = edgeLabels; @@ -33,21 +29,17 @@ struct VertexAdjacency : Constraint { virtual std::string name() const override { return "VertexAdjacency"; } - - virtual bool supportsTerm() const override { - return false; - } private: - template - int matchesImpl(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, const LabelSettings ls, std::false_type) const { + int matchesImpl(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, + const LabelSettings ls, std::false_type) const { assert(ls.type == LabelType::String); // otherwise someone forgot to add the TermData prop using GraphCodom = typename LabelledGraphTraits::GraphType; const GraphCodom &gCodom = get_graph(lgCodom); const auto vCodom = get(m, gDom, gCodom, vConstrained); int count = 0; const auto &string = get_string(lgCodom); - for(const auto eOutCodom : asRange(out_edges(vCodom, gCodom))) { + for(const auto eOutCodom: asRange(out_edges(vCodom, gCodom))) { if(!edgeLabels.empty()) { const auto iter = edgeLabels.find(string[eOutCodom]); if(iter == edgeLabels.end()) continue; @@ -62,7 +54,8 @@ struct VertexAdjacency : Constraint { } template - int matchesImpl(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, const LabelSettings ls, std::true_type) const { + int matchesImpl(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, + const LabelSettings ls, std::true_type) const { assert(ls.type == LabelType::Term); // otherwise someone did something very strange using GraphCodom = typename LabelledGraphTraits::GraphType; const GraphCodom &gCodom = get_graph(lgCodom); @@ -74,7 +67,7 @@ struct VertexAdjacency : Constraint { if(vertexTerms.empty()) { ++count; } else { - for(const auto t : vertexTerms) { + for(const auto t: vertexTerms) { lib::Term::MGU mgu = machine.unifyHeapTemp(h, t); if(mgu.status == lib::Term::MGU::Status::Exists) { ++count; @@ -87,7 +80,7 @@ struct VertexAdjacency : Constraint { if(edgeTerms.empty()) { countPerVertexTerms(hVertex); } else { - for(const auto t : edgeTerms) { + for(const auto t: edgeTerms) { lib::Term::MGU mgu = machine.unifyHeapTemp(hEdge, t); if(mgu.status == lib::Term::MGU::Status::Exists) { countPerVertexTerms(hVertex); @@ -96,19 +89,22 @@ struct VertexAdjacency : Constraint { } } }; - for(const auto eOutCodom : asRange(out_edges(vCodom, gCodom))) { + for(const auto eOutCodom: asRange(out_edges(vCodom, gCodom))) { const auto vOut = target(eOutCodom, gCodom); countPerEdgeTerms(term[eOutCodom], term[vOut]); } return count; } public: - template - bool matches(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, const LabelSettings ls) const { + bool matches(Visitor &vis, const Graph &gDom, const LabelledGraphCodom &lgCodom, VertexMap &m, + const LabelSettings ls) const { using GraphCodom = typename LabelledGraphTraits::GraphType; - static_assert(std::is_same::GraphDom>::value, ""); - static_assert(std::is_same::GraphCodom>::value, ""); + static_assert(std::is_same::GraphDom>::value, + ""); + static_assert( + std::is_same::GraphCodom>::value, + ""); { // verify #ifndef NDEBUG const auto vs = vertices(gDom); @@ -123,11 +119,16 @@ struct VertexAdjacency : Constraint { using HasTerm = GraphMorphism::HasTermData; const int count = matchesImpl(vis, gDom, lgCodom, m, ls, HasTerm()); switch(op) { - case Operator::EQ: return count == this->count; - case Operator::LT: return count < this->count; - case Operator::GT: return count > this->count; - case Operator::LEQ: return count <= this->count; - case Operator::GEQ: return count >= this->count; + case Operator::EQ: + return count == this->count; + case Operator::LT: + return count < this->count; + case Operator::GT: + return count > this->count; + case Operator::LEQ: + return count <= this->count; + case Operator::GEQ: + return count >= this->count; } assert(false); std::abort(); @@ -140,9 +141,6 @@ struct VertexAdjacency : Constraint { std::unordered_set vertexTerms, edgeTerms; }; -} // namespace Constraints -} // namespace GraphMorphism -} // namespace lib -} // namespace mod +} // namespace mod::lib::GraphMorphism::Constraints -#endif /* MOD_LIB_GRAPHMORPHISM_ADJACENCY_H */ \ No newline at end of file +#endif // MOD_LIB_GRAPHMORPHISM_ADJACENCY_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Visitor.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Visitor.hpp index 717791d..df4c736 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Visitor.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Constraints/Visitor.hpp @@ -1,10 +1,7 @@ -#ifndef MOD_LIB_GRAPHMORPHISM_VISITOR_H -#define MOD_LIB_GRAPHMORPHISM_VISITOR_H +#ifndef MOD_LIB_GRAPHMORPHISM_VISITOR_HPP +#define MOD_LIB_GRAPHMORPHISM_VISITOR_HPP -namespace mod { -namespace lib { -namespace GraphMorphism { -namespace Constraints { +namespace mod::lib::GraphMorphism::Constraints { template struct BaseVisitor { @@ -21,10 +18,6 @@ struct Visitor { virtual void operator()(C &c) = 0; }; -} // namespace Constraints -} // namespace GraphMorphism -} // namespace lib -} // namespace mod - -#endif /* MOD_LIB_GRAPHMORPHISM_VISITOR_H */ +} // namespace mod::lib::GraphMorphism::Constraints +#endif // MOD_LIB_GRAPHMORPHISM_VISITOR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/Finder.hpp b/libs/libmod/src/mod/lib/GraphMorphism/Finder.hpp index 5306f40..7ba28bf 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/Finder.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/Finder.hpp @@ -3,21 +3,16 @@ #include -namespace mod { -namespace lib { -namespace GraphMorphism { +namespace mod::lib::GraphMorphism { struct DefaultFinderArgsProvider { - template friend std::vector::vertex_descriptor> - get_vertex_order(const DefaultFinderArgsProvider&, const Graph &g) { + get_vertex_order(const DefaultFinderArgsProvider &, const Graph &g) { return jla_boost::GraphMorphism::vertex_order_by_mult(g); } }; -} // namespace GraphMorphism -} // namespace lib -} // namespace mod +} // namespace mod::lib::GraphMorphism -#endif /* MOD_LIB_GRAPH_MORPHISM_FINDER_HPP */ \ No newline at end of file +#endif // MOD_LIB_GRAPH_MORPHISM_FINDER_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/IO/WriteConstraints.hpp b/libs/libmod/src/mod/lib/GraphMorphism/IO/WriteConstraints.hpp new file mode 100644 index 0000000..97327d9 --- /dev/null +++ b/libs/libmod/src/mod/lib/GraphMorphism/IO/WriteConstraints.hpp @@ -0,0 +1,152 @@ +#ifndef MOD_LIB_GRAPHMORPHISM_IO_WRITECONSTRAINTS_HPP +#define MOD_LIB_GRAPHMORPHISM_IO_WRITECONSTRAINTS_HPP + +#include +#include +#include + +namespace mod::lib::GraphMorphism::Write { +namespace detail { + +template +struct ConstraintsTexPrintVisitor : Constraints::AllVisitor { + ConstraintsTexPrintVisitor(std::ostream &s, const Graph &g) : s(s), g(g) {} + + void printOp(Constraints::Operator op) { + using Constraints::Operator; + switch(op) { + case Operator::EQ: + s << "="; + break; + case Operator::LT: + s << "<"; + break; + case Operator::GT: + s << ">"; + break; + case Operator::LEQ: + s << "\\leq"; + break; + case Operator::GEQ: + s << "\\geq"; + break; + } + } + + virtual void operator()(const Constraints::VertexAdjacency &c) override { + s << "\\begin{align*}\n"; + s << "&|\\{e \\in \\mathrm{outEdges}(" << get(boost::vertex_index_t(), g, c.vConstrained) << ") "; + if(!c.vertexLabels.empty() || !c.edgeLabels.empty()) s << " \\mid \\\\\n"; + if(!c.vertexLabels.empty()) { + s << "&\\quad \\mathrm{label}(\\mathrm{target}(e)) \\in \\{"; + bool first = true; + for(const std::string &str: c.vertexLabels) { + if(!first) s << ", "; + first = false; + s << " \\text{`\\texttt{" << IO::escapeForLatex(str) << "}'}"; + } + s << " \\}\\\\\n"; + } + if(!c.vertexLabels.empty() && !c.edgeLabels.empty()) s << "&\\quad \\wedge\\\\\n"; + if(!c.edgeLabels.empty()) { + s << "&\\quad \\mathrm{label}(e) \\in \\{"; + bool first = true; + for(const std::string &str: c.edgeLabels) { + if(!first) s << ", "; + first = false; + s << " \\text{`\\texttt{" << IO::escapeForLatex(str) << "}'}"; + } + s << " \\}\\\\\n"; + } + if(!c.vertexLabels.empty() || !c.edgeLabels.empty()) s << "&"; + s << "\\}| "; + printOp(c.op); + s << " " << c.count << "\n\\end{align*}\n"; + } + + virtual void operator()(const Constraints::ShortestPath &c) override { + s << "$\\mathrm{shortestPath}(" << get(boost::vertex_index_t(), g, c.vSrc) << ", " + << get(boost::vertex_index_t(), g, c.vTar) << ") "; + printOp(c.op); + s << " " << c.length << "$\n"; + } +private: + std::ostream &s; + const Graph &g; +}; + +template +struct ConstraintsGMLPrintVisitor : Constraints::AllVisitor { + ConstraintsGMLPrintVisitor(std::ostream &s, const Graph &g, const std::string &prefix) + : s(s), g(g), prefix(prefix) {} + + void printOp(Constraints::Operator op) { + using Constraints::Operator; + switch(op) { + case Operator::EQ: + s << "="; + break; + case Operator::LT: + s << "<"; + break; + case Operator::GT: + s << ">"; + break; + case Operator::LEQ: + s << "<="; + break; + case Operator::GEQ: + s << ">="; + break; + } + } + + virtual void operator()(const Constraints::VertexAdjacency &c) { + s << prefix << "constrainAdj [\n"; + s << prefix << " id " << get(boost::vertex_index_t(), g, c.vConstrained) << "\n"; + s << prefix << " op \""; + printOp(c.op); + s << "\"\n"; + s << prefix << " count " << c.count << "\n"; + s << prefix << " nodeLabels ["; + for(const auto &str: c.vertexLabels) s << " label \"" << str << "\""; + s << " ]\n"; + s << prefix << " edgeLabels ["; + for(const auto &str: c.edgeLabels) s << " label \"" << str << "\""; + s << " ]\n"; + s << prefix << "]\n"; + } + + virtual void operator()(const Constraints::ShortestPath &c) { + s << prefix << "constrainShortestPath [\n"; + s << prefix << " source " << get(boost::vertex_index_t(), g, c.vSrc) + << " target " << get(boost::vertex_index_t(), g, c.vTar) << "\n"; + s << prefix << " op \""; + printOp(c.op); + s << "\" length " << c.length << "\n"; + s << prefix << "]\n"; + } +private: + std::ostream &s; + const Graph &g; + const std::string &prefix; +}; + +} // namespace detail + +template +void texConstraint(std::ostream &s, const Graph &g, const Constraints::Constraint &c) { + auto vis = detail::ConstraintsTexPrintVisitor(s, g); + c.accept(vis); +} + +template +void gmlConstraint(std::ostream &s, const Graph &g, const std::string &prefix, + const Constraints::Constraint &c) { + auto vis = detail::ConstraintsGMLPrintVisitor(s, g, prefix); + c.accept(vis); +} + +} // namespace mod::lib::GraphMorphism::Write + +#endif // MOD_LIB_GRAPHMORPHISM_IO_WRITECONSTRAINTS_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/GraphMorphism/StereoVertexMap.hpp b/libs/libmod/src/mod/lib/GraphMorphism/StereoVertexMap.hpp index 350abbb..617cf34 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/StereoVertexMap.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/StereoVertexMap.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include // LocalIso diff --git a/libs/libmod/src/mod/lib/GraphMorphism/TermVertexMap.hpp b/libs/libmod/src/mod/lib/GraphMorphism/TermVertexMap.hpp index 1a06286..e1447c0 100644 --- a/libs/libmod/src/mod/lib/GraphMorphism/TermVertexMap.hpp +++ b/libs/libmod/src/mod/lib/GraphMorphism/TermVertexMap.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include // - TermPredConstants (compare terms, variables are equal to everything) @@ -14,7 +14,8 @@ // for debugging #include -#include +#include + #include namespace mod::lib::GraphMorphism { @@ -48,8 +49,8 @@ struct TermPredConstants { return res && next(veDom, veCodom, gDom, gCodom); } private: - bool compare(std::size_t addrLeft, std::size_t addrRight, const lib::Term::Wam &machineLeft, - const lib::Term::Wam &machineRight) const { + bool compare(std::size_t addrLeft, std::size_t addrRight, const Term::Wam &machineLeft, + const Term::Wam &machineRight) const { assert(stack.empty()); // maybe_unused to silence warnings on GCC < 9 // (perhaps this bug? https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85827) @@ -58,18 +59,18 @@ struct TermPredConstants { stack.emplace_back(addrLeft, addrRight); if constexpr(DEBUG) { std::cout << "TermConstEqual:\n"; - lib::IO::Term::Write::wam(machineLeft, lib::Term::getStrings(), std::cout); - lib::IO::Term::Write::wam(machineRight, lib::Term::getStrings(), std::cout); + Term::Write::wam(machineLeft, Term::getStrings(), std::cout); + Term::Write::wam(machineRight, Term::getStrings(), std::cout); } while(!stack.empty()) { std::size_t l, r; std::tie(l, r) = stack.back(); stack.pop_back(); if constexpr(DEBUG) std::cout << "comp(" << l << ", " << r << ")\n"; - using AddressType = lib::Term::AddressType; - using Address = lib::Term::Address; - using Cell = lib::Term::Cell; - using CellTag = lib::Term::Cell::Tag; + using AddressType = Term::AddressType; + using Address = Term::Address; + using Cell = Term::Cell; + using CellTag = Term::Cell::Tag; Address addrLhs = machineLeft.deref({AddressType::Heap, l}); Address addrRhs = machineRight.deref({AddressType::Heap, r}); if constexpr(DEBUG) std::cout << "compDeref(" << addrLhs.addr << ", " << addrRhs.addr << ")\n"; @@ -119,34 +120,34 @@ struct TermDataT { }; struct TermData { - lib::Term::Wam machine; - lib::Term::MGU mgu; + Term::Wam machine; + Term::MGU mgu; }; struct TermAssociationHandlerUnify { template bool operator()(std::size_t l, std::size_t r, const OuterGraphDom &gDom, const OuterGraphCodom &gCodom, - lib::Term::Wam &res, lib::Term::MGU &mgu) const { + Term::Wam &res, Term::MGU &mgu) const { constexpr bool DEBUG = false; if(DEBUG) { auto &s = std::cout; s << "TermAssociationHandlerUnify:\n"; - lib::IO::Term::Write::wam(res, lib::Term::getStrings(), s); + Term::Write::wam(res, Term::getStrings(), s); } res.unifyHeapTemp(r, l, mgu); if(DEBUG) { auto &s = std::cout; s << "\tunifyHeapTemp(" << r << ", " << l << ")\n"; switch(mgu.status) { - case lib::Term::MGU::Status::Exists: + case Term::MGU::Status::Exists: s << "\tExists\n"; break; - case lib::Term::MGU::Status::Fail: + case Term::MGU::Status::Fail: s << "\tFail\n"; break; } } - if(mgu.status != lib::Term::MGU::Status::Exists) return false; + if(mgu.status != Term::MGU::Status::Exists) return false; else { res.verify(); return true; @@ -173,11 +174,11 @@ struct ToTermVertexMap { const auto &pCodomain = get_term(lgCodom); assert(isValid(pDomain)); assert(isValid(pCodomain)); - lib::Term::Wam machine(getMachine(pCodomain)); + Term::Wam machine(getMachine(pCodomain)); machine.setTemp(getMachine(pDomain)); - lib::Term::MGU mgu(machine.getHeap().size()); + Term::MGU mgu(machine.getHeap().size()); using Handler = typename LabGraphDom::PropTermType::Handler; - for(const auto vDom : asRange(vertices(gDom))) { + for(const auto vDom: asRange(vertices(gDom))) { const auto vCodom = get(m, gDom, gCodom, vDom); if(vCodom == boost::graph_traits::null_vertex()) continue; const bool ok = Handler::reduce( @@ -187,7 +188,7 @@ struct ToTermVertexMap { )); if(!ok) return true; } - for(const auto eDom : asRange(edges(gDom))) { + for(const auto eDom: asRange(edges(gDom))) { const auto vDomSrc = source(eDom, gDom); const auto vDomTar = target(eDom, gDom); const auto vCodomSrc = get(m, gDom, gCodom, vDomSrc); @@ -195,6 +196,7 @@ struct ToTermVertexMap { if(vCodomSrc == boost::graph_traits::null_vertex()) continue; if(vCodomTar == boost::graph_traits::null_vertex()) continue; const auto peCodom = edge(vCodomSrc, vCodomTar, gCodom); + if(!peCodom.second) continue; // no edge in the other side assert(peCodom.second); const auto eCodom = peCodom.first; const bool ok = Handler::reduce( @@ -224,8 +226,8 @@ auto makeToTermVertexMap(const LabGraphDom &gDom, const LabGraphCodom &gCodom, N struct TermFilterRenaming { template bool operator()(const VertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) const { - // lib::IO::Term::Write::wam(m.machine, lib::Term::getStrings(), std::cout); - // lib::IO::Term::Write::mgu(m.machine, m.mgu, lib::Term::getStrings(), std::cout) << "\n"; + // Term::Write::wam(m.machine, Term::getStrings(), std::cout); + // Term::Write::mgu(m.machine, m.mgu, Term::getStrings(), std::cout) << "\n"; const auto &data = get_prop(TermDataT(), m); bool res = data.mgu.isRenaming(data.machine); // std::cout << "Result: " << std::boolalpha << res << "\n"; @@ -236,8 +238,8 @@ struct TermFilterRenaming { struct TermFilterSpecialisation { template bool operator()(const VertexMap &m, const GraphDom &gDom, const GraphCodom &gCodom) const { - // lib::IO::Term::Write::wam(m.machine, lib::Term::getStrings(), std::cout); - // lib::IO::Term::Write::mgu(m.machine, m.mgu, lib::Term::getStrings(), std::cout) << "\n"; + // Term::Write::wam(m.machine, Term::getStrings(), std::cout); + // Term::Write::mgu(m.machine, m.mgu, Term::getStrings(), std::cout) << "\n"; const auto &data = get_prop(TermDataT(), m); bool res = data.mgu.isSpecialisation(data.machine); // std::cout << "Result: " << std::boolalpha << res << "\n"; diff --git a/libs/libmod/src/mod/lib/GraphPimpl.hpp b/libs/libmod/src/mod/lib/GraphPimpl.hpp index f5b5277..5cbfab8 100644 --- a/libs/libmod/src/mod/lib/GraphPimpl.hpp +++ b/libs/libmod/src/mod/lib/GraphPimpl.hpp @@ -22,15 +22,22 @@ Graph getGraphFromStore(std::optional g) { } // namespace -#define MOD_GRAPHPIMPL_Define_Vertex(GraphClass, GraphName, getMacroGraph, g, VertePrint) \ - MOD_GRAPHPIMPL_Define_Vertex_noGraph(GraphClass, GraphName, getMacroGraph, g, VertePrint) \ +#define MOD_GRAPHPIMPL_Define_Vertex(GraphClass, GraphName, getMacroGraph, g, VertexPrint) \ + MOD_GRAPHPIMPL_Define_Vertex_noGraph(GraphClass, GraphName, getMacroGraph, g, VertexPrint) \ + MOD_GRAPHPIMPL_Define_Vertex_graph(GraphClass, g) + +#define MOD_GRAPHPIMPL_Define_Vertex_graph(GraphClass, g) \ \ GraphHandle GraphClass::Vertex::getGraph() const { \ if(!*this) throw LogicError("Can not get graph on a null vertex."); \ return lib::getGraphFromStore(g); \ } -#define MOD_GRAPHPIMPL_Define_Vertex_noGraph(GraphClass, GraphName, getMacroGraph, g, VertePrint)\ +#define MOD_GRAPHPIMPL_Define_Vertex_noGraph(GraphClass, GraphName, getMacroGraph, g, VertexPrint) \ + MOD_GRAPHPIMPL_Define_Vertex_noGraph_noId(GraphClass, GraphName, getMacroGraph, g, VertexPrint) \ + MOD_GRAPHPIMPL_Define_Vertex_id(GraphClass, getMacroGraph) + +#define MOD_GRAPHPIMPL_Define_Vertex_noGraph_noId(GraphClass, GraphName, getMacroGraph, g, VertexPrint)\ \ GraphClass::Vertex::Vertex(GraphHandle g, std::size_t vId) : g(g), vId(vId) { \ using boost::vertices; \ @@ -47,7 +54,7 @@ GraphClass::Vertex::Vertex() : vId(0) { } std::ostream &operator<<(std::ostream &s, const GraphClass::Vertex &v) { \ s << #GraphName "Vertex("; \ if(!v) s << "null"; \ - else s << *(v.g VertePrint) << ", " << v.getId(); \ + else s << *(v.g VertexPrint) << ", " << v.getId(); \ return s << ")"; \ } \ \ @@ -74,7 +81,9 @@ GraphClass::Vertex::operator bool() const { \ bool GraphClass::Vertex::isNull() const { \ return *this == GraphClass::Vertex(); \ -} \ +} + +#define MOD_GRAPHPIMPL_Define_Vertex_id(GraphClass, getMacroGraph) \ \ std::size_t GraphClass::Vertex::getId() const { \ if(!*this) throw LogicError("Can not get id on a null vertex."); \ diff --git a/libs/libmod/src/mod/lib/IO/Config.hpp b/libs/libmod/src/mod/lib/IO/Config.hpp index 72c992c..11005e4 100644 --- a/libs/libmod/src/mod/lib/IO/Config.hpp +++ b/libs/libmod/src/mod/lib/IO/Config.hpp @@ -2,7 +2,7 @@ #define MOD_LIB_IO_CONFIG_HPP #include -#include +#include namespace mod { diff --git a/libs/libmod/src/mod/lib/IO/DFS.cpp b/libs/libmod/src/mod/lib/IO/DFS.cpp new file mode 100644 index 0000000..971c69e --- /dev/null +++ b/libs/libmod/src/mod/lib/IO/DFS.cpp @@ -0,0 +1,412 @@ +#include "DFS.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mod::lib::IO::DFS { + +void escapeLabel(std::ostream &s, const std::string &label, char escChar) { + for(int i = 0; i != label.size(); i++) { + char c = label[i]; + if(c == escChar) s << "\\" << escChar; + else if(c == '\t') s << "\\t"; + else if(c == '\\' && i + 1 != label.size()) { + char next = label[i + 1]; + if(next == '\\' || next == 't') s << "\\\\"; + else s << '\\'; + } else s << c; + } +} + +std::ostream &operator<<(std::ostream &s, const LabelVertex &lv) { + if(lv.implicit) s << lv.label; + else { + s << '['; + escapeLabel(s, lv.label, ']'); + s << ']'; + } + if(lv.id) s << *lv.id; + return s; +} + +std::ostream &operator<<(std::ostream &s, const RingClosure &rc) { + return s << rc.id; +} + +std::ostream &operator<<(std::ostream &s, const Vertex &v) { + s << v.vertex; + for(const auto &b: v.branches) s << b; + return s; +} + +std::ostream &operator<<(std::ostream &s, const Edge &e) { + if(e.label.size() > 1) { + s << '{'; + escapeLabel(s, e.label, '}'); + return s << '}'; + } + switch(e.label.front()) { + case '-': + return s; + case '=': + case '#': + case ':': + return s << e.label; + default: + return s << "{" << e.label << "}"; + } +} + +std::ostream &operator<<(std::ostream &s, const Chain &c) { + s << c.head; + for(const auto &ev: c.tail) s << ev.first << ev.second; + return s; +} + +std::ostream &operator<<(std::ostream &s, const Branch &b) { + for(const auto &ev: b.tail) s << '(' << ev.first << ev.second << ')'; + return s; +} + +std::ostream &operator<<(std::ostream &s, const Rule &r) { + if(r.left) s << *r.left; + s << "<<"; + if(r.right) s << *r.right; + return s; +} + +namespace Read { +namespace { +namespace parser { + +struct SpecialVertexLabels : x3::symbols { + SpecialVertexLabels() { + name("specialVertexLabels"); + for(auto atomId: lib::Chem::getSmilesOrganicSubset()) + add(lib::Chem::symbolFromAtomId(atomId), lib::Chem::symbolFromAtomId(atomId)); + } +} specialVertexLabels; + +struct SpecialEdgeLabels : x3::symbols { + SpecialEdgeLabels() { + name("specialEdgeLabels"); + add("-", "-"); + add(":", ":"); + add("=", "="); + add("#", "#"); + add(".", ""); + } +} specialEdgeLabelSymbols; + +// no recursion +const auto implicitBackslash = x3::attr('\\'); +const auto explicitBackslash = '\\' >> x3::attr('\\'); +const auto tab = 't' >> x3::attr('\t'); +const auto plainBrace /* */ = (x3::char_ - x3::char_('}')); +const auto plainBracket /**/ = (x3::char_ - x3::char_(']')); +const auto escapedBrace /* */ = '\\' >> (x3::char_('}') | tab | explicitBackslash | implicitBackslash); +const auto escapedBracket /**/ = '\\' >> (x3::char_(']') | tab | explicitBackslash | implicitBackslash); +const auto escapedStringBrace /* */ = x3::lexeme[x3::lit('{') > *(escapedBrace /* */ | plainBrace /* */) > + x3::lit('}')]; +const auto escapedStringBracket /**/ = x3::lexeme[x3::lit('[') > *(escapedBracket /**/ | plainBracket /**/) > + x3::lit(']')]; + +const auto specialEdgeLabels = specialEdgeLabelSymbols | x3::attr(std::string(1, '-')); +const auto edge = x3::rule("edge") = escapedStringBrace | specialEdgeLabels; +const auto defRingId = x3::uint_; +const auto ringClosure = x3::rule("ringClosure") = x3::uint_; +const auto specialLabelVertex = x3::rule("specialLabelVertex") +/* */ = specialVertexLabels >> x3::attr(true) >> -defRingId; +const auto explicitLabelVertex = x3::rule("explicitLabelVertex") +/* */ = escapedStringBracket >> x3::attr(false) >> -defRingId; +const auto labelVertex = explicitLabelVertex | specialLabelVertex; +// part of recursion +const x3::rule vertex = "vertex"; +const auto evPair = x3::rule("edgeVertexPair") = edge >> vertex; +const auto branch = x3::rule("branch") = '(' > +evPair > ')'; +const auto branches = *branch; +const auto vertex_def = (labelVertex | ringClosure) >> branches; +BOOST_SPIRIT_DEFINE(vertex) +// no recursion +const auto chain = vertex > *evPair; +const auto graphDFS = x3::rule("graphDFS") +/* */ = chain; +const auto ruleDFS = x3::rule("ruleDFS") +/* */ = -graphDFS > x3::lit(">>") > -graphDFS; + +} // namespace parser + +struct Phase1 { + // - Set connectedComponentIDs + // - Record vertex IDs + using Map = std::map; + // - Resolve rings +public: + Phase1(std::string errorVariation) : errorVariation(std::move(errorVariation)) {} + lib::IO::Result> operator()(Chain &chain) { + (*this)(chain.head); + for(EVPair &ev: chain.tail) + (*this)(ev.second); + if(error.empty()) + return std::pair(nextID, std::move(vertexFromId)); + else + return lib::IO::Result<>::Error(std::move(error)); + } + + void operator()(Vertex &vertex) { + boost::apply_visitor(*this, vertex.vertex); + for(Branch &branch: vertex.branches) + for(EVPair &ev: branch.tail) + (*this)(ev.second); + } + + void operator()(LabelVertex &vertex) { + vertex.connectedComponentID = nextID; + ++nextID; + + if(vertex.id) { + const auto iter = vertexFromId.find(*vertex.id); + if(iter == vertexFromId.end()) { + // use the id as definition + vertexFromId[*vertex.id] = &vertex; + } else { + // use the id as ring closure + vertex.ringClosure = iter->second; + } + } + } + + void operator()(RingClosure &vertex) { + const auto iter = vertexFromId.find(vertex.id); + if(iter != vertexFromId.end()) { + vertex.other = iter->second; + } else { + // there may have been a previous error + if(error.empty()) + error = "Ring closure ID " + std::to_string(vertex.id) + " not found" + errorVariation + "."; + } + } +public: + int nextID = 0; + Map vertexFromId; + std::string error; + std::string errorVariation; +}; + +struct CheckLoop { + // - Check for loop edges, by ring resolution to previous vertex + CheckLoop(std::string errorVariation) : errorVariation(std::move(errorVariation)) {} + + lib::IO::Result<> operator()(const Chain &chain) { + const LabelVertex *prevVertex = (*this)(nullptr, nullptr, chain.head); + for(const EVPair &ev: chain.tail) { + prevVertex = (*this)(prevVertex, &ev.first, ev.second); + if(!error.empty()) break; + } + + if(error.empty()) + return {}; + else + return lib::IO::Result<>::Error(std::move(error)); + } + + const LabelVertex *operator()(const LabelVertex *pVertex, const Edge *edge, const Vertex &vertex) { + assert((pVertex == nullptr) == (edge == nullptr)); + auto [thisVertex, ringId] = boost::apply_visitor(*this, vertex.vertex); + assert(thisVertex); + if(ringId >= 0 && thisVertex == pVertex && !edge->label.empty()) { + assert(error.empty()); + error = "Loop edge in DFS on vertex" + errorVariation + " with ID " + std::to_string(ringId) + "."; + return pVertex; + } + + const LabelVertex *baseVertex = ringId < 0 ? thisVertex : pVertex; + + for(const Branch &branch: vertex.branches) { + const LabelVertex *pBranchVertex = baseVertex; + for(const EVPair &ev: branch.tail) { + pBranchVertex = (*this)(pBranchVertex, &ev.first, ev.second); + if(!error.empty()) break; + } + } + + return baseVertex; + } + + std::pair operator()(const LabelVertex &vertex) { + return {&vertex, -1}; + } + + std::pair operator()(const RingClosure &vertex) { + return {vertex.other, vertex.id}; + } +public: + std::string error; + std::string errorVariation; +}; + +struct CheckParallel { + // - Check for parallel edges, by ring-closure to just previous vertex + CheckParallel(std::string errorVariation) : errorVariation(std::move(errorVariation)) {} + + lib::IO::Result<> operator()(const Chain &chain) { + auto [ppVertex, pVertex] = (*this)(nullptr, nullptr, nullptr, chain.head); + for(const EVPair &ev: chain.tail) { + std::tie(ppVertex, pVertex) = (*this)(ppVertex, pVertex, &ev.first, ev.second); + if(!error.empty()) break; + } + + if(error.empty()) + return {}; + else + return lib::IO::Result<>::Error(std::move(error)); + } + + std::pair + operator()(const LabelVertex *ppVertex, const LabelVertex *pVertex, const Edge *edge, const Vertex &vertex) { + assert((pVertex == nullptr) == (edge == nullptr)); + auto [thisVertex, ringId] = boost::apply_visitor(*this, vertex.vertex); + assert(thisVertex); + if(edge && !edge->label.empty()) { + // not [A].[B] with (edge, vertex) being (., [B]) + if(ringId >= 0 && thisVertex == ppVertex) { + // [A]1[B]-1 with (edge, vertex) being (-, 1) + assert(error.empty()); + error = "Parallel edge in DFS" + errorVariation + ". Back-edge is to vertex with ID " + + std::to_string(ringId) + "."; + return {nullptr, nullptr}; + } + if(ringId < 0 && thisVertex->ringClosure && thisVertex->ringClosure == pVertex) { + // [A]1[B]1 with (edge, vertex) being ('-', [B]1) + assert(error.empty()); + error = "Parallel edge in DFS" + errorVariation + ". Back-edge is to vertex with ID " + + std::to_string(*thisVertex->id) + "."; + return {nullptr, nullptr}; + } + } + + if(ringId < 0) { + // thisVertex is an actual vertex + if(!edge || !edge->label.empty()) { + // [ppVertex][pVertex][thisVertex] + ppVertex = pVertex; + pVertex = thisVertex; + } else { + // [ppVertex][pVertex].[thisVertex] + // so semi-reset + ppVertex = nullptr; + pVertex = thisVertex; + } + } else { + // thisVertex is just a ringClosure, i.e., an implicit branch + // keep ppVertex and pVertex + } + for(const Branch &branch: vertex.branches) { + const LabelVertex *ppBranchVertex = ppVertex; + const LabelVertex *pBranchVertex = pVertex; + for(const EVPair &ev: branch.tail) { + std::tie(ppBranchVertex, pBranchVertex) = (*this)(ppBranchVertex, pBranchVertex, &ev.first, ev.second); + if(!error.empty()) break; + } + } + + return {ppVertex, pVertex}; + } + + std::pair operator()(const LabelVertex &vertex) { + return {&vertex, -1}; + } + + std::pair operator()(const RingClosure &vertex) { + return {vertex.other, vertex.id}; + } +public: + std::string error; + std::string errorVariation; +}; + +} // namespace + +Result graph(std::string_view data) { + auto ast = std::make_unique(); + try { + lib::IO::parse(data.begin(), data.end(), parser::graphDFS, *ast, true, x3::ascii::space); + } catch(const lib::IO::ParsingError &e) { + return lib::IO::Result<>::Error(e.msg); + } + + auto resPhase1 = Phase1("")(*ast); + if(!resPhase1) return lib::IO::Result<>::Error(resPhase1.extractError()); + auto [numVertices, vertexFromId] = *resPhase1; + if(auto res = CheckLoop("")(*ast); !res) return res; + if(auto res = CheckParallel("")(*ast); !res) return res; + return GraphResult{std::move(ast), numVertices, std::move(vertexFromId)}; +} + +Result rule(std::string_view data) { + Rule ast; + try { + lib::IO::parse(data.begin(), data.end(), parser::ruleDFS, ast, true, x3::ascii::space); + } catch(const lib::IO::ParsingError &e) { + return lib::IO::Result<>::Error(e.msg); + } + RuleResult res; + if(ast.left) { + res.left.ast = std::make_unique(std::move(*ast.left)); + auto resPhase1 = Phase1(", in the left side")(*res.left.ast); + if(!resPhase1) return lib::IO::Result<>::Error(resPhase1.extractError()); + std::tie(res.left.numVertices, res.left.vertexFromId) = *resPhase1; + if(auto resLoop = CheckLoop(" in the left side")(*res.left.ast); !resLoop) return resLoop; + if(auto resPar = CheckParallel(" in the left side")(*res.left.ast); !resPar) return resPar; + } + if(ast.right) { + res.right.ast = std::make_unique(std::move(*ast.right)); + auto resPhase1 = Phase1(", in the right side")(*res.right.ast); + if(!resPhase1) return lib::IO::Result<>::Error(resPhase1.extractError()); + std::tie(res.right.numVertices, res.right.vertexFromId) = *resPhase1; + if(auto resLoop = CheckLoop(" in the right side")(*res.right.ast); !resLoop) return resLoop; + if(auto resPar = CheckParallel(" in the right side")(*res.right.ast); !resPar) return resPar; + } + return std::move(res); // TODO: remove std::move when C++20/P1825R0 is available +} + +} // namespace Read +} // namespace mod::lib::IO::DFS + +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::LabelVertex, + (std::string, label) + (bool, implicit) + (std::optional, id)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::RingClosure, + (unsigned int, id)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::Vertex, + (mod::lib::IO::DFS::BaseVertex, vertex) + (std::vector, branches)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::Edge, + (std::string, label)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::Chain, + (mod::lib::IO::DFS::Vertex, head) + (std::vector, tail)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::Branch, + (std::vector, tail)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::DFS::Rule, + (std::optional, left) + (std::optional, right)) \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/DFS.hpp b/libs/libmod/src/mod/lib/IO/DFS.hpp new file mode 100644 index 0000000..fcde79d --- /dev/null +++ b/libs/libmod/src/mod/lib/IO/DFS.hpp @@ -0,0 +1,103 @@ +#ifndef MOD_LIB_IO_DFS_HPP +#define MOD_LIB_IO_DFS_HPP + +#include + +#include + +#include +#include +#include +#include +#include + +namespace mod::lib::IO::DFS { + +void escapeLabel(std::ostream &s, const std::string &label, char escChar); + +struct LabelVertex; +struct RingClosure; +struct Branch; + +// x3 does not yet fully support std::variant +// https://github.com/boostorg/spirit/issues/321 +using BaseVertex = boost::variant; + +struct LabelVertex { + std::string label; + bool implicit = false; + std::optional id; +public: // set by Phase1 + int connectedComponentID; + LabelVertex *ringClosure = nullptr; // if id turns out to be a ring-closure, then this gets set +public: // used by consumer + int gVertexId = -1; +public: + friend std::ostream &operator<<(std::ostream &s, const LabelVertex &lv); +}; + +struct RingClosure { + // must use unsigned so the parser understands how to store it + unsigned int id = std::numeric_limits::max(); +public: + LabelVertex *other; +public: + friend std::ostream &operator<<(std::ostream &s, const RingClosure &rc); +}; + +struct Vertex { + BaseVertex vertex; + std::vector branches; +public: + friend std::ostream &operator<<(std::ostream &s, const Vertex &v); +}; + +struct Edge { + std::string label; +public: + friend std::ostream &operator<<(std::ostream &s, const Edge &e); +}; + +using EVPair = std::pair; + +struct Chain { + Vertex head; + std::vector tail; + bool hasNonSmilesRingClosure; // only set when creating GraphDFS from a graph +public: + friend std::ostream &operator<<(std::ostream &s, const Chain &c); +}; + +struct Branch { + std::vector tail; +public: + friend std::ostream &operator<<(std::ostream &s, const Branch &b); +}; + +struct Rule { + std::optional left, right; +public: + friend std::ostream &operator<<(std::ostream &s, const Rule &r); +}; + +namespace Read { + +struct GraphResult { + // returns a pointer such that all resolved rings have stable pointers + std::unique_ptr ast; + int numVertices; + std::map vertexFromId; +}; + +struct RuleResult { + // the ast may be null if that side doesn't exist + GraphResult left, right; +}; + +Result graph(std::string_view data); +Result rule(std::string_view data); + +} // namespace Read +} // namespace mod::lib::IO::DF + +#endif // MOD_LIB_IO_DFS_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Derivation.hpp b/libs/libmod/src/mod/lib/IO/Derivation.hpp deleted file mode 100644 index fd16ce1..0000000 --- a/libs/libmod/src/mod/lib/IO/Derivation.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef MOD_LIB_IO_DERIVATION_H -#define MOD_LIB_IO_DERIVATION_H - -#include - -namespace mod { -namespace lib { -namespace DG { -class NonHyper; -} // namespace DG -namespace IO { -namespace Graph { -namespace Write { -struct Options; -} // namespace Write -} // namespace Graph -namespace Derivation { -namespace Write { -std::vector> -summary(const lib::DG::NonHyper &dg, lib::DG::HyperVertex v, const IO::Graph::Write::Options &options, - const std::string &nomatchColour, const std::string &matchColour); -} // namespace Write -} // namespace Derivation -} // namespace IO -} // namespace lib -} // namespace mod - -#endif /* MOD_LIB_IO_DERIVATION_H */ \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/GMLUtil.cpp b/libs/libmod/src/mod/lib/IO/GML.cpp similarity index 90% rename from libs/libmod/src/mod/lib/IO/GMLUtil.cpp rename to libs/libmod/src/mod/lib/IO/GML.cpp index a15c605..be89a7d 100644 --- a/libs/libmod/src/mod/lib/IO/GMLUtil.cpp +++ b/libs/libmod/src/mod/lib/IO/GML.cpp @@ -1,4 +1,4 @@ -#include +#include "GML.hpp" #include diff --git a/libs/libmod/src/mod/lib/IO/GMLUtils.hpp b/libs/libmod/src/mod/lib/IO/GML.hpp similarity index 89% rename from libs/libmod/src/mod/lib/IO/GMLUtils.hpp rename to libs/libmod/src/mod/lib/IO/GML.hpp index af9dac2..a93ee10 100644 --- a/libs/libmod/src/mod/lib/IO/GMLUtils.hpp +++ b/libs/libmod/src/mod/lib/IO/GML.hpp @@ -1,7 +1,8 @@ -#ifndef MOD_LIB_IO_GMLUTILS_HPP -#define MOD_LIB_IO_GMLUTILS_HPP +#ifndef MOD_LIB_IO_GML_HPP +#define MOD_LIB_IO_GML_HPP + +#include -#include #include #include @@ -16,7 +17,7 @@ struct Vertex { std::optional label; std::optional stereo; public: - std::optional parsedEmbedding; + std::optional parsedEmbedding; }; struct Edge { @@ -77,4 +78,4 @@ static const auto makeEdgeConverter = [](std::size_t lowerBound) { } // namespace mod::lib::IO::GML -#endif // MOD_LIB_IO_GMLUTILS_HPP \ No newline at end of file +#endif // MOD_LIB_IO_GML_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Graph.hpp b/libs/libmod/src/mod/lib/IO/Graph.hpp deleted file mode 100644 index 328ef83..0000000 --- a/libs/libmod/src/mod/lib/IO/Graph.hpp +++ /dev/null @@ -1,254 +0,0 @@ -#ifndef MOD_LIB_IO_GRAPH_HPP -#define MOD_LIB_IO_GRAPH_HPP - -#include -#include - -#include -#include -#include -#include - -namespace mod { -enum class SmilesClassPolicy; -} // namespace mod -namespace mod::lib::Graph { -struct DepictionData; -struct LabelledGraph; -struct PropStereo; -struct PropString; -struct Single; -} // namespace mod::lib::Graph -namespace mod::lib::IO::Graph::Read { - -struct Data { - Data(); - Data(std::unique_ptr graph, std::unique_ptr label); - Data(Data &&other); - ~Data(); - void reset(); -public: - std::unique_ptr g; - std::unique_ptr pString; - std::unique_ptr pStereo; - std::map externalToInternalIds; -}; - -struct ConnectedComponents { - ConnectedComponents(int n) : components(n) { - for(int i = 0; i != n; ++i) - components[i] = i; - } - - int findRoot(int i) { - while(components[i] != i) - i = components[i]; - return i; - } - - void join(int a, int b) { - a = findRoot(a); - b = findRoot(b); - // always make the smallest the root - if(a < b) components[b] = a; - else if(b < a) components[a] = b; - } -public: - int finalize() { - int next = 0; - for(int i = 0; i != components.size(); ++i) { - // everything below i has been changed to component numbers - // and joining always goes downward, so just to a single root-finding step - const int root = components[i]; - if(root == i) { - components[i] = next; - ++next; - } else { - assert(root < i); - components[i] = components[root]; - } - } - return next; - } -public: - int operator[](int i) const { - return components[i]; - } -private: - std::vector components; -}; - -auto gml(lib::IO::Warnings &warnings, std::string_view src) -> Result>; -auto dfs(const std::string &dfs) -> Result; -auto smiles(lib::IO::Warnings &warnings, const std::string &smiles, bool allowAbstract, - SmilesClassPolicy classPolicy) -> Result>; -} // namespace mod::lib::IO::Graph::Read -namespace mod::lib::IO::Graph::Write { - -enum class EdgeFake3DType { - None, WedgeSL, WedgeLS, HashSL, HashLS -}; - -EdgeFake3DType invertEdgeFake3DType(EdgeFake3DType t); - -struct Options { - Options &Non() { - return EdgesAsBonds(false).CollapseHydrogens(false).RaiseIsotopes(false).RaiseCharges(false).SimpleCarbons( - false).Thick(false).WithColour(false).WithIndex(false); - } - - Options &All() { - return EdgesAsBonds(true).CollapseHydrogens(true).RaiseIsotopes(true).RaiseCharges(true).SimpleCarbons( - true).Thick(true).WithColour(true).WithIndex(true); - } - - Options &EdgesAsBonds(bool v) { - edgesAsBonds = v; - return *this; - } - - Options &CollapseHydrogens(bool v) { - collapseHydrogens = v; - return *this; - } - - Options &RaiseIsotopes(bool v) { - raiseIsotopes = v; - return *this; - } - - Options &RaiseCharges(bool v) { - raiseCharges = v; - return *this; - } - - Options &SimpleCarbons(bool v) { - simpleCarbons = v; - return *this; - } - - Options &Thick(bool v) { - thick = v; - return *this; - } - - Options &WithColour(bool v) { - withColour = v; - return *this; - } - - Options &WithIndex(bool v) { - withIndex = v; - return *this; - } - - Options &WithTexttt(bool v) { - withTexttt = v; - return *this; - } - - Options &WithRawStereo(bool v) { - withRawStereo = v; - return *this; - } - - Options &WithPrettyStereo(bool v) { - withPrettyStereo = v; - return *this; - } - - Options &Rotation(int degrees) { - rotation = degrees; - return *this; - } - - Options &Mirror(bool v) { - mirror = v; - return *this; - } - - std::string getStringEncoding() const { - auto toChar = [](bool b) { - return b ? '1' : '0'; - }; - std::string res; - res += toChar(edgesAsBonds); - res += toChar(collapseHydrogens); - char raise = 0; - if(raiseCharges) raise += 1; - if(raiseIsotopes) raise += 2; - res += '0' + raise; - res += toChar(simpleCarbons); - res += toChar(thick); - res += toChar(withColour); - res += toChar(withIndex); - res += toChar(withTexttt); - char stereo = 0; - if(withRawStereo) stereo += 1; - if(withPrettyStereo) stereo += 2; - if(stereo != 0) res += '0' + stereo; - if(rotation != 0 || mirror) { - res += "_"; - res += std::to_string(rotation); - if(mirror) { - res += "_"; - res += toChar(mirror); - } - } - return res; - } - - friend bool operator==(const Options &a, const Options &b) { - return a.getStringEncoding() == b.getStringEncoding(); - } - - friend bool operator!=(const Options &a, const Options &b) { - return !(a == b); - } - -public: - bool edgesAsBonds = false; - bool collapseHydrogens = false; - bool raiseCharges = false; - bool raiseIsotopes = false; - bool simpleCarbons = false; - bool thick = false; - bool withColour = false; - bool withIndex = false; - bool withTexttt = false; - bool withRawStereo = false; - bool withPrettyStereo = false; - int rotation = 0; - bool mirror = false; -}; - -// all return the filename _with_ extension -void gml(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - bool withCoords, std::ostream &s); -std::string gml(const lib::Graph::Single &g, bool withCoords); -std::string dot(const lib::Graph::LabelledGraph &gLabelled, const std::size_t gId); -std::string -coords(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options, bool asInline); -std::pair -tikz(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options, - bool asInline, const std::string &idPrefix); -std::string -pdf(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options); -std::string -svg(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options); -std::pair summary(const lib::Graph::Single &g, const Options &first, const Options &second); -void termState(const lib::Graph::Single &g); - -// simplified interface for lib::Graph::Single -void gml(const lib::Graph::Single &g, bool withCoords, std::ostream &s); -std::string tikz(const lib::Graph::Single &g, const Options &options, bool asInline, const std::string &idPrefix); -std::string pdf(const lib::Graph::Single &g, const Options &options); -std::string svg(const lib::Graph::Single &g, const Options &options); - -} // namespace mod::lib::IO::Graph::Write - -#endif // MOD_LIB_IO_GRAPH_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/GraphRead.cpp b/libs/libmod/src/mod/lib/IO/GraphRead.cpp deleted file mode 100644 index 8c04bcc..0000000 --- a/libs/libmod/src/mod/lib/IO/GraphRead.cpp +++ /dev/null @@ -1,282 +0,0 @@ -#include "Graph.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include - -namespace mod::lib::IO::Graph::Read { - -Data::Data() {} - -Data::Data(Data &&other) : g(std::move(other.g)), pString(std::move(other.pString)), pStereo(std::move(other.pStereo)), - externalToInternalIds(std::move(other.externalToInternalIds)) {} - -Data::~Data() { - if(std::uncaught_exceptions() != 0) return; - if(g) MOD_ABORT; - if(pString) MOD_ABORT; - if(pStereo) MOD_ABORT; -} - -void Data::reset() { - pStereo.reset(); - pString.reset(); - g.reset(); -} - -Result> gml(lib::IO::Warnings &warnings, std::string_view src) { - GML::Graph gGML; - { - gml::ast::KeyValue ast; - try { - ast = gml::parser::parse(src); - } catch(const gml::parser::error &e) { - return Result<>::Error(e.what()); - } - using namespace gml::converter::edsl; - auto cVertex = GML::makeVertexConverter(1); - auto cEdge = GML::makeEdgeConverter(1); - auto cGraph = list("graph")(cVertex)(cEdge); - auto iterBegin = * - auto iterEnd = iterBegin + 1; - try { - gml::converter::convert(iterBegin, iterEnd, cGraph, gGML); - } catch(const gml::converter::error &e) { - return Result<>::Error(e.what()); - } - } - - std::sort(begin(gGML.vertices), end(gGML.vertices), [](const GML::Vertex &v1, const GML::Vertex &v2) -> bool { - return v1.id < v2.id; - }); - - // Check that is mathematically is a graph - - std::unordered_map globalIddFromExtID; - { - int i = 0; - for(const auto &vGML : gGML.vertices) { - if(globalIddFromExtID.find(vGML.id) != end(globalIddFromExtID)) - return Result<>::Error("Vertex id " + std::to_string(vGML.id) + " used multiple times."); - globalIddFromExtID.emplace(vGML.id, i++); - } - } - - for(const auto &eGML : gGML.edges) { - if(eGML.source == eGML.target) - return Result<>::Error("Loop edge (on " + std::to_string(eGML.source) + ") is not allowed."); - if(globalIddFromExtID.find(eGML.source) == end(globalIddFromExtID)) - return Result<>::Error("Source " + std::to_string(eGML.source) + " does not exist in '" - + boost::lexical_cast(eGML) + "'."); - if(globalIddFromExtID.find(eGML.target) == end(globalIddFromExtID)) - return Result<>::Error("Target " + std::to_string(eGML.target) + " does not exist in '" - + boost::lexical_cast(eGML) + "'."); - } - - // Now we can calculate connected components and start converting data - - ConnectedComponents components(gGML.vertices.size()); - for(const auto &e : gGML.edges) { - const auto iterSrc = globalIddFromExtID.find(e.source); - const auto iterTar = globalIddFromExtID.find(e.target); - components.join(iterSrc->second, iterTar->second); - } - const auto numComponents = components.finalize(); - - std::vector datas(numComponents); - std::vector> extIDFromVertex(numComponents); - for(auto &d : datas) { - d.g = std::make_unique(); - d.pString = std::make_unique(*d.g); - } - - const auto atError = [&datas](std::string msg) -> Result<> { - for(auto &d : datas) d.reset(); - return Result<>::Error(std::move(msg)); - }; - - for(const auto &vGML : gGML.vertices) { - const auto comp = components[globalIddFromExtID.find(vGML.id)->second]; - auto &g = *datas[comp].g; - const auto v = add_vertex(g); - assert(vGML.label); - datas[comp].pString->addVertex(v, *vGML.label); - datas[comp].externalToInternalIds.emplace(vGML.id, get(boost::vertex_index_t(), g, v)); - extIDFromVertex[comp].emplace(v, vGML.id); - } - const auto vFromVertexId = [&](int id) { - const auto globalIDIter = globalIddFromExtID.find(id); - assert(globalIDIter != end(globalIddFromExtID)); - const auto comp = components[globalIDIter->second]; - const auto vIdIter = datas[comp].externalToInternalIds.find(id); - assert(vIdIter != end(datas[comp].externalToInternalIds)); - return std::pair(comp, vertex(vIdIter->second, *datas[comp].g)); - }; - for(const auto &eGML : gGML.edges) { - const auto[comp, vSrc] = vFromVertexId(eGML.source); - const auto[compTar, vTar] = vFromVertexId(eGML.target); - assert(comp == compTar); - auto &g = *datas[comp].g; - const auto eQuery = edge(vSrc, vTar, g); - if(eQuery.second) - return atError("Duplicate edge with source " + std::to_string(eGML.source) - + " and target " + std::to_string(eGML.target) + "."); - const auto e = add_edge(vSrc, vTar, g); - assert(eGML.label); - datas[comp].pString->addEdge(e.first, *eGML.label); - } - - bool doStereo = false; - for(const auto &vGML : gGML.vertices) doStereo = doStereo || vGML.stereo; - for(const auto &eGML : gGML.edges) doStereo = doStereo || eGML.stereo; - if(!doStereo) return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available - // Stereo - //============================================================================ - std::vector molStates; - std::vector> stereoInferences; - molStates.reserve(numComponents); - stereoInferences.reserve(numComponents); - for(int i = 0; i != numComponents; ++i) { - const auto &g = *datas[i].g; - molStates.emplace_back(g, *datas[i].pString); - stereoInferences.push_back(lib::Stereo::makeInference(g, molStates.back(), false)); - } - - const auto &gGeometry = lib::Stereo::getGeometryGraph(); - // Set the explicitly defined edge categories. - //---------------------------------------------------------------------------- - for(const auto &eGML : gGML.edges) { - const auto[comp, vSrc] = vFromVertexId(eGML.source); - const auto[compTar, vTar] = vFromVertexId(eGML.target); - assert(comp == compTar); - const auto &g = *datas[comp].g; - const auto ePair = edge(vSrc, vTar, g); - assert(ePair.second); - if(!eGML.stereo) continue; - const std::string &s = *eGML.stereo; - if(s.size() != 1) - return atError( - "Error in stereo data for edge (" + std::to_string(eGML.source) - + ", " + std::to_string(eGML.target) + "). Parsing error in stereo data '" + s + "'."); - lib::Stereo::EdgeCategory cat; - switch(s.front()) { - case '*': - cat = lib::Stereo::EdgeCategory::Any; - break; - default: - return atError("Error in stereo data for edge (" + std::to_string(eGML.source) - + ", " + std::to_string(eGML.target) + "). Parsing error in stereo data '" + s + "'."); - } - if(auto res = stereoInferences[comp].assignEdgeCategory(ePair.first, cat); !res) - return atError("Error in stereo data for edge (" + std::to_string(eGML.source) - + ", " + std::to_string(eGML.target) + "). " - + res.extractError()); - } - // Set the explicitly stereo data. - //---------------------------------------------------------------------------- - for(auto &vGML : gGML.vertices) { - if(!vGML.stereo) continue; - const auto[comp, v] = vFromVertexId(vGML.id); - if(auto res = lib::IO::Stereo::Read::parseEmbedding(*vGML.stereo)) { - vGML.parsedEmbedding = std::move(*res); - } else { - return atError("Error in stereo data for vertex " + std::to_string(vGML.id) + ". " - + res.extractError()); - } - // Geometry - //.......................................................................... - const auto &embGML = *vGML.parsedEmbedding; - if(embGML.geometry) { - const auto vGeo = gGeometry.findGeometry(*embGML.geometry); - if(vGeo == gGeometry.nullGeometry()) - return atError("Error in stereo data for vertex " + std::to_string(vGML.id) - + ". Invalid gGeometry '" + *embGML.geometry + "'."); - if(auto res = stereoInferences[comp].assignGeometry(v, vGeo); !res) - return atError("Error in stereo data for vertex " + std::to_string(vGML.id) + ". " - + res.extractError()); - } - // Edges - //.......................................................................... - if(embGML.edges) { - stereoInferences[comp].initEmbedding(v); - for(const auto &e : *embGML.edges) { - if(const int *idPtr = std::get_if(&e)) { - const auto extIDNeighbour = *idPtr; - if(globalIddFromExtID.find(extIDNeighbour) == end(globalIddFromExtID)) - return atError("Neighbour vertex " + std::to_string(extIDNeighbour) - + " in stereo embedding for vertex " + std::to_string(vGML.id) + " does not exist."); - const auto[compNeighbour, vNeighbour] = vFromVertexId(extIDNeighbour); - const auto ePair = edge(v, vNeighbour, *datas[comp].g); - if(!ePair.second) - return atError("Error in graph GML. Vertex " + std::to_string(extIDNeighbour) + - " in stereo embedding for vertex " + std::to_string(vGML.id) + " is not a neighbour."); - assert(compNeighbour == comp); - stereoInferences[comp].addEdge(v, ePair.first); - } else if(const char *virtPtr = std::get_if(&e)) { - switch(*virtPtr) { - case 'e': - stereoInferences[comp].addLonePair(v); - break; - case 'r': - stereoInferences[comp].addRadical(v); - break; - default: - MOD_ABORT; // the parser should know what is allowed - } - } else { - MOD_ABORT; // the parser should know what is allowed - } - } - } - // Fixation - //.......................................................................... - if(embGML.fixation) { - // TODO: expand this when more complicated geometries are implemented - const bool isFixed = *embGML.fixation; - if(isFixed) stereoInferences[comp].fixSimpleGeometry(v); - } - } // end of explicit stereo data - - for(int comp = 0; comp != numComponents; ++comp) { - // TODO: the warning should only be printed once, instead of for each connected component - lib::IO::Warnings stereoWarnings; - auto stereoResult = stereoInferences[comp].finalize( - stereoWarnings, [comp, &extIDFromVertex](lib::Graph::Vertex v) { - const auto iter = extIDFromVertex[comp].find(v); - assert(iter != extIDFromVertex[comp].end()); - return std::to_string(iter->second); - }); - warnings.addFrom(std::move(stereoWarnings), !getConfig().stereo.silenceDeductionWarnings.get()); - if(!stereoResult) - return atError(stereoResult.extractError()); - datas[comp].pStereo = std::make_unique(*datas[comp].g, std::move(stereoInferences[comp])); - } - return std::move(datas); // TODO: remove std::move when C++20/P1825R0 is available -} - -Result dfs(const std::string &dfs) { - return lib::Graph::DFSEncoding::parse(dfs); -} - -Result> -smiles(lib::IO::Warnings &warnings, const std::string &smiles, const bool allowAbstract, SmilesClassPolicy classPolicy) { - return lib::Chem::readSmiles(warnings, smiles, allowAbstract, classPolicy); -} - -} // namespace mod::lib::IO::Graph::Read \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/GraphWrite.cpp b/libs/libmod/src/mod/lib/IO/GraphWrite.cpp index 3f8f1dc..5007935 100644 --- a/libs/libmod/src/mod/lib/IO/GraphWrite.cpp +++ b/libs/libmod/src/mod/lib/IO/GraphWrite.cpp @@ -1,49 +1,6 @@ -#include "Graph.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include +#include "GraphWrite.hpp" namespace mod::lib::IO::Graph::Write { -namespace { - -// returns the filename _without_ extension - -std::string getFilePrefix(const std::size_t gId, bool useCache) { - static std::map cache; - auto iter = cache.find(gId); - if(!useCache || iter == end(cache)) { - std::string prefix = IO::getUniqueFilePrefix() + "g_" + boost::lexical_cast(gId); - if(useCache) cache[gId] = prefix; - return prefix; - } else return iter->second; -} - -void escapeLabelForDot(const std::string &label, std::ostream &s) { - for(char c : label) { - if(c == '"') s << "\\\""; - else if(c == '\\') s << "\\\\"; - else s << c; - } -} - -} // namespace EdgeFake3DType invertEdgeFake3DType(EdgeFake3DType t) { switch(t) { @@ -58,295 +15,7 @@ EdgeFake3DType invertEdgeFake3DType(EdgeFake3DType t) { case EdgeFake3DType::HashLS: return EdgeFake3DType::HashSL; } - MOD_ABORT; // no, GCC, shh -} - -void gml(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - bool withCoords, std::ostream &s) { - if(!depict.getHasCoordinates() && withCoords) MOD_ABORT; - const auto &g = get_graph(gLabelled); - const auto &pString = get_string(gLabelled); - s << "graph [\n"; - for(auto v : asRange(vertices(g))) { - s << "\tnode [ id " << get(boost::vertex_index_t(), g, v) << " label \"" << pString[v] << "\""; - if(withCoords) - s << " vis2d [ x " << depict.getX(v, true) << " y " << depict.getY(v, true) << " ]"; - s << " ]\n"; - } - for(auto e : asRange(edges(g))) { - s << "\tedge [ source " << get(boost::vertex_index_t(), g, source(e, g)) - << " target " << get(boost::vertex_index_t(), g, target(e, g)) - << " label \"" << pString[e] << "\" ]\n"; - } - s << "]\n"; -} - -std::string gml(const lib::Graph::Single &g, bool withCoords) { - static std::set > cache; - auto iter = cache.find(std::make_pair(g.getId(), withCoords)); - std::string fileNoExt = getFilePrefix(g.getId(), true); - if(iter != end(cache)) return fileNoExt; - cache.emplace(g.getId(), withCoords); - post::FileHandle s(fileNoExt + ".gml"); - gml(g.getLabelledGraph(), g.getDepictionData(), g.getId(), withCoords, s); - return s; -} - -std::string dot(const lib::Graph::LabelledGraph &gLabelled, const std::size_t gId) { - static std::set cache; - auto iter = cache.find(gId); - std::string file = getFilePrefix(gId, true) + ".dot"; - if(iter != end(cache)) return file; - cache.insert(gId); - post::FileHandle s(file); - const auto &g = get_graph(gLabelled); - const auto &pString = get_string(gLabelled); - { - s << "graph g {\n"; - s << "\tnode [shape=plaintext]\n"; - s << getConfig().io.dotCoordOptions.get() << "\n"; - - for(auto v : asRange(vertices(g))) { - unsigned int id = get(boost::vertex_index_t(), g, v); - const std::string &label = pString[v]; - s << "\t" << id << " [ label=\""; - escapeLabelForDot(label, s); - s << "\" ]\n"; - } - - for(auto e : asRange(edges(g))) { - auto fromId = get(boost::vertex_index_t(), g, source(e, g)); - auto toId = get(boost::vertex_index_t(), g, target(e, g)); - const std::string &label = pString[e]; - if(label == "-" || label == "=" || label == "#") { - s << fromId << " -- " << toId << std::endl; - if(label != "-") s << fromId << " -- " << toId << std::endl; - if(label == "#") s << fromId << " -- " << toId << std::endl; - } else { - s << fromId << " -- " << toId << " [ label=\""; - escapeLabelForDot(label, s); - s << "\" ]\n"; - } - } - s << "}\n"; - } - return s; -} - -struct CoordsCacheEntry { - std::size_t id; - bool collapseHydrogens; - int rotation; - bool mirror; -public: - friend bool operator<(const CoordsCacheEntry &a, const CoordsCacheEntry &b) { - return std::tie(a.id, a.collapseHydrogens, a.rotation, a.mirror) - < std::tie(b.id, b.collapseHydrogens, b.rotation, b.mirror); - } -}; - -std::string coords(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, - const std::size_t gId, const Options &options, bool asInline) { - static std::map cache; - if(!asInline) { - const auto iter = cache.find({gId, options.collapseHydrogens, options.rotation, options.mirror}); - if(iter != end(cache)) return iter->second; - } - if(!depict.getHasCoordinates()) { - dot(gLabelled, gId); - std::string fileNoExt = getFilePrefix(gId, !asInline); - IO::post() << "coordsFromGV graph \"" << fileNoExt << "\"\n"; - std::string file = std::move(fileNoExt) + "_coord.tex"; - if(!asInline) { - for(bool collapse :{true, false}) { - for(bool mirror :{true, false}) { - cache[{gId, collapse, options.rotation, mirror} - ] = file; - } - } - } - return file; - } else { - const auto &g = get_graph(gLabelled); - std::string f = getFilePrefix(gId, !asInline); - if(options.collapseHydrogens) f += "_mol"; - if(options.rotation != 0) f += "_r" + std::to_string(options.rotation); - if(options.mirror) f += "_m" + std::to_string(options.mirror); - if(asInline) f += "i"; - post::FileHandle s(f + "_coord.tex"); - s << "% dummy\n"; - for(const auto v : asRange(vertices(g))) { - const auto vId = get(boost::vertex_index_t(), g, v); - if(options.collapseHydrogens && Chem::isCollapsible(v, g, depict, depict, [&depict](const auto v) { - return depict.hasImportantStereo(v); - })) - continue; - double x, y; - std::tie(x, y) = pointTransform( - depict.getX(v, !options.collapseHydrogens), - depict.getY(v, !options.collapseHydrogens), - options.rotation, options.mirror); - s << "\\coordinate[overlay] (\\modIdPrefix v-coord-" << vId << ") at (" << std::fixed << x << ", " << y - << ") {};\n"; - } - std::string file = s; - if(!asInline) { - cache[{gId, options.collapseHydrogens, options.rotation, options.mirror} - ] = file; - } - return file; - } -} - -std::pair -tikz(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options, - bool asInline, const std::string &idPrefix) { - static std::set > cache; - std::string strOptions = options.getStringEncoding(); - std::string file = getFilePrefix(gId, !asInline) + "_" + strOptions; - if(asInline) file += "i"; - file += ".tex"; - std::string fileCoordsExt = coords(gLabelled, depict, gId, options, asInline); - if(!asInline) { - const auto iter = cache.find(std::make_pair(gId, strOptions)); - if(iter != end(cache)) return std::make_pair(file, fileCoordsExt); - cache.insert(std::make_pair(gId, strOptions)); - } - post::FileHandle s(file); - tikz(s, options, get_graph(gLabelled), depict, fileCoordsExt, asInline, idPrefix); - return std::make_pair(file, fileCoordsExt); -} - -std::string -pdf(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options) { - { // user-specified depiction - static std::map userCache; - auto iter = userCache.find(gId); - if(iter != end(userCache)) return iter->second; - auto image = depict.getImage(); - if(image) { - std::string imageNoExt = (*image)(); - if(imageNoExt.empty()) { - std::cout << "User-specified depiction file for graph with id " << gId << " can not be empty." << std::endl; - throw 0; - } - std::string cmd = depict.getImageCommand(); - if(!cmd.empty()) IO::post() << cmd << std::endl; - std::string image = imageNoExt + ".pdf"; - userCache[gId] = image; - return image; - } - } - // auto-generated depiction - static std::set > cache; - std::string strOptions = options.getStringEncoding(); - std::string fileNoExt = getFilePrefix(gId, true) + "_" + strOptions; - std::string file = fileNoExt + ".pdf"; - auto iter = cache.find(std::make_pair(gId, strOptions)); - if(iter != end(cache)) return file; - cache.insert(std::make_pair(gId, strOptions)); - auto tikzFiles = tikz(gLabelled, depict, gId, options, false, ""); - std::string fileCoordsNoExt = tikzFiles.second.substr(0, tikzFiles.second.length() - 4); - IO::post() << "compileTikz \"" << fileNoExt << "\" \"" << fileCoordsNoExt << "\"" << std::endl; - return file; -} - -std::string -svg(const lib::Graph::LabelledGraph &gLabelled, const lib::Graph::DepictionData &depict, const std::size_t gId, - const Options &options) { - static std::set > cache; - std::string strOptions = options.getStringEncoding(); - std::string fileNoExt = getFilePrefix(gId, true) + "_" + strOptions; - std::string file = fileNoExt + ".svg"; - auto iter = cache.find(std::make_pair(gId, strOptions)); - if(iter != end(cache)) return file; - cache.insert(std::make_pair(gId, strOptions)); - std::string pdfFileNoExt = pdf(gLabelled, depict, gId, options); - pdfFileNoExt.erase(end(pdfFileNoExt) - 4, end(pdfFileNoExt)); - IO::post() << "pdfToSvg \"" << pdfFileNoExt << "\" \"" << fileNoExt << "\"" << std::endl; - return file; -} - -std::pair summary(const lib::Graph::Single &g, const Options &first, const Options &second) { - std::string graphLike = pdf(g, first); - std::string molLike = first == second ? "" : pdf(g, second); - IO::post() << "summaryGraph \"" << g.getName() << "\" \"" - << std::string(begin(graphLike), end(graphLike) - 4) << "\" \""; - if(!molLike.empty()) - IO::post() << std::string(begin(molLike), end(molLike) - 4); - IO::post() << "\"" << std::endl; - if(molLike.empty()) - return std::make_pair(graphLike, graphLike); - else - return std::make_pair(graphLike, molLike); -} - -void termState(const lib::Graph::Single &g) { - using Vertex = lib::Graph::Vertex; - using Edge = lib::Graph::Edge; - using namespace lib::Term; - IO::post() << "summarySubsection \"Term State for " << g.getName() << "\"" << std::endl; - post::FileHandle s(getUniqueFilePrefix() + "termState.tex"); - s << "\\begin{verbatim}\n"; - const auto &termState = get_term(g.getLabelledGraph()); - if(isValid(termState)) { - std::unordered_map > addrToVertex; - std::unordered_map > addrToEdge; - for(Vertex v : asRange(vertices(g.getGraph()))) { - Address a{AddressType::Heap, termState[v]}; - addrToVertex[a].insert(v); - } - for(Edge e : asRange(edges(g.getGraph()))) { - Address a{AddressType::Heap, termState[e]}; - addrToEdge[a].insert(e); - } - lib::IO::Term::Write::wam(getMachine(termState), lib::Term::getStrings(), s, [&](Address addr, std::ostream &s) { - s << " "; - bool first = true; - for(auto v : addrToVertex[addr]) { - if(!first) s << ", "; - first = false; - s << "v" << get(boost::vertex_index_t(), g.getGraph(), v); - } - for(auto e : addrToEdge[addr]) { - if(!first) s << ", "; - first = false; - s << "e(" - << get(boost::vertex_index_t(), g.getGraph(), source(e, g.getGraph())) - << ", " - << get(boost::vertex_index_t(), g.getGraph(), target(e, g.getGraph())) - << ")"; - } - }); - } else { - std::string msg = "Parsing failed for graph '" + g.getName() + "'. " + termState.getParsingError(); - throw TermParsingError(std::move(msg)); - } - s << "\\end{verbatim}\n"; - IO::post() << "summaryInput \"" << std::string(s) << "\"" << std::endl; -} - -//------------------------------------------------------------------------------ -// Simplified interface for lib::Graph::Single -//------------------------------------------------------------------------------ - -void gml(const lib::Graph::Single &g, bool withCoords, std::ostream &s) { - gml(g.getLabelledGraph(), g.getDepictionData(), g.getId(), withCoords, s); -} - -std::string tikz(const lib::Graph::Single &g, const Options &options, bool asInline, const std::string &idPrefix) { - auto res = tikz(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options, asInline, idPrefix); - return res.first; -} - -std::string pdf(const lib::Graph::Single &g, const Options &options) { - return pdf(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options); -} - -std::string svg(const lib::Graph::Single &g, const Options &options) { - return svg(g.getLabelledGraph(), g.getDepictionData(), g.getId(), options); + __builtin_unreachable(); } } // namespace mod::lib::IO::Graph::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/GraphWrite.hpp b/libs/libmod/src/mod/lib/IO/GraphWrite.hpp new file mode 100644 index 0000000..0c1a7ff --- /dev/null +++ b/libs/libmod/src/mod/lib/IO/GraphWrite.hpp @@ -0,0 +1,155 @@ +#ifndef MOD_LIB_IO_GRAPHWRITE_HPP +#define MOD_LIB_IO_GRAPHWRITE_HPP + +#include + +namespace mod::lib::IO::Graph::Write { + +struct Options { + Options &Non() { + return EdgesAsBonds(false).CollapseHydrogens(false).RaiseIsotopes(false).RaiseCharges(false) + .SimpleCarbons(false).Thick(false).WithColour(false).WithIndex(false); + } + + Options &All() { + return EdgesAsBonds(true).CollapseHydrogens(true).RaiseIsotopes(true).RaiseCharges(true) + .SimpleCarbons(true).Thick(true).WithColour(true).WithIndex(true); + } + + Options &EdgesAsBonds(bool v) { + edgesAsBonds = v; + return *this; + } + + Options &CollapseHydrogens(bool v) { + collapseHydrogens = v; + return *this; + } + + Options &RaiseIsotopes(bool v) { + raiseIsotopes = v; + return *this; + } + + Options &RaiseCharges(bool v) { + raiseCharges = v; + return *this; + } + + Options &SimpleCarbons(bool v) { + simpleCarbons = v; + return *this; + } + + Options &Thick(bool v) { + thick = v; + return *this; + } + + Options &WithColour(bool v) { + withColour = v; + return *this; + } + + Options &WithIndex(bool v) { + withIndex = v; + return *this; + } + + Options &WithTexttt(bool v) { + withTexttt = v; + return *this; + } + + Options &WithRawStereo(bool v) { + withRawStereo = v; + return *this; + } + + Options &WithPrettyStereo(bool v) { + withPrettyStereo = v; + return *this; + } + + Options &Rotation(int degrees) { + rotation = degrees; + return *this; + } + + Options &Mirror(bool v) { + mirror = v; + return *this; + } + + Options &WithGraphvizCoords(bool v) { + withGraphvizCoords = v; + return *this; + } + + std::string getStringEncoding() const { + auto toChar = [](bool b) { + return b ? '1' : '0'; + }; + std::string res; + res += toChar(edgesAsBonds); + res += toChar(collapseHydrogens); + char raise = 0; + if(raiseCharges) raise += 1; + if(raiseIsotopes) raise += 2; + res += '0' + raise; + res += toChar(simpleCarbons); + res += toChar(thick); + res += toChar(withColour); + res += toChar(withIndex); + res += toChar(withTexttt); + char stereo = 0; + if(withRawStereo) stereo += 1; + if(withPrettyStereo) stereo += 2; + if(stereo != 0) res += '0' + stereo; + if(rotation != 0 || mirror) { + res += "_"; + res += std::to_string(rotation); + if(mirror) { + res += "_"; + res += toChar(mirror); + } + } + return res; + } + + friend bool operator==(const Options &a, const Options &b) { + return a.getStringEncoding() == b.getStringEncoding() + && a.graphvizPrefix == b.graphvizPrefix; + } + + friend bool operator!=(const Options &a, const Options &b) { + return !(a == b); + } +public: + bool edgesAsBonds = false; + bool collapseHydrogens = false; + bool raiseCharges = false; + bool raiseIsotopes = false; + bool simpleCarbons = false; + bool thick = false; + bool withColour = false; + bool withIndex = false; + bool withTexttt = false; + bool withRawStereo = false; + bool withPrettyStereo = false; + int rotation = 0; + bool mirror = false; + bool withGraphvizCoords = false; +public: // not participating in string encoding + std::string graphvizPrefix; +}; + +enum class EdgeFake3DType { + None, WedgeSL, WedgeLS, HashSL, HashLS +}; + +EdgeFake3DType invertEdgeFake3DType(EdgeFake3DType t); + +} // namespace mod::lib::IO::Graph::Write + +#endif // MOD_LIB_IO_GRAPHWRITE_HPP diff --git a/libs/libmod/src/mod/lib/IO/GraphWriteDetail.hpp b/libs/libmod/src/mod/lib/IO/GraphWriteGeneric.hpp similarity index 80% rename from libs/libmod/src/mod/lib/IO/GraphWriteDetail.hpp rename to libs/libmod/src/mod/lib/IO/GraphWriteGeneric.hpp index cf1677c..d81cb8d 100644 --- a/libs/libmod/src/mod/lib/IO/GraphWriteDetail.hpp +++ b/libs/libmod/src/mod/lib/IO/GraphWriteGeneric.hpp @@ -1,9 +1,9 @@ -#ifndef MOD_LIB_IO_GRAPHWRITEDETAIL_HPP -#define MOD_LIB_IO_GRAPHWRITEDETAIL_HPP +#ifndef MOD_LIB_IO_GRAPHWRITEGENERIC_HPP +#define MOD_LIB_IO_GRAPHWRITEGENERIC_HPP #include +#include #include -#include #include #include @@ -46,36 +46,14 @@ static constexpr unsigned int } // namespace Loc } // namespace -namespace mod::lib::IO { - -constexpr double pi = 3.14159265358979323846; - -inline std::pair pointRotation(double xRaw, double yRaw, int rotation) { - double angle = rotation * pi / 180; - auto s = std::sin(angle); - auto c = std::cos(angle); - double x = xRaw * c - yRaw * s; - double y = xRaw * s + yRaw * c; - return std::make_pair(x, y); -} - -inline std::pair pointTransform(double xRaw, double yRaw, int rotation, bool mirror) { - if(mirror) xRaw *= -1; - return pointRotation(xRaw, yRaw, rotation); -} - -} // mod::lib::IO namespace mod::lib::IO::Graph::Write { template void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict &depict, const std::string &fileCoords, const AdvOptions &advOptions, BonusWriter bonusWriter, const std::string &idPrefix) { - typedef typename boost::graph_traits::vertex_descriptor Vertex; - typedef typename boost::graph_traits::edge_descriptor Edge; - [[maybe_unused]] const auto printBlocked = [&s](auto b) { - auto at = [&b](int i) { + const auto at = [&b](int i) { return bool((b & (1 << i)) != 0); }; s << "% " << at(6) << " " << at(5) << at(4) << at(3) << " " << at(2) << "\n"; @@ -91,26 +69,27 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict std::vector implicitHydrogenCount(num_vertices(g), 0); std::vector hasBondBlockingAtChargeRight(num_vertices(g), false); std::vector hasBondBlockingAtChargeLeft(num_vertices(g), false); + + const auto localIdx = get(boost::vertex_index_t(), g); + { // visibility from adv - for(Vertex v : asRange(vertices(g))) { - unsigned int vId = get(boost::vertex_index_t(), g, v); - isVisible[vId] = advOptions.isVisible(v); - } + for(const auto v: asRange(vertices(g))) + isVisible[get(localIdx, v)] = advOptions.isVisible(v); } { // stuff not depending on coordinates being available now if(options.simpleCarbons) { - for(Vertex v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { if(depict.getAtomId(v) != AtomIds::Carbon) continue; - unsigned int adjCount = 0; - for(Vertex vAdj : asRange(adjacent_vertices(v, g))) { - // for now we allow it even though the edges are not bonds - auto adjAtomId = depict.getAtomId(vAdj); + int adjCount = 0; + for(const auto vAdj: asRange(adjacent_vertices(v, g))) { + // for now, we allow it even though the edges are not bonds + const auto adjAtomId = depict.getAtomId(vAdj); switch(adjAtomId) { case AtomIds::Carbon: case AtomIds::Oxygen: case AtomIds::Nitrogen: case AtomIds::Sulfur: - adjCount++; + ++adjCount; } } if(adjCount < 2) continue; @@ -121,23 +100,22 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict } } - if(options.collapseHydrogens) { // collapse most hydrogens to it's neighbour - for(Vertex v : asRange(vertices(g))) { - unsigned int vId = get(boost::vertex_index_t(), g, v); + if(options.collapseHydrogens) { // collapse most hydrogen atoms to it's neighbour + for(const auto v: asRange(vertices(g))) { + const auto vId = get(localIdx, v); if(!isVisible[vId]) continue; const auto hasImportantStereo = [&depict](const auto v) { return depict.hasImportantStereo(v); }; - if(Chem::isCollapsible(v, g, depict, depict, hasImportantStereo)) { - assert(out_degree(v, g) == 1); - Edge e = *out_edges(v, g).first; - Vertex vAdj = target(e, g); // TODO: just use adjacent_vertices, we don't need the edge - unsigned int vAdjId = get(boost::vertex_index_t(), g, vAdj); - if(!isVisible[vAdjId]) continue; - if(advOptions.disallowCollapse(v)) continue; - implicitHydrogenCount[vAdjId]++; - isVisible[vId] = false; - } + if(!Chem::isCollapsibleHydrogen(v, g, depict, depict, hasImportantStereo)) continue; + assert(out_degree(v, g) == 1); + const auto e = *out_edges(v, g).first; + const auto vAdj = target(e, g); // TODO: just use adjacent_vertices, we don't need the edge + const auto vAdjId = get(boost::vertex_index_t(), g, vAdj); + if(!isVisible[vAdjId]) continue; + if(advOptions.disallowHydrogenCollapse(v)) continue; + ++implicitHydrogenCount[vAdjId]; + isVisible[vId] = false; } } } @@ -146,22 +124,21 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict return (b & (Loc::R_down * 2 - 1)) == (Loc::R_down * 2 - 1); }; std::vector auxLabelBlocked(num_vertices(g), 0); - if(depict.getHasCoordinates()) { // stuff where we absolutely need coordinates right now + // stuff where we absolutely need coordinates right now + if(!options.withGraphvizCoords && depict.getHasCoordinates()) { // see where we can have aux data - for(Vertex v : asRange(vertices(g))) { - unsigned int vId = get(boost::vertex_index_t(), g, v); + for(const auto v: asRange(vertices(g))) { + const auto vId = get(localIdx, v); if(!isVisible[vId]) continue; - double x, y; - std::tie(x, y) = pointTransform( + const auto[x, y] = pointTransform( depict.getX(v, !options.collapseHydrogens), depict.getY(v, !options.collapseHydrogens), options.rotation, options.mirror); - for(const Edge eOut : asRange(out_edges(v, g))) { - const Vertex vAdj = target(eOut, g); - const unsigned int vAdjId = get(boost::vertex_index_t(), g, vAdj); + for(const auto eOut: asRange(out_edges(v, g))) { + const auto vAdj = target(eOut, g); + const auto vAdjId = get(localIdx, vAdj); if(!isVisible[vAdjId]) continue; - double xAdj, yAdj; - std::tie(xAdj, yAdj) = pointTransform( + const auto[xAdj, yAdj] = pointTransform( depict.getX(vAdj, !options.collapseHydrogens), depict.getY(vAdj, !options.collapseHydrogens), options.rotation, options.mirror); @@ -172,7 +149,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict double angle = std::atan2(yDiff, xDiff); // [-pi; pi], later shifted to [0; 2*pi] const BondType bType = depict.getBondData(eOut); - // a single bond blocks less than a double/aromatic and they less than a triple + // a single bond blocks less than a double/aromatic, and they block less than a triple const double f = [&]() { switch(bType) { case BondType::Single: @@ -184,7 +161,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict case BondType::Triple: return 3; } - std::abort(); + __builtin_unreachable(); }(); // check if a bond blocks a charge, using angle in [-pi/2; 3/2*pi] { // right @@ -234,7 +211,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict } } } else { // we don't have coordinates, block all - for(auto &c : auxLabelBlocked) c = ~0L; + for(auto &c: auxLabelBlocked) c = ~0L; } // Tikz code @@ -251,22 +228,22 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict s << "\\begin{tikzpicture}[remember picture, scale=\\modGraphScale"; s << R"XXX(, baseline={([yshift={-0.5ex}]current bounding box)})XXX"; if(options.thick) s << ", thick"; - s - << ", solid"; // circumvent the strange inherited 'dashed' http://tex.stackexchange.com/questions/115887/pattern-in-addplot-inherits-dashed-option-from-previous-draw + // circumvent the strange inherited 'dashed' http://tex.stackexchange.com/questions/115887/pattern-in-addplot-inherits-dashed-option-from-previous-draw + s << ", solid"; s << "]\n"; if(!idPrefix.empty()) s << "\\renewcommand\\modIdPrefix{" << idPrefix << "}\n"; s << "\\input{\\modInputPrefix/" << fileCoords << "}\n"; - for(Vertex v : asRange(vertices(g))) { - const auto vId = get(boost::vertex_index_t(), g, v); + for(const auto v: asRange(vertices(g))) { + const auto vId = get(localIdx, v); if(!isVisible[vId]) continue; const auto atomId = depict.getAtomId(v); - const auto createDummy = [&s, vId, &advOptions, &textModifiersBegin, &textModifiersEnd]( + const auto createDummy = [&s, v, &advOptions, &textModifiersBegin, &textModifiersEnd]( std::string suffix, bool subscript, bool superscript) { // create dummy vertex to make sure the bounding box is large enough - s << "\\node[modStyleGraphVertex, at=(\\modIdPrefix v-" << (vId + advOptions.idOffset) << suffix << ")"; + s << "\\node[modStyleGraphVertex, at=(\\modIdPrefix v-" << advOptions.getOutputId(v) << suffix << ")"; if(subscript) s << ", text depth=.25ex"; if(superscript) s << ", text height=2.25ex"; s << "] {\\phantom{" << textModifiersBegin << "H"; @@ -308,25 +285,25 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict if(indexString.empty()) s << ", modStyleCarbon"; else s << ", modStyleCarbonIndex"; } - s << ", at=(\\modIdPrefix v-coord-" << (vId + advOptions.idOffset) << ")"; + s << ", at=(\\modIdPrefix v-coord-" << advOptions.getOutputId(v) << ")"; std::string userOpts = advOptions.getOpts(v); if(!userOpts.empty()) s << ", " << userOpts; - s << "] (\\modIdPrefix v-" << (vId + advOptions.idOffset) << ") "; + s << "] (\\modIdPrefix v-" << advOptions.getOutputId(v) << ") "; auto auxBlocked = auxLabelBlocked[vId]; if(isSimpleCarbon[vId]) { // Simple Cs s << "{" << textModifiersBegin << indexString << textModifiersEnd << "};\n"; if(!indexString.empty()) createDummy("", false, false); } else { // not simple carbon - const Isotope isotope = depict.getIsotope(v); - const char charge = depict.getCharge(v); - const unsigned int hCount = implicitHydrogenCount[vId]; + const auto isotope = depict.getIsotope(v); + const auto charge = depict.getCharge(v); + const auto hCount = implicitHydrogenCount[vId]; const bool allAuxBlocked = areAllBlocked(auxBlocked); const bool hInLabel = !options.collapseHydrogens || allAuxBlocked; const bool chargeOnLeft = hasBondBlockingAtChargeRight[vId] && !hasBondBlockingAtChargeLeft[vId]; // && isotope == Isotope(); - std::string labelNoAux = escapeForLatex(depict.getVertexLabelNoIsotopeChargeRadical(v)); + std::string labelNoAux = lib::IO::escapeForLatex(depict.getVertexLabelNoIsotopeChargeRadical(v)); std::string isotopeString; if(isotope != Isotope()) { if(options.raiseIsotopes) isotopeString += "$^{"; @@ -440,7 +417,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict if(!chargeInAux && charge != 0) { s << "\\node[modStyleGraphVertex" << colourString << ", at=(\\modIdPrefix v-" - << (vId + advOptions.idOffset); + << advOptions.getOutputId(v); if(chargeOnLeft) s << ".west), anchor=east"; else s << ".east), anchor=west"; s << "] {"; @@ -452,7 +429,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict } if(!isotopeInAux && isotope != Isotope()) { s << "\\node[modStyleGraphVertex" << colourString << ", at=(\\modIdPrefix v-" - << (vId + advOptions.idOffset); + << advOptions.getOutputId(v); s << ".west), anchor=east"; s << "] {"; s << textModifiersBegin; @@ -463,7 +440,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict } if(auxHPosition != -1) { - s << "\\node[modStyleGraphVertex" << colourString << ", at=(\\modIdPrefix v-" << (vId + advOptions.idOffset) + s << "\\node[modStyleGraphVertex" << colourString << ", at=(\\modIdPrefix v-" << advOptions.getOutputId(v) << "."; /**/ if(auxHPosition == Loc::R_narrow) s << "east), anchor=west"; else if(auxHPosition == Loc::L_narrow) s << "west), anchor=east"; @@ -471,7 +448,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict else if(auxHPosition == Loc::B_narrow) s << "south), anchor=north, yshift=-1pt"; else MOD_ABORT; - s << "] (\\modIdPrefix v-" << (vId + advOptions.idOffset) << "-aux) {"; + s << "] (\\modIdPrefix v-" << advOptions.getOutputId(v) << "-aux) {"; s << textModifiersBegin; if(chargeInAux && chargeOnLeft) s << chargeString; std::string output; @@ -493,7 +470,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict createDummy("-aux", hCount > 1, isotopeInAux && isotope != Isotope()); if(isAuxVertical && hCount > 1) { s << "\\node[modStyleGraphVertex" << colourString << ", at=(\\modIdPrefix v-" - << (vId + advOptions.idOffset) << "-aux.east), anchor=west] {"; + << advOptions.getOutputId(v) << "-aux.east), anchor=west] {"; s << textModifiersBegin; s << "$_{" << hCount << "}$"; s << textModifiersEnd << "};\n"; @@ -517,7 +494,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict auxBlocked |= (1 << auxRadicalPosition); const double atAngle = auxRadicalPosition * 22.5; s << "\\node[outer sep=0, inner sep=1, minimum size=0, fill=black, circle, " - << "at=(\\modIdPrefix v-" << (vId + advOptions.idOffset) << "." << atAngle << "), " + << "at=(\\modIdPrefix v-" << advOptions.getOutputId(v) << "." << atAngle << "), " << "shift=(" << atAngle << ":3pt)] {};\n"; } // end if has radical } // end if simpleCarbon @@ -527,7 +504,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict + (options.withPrettyStereo ? advOptions.getPrettyStereoString(v) : std::string()); if(!strStereo.empty()) { const auto stereoPosition = [&]() { - for(const unsigned int loc :{0, 8, 4, 12, 1, 15, 7, 9}) { + for(const unsigned int loc: {0, 8, 4, 12, 1, 15, 7, 9}) { if((auxBlocked & (1 << loc)) == 0) return loc; } for(unsigned int loc = 16; loc > 0; --loc) { @@ -537,7 +514,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict }(); auxBlocked |= (1u << stereoPosition); double atAngle = stereoPosition * 22.5; - s << "\\node[outer sep=0, inner sep=1, at=(\\modIdPrefix v-" << (vId + advOptions.idOffset) + s << "\\node[outer sep=0, inner sep=1, at=(\\modIdPrefix v-" << advOptions.getOutputId(v) << "." << atAngle << "), anchor="; [&s, stereoPosition]() -> std::ostream & { switch(stereoPosition) { @@ -556,7 +533,7 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict if(stereoPosition > 4 && stereoPosition < 12) return s << " east"; else return s << " west"; }(); - s << "] (\\modIdPrefix v-" << (vId + advOptions.idOffset) << "-stereoId) "; + s << "] (\\modIdPrefix v-" << advOptions.getOutputId(v) << "-stereoId) "; s << "{\\tiny " << textModifiersBegin << strStereo << textModifiersEnd << "};\n"; } } // end if with any kind of stereo @@ -584,9 +561,9 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict auxBlocked |= (1 << auxIndexPosition); const double atAngle = auxIndexPosition * 22.5; s << "\\node[outer sep=0, inner sep=1" << colourString - << ", at=(\\modIdPrefix v-" << (vId + advOptions.idOffset) + << ", at=(\\modIdPrefix v-" << advOptions.getOutputId(v) << "." << atAngle << "), anchor=" << (atAngle + 180) - << "] (\\modIdPrefix v-" << (vId + advOptions.idOffset) << "-auxId) {"; + << "] (\\modIdPrefix v-" << advOptions.getOutputId(v) << "-auxId) {"; s << textModifiersBegin; s << indexString; s << textModifiersEnd << "};\n"; @@ -594,11 +571,13 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict } // end if withIndex } // foreach vertex - for(Edge e : asRange(edges(g))) { - unsigned int fromId = get(boost::vertex_index_t(), g, source(e, g)); - unsigned int toId = get(boost::vertex_index_t(), g, target(e, g)); - if(!isVisible[fromId]) continue; - if(!isVisible[toId]) continue; + for(const auto e: asRange(edges(g))) { + const auto vSrc = source(e, g); + const auto vTar = target(e, g); + const auto vSrcId = get(localIdx, vSrc); + const auto vTarId = get(localIdx, vTar); + if(!isVisible[vSrcId]) continue; + if(!isVisible[vTarId]) continue; std::string colourString = advOptions.getColour(e); if(!colourString.empty()) colourString += ", text=" + colourString; BondType bType = depict.getBondData(e); @@ -641,17 +620,17 @@ void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict drawCommand += "HashLS"; break; } - unsigned int fromOffset = 1, toOffset = 1; + int fromOffset = 1, toOffset = 1; if(!options.withIndex) { - if(isSimpleCarbon[fromId]) fromOffset = 0; - if(isSimpleCarbon[toId]) toOffset = 0; + if(isSimpleCarbon[vSrcId]) fromOffset = 0; + if(isSimpleCarbon[vTarId]) toOffset = 0; } - s << drawCommand << "{" << (fromId + advOptions.idOffset) << "}{" << (toId + advOptions.idOffset) << "}{" + s << drawCommand << "{" << advOptions.getOutputId(vSrc) << "}{" << advOptions.getOutputId(vTar) << "}{" << fromOffset << "}{" << toOffset << "}"; s << "{" << colourString << "}"; // find the label text const std::string label = (!options.edgesAsBonds || bType == BondType::Invalid) - ? escapeForLatex(depict.getEdgeLabel(e)) : std::string(); + ? lib::IO::escapeForLatex(depict.getEdgeLabel(e)) : std::string(); const std::string rawStereo = options.withRawStereo ? "{\\tiny " + advOptions.getStereoString(e) + "}" : std::string(); const std::string extraAnnotation = advOptions.getEdgeAnnotation(e); @@ -676,16 +655,15 @@ template struct DefaultAdvancedOptions { using Vertex = typename boost::graph_traits::vertex_descriptor; using Edge = typename boost::graph_traits::edge_descriptor; - +public: DefaultAdvancedOptions(const Graph &g, const Depict &depict) : g(g), depict(depict) {} - template - std::string getColour(T) const { + template + std::string getColour(VE) const { return ""; } - template - bool isVisible(T) const { + bool isVisible(Vertex) const { return true; } @@ -705,33 +683,29 @@ struct DefaultAdvancedOptions { return ""; } - template - std::string getRawStereoString(VE ve) const { - return depict.getRawStereoString(ve); + std::string getRawStereoString(Vertex v) const { + return depict.getRawStereoString(v); } - template - std::string getPrettyStereoString(VE ve) const { - return depict.getPrettyStereoString(ve); + std::string getPrettyStereoString(Vertex v) const { + return depict.getPrettyStereoString(v); } - template - std::string getStereoString(VE ve) const { - return depict.getStereoString(ve); + std::string getStereoString(Edge e) const { + return depict.getStereoString(e); } std::string getOpts(Vertex v) const { return std::string(); } -public: - - template - bool disallowCollapse(T) const { + bool disallowHydrogenCollapse(Vertex) const { return false; } - unsigned int idOffset = 0; + auto getOutputId(Vertex v) const { + return get(boost::vertex_index_t(), g, v); + } private: const Graph &g; const Depict &depict; @@ -741,10 +715,9 @@ template void tikz(std::ostream &s, const Options &options, const Graph &g, const Depict &depict, const std::string &fileCoords, bool asInline, const std::string &idPrefix) { DefaultAdvancedOptions adv(g, depict); - tikz(s, options, g, depict, fileCoords, adv, [](std::ostream &s) { - }, idPrefix); + tikz(s, options, g, depict, fileCoords, adv, [](std::ostream &s) {}, idPrefix); } } // namespace mod::lib::IO::Graph::Write -#endif // MOD_LIB_IO_GRAPHWRITEDETAIL_HPP \ No newline at end of file +#endif // MOD_LIB_IO_GRAPHWRITEGENERIC_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/IO.cpp b/libs/libmod/src/mod/lib/IO/IO.cpp index d232106..04b2f6b 100644 --- a/libs/libmod/src/mod/lib/IO/IO.cpp +++ b/libs/libmod/src/mod/lib/IO/IO.cpp @@ -33,6 +33,7 @@ struct PostStream { std::cerr << "Does '" << prefix << "' exist?" << std::endl; std::exit(1); } + private: bool enabled; std::ofstream s; @@ -44,7 +45,7 @@ PostStream postStream; } // namespace -std::string getUniqueFilePrefix() { +std::string makeUniqueFilePrefix() { static int count = 0; std::string strCount; @@ -61,7 +62,7 @@ std::string getUniqueFilePrefix() { std::string escapeForLatex(const std::string &str) { std::string res; res.reserve(str.size()); - for(char c : str) { + for(char c: str) { switch(c) { case '#': case '_': @@ -82,9 +83,10 @@ std::string escapeForLatex(const std::string &str) { std::string asLatexMath(const std::string &str) { std::string res = "$\\mathrm{"; - for(char c : str) { + for(char c: str) { switch(c) { case ' ': + case '#': res += "\\"; res += c; break; @@ -103,6 +105,7 @@ std::ostream &nullStream() { }; struct NullStream : public std::ostream { NullStream() : std::ostream(&buffer) {} + private: NullBuffer buffer; }; @@ -115,14 +118,18 @@ std::ostream &post() { return postStream.getStream(); } -void postReset() { - postStream.resetStream(); -} - void postDisable() { postStream.dynamicallyEnabled = false; } +void postEnable() { + postStream.dynamicallyEnabled = true; +} + +void postReopenCommandFile() { + postStream.resetStream(); +} + std::ostream &Logger::indent() const { assert(indentLevel >= 0); return s << std::string(indentLevel * 2, ' '); diff --git a/libs/libmod/src/mod/lib/IO/IO.hpp b/libs/libmod/src/mod/lib/IO/IO.hpp index 94db330..7fe7ab4 100644 --- a/libs/libmod/src/mod/lib/IO/IO.hpp +++ b/libs/libmod/src/mod/lib/IO/IO.hpp @@ -6,15 +6,16 @@ namespace mod::lib::IO { -std::string getUniqueFilePrefix(); +std::string makeUniqueFilePrefix(); std::string escapeForLatex(const std::string &str); std::string asLatexMath(const std::string &str); std::ostream &nullStream(); std::ostream &post(); -void postReset(); void postDisable(); +void postEnable(); +void postReopenCommandFile(); struct Logger { explicit Logger(std::ostream &s) : s(s) {} diff --git a/libs/libmod/src/mod/lib/IO/JsonUtils.cpp b/libs/libmod/src/mod/lib/IO/Json.cpp similarity index 98% rename from libs/libmod/src/mod/lib/IO/JsonUtils.cpp rename to libs/libmod/src/mod/lib/IO/Json.cpp index fab65f8..114cc34 100644 --- a/libs/libmod/src/mod/lib/IO/JsonUtils.cpp +++ b/libs/libmod/src/mod/lib/IO/Json.cpp @@ -1,4 +1,4 @@ -#include "JsonUtils.hpp" +#include "Json.hpp" #include #include diff --git a/libs/libmod/src/mod/lib/IO/JsonUtils.hpp b/libs/libmod/src/mod/lib/IO/Json.hpp similarity index 88% rename from libs/libmod/src/mod/lib/IO/JsonUtils.hpp rename to libs/libmod/src/mod/lib/IO/Json.hpp index 29a5ea4..d4d3c6f 100644 --- a/libs/libmod/src/mod/lib/IO/JsonUtils.hpp +++ b/libs/libmod/src/mod/lib/IO/Json.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_LIB_IO_JSONUTILS_HPP -#define MOD_LIB_IO_JSONUTILS_HPP +#ifndef MOD_LIB_IO_JSON_HPP +#define MOD_LIB_IO_JSON_HPP // We don't control the symbol visibility of the json library and json-schema library, // so we need to restore the default when using their header files. @@ -24,4 +24,4 @@ bool validateJson(const nlohmann::json &j, } // namespace mod::lib::IO -#endif // MOD_LIB_IO_JSONUTILS_HPP +#endif // MOD_LIB_IO_JSON_HPP diff --git a/libs/libmod/src/mod/lib/IO/MorphismConstraints.hpp b/libs/libmod/src/mod/lib/IO/MorphismConstraints.hpp deleted file mode 100644 index 8612883..0000000 --- a/libs/libmod/src/mod/lib/IO/MorphismConstraints.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef MOD_LIB_IO_MORPHISMCONSTRAINTS_H -#define MOD_LIB_IO_MORPHISMCONSTRAINTS_H - -#include -#include -#include - -namespace mod { -namespace lib { -namespace IO { -namespace MatchConstraint { -namespace Write { - -template -struct TexPrintVisitor : lib::GraphMorphism::Constraints::AllVisitor { - - TexPrintVisitor(std::ostream &s, const Graph &g) : s(s), g(g) { } - - void printOp(lib::GraphMorphism::Constraints::Operator op) { - using lib::GraphMorphism::Constraints::Operator; - switch(op) { - case Operator::EQ: s << "="; - break; - case Operator::LT: s << "<"; - break; - case Operator::GT: s << ">"; - break; - case Operator::LEQ: s << "\\leq"; - break; - case Operator::GEQ: s << "\\geq"; - break; - } - } - - virtual void operator()(const lib::GraphMorphism::Constraints::VertexAdjacency &c) override { - s << "\\begin{align*}\n"; - s << "&|\\{e \\in \\mathrm{outEdges}(" << get(boost::vertex_index_t(), g, c.vConstrained) << ") "; - if(!c.vertexLabels.empty() || !c.edgeLabels.empty()) s << " \\mid \\\\\n"; - if(!c.vertexLabels.empty()) { - s << "&\\quad \\mathrm{label}(\\mathrm{target}(e)) \\in \\{"; - bool first = true; - for(const std::string &str : c.vertexLabels) { - if(!first) s << ", "; - first = false; - s << " \\text{`\\texttt{" << escapeForLatex(str) << "}'}"; - } - s << " \\}\\\\\n"; - } - if(!c.vertexLabels.empty() && !c.edgeLabels.empty()) s << "&\\quad \\wedge\\\\\n"; - if(!c.edgeLabels.empty()) { - s << "&\\quad \\mathrm{label}(e) \\in \\{"; - bool first = true; - for(const std::string &str : c.edgeLabels) { - if(!first) s << ", "; - first = false; - s << " \\text{`\\texttt{" << escapeForLatex(str) << "}'}"; - } - s << " \\}\\\\\n"; - } - if(!c.vertexLabels.empty() || !c.edgeLabels.empty()) s << "&"; - s << "\\}| "; - printOp(c.op); - s << " " << c.count << "\n\\end{align*}\n"; - } - - virtual void operator()(const lib::GraphMorphism::Constraints::ShortestPath &c) override { - s << "$\\mathrm{shortestPath}(" << get(boost::vertex_index_t(), g, c.vSrc) << ", " << get(boost::vertex_index_t(), g, c.vTar) << ") "; - printOp(c.op); - s << " " << c.length << "$\n"; - } -private: - std::ostream &s; - const Graph &g; -}; - -template -auto makeTexPrintVisitor(std::ostream &s, const Graph &g) { - return TexPrintVisitor(s, g); -} - -template -struct GMLPrintVisitor : lib::GraphMorphism::Constraints::AllVisitor { - - GMLPrintVisitor(std::ostream &s, const Graph &g, std::string prefix) : s(s), g(g), prefix(std::move(prefix)) { } - - void printOp(lib::GraphMorphism::Constraints::Operator op) { - using lib::GraphMorphism::Constraints::Operator; - switch(op) { - case Operator::EQ: s << "="; - break; - case Operator::LT: s << "<"; - break; - case Operator::GT: s << ">"; - break; - case Operator::LEQ: s << "<="; - break; - case Operator::GEQ: s << ">="; - break; - } - } - - virtual void operator()(const lib::GraphMorphism::Constraints::VertexAdjacency &c) { - s << prefix << "constrainAdj [\n"; - s << prefix << " id " << get(boost::vertex_index_t(), g, c.vConstrained) << "\n"; - s << prefix << " op \""; - printOp(c.op); - s << "\"\n"; - s << prefix << " count " << c.count << "\n"; - s << prefix << " nodeLabels ["; - for(const auto &str : c.vertexLabels) s << " label \"" << str << "\""; - s << " ]\n"; - s << prefix << " edgeLabels ["; - for(const auto &str : c.edgeLabels) s << " label \"" << str << "\""; - s << " ]\n"; - s << prefix << "]\n"; - } - - virtual void operator()(const lib::GraphMorphism::Constraints::ShortestPath &c) { - s << prefix << "constrainShortestPath [\n"; - s << prefix << " source " << get(boost::vertex_index_t(), g, c.vSrc) - << " target " << get(boost::vertex_index_t(), g, c.vTar) << "\n"; - s << prefix << " op \""; - printOp(c.op); - s << "\" length " << c.length << "\n"; - s << prefix << "]\n"; - } -private: - std::ostream &s; - const Graph &g; - std::string prefix; -}; - -template -auto makeGMLPrintVisitor(std::ostream &s, const Graph &g, std::string prefix) { - return GMLPrintVisitor(s, g, prefix); -} - -} // namespace Write -} // namespace MatchConstraint -} // namespace IO -} // namespace lib -} // namespace mod - -#endif /* MOD_LIB_IO_MORPHISMCONSTRAINTS_H */ \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/ParsingUtil.hpp b/libs/libmod/src/mod/lib/IO/Parsing.hpp similarity index 72% rename from libs/libmod/src/mod/lib/IO/ParsingUtil.hpp rename to libs/libmod/src/mod/lib/IO/Parsing.hpp index 15fc893..3672c45 100644 --- a/libs/libmod/src/mod/lib/IO/ParsingUtil.hpp +++ b/libs/libmod/src/mod/lib/IO/Parsing.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_LIB_IO_PARSINGUTIL_HPP -#define MOD_LIB_IO_PARSINGUTIL_HPP +#ifndef MOD_LIB_IO_PARSING_HPP +#define MOD_LIB_IO_PARSING_HPP #include @@ -36,19 +36,22 @@ struct ParseDispatch { }; template -std::string makeParserError(const TextIter &textIter, const PosIter &iter, const PosIter &iterEnd) { +std::string +makeParserError(const TextIter &textIter, const PosIter &iter, const PosIter &iterEnd, const bool withPosition) { const auto lineNumber = iter.position(); const auto lineRange = get_current_line(PosIter(textIter), iter, iterEnd); const auto column = get_column(lineRange.begin(), iter); - return "Parsing failed at " + std::to_string(lineNumber) - + ":" + std::to_string(column) + ":\n" - + boost::lexical_cast(lineRange) + "\n" + std::string(column - 1, '-') + "^"; + std::string msg = "Parsing failed"; + if(withPosition) + msg += " at " + std::to_string(lineNumber) + ":" + std::to_string(column); + msg += ":\n" + boost::lexical_cast(lineRange) + "\n" + std::string(column - 1, '-') + "^"; + return msg; } template std::string makeParserExpectationError(const x3::expectation_failure &e, const TextIter &textIter, - const PosIter &iterEnd) { - return detail::makeParserError(textIter, e.where(), iterEnd) + const PosIter &iterEnd, const bool withPosition) { + return detail::makeParserError(textIter, e.where(), iterEnd, withPosition) + "\nExpected " + e.which() + "."; } @@ -59,24 +62,24 @@ using PositionIter = spirit::line_pos_iterator; template void parse(const TextIter &textFirst, PositionIter &first, const PositionIter &last, - const Parser &p, Attr &attr, const Skipper &...skipper) { + const Parser &p, Attr &attr, const bool withPosition, const Skipper &...skipper) { bool res; try { res = detail::ParseDispatch::parse(first, last, p, attr, skipper...); } catch(const x3::expectation_failure > &e) { - throw ParsingError{detail::makeParserExpectationError(e, textFirst, last)}; + throw ParsingError{detail::makeParserExpectationError(e, textFirst, last, withPosition)}; } if(!res || first != last) - throw ParsingError{detail::makeParserError(textFirst, first, last)}; + throw ParsingError{detail::makeParserError(textFirst, first, last, withPosition)}; } template void parse(const TextIter &textFirst, const TextIter &textLast, - const Parser &p, Attr &attr, const Skipper &...skipper) { + const Parser &p, Attr &attr, const bool withPosition, const Skipper &...skipper) { PositionIter first(textFirst); - return parse(textFirst, first, PositionIter(textLast), p, attr, skipper...); + return parse(textFirst, first, PositionIter(textLast), p, attr, withPosition, skipper...); } } // namespace mod::lib::IO -#endif // MOD_LIB_IO_PARSINGUTIL_HPP \ No newline at end of file +#endif // MOD_LIB_IO_PARSING_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Result.hpp b/libs/libmod/src/mod/lib/IO/Result.hpp index 20beb7a..19962a6 100644 --- a/libs/libmod/src/mod/lib/IO/Result.hpp +++ b/libs/libmod/src/mod/lib/IO/Result.hpp @@ -9,7 +9,7 @@ namespace mod::lib::IO { struct Warnings { Warnings() = default; - Warnings(const Warnings &) = delete; + explicit Warnings(const Warnings &warnings) = default; Warnings &operator=(const Warnings &) = delete; Warnings(Warnings &&) = default; Warnings &operator=(Warnings &&) = default; @@ -23,6 +23,7 @@ struct Warnings { warnings.emplace_back(std::move(msg), print); } public: + std::size_t empty() const { return warnings.empty(); } friend std::ostream &operator<<(std::ostream &s, const Warnings &ws); public: std::vector> extractWarnings() { return std::move(warnings); } @@ -66,6 +67,7 @@ struct [[nodiscard]] Result : Result { // TODO: change to by-value when C++20/P1825R0 is available Result(Result &&other) : Result(std::move(other)) {} T &operator*() { return *value; } + T *operator->() { return &*value; } private: Result() = default; private: diff --git a/libs/libmod/src/mod/lib/IO/Rule.hpp b/libs/libmod/src/mod/lib/IO/Rule.hpp deleted file mode 100644 index 477fa72..0000000 --- a/libs/libmod/src/mod/lib/IO/Rule.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef MOD_LIB_IO_RULE_HPP -#define MOD_LIB_IO_RULE_HPP - -#include -#include // to make sure the write options are defined -#include -#include - -#include -#include -#include -#include - -namespace mod::lib::Rules { -struct Real; -} // namespace mod::lib::Rules -namespace mod::lib::IO::Rules { -namespace Read { - -struct Data { - std::optional rule; - std::optional name; - std::optional labelType; - std::map externalToInternalIds; -}; - -Result gml(lib::IO::Warnings &warnings, std::string_view input); - -} // namespace Read -namespace Write { - -using Options = IO::Graph::Write::Options; -using CoreVertex = lib::Rules::Vertex; -using CoreEdge = lib::Rules::Edge; - -struct BaseArgs { - std::function visible; - std::function vColour; - std::function eColour; -}; - -// returns the filename _with_ extension -void gml(const lib::Rules::Real &r, bool withCoords, std::ostream &s); -std::string gml(const lib::Rules::Real &r, bool withCoords); -// returns the filename without extension -std::string dotCombined(const lib::Rules::Real &r); -std::string svgCombined(const lib::Rules::Real &r); -std::string pdfCombined(const lib::Rules::Real &r); -// returns the filename _without_ extension -std::string dot(const lib::Rules::Real &r); // does not handle labels correctly, is for coordinate generation -std::string coords(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - std::function disallowCollapse_); -std::pair -tikz(const std::string &fileCoordsNoExt, const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, const BaseArgs &args, - std::function disallowCollapse); -std::pair tikz(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffixL, const std::string &suffixK, - const std::string &suffixR, const BaseArgs &args, - std::function disallowCollapse); -std::string pdf(const lib::Rules::Real &r, const Options &options, - const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, - const BaseArgs &args); -std::pair -tikzTransitionState(const std::string &fileCoordsNoExt, const lib::Rules::Real &r, unsigned int idOffset, - const Options &options, - const std::string &suffix, const BaseArgs &args); -std::pair -tikzTransitionState(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffix, const BaseArgs &args); -std::string pdfTransitionState(const lib::Rules::Real &r, const Options &options, - const std::string &suffix, const BaseArgs &args); -//std::string pdfCombined(const lib::Rules::Real &r, const Options &options); // TODO -std::pair summary(const lib::Rules::Real &r, bool printCombined); -std::pair -summary(const lib::Rules::Real &r, const Options &first, const Options &second, bool printCombined); -void termState(const lib::Rules::Real &r); -} // namespace Write -} // namespace mod::lib::IO::Rules - -#endif // MOD_LIB_IO_RULE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/RuleRead.cpp b/libs/libmod/src/mod/lib/IO/RuleRead.cpp deleted file mode 100644 index 8ae77b8..0000000 --- a/libs/libmod/src/mod/lib/IO/RuleRead.cpp +++ /dev/null @@ -1,538 +0,0 @@ -#include "Rule.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace mod::lib::IO::Rules::Read { -namespace { - -template -struct Label { - std::optional left, context, right; -}; - -} // namespace - -Result gml(lib::IO::Warnings &warnings, std::string_view input) { - GML::Rule rule; - { // GML parsing and conversion - gml::ast::KeyValue ast; - try { - ast = gml::parser::parse(input); - } catch(const gml::parser::error &e) { - return Result<>::Error(e.what()); - } - using namespace gml::converter::edsl; - auto cVertex = GML::makeVertexConverter(0); - auto cEdge = GML::makeEdgeConverter(0); - auto nodeLabels = list("nodeLabels") - (string("label", &GML::AdjacencyConstraint::nodeLabels)); - auto edgeLabels = list("edgeLabels") - (string("label", &GML::AdjacencyConstraint::edgeLabels)); - auto constrainAdj = list("constrainAdj", &GML::Rule::matchConstraints) - (int_("id", &GML::AdjacencyConstraint::id), 1, 1) - (string("op", &GML::AdjacencyConstraint::op), 1, 1) - (int_("count", &GML::AdjacencyConstraint::count), 1, 1) - (nodeLabels)(edgeLabels); - auto constrainShortestPath = list("constrainShortestPath", - &GML::Rule::matchConstraints) - (int_("source", &GML::ShortestPathConstraint::source), 1, 1) - (int_("target", &GML::ShortestPathConstraint::target), 1, 1) - (string("op", &GML::ShortestPathConstraint::op), 1, 1) - (int_("length", &GML::ShortestPathConstraint::length), 1, 1); - auto makeSide = [&](std::string name, GML::Graph GML::Rule::*side) { - return list(name, side)(cVertex)(cEdge); - }; - auto cRule = list("rule") - (string("ruleID", &GML::Rule::id), 0, 1) - (string("labelType", &GML::Rule::labelType), 0, 1) - (makeSide("left", &GML::Rule::left), 0, 1) - (makeSide("context", &GML::Rule::context), 0, 1) - (makeSide("right", &GML::Rule::right), 0, 1) - (constrainAdj)(constrainShortestPath); - auto iterBegin = * - auto iterEnd = iterBegin + 1; - try { - gml::converter::convert(iterBegin, iterEnd, cRule, rule); - } catch(const gml::converter::error &e) { - return Result<>::Error(e.what()); - } - } // end of GML parsing - - const auto checkSide = [](const GML::Graph &side, std::string name) -> Result<> { - std::unordered_set vertexIds; - for(const GML::Vertex &v : side.vertices) { - if(vertexIds.find(v.id) != end(vertexIds)) - return Result<>::Error("Duplicate vertex " + std::to_string(v.id) + " in " + name + " graph."); - vertexIds.insert(v.id); - } - std::set > edgeIds; - for(const GML::Edge &e : side.edges) { - if(e.source == e.target) - return Result<>::Error("Loop edge (on " + std::to_string(e.source) + ", in " + name + ") is not allowed."); - auto eSorted = std::minmax(e.source, e.target); - if(edgeIds.find(eSorted) != end(edgeIds)) - return Result<>::Error( - "Duplicate edge (" + std::to_string(e.source) + ", " + std::to_string(e.target) + ") in " - + name + " graph."); - edgeIds.insert(eSorted); - } - return Result<>(); - }; - - if(auto res = checkSide(rule.left, "left"); !res) return res; - if(auto res = checkSide(rule.context, "context"); !res) return res; - if(auto res = checkSide(rule.right, "right"); !res) return res; - - using Vertex = lib::Rules::Vertex; - using Edge = lib::Rules::Edge; - Data data; - data.rule = lib::Rules::LabelledRule(); - data.name = rule.id; - if(rule.labelType) { - const std::string <String = *rule.labelType; - if(ltString == "string") data.labelType = LabelType::String; - else if(ltString == "term") data.labelType = LabelType::Term; - else return Result<>::Error("Error in rule GML. Unknown labelType '" + ltString + "'."); - } - - auto &dpoResult = *data.rule; - auto &g = get_graph(dpoResult); - dpoResult.pString = std::make_unique(g); - auto &pString = *dpoResult.pString; - - struct VertexLabels { - bool inLeft = false, inContext = false, inRight = false; - Label string, stereo; - public: - Vertex v; - std::optional parsedEmbeddingLeft, parsedEmbeddingRight; - }; - - struct EdgeLabels { - bool inLeft = false, inContext = false, inRight = false; - Label string, stereo; - public: - Edge e; - }; - std::map idMapVertex; - std::map vertexMapId; - std::map, EdgeLabels> idMapEdge; - for(const GML::Vertex &vGML : rule.left.vertices) { - auto &v = idMapVertex[vGML.id]; - v.inLeft = true; - v.string.left = vGML.label; - v.stereo.left = vGML.stereo; - } - for(const GML::Vertex &vGML : rule.context.vertices) { - auto &v = idMapVertex[vGML.id]; - v.inContext = true; - v.string.context = vGML.label; - v.stereo.context = vGML.stereo; - } - for(const GML::Vertex &vGML : rule.right.vertices) { - auto &v = idMapVertex[vGML.id]; - v.inRight = true; - v.string.right = vGML.label; - v.stereo.right = vGML.stereo; - } - for(const GML::Edge &eGML : rule.left.edges) { - auto eSorted = std::minmax(eGML.source, eGML.target); - auto &e = idMapEdge[eSorted]; - e.inLeft = true; - e.string.left = eGML.label; - e.stereo.left = eGML.stereo; - } - for(const GML::Edge &eGML : rule.context.edges) { - auto eSorted = std::minmax(eGML.source, eGML.target); - auto &e = idMapEdge[eSorted]; - e.inContext = true; - e.string.context = eGML.label; - e.stereo.context = eGML.stereo; - } - for(const GML::Edge &eGML : rule.right.edges) { - auto eSorted = std::minmax(eGML.source, eGML.target); - auto &e = idMapEdge[eSorted]; - e.inRight = true; - e.string.right = eGML.label; - e.stereo.right = eGML.stereo; - } - - for(auto &p : idMapVertex) { - int id = p.first; - auto &vData = p.second; - // First find the right membership: - // inContext <=> inLeft && inRight - if(vData.inContext) vData.inLeft = vData.inRight = true; - else if(vData.inLeft && vData.inRight) vData.inContext = true; - - // Check labels and make (left, right) the correct labels - if(vData.string.context) { - if(vData.string.left) - return Result<>::Error( - "Error in rule GML. Vertex " + std::to_string(id) + " has a label both in 'context' and 'left'."); - if(vData.string.right) - return Result<>::Error( - "Error in rule GML. Vertex " + std::to_string(id) + " has a label both in 'context' and 'right'."); - // Note: terms follow the same semantics as string, i.e., the same string in L and R becomes the exact same terms. - vData.string.left = vData.string.right = vData.string.context; - } - if(vData.stereo.context) { - if(vData.stereo.left) - return Result<>::Error( - "Error in rule GML. Vertex " + std::to_string(id) + " has stereo both in 'context' and 'left'."); - if(vData.stereo.right) - return Result<>::Error( - "Error in rule GML. Vertex " + std::to_string(id) + " has stereo both in 'context' and 'right'."); - // for stereo it matters if it's L+R or it's K - } - - // Check that there is a string/stereo in left/right when inLeft/inRight - if(vData.inLeft && !vData.string.left) - return Result<>::Error("Error in rule GML. Vertex " + std::to_string(id) + " is in L, but has no label."); - if(vData.inRight && !vData.string.right) - return Result<>::Error("Error in rule GML. Vertex " + std::to_string(id) + " is in R, but has no label."); - - vData.v = add_vertex(g); - vertexMapId[vData.v] = id; - data.externalToInternalIds[id] = get(boost::vertex_index_t(), g, vData.v); - if(vData.inContext) { - put_membership(dpoResult, vData.v, lib::Rules::Membership::Context); - pString.add(vData.v, *vData.string.left, *vData.string.right); - } else if(vData.inLeft) { - assert(!vData.inRight); - put_membership(dpoResult, vData.v, lib::Rules::Membership::Left); - pString.add(vData.v, *vData.string.left, ""); - } else { - assert(vData.inRight); - put_membership(dpoResult, vData.v, lib::Rules::Membership::Right); - pString.add(vData.v, "", *vData.string.right); - } - } // for each vertex - - for(auto &p : idMapEdge) { - const int src = p.first.first; - const int tar = p.first.second; - if(idMapVertex.find(src) == end(idMapVertex)) - return Result<>::Error( - "Error in rule GML. Edge endpoint '" + std::to_string(src) + "' does not exist for edge (" - + std::to_string(src) + ", " + std::to_string(tar) + ")."); - if(idMapVertex.find(tar) == end(idMapVertex)) - return Result<>::Error( - "Error in rule GML. Edge endpoint '" + std::to_string(tar) + "' does not exist for edge (" - + std::to_string(src) + ", " + std::to_string(tar) + ")."); - Vertex vSrc = idMapVertex[src].v, vTar = idMapVertex[tar].v; - auto &eData = p.second; - // First find the right membership: - // inContext <=> inLeft && inRight - if(eData.inContext) eData.inLeft = eData.inRight = true; - else if(eData.inLeft && eData.inRight) eData.inContext = true; - - // Check labels and make (left, right) the correct labels - if(eData.string.context) { - if(eData.string.left) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") has a label both in 'context' and 'left'."); - if(eData.string.right) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") has a label both in 'context' and 'right'."); - eData.string.left = eData.string.right = eData.string.context; - } - if(eData.stereo.context) { - if(eData.stereo.left) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") has stereo both in 'context' and 'left'."); - if(eData.stereo.right) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") has stereo both in 'context' and 'right'."); - // for stereo it matters if it's L+R or it's K - } - - // Check that there is a string in left/right when inLeft/inRight - if(eData.inLeft && !eData.string.left) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") is in L, but has no label."); - if(eData.inRight && !eData.string.right) - return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + - ") is in R, but has no label."); - - eData.e = add_edge(vSrc, vTar, g).first; - if(eData.inContext) { - put_membership(dpoResult, eData.e, lib::Rules::Membership::Context); - pString.add(eData.e, *eData.string.left, *eData.string.right); - } else if(eData.inLeft) { - assert(!eData.inRight); - put_membership(dpoResult, eData.e, lib::Rules::Membership::Left); - pString.add(eData.e, *eData.string.left, ""); - } else { - assert(eData.inRight); - put_membership(dpoResult, eData.e, lib::Rules::Membership::Right); - pString.add(eData.e, "", *eData.string.right); - } - } // for each edge - // the graph is set, so initialise the component storage - dpoResult.initComponents(); - - // constraints - for(const GML::MatchConstraint &cGML : rule.matchConstraints) { - struct MatchConstraintConverter { - MatchConstraintConverter(lib::Rules::LabelledRule &dpoResult, const std::map &idMapVertex) - : dpoResult(dpoResult), idMapVertex(idMapVertex) {} - - Result<> operator()(const GML::AdjacencyConstraint &cGML) { - const auto iter = idMapVertex.find(cGML.id); - if(iter == end(idMapVertex)) - return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.id) + - " in adjacency constraint does not exist."); - const Vertex vConstrained = iter->second.v; - lib::GraphMorphism::Constraints::Operator op; - { - const auto &s = cGML.op; - using namespace lib::GraphMorphism::Constraints; - if(s == "<") op = Operator::LT; - else if(s == "<=") op = Operator::LEQ; - else if(s == "=") op = Operator::EQ; - else if(s == ">=") op = Operator::GEQ; - else if(s == ">") op = Operator::GT; - else return Result<>::Error("Error in rule GML. Unknown operator '" + s + "' in adjacency constraint."); - } - auto c = std::make_unique< - lib::GraphMorphism::Constraints::VertexAdjacency < lib::Rules::LabelledRule::LeftGraphType> - > (vConstrained, op, cGML.count); - c->vertexLabels.insert(cGML.nodeLabels.begin(), cGML.nodeLabels.end()); - c->edgeLabels.insert(cGML.edgeLabels.begin(), cGML.edgeLabels.end()); - dpoResult.leftMatchConstraints.push_back(std::move(c)); - return Result<>(); - } - - Result<> operator()(const GML::ShortestPathConstraint &cGML) { - const auto iterSrc = idMapVertex.find(cGML.source); - const auto iterTar = idMapVertex.find(cGML.target); - if(iterSrc == end(idMapVertex)) - return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.source) + - " in shortest path constraint does not exist."); - if(iterTar == end(idMapVertex)) - return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.target) + - " in shortest path constraint does not exist."); - const Vertex vSrc = iterSrc->second.v; - const Vertex vTar = iterTar->second.v; - lib::GraphMorphism::Constraints::Operator op; - { - const auto &s = cGML.op; - using namespace lib::GraphMorphism::Constraints; - if(s == "<") op = Operator::LT; - else if(s == "<=") op = Operator::LEQ; - else if(s == "=") op = Operator::EQ; - else if(s == ">=") op = Operator::GEQ; - else if(s == ">") op = Operator::GT; - else - return Result<>::Error( - "Error in rule GML. Unknown operator '" + s + "' in shortest path constraint."); - } - const auto compSrc = dpoResult.leftComponents[get(boost::vertex_index_t(), get_graph(dpoResult), vSrc)]; - const auto compTar = dpoResult.leftComponents[get(boost::vertex_index_t(), get_graph(dpoResult), vTar)]; - if(compSrc != compTar) - return Result<>::Error( - "Error in rule GML. Vertex " + std::to_string(cGML.source) + " and " + std::to_string(cGML.target) - + " are in different connected components of the left graph. " - + "This is currently not supported for the shortest path constraint."); - auto c = std::make_unique< - lib::GraphMorphism::Constraints::ShortestPath < lib::Rules::LabelledRule::LeftGraphType> - > (vSrc, vTar, op, cGML.length); - dpoResult.leftMatchConstraints.push_back(std::move(c)); - return Result<>(); - } - public: - lib::Rules::LabelledRule &dpoResult; - const std::map &idMapVertex; - } visitor(dpoResult, idMapVertex); - if(auto res = std::visit(visitor, cGML); !res) return res; - } // for each constriant - bool doStereo = false; - for(const auto &v : rule.left.vertices) doStereo = doStereo || v.stereo; - for(const auto &v : rule.context.vertices) doStereo = doStereo || v.stereo; - for(const auto &v : rule.right.vertices) doStereo = doStereo || v.stereo; - for(const auto &e : rule.left.edges) doStereo = doStereo || e.stereo; - for(const auto &e : rule.context.edges) doStereo = doStereo || e.stereo; - for(const auto &e : rule.right.edges) doStereo = doStereo || e.stereo; - if(!doStereo) return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available - - // Stereo - //========================================================================== - const auto vFromVertexId = [&idMapVertex](int id) { - auto iter = idMapVertex.find(id); - assert(iter != end(idMapVertex)); - return iter->second.v; - }; - lib::Rules::PropMoleculeCore mol(g, pString); // temporary for doing the inference - lib::Rules::DPOProjection gLeft(g, lib::Rules::Membership::Left), gRight(g, lib::Rules::Membership::Right); - auto molLeft = mol.getLeft(); - auto molRight = mol.getRight(); - auto leftInference = lib::Stereo::makeInference(gLeft, molLeft, true); - auto rightInference = lib::Stereo::makeInference(gRight, molRight, true); - const auto &gGeometry = lib::Stereo::getGeometryGraph(); - // Set the explicitly defined edge categories. - //---------------------------------------------------------------------------- - for(const auto &p : idMapEdge) { - const auto handleSide = [&p](const std::optional &os, const std::string &side, - auto &inference) -> Result<> { - if(!os) return Result<>(); - const std::string &s = *os; - if(s.size() != 1) - return Result<>::Error("Error in stereo data for edge (" + std::to_string(p.first.first) - + ", " + std::to_string(p.first.second) + ") in " + side - + ". Parsing error in stereo data '" + s + "'."); - lib::Stereo::EdgeCategory cat; - switch(s.front()) { - case '*': - cat = lib::Stereo::EdgeCategory::Any; - break; - default: - return Result<>::Error("Error in stereo data for edge (" + std::to_string(p.first.first) + ", " - + std::to_string(p.first.second) + ") in " + side - + ". Parsing error in stereo data '" + s + "'."); - } - auto res = inference.assignEdgeCategory(p.second.e, cat); - if(!res) { - res.setError("Error in stereo data for edge (" + std::to_string(p.first.first) + ", " - + std::to_string(p.first.second) + ") in " + side + ". " + res.extractError()); - return res; - } - return res; - }; - if(auto res = handleSide(p.second.stereo.left, "L", leftInference); !res) return res; - if(auto res = handleSide(p.second.stereo.right, "R", rightInference); !res) return res; - } // for each edge - // Set the explicitly stereo data. - //---------------------------------------------------------------------------- - for(auto &p : idMapVertex) { - const auto handleSide = [&](const std::optional &os, const std::string &side, auto &inference, - auto &parsedEmbedding, const auto &gSide) { - if(!os) return Result<>(); - const auto &v = p.second.v; - if(auto parsedEmbeddingRes = lib::IO::Stereo::Read::parseEmbedding(*os)) { - parsedEmbedding = std::move(*parsedEmbeddingRes); - } else { - return Result<>::Error( - "Error in stereo data for vertex " + std::to_string(p.first) + " in " + side + ". " + - parsedEmbeddingRes.extractError()); - } - // Geometry - //.......................................................................... - const auto &embGML = *parsedEmbedding; - if(embGML.geometry) { - auto vGeo = gGeometry.findGeometry(*embGML.geometry); - if(vGeo == gGeometry.nullGeometry()) - return Result<>::Error("Error in stereo data for vertex " + std::to_string(p.first) + " in " + side + - ". Invalid gGeometry '" + *embGML.geometry + "'."); - if(auto res = inference.assignGeometry(v, vGeo); !res) { - return Result<>::Error( - "Error in stereo data for vertex " + std::to_string(p.first) + " in " + side + ". " + - res.extractError()); - } - } - // Edges - //.......................................................................... - if(embGML.edges) { - inference.initEmbedding(v); - for(const auto &e : *embGML.edges) { - if(const int *idPtr = std::get_if(&e)) { - int idNeighbour = *idPtr; - if(idMapVertex.find(idNeighbour) == end(idMapVertex)) - return Result<>::Error("Error in graph GML. Neighbour vertex " + std::to_string(idNeighbour) + - " in stereo embedding for vertex " - + std::to_string(p.first) + " in " + side + " does not exist."); - auto ePair = edge(v, vFromVertexId(idNeighbour), gSide); - if(!ePair.second) - return Result<>::Error("Error in graph GML. Vertex " + std::to_string(idNeighbour) + - " in stereo embedding for vertex " - + std::to_string(p.first) + " in " + side + " is not a neighbour."); - inference.addEdge(v, ePair.first); - } else if(const char *virtPtr = std::get_if(&e)) { - switch(*virtPtr) { - case 'e': - inference.addLonePair(v); - break; - default: - MOD_ABORT; // the parser should know what is allowed - } - } else { - MOD_ABORT; // the parser should know what is allowed - } - } - } - // Fixation - //.......................................................................... - if(embGML.fixation) { - // TODO: expand this when more complicated geometries are implemented - const bool isFixed = *embGML.fixation; - if(isFixed) inference.fixSimpleGeometry(v); - } - return Result<>(); - }; - if(auto res = handleSide(p.second.stereo.left, "L", leftInference, p.second.parsedEmbeddingLeft, - gLeft); !res) - return res; - if(auto res = handleSide(p.second.stereo.right, "R", rightInference, p.second.parsedEmbeddingRight, - gRight); !res) - return res; - } // for each vertex - - const auto finalize = [&warnings, &vertexMapId](auto &inference, const std::string &side) { - return inference.finalize(warnings, [&vertexMapId, &side](Vertex v) { - auto iter = vertexMapId.find(v); - assert(iter != vertexMapId.end()); - return std::to_string(iter->second) + " in " + side; - }); - }; - if(auto resLeft = finalize(leftInference, "L"); !resLeft) return resLeft; - if(auto resRight = finalize(rightInference, "R"); !resRight) return resRight; - - const auto vertexInContext = [&](Vertex v) -> bool { - const auto idIter = vertexMapId.find(v); - assert(idIter != end(vertexMapId)); - const auto lIter = idMapVertex.find(idIter->second); - assert(lIter != end(idMapVertex)); - assert(lIter->second.inContext); - const auto &stereo = lIter->second.stereo; - // if there is any stereo data, maybe we are in context - if(stereo.left.has_value() || stereo.context.has_value() || stereo.right.has_value()) - return stereo.context.has_value(); - else // otherwise, default to be in context - return true; - }; - const auto edgeInContext = [&](Edge e) -> bool { - const auto vSrc = source(e, g); - const auto vTar = target(e, g); - const auto idSrcIter = vertexMapId.find(vSrc); - const auto idTarIter = vertexMapId.find(vTar); - assert(idSrcIter != end(vertexMapId)); - assert(idTarIter != end(vertexMapId)); - const auto lIter = idMapEdge.find(std::make_pair(idSrcIter->second, idTarIter->second)); - assert(lIter != end(idMapEdge)); - assert(lIter->second.inContext); - const auto &stereo = lIter->second.stereo; - // if there is any stereo data, maybe we are in context - if(stereo.left.has_value() || stereo.context.has_value() || stereo.right.has_value()) - return stereo.context.has_value(); - else // otherwise, default to be in context - return true; - }; - dpoResult.pStereo = std::make_unique(g, std::move(leftInference), - std::move(rightInference), vertexInContext, - edgeInContext); - return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available -} - -} // namespace mod::lib::IO::Rules::Read \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/RuleWrite.cpp b/libs/libmod/src/mod/lib/IO/RuleWrite.cpp deleted file mode 100644 index 0d51eb1..0000000 --- a/libs/libmod/src/mod/lib/IO/RuleWrite.cpp +++ /dev/null @@ -1,832 +0,0 @@ -#include "Rule.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace mod::lib::IO::Rules::Write { -namespace { - -// returns the filename _without_ extension - -const std::string &getFilePrefix(const lib::Rules::Real &r) { - static std::map cache; - auto iter = cache.find(r.getId()); - if(iter == end(cache)) { - std::string prefix = IO::getUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()); - return cache[r.getId()] = prefix; - } else return iter->second; -} - -void gmlSide(const lib::Rules::Real &r, std::ostream &s, lib::Rules::Membership printMembership, bool withCoords) { - if(withCoords) { - const auto &depict = r.getDepictionData(); - if(!depict.getHasCoordinates()) MOD_ABORT; - } - using Vertex = lib::Rules::Vertex; - using Edge = lib::Rules::Edge; - const lib::Rules::GraphType &core = r.getGraph(); - const lib::Rules::PropStringCore &labelState = r.getStringState(); - - for(Vertex v : asRange(vertices(core))) { - auto vMembership = core[v].membership; - if(printMembership == lib::Rules::Membership::Context) { - if(vMembership != lib::Rules::Membership::Context) continue; - if(labelState.isChanged(v)) continue; - } else { - if(vMembership == lib::Rules::Membership::Context) { - if(!labelState.isChanged(v)) continue; - } else { - if(printMembership != vMembership) continue; - } - } - s << "\t\tnode [ id " << get(boost::vertex_index_t(), core, v) << " label \""; - switch(printMembership) { - case lib::Rules::Membership::Left: - s << labelState.getLeft()[v]; - break; - case lib::Rules::Membership::Context: - s << labelState.getLeft()[v]; - break; - case lib::Rules::Membership::Right: - s << labelState.getRight()[v]; - break; - } - s << "\""; - if(withCoords) { - const auto &depict = r.getDepictionData(); - s << " vis2d [ x " << depict.getX(v, true) << " y " << depict.getY(v, true) << " ]"; - } - s << " ]\n"; - } - - for(Edge e : asRange(edges(core))) { - auto eMembership = core[e].membership; - if(printMembership == lib::Rules::Membership::Context) { - if(eMembership != lib::Rules::Membership::Context) continue; - if(labelState.isChanged(e)) continue; - } else { - if(eMembership == lib::Rules::Membership::Context) { - if(!labelState.isChanged(e)) continue; - } else { - if(printMembership != eMembership) continue; - } - } - s << "\t\tedge [ source " << get(boost::vertex_index_t(), core, source(e, core)) - << " target " << get(boost::vertex_index_t(), core, target(e, core)) - << " label \""; - switch(printMembership) { - case lib::Rules::Membership::Left: - s << labelState.getLeft()[e]; - break; - case lib::Rules::Membership::Context: - s << labelState.getLeft()[e]; - break; - case lib::Rules::Membership::Right: - s << labelState.getRight()[e]; - break; - } - s << "\" ]\n"; - } -} - -void printEdgeStyle(std::ostream &s, lib::Rules::Membership eSide, int src, int tar) { - s << "\t" << src << " -- " << tar << " [ "; - switch(eSide) { - case lib::Rules::Membership::Left: - s << "style=dashed "; - break; - case lib::Rules::Membership::Right: - s << "style=dotted "; - break; - default: - break; - } -} - -} // namespace - -void gml(const lib::Rules::Real &r, bool withCoords, std::ostream &s) { - s << "rule [" << std::endl; - s << "\truleID \"" << r.getName() << "\"\n"; - if(r.getLabelType()) { - s << "\tlabelType \""; - switch(*r.getLabelType()) { - case LabelType::String: - s << "string"; - break; - case LabelType::Term: - s << "term"; - break; - } - s << "\"\n"; - } - { - std::stringstream str; - gmlSide(r, str, lib::Rules::Membership::Left, withCoords); - if(str.str().size() > 0) s << "\tleft [\n" << str.str() << "\t]\n"; - } - { - std::stringstream str; - gmlSide(r, str, lib::Rules::Membership::Context, withCoords); - if(str.str().size() > 0) s << "\tcontext [\n" << str.str() << "\t]\n"; - } - { - std::stringstream str; - gmlSide(r, str, lib::Rules::Membership::Right, withCoords); - if(str.str().size() > 0) s << "\tright [\n" << str.str() << "\t]\n"; - } - auto printer = lib::IO::MatchConstraint::Write::makeGMLPrintVisitor(s, get_left(r.getDPORule()), "\t"); - for(const auto &c : r.getDPORule().leftMatchConstraints) { - c->accept(printer); - } - s << "]"; -} - -std::string gml(const lib::Rules::Real &r, bool withCoords) { - post::FileHandle s(getFilePrefix(r) + ".gml"); - gml(r, withCoords, s); - return s; -} - -std::string dotCombined(const lib::Rules::Real &r) { - std::stringstream fileName; - fileName << "r_" << r.getId() << "_combined.dot"; - post::FileHandle s(getUniqueFilePrefix() + fileName.str()); - std::string fileNoExt = s; - fileNoExt.erase(fileNoExt.end() - 4, fileNoExt.end()); - using Vertex = lib::Rules::Vertex; - using Edge = lib::Rules::Edge; - const lib::Rules::GraphType &g = r.getGraph(); - const lib::Rules::PropStringCore &labelState = r.getStringState(); - s << "graph G {" << std::endl; - for(Vertex v : asRange(vertices(g))) { - auto membership = g[v].membership; - s << "\t" << get(boost::vertex_index_t(), g, v) << " [ label=\""; - switch(membership) { - case lib::Rules::Membership::Left: - s << labelState.getLeft()[v]; - break; - case lib::Rules::Membership::Context: - s << labelState.getLeft()[v] << " | " << labelState.getRight()[v]; - break; - case lib::Rules::Membership::Right: - s << labelState.getRight()[v]; - break; - } - s << "\""; - switch(membership) { - case lib::Rules::Membership::Left: - s << " style=dashed"; - break; - case lib::Rules::Membership::Right: - s << " style=dotted"; - break; - default: - break; - } - s << " ]" << std::endl; - } - - for(Edge e : asRange(edges(g))) { - auto membership = g[e].membership; - auto vSrcId = get(boost::vertex_index_t(), g, source(e, g)); - auto vTarId = get(boost::vertex_index_t(), g, target(e, g)); - std::string label; - switch(membership) { - case lib::Rules::Membership::Left: - label = labelState.getLeft()[e]; - break; - case lib::Rules::Membership::Context: - label = labelState.getLeft()[e] + " | " + labelState.getRight()[e]; - break; - case lib::Rules::Membership::Right: - label = labelState.getRight()[e]; - break; - } - switch(label[0]) { - // case '=': // fall through to make two edges - // // assert(false); - // printEdgeStyle(s, membership, vSrcId, vTarId); - // s << "]" << std::endl; - // case '-': // print the rest of the label - // printEdgeStyle(s, membership, vSrcId, vTarId); - // s << "label=\"" << (label.c_str() + 1) << "\" ]" << std::endl; - // break; - default: - printEdgeStyle(s, membership, vSrcId, vTarId); - s << "label=\"" << label << "\" ]" << std::endl; - break; - } - } - s << "}" << std::endl; - return fileNoExt; -} - -std::string svgCombined(const lib::Rules::Real &r) { - std::string fileNoExt = dotCombined(r); - IO::post() << "gv ruleCombined \"" << fileNoExt << "\" svg" << std::endl; - return fileNoExt; -} - -std::string pdfCombined(const lib::Rules::Real &r) { - std::string fileNoExt = svgCombined(r); - IO::post() << "svgToPdf \"" << fileNoExt << "\"" << std::endl; - return fileNoExt; -} - -std::string dot(const lib::Rules::Real &r) { - static std::set cache; - std::string fileNoExt = getFilePrefix(r); - auto iter = cache.find(r.getId()); - if(iter != end(cache)) return fileNoExt; - - using Vertex = lib::Rules::Vertex; - using Edge = lib::Rules::Edge; - const lib::Rules::GraphType &g = r.getGraph(); - const lib::Rules::PropStringCore &labelState = r.getStringState(); - - post::FileHandle s(fileNoExt + ".dot"); - s << "graph g {" << std::endl; - s << getConfig().io.dotCoordOptions.get() << std::endl; - for(Vertex v : asRange(vertices(g))) { - unsigned int vId = get(boost::vertex_index_t(), g, v); - s << vId << " [ label=\""; - auto membership = g[v].membership; - switch(membership) { - case lib::Rules::Membership::Left: - s << labelState.getLeft()[v]; - break; - case lib::Rules::Membership::Context: - s << labelState.getLeft()[v] << " | " << labelState.getRight()[v]; - break; - case lib::Rules::Membership::Right: - s << labelState.getRight()[v]; - break; - } - s << "\" ];" << std::endl; - } - for(Edge e : asRange(edges(g))) { - unsigned int vSrcId = get(boost::vertex_index_t(), g, source(e, g)); - unsigned int vTarId = get(boost::vertex_index_t(), g, target(e, g)); - s << vSrcId << " -- " << vTarId << " [ label=\""; - auto membership = g[e].membership; - switch(membership) { - case lib::Rules::Membership::Left: - s << labelState.getLeft()[e]; - break; - case lib::Rules::Membership::Context: - s << labelState.getLeft()[e] << " | " << labelState.getRight()[e]; - break; - case lib::Rules::Membership::Right: - s << labelState.getRight()[e]; - break; - } - s << "\" ];" << std::endl; - } - s << "}" << std::endl; - return fileNoExt; -} - -struct CoordsCacheEntry { - std::size_t id; - bool collapseHydrogens; - int rotation; - bool mirror; -public: - friend bool operator<(const CoordsCacheEntry &a, const CoordsCacheEntry &b) { - return std::tie(a.id, a.collapseHydrogens, a.rotation, a.mirror) - < std::tie(b.id, b.collapseHydrogens, b.rotation, b.mirror); - } -}; - -namespace { - -bool disallowVertexCollapse(const lib::Rules::Real &r, const CoreVertex v, - std::function disallowCollapse) { - if(getConfig().rule.collapseChangedHydrogens.get()) - return disallowCollapse(v); - if(r.getGraph()[v].membership != lib::Rules::Membership::Context) - return true; - for(const auto e : asRange(out_edges(v, r.getGraph()))) { - if(r.getGraph()[e].membership != lib::Rules::Membership::Context) - return true; - } - return disallowCollapse(v); -} - -} // namespace - -std::string coords(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - std::function disallowCollapse_) { - static std::map cache; - const auto iter = cache.find({r.getId(), options.collapseHydrogens, options.rotation, options.mirror}); - if(iter != end(cache)) return iter->second; - std::string fileNoExt = getFilePrefix(r); - const auto &depict = r.getDepictionData(); - if(!depict.getHasCoordinates()) { - if(idOffset != 0) { - std::cout << "Blame the lazy programmer. Offset " << idOffset << " not yet supported in dot coords." - << std::endl; - MOD_ABORT; - } - dot(r); - IO::post() << "coordsFromGV rule \"" << fileNoExt << "\" noOverlay" << std::endl; - fileNoExt = fileNoExt + "_coord"; - cache[{r.getId(), true, options.rotation, options.mirror} - ] = fileNoExt; - cache[{r.getId(), false, options.rotation, options.mirror} - ] = fileNoExt; - return fileNoExt; - } else { - const auto &g = r.getGraph(); - const std::string molString = options.collapseHydrogens ? "_mol" : ""; - const std::string fileNoExt = getFilePrefix(r) + molString + "_coord"; - post::FileHandle s(fileNoExt + ".tex"); - s << "% dummy\n"; - const bool useCollapsedCoords = [&]() { - if(!options.collapseHydrogens) return false; - for(const auto v : asRange(vertices(g))) { - if(disallowCollapse_(v)) return false; - } - return true; - }(); - - for(const auto v : asRange(vertices(g))) { - const auto vId = get(boost::vertex_index_t(), g, v); - const bool printCoord = [&]() { - if(!useCollapsedCoords) return true; - if(disallowVertexCollapse(r, v, disallowCollapse_)) return true; - return !Chem::isCollapsible(v, g, depict, depict, [&depict](const auto v) { - return depict.hasImportantStereo(v); - }); - }(); - if(!printCoord) continue; - double x, y; - std::tie(x, y) = pointTransform( - depict.getX(v, !useCollapsedCoords), - depict.getY(v, !useCollapsedCoords), - options.rotation, options.mirror); - s << "\\coordinate[overlay] (v-coord-" << (vId + idOffset) << ") at (" << std::fixed << x << ", " << y - << ") {};" << std::endl; - } - if(options.collapseHydrogens && !useCollapsedCoords) { - // don't cache these as the user predicate influences it - } else { - cache[{r.getId(), options.collapseHydrogens, options.rotation, options.mirror} - ] = fileNoExt; - } - return fileNoExt; - } -} - -namespace { - -template -struct AdvOptions { - AdvOptions(const lib::Rules::Real &r, unsigned int idOffset, const BaseArgs &args, - std::function disallowCollapse) - : idOffset(idOffset), changeColour(changeColourFromMembership()), r(r), args(args), - disallowCollapse_(disallowCollapse) {} -private: - static std::string changeColourFromMembership() { - std::string side = []() { - switch(membership) { - case lib::Rules::Membership::Left: - return getConfig().rule.changeColourL.get(); - case lib::Rules::Membership::Context: - return getConfig().rule.changeColourK.get(); - case lib::Rules::Membership::Right: - return getConfig().rule.changeColourR.get(); - } - }(); - if(side.empty()) return getConfig().rule.changeColour.get(); - else return side; - } - -public: - std::string getColour(CoreVertex v) const { - bool isChanged = r.getGraph()[v].membership != lib::Rules::Membership::Context - || r.getStringState().isChanged(v); - if(isChanged) { - return changeColour; - } else return args.vColour(v); - } - - std::string getColour(CoreEdge e) const { - bool isChanged = r.getGraph()[e].membership != lib::Rules::Membership::Context - || r.getStringState().isChanged(e); - if(isChanged) { - return changeColour; - } else return args.eColour(e); - } - - bool isVisible(CoreVertex v) const { - return args.visible(v); - } - - std::string getShownId(CoreVertex v) const { - return boost::lexical_cast(get(boost::vertex_index_t(), r.getGraph(), v)); - } - - bool overwriteWithIndex(CoreVertex) const { - return false; - } - - lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(CoreEdge e, bool withHydrogen) const { - const auto get = [&](const auto &depict) { - return depict.getEdgeFake3DType(e, withHydrogen); - }; - const auto &depict = r.getDepictionData(); - switch(membership) { - case lib::Rules::Membership::Left: - return get(depict.getLeft()); - case lib::Rules::Membership::Context: - return get(depict.getContext()); - case lib::Rules::Membership::Right: - return get(depict.getRight()); - } - } - - std::string getOpts(CoreVertex v) const { - return std::string(); - } - -private: - - template - std::string getStereoStringVertex(const CoreVertex v, const F f) const { - const auto apply = [&](const auto &lg) { - const auto &conf = *get_stereo(lg)[v]; - const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { - const auto &g = get_graph(lg); - return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); - }; - std::string res = f(conf, getNeighbourId); - if(!get_stereo(r.getDPORule()).inContext(v)) { - res = "{\\color{" + getConfig().rule.changeColourL.get() + "}" + res + "}"; - } - return res; - }; - - const auto &lr = r.getDPORule(); - switch(membership) { - case lib::Rules::Membership::Left: - return apply(get_labelled_left(lr)); - case lib::Rules::Membership::Right: - return apply(get_labelled_right(lr)); - case lib::Rules::Membership::Context: - if(get_stereo(lr).inContext(v)) { - const auto &lg = get_labelled_left(r.getDPORule()); - const auto &conf = *get_stereo(lg)[v]; - const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { - const auto &g = get_graph(lg); - return get(boost::vertex_index_t(), g, target(emb.getEdge(v, g), g)); - }; - return f(conf, getNeighbourId); - } else return ""; - } - } - -public: - - std::string getRawStereoString(CoreVertex v) const { - return getStereoStringVertex(v, [&](const auto &conf, auto getNId) { - return conf.asRawString(getNId); - }); - } - - std::string getPrettyStereoString(CoreVertex v) const { - return getStereoStringVertex(v, [&](const auto &conf, auto getNId) { - return conf.asPrettyString(getNId); - }); - } - - std::string getStereoString(CoreEdge e) const { - const auto apply = [&](const auto &lg) { - const auto cat = get_stereo(lg)[e]; - std::string res = boost::lexical_cast(cat); - if(!get_stereo(r.getDPORule()).inContext(e)) { - res = "{\\color{" + getConfig().rule.changeColourL.get() + "}" + res + "}"; - } - return res; - }; - - const auto &lr = r.getDPORule(); - switch(membership) { - case lib::Rules::Membership::Left: - return apply(get_labelled_left(lr)); - case lib::Rules::Membership::Right: - return apply(get_labelled_right(lr)); - case lib::Rules::Membership::Context: - if(get_stereo(lr).inContext(e)) { - const auto &lg = get_labelled_left(r.getDPORule()); - const auto cat = get_stereo(lg)[e]; - return boost::lexical_cast(cat); - } else return ""; - } - } - - std::string getEdgeAnnotation(CoreEdge) const { - return ""; - } - - bool disallowCollapse(CoreVertex v) const { - return disallowVertexCollapse(r, v, disallowCollapse_); - } - -public: - const unsigned int idOffset; -private: - const std::string changeColour; - const lib::Rules::Real &r; - const BaseArgs &args; - std::function disallowCollapse_; -}; - -} // namespace - -std::pair -tikz(const std::string &fileCoordsNoExt, const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, const BaseArgs &args, - std::function disallowCollapse) { - std::string strOptions = options.getStringEncoding(); - std::string fileNoExt = IO::getUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()); - fileNoExt += "_" + strOptions; - - std::string fileCoords = fileCoordsNoExt + ".tex"; - { // left - post::FileHandle s(fileNoExt + "_" + suffixL + ".tex"); - const auto &g = get_left(r.getDPORule()); - const auto &depict = r.getDepictionData().getLeft(); - const auto adv = AdvOptions(r, idOffset, args, disallowCollapse); - IO::Graph::Write::tikz(s, options, g, depict, fileCoords, adv, jla_boost::Nop<>(), ""); - } - { // context - post::FileHandle s(fileNoExt + "_" + suffixK + ".tex"); - const auto &g = get_context(r.getDPORule()); - const auto &depict = r.getDepictionData().getContext(); - - struct EdgeVisible { - EdgeVisible() = default; - - EdgeVisible(const lib::Rules::Real &r) : r(&r) {} - - bool operator()(const CoreEdge e) const { - if(getConfig().rule.printChangedEdgesInContext.get()) return true; - return !r->getStringState().isChanged(e); - } - - private: - const lib::Rules::Real *r = nullptr; - }; - boost::filtered_graph gFiltered(g, EdgeVisible(r)); - const auto adv = AdvOptions(r, idOffset, args, disallowCollapse); - IO::Graph::Write::tikz(s, options, gFiltered, depict, fileCoords, adv, jla_boost::Nop<>(), ""); - } - { // right - post::FileHandle s(fileNoExt + "_" + suffixR + ".tex"); - const auto &g = get_right(r.getDPORule()); - const auto &depict = r.getDepictionData().getRight(); - const auto adv = AdvOptions(r, idOffset, args, disallowCollapse); - IO::Graph::Write::tikz(s, options, g, depict, fileCoords, adv, jla_boost::Nop<>(), ""); - } - return std::make_pair(fileNoExt, fileCoordsNoExt); -} - -std::pair tikz(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffixL, const std::string &suffixK, - const std::string &suffixR, const BaseArgs &args, - std::function disallowCollapse) { - std::string fileCoordsNoExt = coords(r, idOffset, options, disallowCollapse); - return tikz(fileCoordsNoExt, r, idOffset, options, suffixL, suffixK, suffixR, args, disallowCollapse); -} - -std::string pdf(const lib::Rules::Real &r, const Options &options, - const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, - const BaseArgs &args) { - std::string fileNoExt, fileCoordsNoExt; - const unsigned int idOffset = 0; - std::tie(fileNoExt, fileCoordsNoExt) = tikz(r, idOffset, options, suffixL, suffixK, suffixR, args, - jla_boost::AlwaysFalse()); - IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixL << "\" \"" << fileCoordsNoExt << "\"\n"; - IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixK << "\" \"" << fileCoordsNoExt << "\"\n"; - IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixR << "\" \"" << fileCoordsNoExt << "\"\n"; - IO::post().flush(); - return fileNoExt; -} - -std::pair -tikzTransitionState(const std::string &fileCoordsNoExt, const lib::Rules::Real &r, unsigned int idOffset, - const Options &options, - const std::string &suffix, const BaseArgs &args) { - MOD_ABORT; -} - -std::pair -tikzTransitionState(const lib::Rules::Real &r, unsigned int idOffset, const Options &options, - const std::string &suffix, const BaseArgs &args) { - MOD_ABORT; -} - -std::string pdfTransitionState(const lib::Rules::Real &r, const Options &options, - const std::string &suffix, const BaseArgs &args) { - std::string fileNoExt, fileCoordsNoExt; - const unsigned int idOffset = 0; - std::tie(fileNoExt, fileCoordsNoExt) = tikzTransitionState(r, idOffset, options, suffix, args); - IO::post() << "compileTikz \"" << fileNoExt << "_" << suffix << "\" \"" << fileCoordsNoExt << "\"" << std::endl; - return fileNoExt; -} - -std::string pdfCombined(const lib::Rules::Real &r, const Options &options) { - MOD_ABORT; -} - -std::pair summary(const lib::Rules::Real &r, bool printCombined) { - graph::Printer first; - graph::Printer second; - second.setReactionDefault(); - return summary(r, first.getOptions(), second.getOptions(), printCombined); -} - -std::pair -summary(const lib::Rules::Real &r, const Options &first, const Options &second, bool printCombined) { - auto visible = jla_boost::AlwaysTrue(); - auto vColour = jla_boost::Nop(); - auto eColour = jla_boost::Nop(); - const BaseArgs args{visible, vColour, eColour}; - std::string graphLike = pdf(r, first, "L", "K", "R", args); - std::string molLike = first == second ? "" : pdf(r, second, "L", "K", "R", args); - std::string combined = printCombined - ? pdfCombined(r /*, Options().EdgesAsBonds().RaiseCharges()*/) - : ""; - std::string constraints = - getUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()) + "_constraints.tex"; - { - post::FileHandle s(constraints); - auto printer = lib::IO::MatchConstraint::Write::makeTexPrintVisitor(s, get_left(r.getDPORule())); - for(const auto &c : r.getDPORule().leftMatchConstraints) { - c->accept(printer); - s << "\n"; - } - } - IO::post() << "summaryRule \"" << r.getName() << "\" \"" << graphLike << "\" \"" << molLike << "\" \"" << combined - << "\" \"" << constraints << "\"" << std::endl; - if(molLike.empty()) - return std::make_pair(graphLike, graphLike); - else - return std::make_pair(graphLike, molLike); -} - -void termState(const lib::Rules::Real &r) { - using Vertex = lib::Rules::Vertex; - using Edge = lib::Rules::Edge; - using Membership = lib::Rules::Membership; - using namespace lib::Term; - IO::post() << "summarySubsection \"Term State for " << r.getName() << "\"" << std::endl; - post::FileHandle s(getUniqueFilePrefix() + "termState.tex"); - s << "\\begin{verbatim}\n"; - const auto &termState = r.getTermState(); - if(isValid(termState)) { - std::unordered_map > > addrToVertex; - std::unordered_map > > addrToEdge; - std::unordered_map > addrToConstraintInfo; - auto insertVertex = [&addrToVertex](std::size_t addr, Vertex v, Membership membership) { - Address a{AddressType::Heap, addr}; - addrToVertex[a].insert({v, membership}); - }; - auto insertEdge = [&addrToEdge](std::size_t addr, Edge e, Membership membership) { - Address a{AddressType::Heap, addr}; - addrToEdge[a].insert({e, membership}); - }; - for(Vertex v : asRange(vertices(r.getGraph()))) { - switch(r.getGraph()[v].membership) { - case Membership::Left: - insertVertex(termState.getLeft()[v], v, Membership::Left); - break; - case Membership::Right: - insertVertex(termState.getRight()[v], v, Membership::Right); - break; - case Membership::Context: - insertVertex(termState.getLeft()[v], v, Membership::Left); - insertVertex(termState.getRight()[v], v, Membership::Right); - break; - } - } - for(Edge e : asRange(edges(r.getGraph()))) { - switch(r.getGraph()[e].membership) { - case Membership::Left: - insertEdge(termState.getLeft()[e], e, Membership::Left); - break; - case Membership::Right: - insertEdge(termState.getRight()[e], e, Membership::Right); - break; - case Membership::Context: - insertEdge(termState.getLeft()[e], e, Membership::Left); - insertEdge(termState.getRight()[e], e, Membership::Right); - break; - } - } - - struct Visitor : lib::GraphMorphism::Constraints::AllVisitor { - Visitor(std::unordered_map > &addrMap, const lib::Rules::GraphType &g) - : addrMap(addrMap), g(g) {} - - virtual void - operator()(const lib::GraphMorphism::Constraints::VertexAdjacency &c) override { - const auto vStr = boost::lexical_cast(get(boost::vertex_index_t(), g, c.vConstrained)); - for(const auto a : c.vertexTerms) { - Address addr{AddressType::Heap, a}; - std::string msg = "VertexAdj(" + vStr + ", " + side + ", V)"; - addrMap[addr].insert(std::move(msg)); - } - for(const auto a : c.edgeTerms) { - Address addr{AddressType::Heap, a}; - std::string msg = "VertexAdj(" + vStr + ", " + side + ", E)"; - addrMap[addr].insert(std::move(msg)); - } - } - - virtual void - operator()(const lib::GraphMorphism::Constraints::ShortestPath &c) override {} - public: - std::unordered_map > &addrMap; - const lib::Rules::GraphType &g; - std::string side; - }; - Visitor vis(addrToConstraintInfo, r.getGraph()); - vis.side = "L"; - for(const auto &c : r.getDPORule().leftMatchConstraints) - c->accept(vis); - vis.side = "R"; - for(const auto &c : r.getDPORule().rightMatchConstraints) - c->accept(vis); - - lib::IO::Term::Write::wam(getMachine(termState), lib::Term::getStrings(), s, [&](Address addr, std::ostream &s) { - s << " "; - bool first = true; - for(auto vm : addrToVertex[addr]) { - if(!first) s << ", "; - first = false; - s << "v(" << get(boost::vertex_index_t(), r.getGraph(), vm.first) << ", "; - switch(vm.second) { - case Membership::Left: - s << "L"; - break; - case Membership::Right: - s << "R"; - break; - case Membership::Context: - s << "K"; - break; - } - s << ")"; - } - for(auto em : addrToEdge[addr]) { - if(!first) s << ", "; - first = false; - s << "e(" - << get(boost::vertex_index_t(), r.getGraph(), source(em.first, r.getGraph())) - << ", " - << get(boost::vertex_index_t(), r.getGraph(), target(em.first, r.getGraph())) - << ", "; - switch(em.second) { - case Membership::Left: - s << "L"; - break; - case Membership::Right: - s << "R"; - break; - case Membership::Context: - s << "K"; - break; - } - s << ")"; - } - for(auto &msg : addrToConstraintInfo[addr]) { - if(!first) s << ", "; - first = false; - s << msg; - } - }); - } else { - std::string msg = "Parsing failed for rule '" + r.getName() + "'. " + termState.getParsingError(); - throw TermParsingError(std::move(msg)); - } - s << "\\end{verbatim}\n"; - IO::post() << "summaryInput \"" << std::string(s) << "\"" << std::endl; -} - -} // namespace mod::lib::IO::Rules::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Stereo.hpp b/libs/libmod/src/mod/lib/IO/Stereo.hpp deleted file mode 100644 index 6fb2433..0000000 --- a/libs/libmod/src/mod/lib/IO/Stereo.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef MOD_LIB_IO_STEREO_HPP -#define MOD_LIB_IO_STEREO_HPP - -#include -#include - -#include -#include -#include - -namespace mod::lib::Rules { -struct PropStereoCore; -} // namespace mod::lib::Rules -namespace mod::lib::Stereo { -struct Configuration; -struct GeometryGraph; -} // namespace mod::lib::Stereo -namespace mod::lib::IO::Stereo::Read { -using ParsedEmbeddingEdge = std::variant; - -struct ParsedEmbedding { - std::optional geometry; - std::optional > edges; - std::optional fixation; -}; - -lib::IO::Result parseEmbedding(const std::string &str); - -} // namesapce mod::lib::IO::Stereo::Read -namespace mod::lib::IO::Stereo::Write { - -std::string summary(const lib::Graph::Single &g, lib::Graph::Vertex v, const lib::Stereo::Configuration &conf, - const IO::Graph::Write::Options &options, int shownIdOffset, const std::string &nameSuffix); -std::string summary(const lib::Rules::Real &r, lib::Rules::Vertex v, lib::Rules::Membership m, - const IO::Graph::Write::Options &options); - -// old/new delimiter - -void summary(const lib::Stereo::GeometryGraph &g); - -} // namesapce mod::lib::IO::Stereo::Write - -#endif // MOD_LIB_IO_STEREO_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/StereoWrite.cpp b/libs/libmod/src/mod/lib/IO/StereoWrite.cpp deleted file mode 100644 index 3739d83..0000000 --- a/libs/libmod/src/mod/lib/IO/StereoWrite.cpp +++ /dev/null @@ -1,581 +0,0 @@ -#include "Stereo.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -namespace mod::lib::Stereo { -namespace { - -#define MOD_anyPrintCoords() \ - Configuration::printCoords(s, vIds); \ - if(vIds.size() == 1) return; \ - auto centerId = vIds.back(); \ - double angle = 360.0 / (vIds.size() - 1); \ - for(std::size_t i = 0; i < vIds.size() - 1; i++) \ - printSateliteCoord(s, centerId, angle * i + 90, vIds[i]); - -void printSateliteCoord(std::ostream &s, std::size_t centerId, double angle, std::size_t id) { - s << "\\coordinate[overlay, at=($(v-coord-" << centerId << ")+(" << angle << ":1)"; - s << "$)] (v-coord-" << id << ") {};" << std::endl; -} - -} // namespace - -// Configuration -//------------------------------------------------------------------------------ - -IO::Graph::Write::EdgeFake3DType Configuration::getEdgeDepiction(std::size_t i) const { - return IO::Graph::Write::EdgeFake3DType::None; -} - -void Configuration::printCoords(std::ostream &s, const std::vector &vIds) const { - const std::string &name = getGeometryGraph().getGraph()[getGeometryVertex()].name; - s << "\\coordinate[overlay] (v-coord-" << vIds.back() << ") {};\n"; - s << "\\node[at=($(v-coord-" << vIds.back() << ")+(-90:1)+(-90:12pt)$)] {" << name << "};\n"; -} - -std::string Configuration::getEdgeAnnotation(std::size_t i) const { - return ""; -} - -// Any -//------------------------------------------------------------------------------ - -void Any::printCoords(std::ostream &s, const std::vector &vIds) const { - MOD_anyPrintCoords(); -} - -// Linear -//------------------------------------------------------------------------------ - -void Linear::printCoords(std::ostream &s, const std::vector &vIds) const { - Configuration::printCoords(s, vIds); - printSateliteCoord(s, vIds.back(), 180, vIds[0]); - printSateliteCoord(s, vIds.back(), 0, vIds[1]); -} - -// TrigonalPlanar -//------------------------------------------------------------------------------ - -void TrigonalPlanar::printCoords(std::ostream &s, const std::vector &vIds) const { - Configuration::printCoords(s, vIds); - printSateliteCoord(s, vIds.back(), 180, vIds[0]); - printSateliteCoord(s, vIds.back(), 300, vIds[1]); - printSateliteCoord(s, vIds.back(), 60, vIds[2]); -} - -std::string TrigonalPlanar::getEdgeAnnotation(std::size_t i) const { - if(fixed) return ""; - else return "?"; -} - -// Tetrahedral -//------------------------------------------------------------------------------ - -IO::Graph::Write::EdgeFake3DType Tetrahedral::getEdgeDepiction(std::size_t i) const { - switch(i) { - case 0: - case 1: - return lib::IO::Graph::Write::EdgeFake3DType::None; - case 2: - return lib::IO::Graph::Write::EdgeFake3DType::WedgeSL; - case 3: - return lib::IO::Graph::Write::EdgeFake3DType::HashSL; - default: - MOD_ABORT; - } -} - -void Tetrahedral::printCoords(std::ostream &s, const std::vector &vIds) const { - Configuration::printCoords(s, vIds); - printSateliteCoord(s, vIds.back(), 90, vIds[0]); - printSateliteCoord(s, vIds.back(), 210, vIds[1]); - printSateliteCoord(s, vIds.back(), -60, vIds[2]); - printSateliteCoord(s, vIds.back(), -10, vIds[3]); -} - -std::string Tetrahedral::getEdgeAnnotation(std::size_t i) const { - if(fixed) return ""; - else return "?"; -} - -} // namespace mod::lib::Stereo -namespace mod::lib::IO::Stereo::Read { -} // namesapce mod::lib::IO::Stereo::Read -namespace mod::lib::IO::Stereo::Write { -namespace { - -template -std::string coords(const GraphInner &gStereo, const lib::Stereo::Configuration &conf, const std::string &name, - std::map::vertex_descriptor, int> &vMap) { - post::FileHandle s(getUniqueFilePrefix() + name + "_coord.tex"); - std::vector vIds(num_vertices(gStereo)); - using SVertex = typename boost::graph_traits::vertex_descriptor; - for(SVertex vStereo : asRange(vertices(gStereo))) { - auto vId = get(boost::vertex_index_t(), gStereo, vStereo); - auto iter = vMap.find(vStereo); - assert(iter != end(vMap)); - if(iter->second == -1) vIds[vIds.size() - 1] = vId; - else vIds[iter->second] = vId; - } - conf.printCoords(s, vIds); - return s; -} - -template -std::pair -tikz(const GraphPrint &g, typename boost::graph_traits::vertex_descriptor v, - const lib::Stereo::Configuration &conf, const std::string &name, const Depict &depict, - const IO::Graph::Write::Options &options, ShownId shownId) { - const bool printLonePairs = true; - using GVertex = typename boost::graph_traits::vertex_descriptor; - using GEdge = typename boost::graph_traits::edge_descriptor; - using GraphStereo = lib::Graph::GraphType; - using SVertex = lib::Graph::Vertex; - using SEdge = lib::Graph::Edge; - - // we make a new graph with copies and then the extra lone pairs - GraphStereo gStereo; - std::map vMap; // the order id, though -1 => the center - SVertex vCenter = add_vertex(gStereo); - vMap[vCenter] = -1; - std::map, IO::Graph::Write::EdgeFake3DType> edgeDepiction; - for(std::size_t i = 0; i < conf.degree(); ++i) { - SVertex vStereo = add_vertex(gStereo); - vMap[vStereo] = i; - add_edge(vCenter, vStereo, gStereo); - auto edge3Dtype = conf.getEdgeDepiction(i); - edgeDepiction[std::make_pair(vCenter, vStereo)] = edge3Dtype; - edgeDepiction[std::make_pair(vStereo, vCenter)] = IO::Graph::Write::invertEdgeFake3DType(edge3Dtype); - } - - std::string coordFile = coords(gStereo, conf, name, vMap); - post::FileHandle s(getUniqueFilePrefix() + name + ".tex"); - - struct DepictorAndAdvOptions { - DepictorAndAdvOptions(const GraphPrint &gOuter, GVertex vOuterCenter, const lib::Graph::GraphType &g, - const Depict &depict, bool printLonePairs, const std::map &vMap, - const lib::Stereo::Configuration &conf, - const std::map, IO::Graph::Write::EdgeFake3DType> &edgeDepiction, - ShownId shownId) - : gOuter(gOuter), vOuterCenter(vOuterCenter), - nullVertexOuter(boost::graph_traits::null_vertex()), gInner(g), - depict(depict), printLonePairs(printLonePairs), vMap(vMap), conf(conf), edgeDepiction(edgeDepiction), - shownId(shownId) {} - - GVertex getOuterVertexFromInnerVertex(SVertex vInner) const { - auto iter = vMap.find(vInner); - assert(iter != end(vMap)); - if(iter->second == -1) return vOuterCenter; - const auto &emb = conf.begin()[iter->second]; - if(emb.type != lib::Stereo::EmbeddingEdge::Type::Edge) return nullVertexOuter; - auto eOuter = emb.getEdge(vOuterCenter, gOuter); - return target(eOuter, gOuter); - } - - GEdge getOuterEdgeFromInnerEdge(SEdge eInner) const { - GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); - GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); - assert(vSrcOuter != nullVertexOuter); - assert(vTarOuter != nullVertexOuter); - auto p = edge(vSrcOuter, vTarOuter, gOuter); - assert(p.second); - return p.first; - } - - unsigned char getAtomId(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter == nullVertexOuter) return AtomIds::Invalid; - else return depict.getAtomId(vOuter); - } - - Isotope getIsotope(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter == nullVertexOuter) return Isotope(); - else return depict.getIsotope(vOuter); - } - - char getCharge(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter == nullVertexOuter) return 0; - else return depict.getCharge(vOuter); - } - - bool getRadical(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter == nullVertexOuter) return false; - else return depict.getRadical(vOuter); - } - - BondType getBondData(SEdge eInner) const { - GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); - GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); - if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return BondType::Invalid; - else return depict.getBondData(getOuterEdgeFromInnerEdge(eInner)); - } - - AtomData operator()(SVertex v) const { - GVertex vOuter = getOuterVertexFromInnerVertex(v); - if(vOuter == nullVertexOuter) return AtomData(); - else return depict(vOuter); - } - - BondType operator()(SEdge eInner) const { - GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); - GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); - if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return BondType::Invalid; - else return depict(getOuterEdgeFromInnerEdge(eInner)); - } - - std::string getVertexLabelNoIsotopeChargeRadical(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter != nullVertexOuter)return depict.getVertexLabelNoIsotopeChargeRadical(vOuter); - auto iter = vMap.find(vInner); - assert(iter != end(vMap)); - assert(iter->second != -1); - const auto &emb = conf.begin()[iter->second]; - switch(emb.type) { - case lib::Stereo::EmbeddingEdge::Type::Edge: - MOD_ABORT; - case lib::Stereo::EmbeddingEdge::Type::LonePair: - return "e"; - case lib::Stereo::EmbeddingEdge::Type::Radical: - return "r"; - } - MOD_ABORT; - } - - std::string getEdgeLabel(SEdge eInner) const { - GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); - GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); - if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return ""; - else return depict.getEdgeLabel(getOuterEdgeFromInnerEdge(eInner)); - } - - bool hasImportantStereo(SVertex vInner) const { - return true; - } - - bool getHasCoordinates() const { - return false; - } - - double getX(SVertex v, bool b) const { - return 0; - } - - double getY(SVertex v, bool b) const { - return 0; - } - - bool isVisible(SVertex v) const { - if(printLonePairs) return true; - else - MOD_ABORT; - return true; - } - - std::string getColour(SVertex) const { - return ""; - } - - std::string getColour(SEdge) const { - return ""; - } - - std::string getShownId(SVertex vInner) const { - GVertex vOuter = getOuterVertexFromInnerVertex(vInner); - if(vOuter == nullVertexOuter) MOD_ABORT; - else return boost::lexical_cast(shownId(gOuter, vOuter)); - } - - bool overwriteWithIndex(SVertex vInner) const { - auto vOuter = getOuterVertexFromInnerVertex(vInner); - return vOuter == nullVertexOuter; - } - - IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(SEdge eInner, bool withHydrogen) const { - auto iter = edgeDepiction.find(std::make_pair(source(eInner, gInner), target(eInner, gInner))); - assert(iter != end(edgeDepiction)); - return iter->second; - } - - std::string getRawStereoString(SVertex vInner) const { - return ""; - } - - std::string getPrettyStereoString(SVertex vInner) const { - return ""; - } - - std::string getStereoString(SEdge eInner) const { - return ""; - } - - std::string getEdgeAnnotation(SEdge eInner) const { - SVertex vSrcInner = source(eInner, gInner), vTarInner = target(eInner, gInner); - auto iterSrc = vMap.find(vSrcInner), iterTar = vMap.find(vTarInner); - assert(iterSrc != end(vMap)); - assert(iterTar != end(vMap)); - assert(iterSrc->second == -1 || iterTar->second == -1); - bool swapped = false; - if(iterSrc->second != -1) { - std::swap(vSrcInner, vTarInner); - std::swap(iterSrc, iterTar); - swapped = true; - } - std::string res; - // if the edge category does not correspond to the bond type, then print it - const lib::Stereo::EmbeddingEdge &emb = conf.begin()[iterTar->second]; - if(emb.type == lib::Stereo::EmbeddingEdge::Type::Edge) { - auto eOuter = getOuterEdgeFromInnerEdge(eInner); - auto eCatFromBt = lib::Stereo::bondTypeToEdgeCategory(depict.getBondData(eOuter)); - if(eCatFromBt != emb.cat) { - res += " node[auto] {"; - res += boost::lexical_cast(emb.cat); - res += "}"; - } - } - - // print the offset and whatever the configuration wants - res += " node[auto, pos="; - if(swapped) res += "0.25"; - else res += "0.75"; - res += "] {{\\tiny "; - res += boost::lexical_cast(iterTar->second); - res += conf.getEdgeAnnotation(iterTar->second); - res += "}} "; - return res; - } - - bool disallowCollapse(SVertex) const { - return false; - } - - bool disallowCollapse(SEdge) const { - return false; - } - - std::string getOpts(SVertex v) const { - return std::string(); - } - public: - unsigned int idOffset = 0; - private: - const GraphPrint &gOuter; - GVertex vOuterCenter, nullVertexOuter; - const lib::Graph::GraphType &gInner; - const Depict &depict; - bool printLonePairs; - const std::map &vMap; - const lib::Stereo::Configuration &conf; - const std::map, IO::Graph::Write::EdgeFake3DType> &edgeDepiction; - ShownId shownId; - } depictAndAdvOptions(g, v, gStereo, depict, printLonePairs, vMap, conf, edgeDepiction, shownId); - auto bonusWriter = [&](std::ostream &s) { - }; - lib::IO::Graph::Write::tikz(s, options, gStereo, depictAndAdvOptions, coordFile, depictAndAdvOptions, bonusWriter, - ""); - return std::pair(s, coordFile); -} - -template -std::string pdf(const Graph &g, typename boost::graph_traits::vertex_descriptor v, - const lib::Stereo::Configuration &conf, const std::string &name, const Depict &depict, - const IO::Graph::Write::Options &options, ShownId shownId) { - const auto p = tikz(g, v, conf, name, depict, options, shownId); - std::string fileTikz = p.first, fileCoords = p.second; - std::string fileNoExt = fileTikz.substr(0, fileTikz.size() - 4); - std::string fileCoordsNoExt = fileCoords.substr(0, fileCoords.size() - 4); - IO::post() << "compileTikz \"" << fileNoExt << "\" \"" << fileCoordsNoExt << "\"" << std::endl; - return fileNoExt + ".pdf"; -} - -} // namespace - -std::string summary(const lib::Graph::Single &gLib, lib::Graph::Vertex v, const lib::Stereo::Configuration &conf, - const IO::Graph::Write::Options &options, int shownIdOffset, const std::string &nameSuffix) { - const auto &g = gLib.getGraph(); - const auto vId = get(boost::vertex_index_t(), g, v); - std::string name = "g_" + boost::lexical_cast(gLib.getId()) + "_stereo_" + - boost::lexical_cast(vId); - IO::post() << "summarySubsection \"Stereo, g " << gLib.getId() << ", v " << vId - << nameSuffix << "\"\n"; - std::string f = pdf(g, v, conf, name, gLib.getDepictionData(), options, [shownIdOffset](const auto &g, const auto v) { - return get(boost::vertex_index_t(), g, v) + shownIdOffset; - }); - post::FileHandle s(getUniqueFilePrefix() + "stereo.tex"); - s << "\\begin{center}\n"; - s << "\\includegraphics{" << f << "}\\\\\n"; - s << "File: \\texttt{" << escapeForLatex(f) << "}\n"; - s << "\\end{center}\n"; - IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; - return f; -} - -std::string summary(const lib::Rules::Real &r, lib::Rules::Vertex v, lib::Rules::Membership m, - const IO::Graph::Write::Options &options) { - assert(m != lib::Rules::Membership::Context); - if(m == lib::Rules::Membership::Left) assert(membership(r.getDPORule(), v) != lib::Rules::Membership::Right); - if(m == lib::Rules::Membership::Right) assert(membership(r.getDPORule(), v) != lib::Rules::Membership::Left); - const std::string side = m == lib::Rules::Membership::Left ? "L" : "R"; - const auto &g = get_graph(r.getDPORule()); - std::string name = "r_" + std::to_string(r.getId()) + "_" + side + "_stereo_" + - std::to_string(get(boost::vertex_index_t(), g, v)); - IO::post() << "summarySubsection \"Stereo, r " << r.getId() << ", v " << get(boost::vertex_index_t(), g, v) << " " - << side << "\"" << std::endl; - const auto handler = [&](const auto &gLabelled, const auto &depict) { - const auto &g = get_graph(gLabelled); - return pdf(g, v, *get_stereo(gLabelled)[v], name, depict, options, [](const auto &g, const auto v) { - return get(boost::vertex_index_t(), g, v); - }); - }; - std::string f = m == lib::Rules::Membership::Left - ? handler(get_labelled_left(r.getDPORule()), r.getDepictionData().getLeft()) - : handler(get_labelled_right(r.getDPORule()), r.getDepictionData().getRight()); - post::FileHandle s(getUniqueFilePrefix() + "stereo.tex"); - s << "\\begin{center}\n"; - s << "\\includegraphics{" << f << "}\\\\\n"; - s << "File: \\texttt{" << escapeForLatex(f) << "}\n"; - s << "\\end{center}\n"; - IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; - return f; -} - -//------------------------------------------------------------------------------ - -void summary(const lib::Stereo::GeometryGraph &graph) { - const auto &g = graph.getGraph(); - const auto n = num_vertices(g); - std::string fileDot = [&]() { - post::FileHandle s(IO::getUniqueFilePrefix() + "geometryGraph.dot"); - s << "digraph g {\nrankdir=LR;\n"; - for(auto v : asRange(vertices(g))) { - s << get(boost::vertex_index_t(), g, v) << " [ label=\"" << g[v].name << "\" ];\n"; - } - for(auto e : asRange(edges(g))) { - auto vSrc = source(e, g); - auto vTar = target(e, g); - s << get(boost::vertex_index_t(), g, vSrc) << " -> " << get(boost::vertex_index_t(), g, vTar); - s << " [];\n"; - } - for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { - auto &cv = graph.chemValids[i]; - s << (i + n) << " [ label=\"" << lib::Chem::symbolFromAtomId(cv.atomId); - if(cv.charge != 0) { - if(cv.charge > 0) s << "+"; - else s << "-"; - if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); - } - if(cv.catCount.sum() > 0) s << ", " << cv.catCount; - if(cv.lonePair > 0) s << ", e = " << cv.lonePair; - s << "\" ];\n"; - s << get(boost::vertex_index_t(), g, cv.geometry) << " -> " << (i + n) << " [];\n"; - } - s << "}\n"; - std::string f = s; - return std::string(f.begin(), f.end() - 4); - }(); - IO::post() << "coordsFromGV dgHyperDot \"" << fileDot << "\"\n"; - std::string fileCoords = fileDot + "_coord"; - std::string fileFig = [&]() { - post::FileHandle s(IO::getUniqueFilePrefix() + "geometryGraph.tex"); - s << "\\begin{tikzpicture}[scale=\\modDGHyperScale]\n"; - s << "\\input{" << fileCoords << ".tex}\n"; - for(auto v : asRange(vertices(g))) { - auto vId = get(boost::vertex_index_t(), g, v); - s << "\\node[draw] (v-" << vId << ") at (v-coord-" << vId << "){"; - s << g[v].name; - s << "};\n"; - } - for(auto e : asRange(edges(g))) { - auto vSrc = source(e, g); - auto vTar = target(e, g); - s << "\\path[draw, ->, >=stealth] (v-" << get(boost::vertex_index_t(), g, vSrc) - << ") to (v-" << get(boost::vertex_index_t(), g, vTar) - << ");\n"; - } - for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { - auto &cv = graph.chemValids[i]; - s << "\\node[draw, ellipse] (v-" << (i + n) << ") at (v-coord-" << (i + n) << "){"; - s << lib::Chem::symbolFromAtomId(cv.atomId); - if(cv.charge != 0) { - s << "$^{"; - if(cv.charge > 0) s << "+"; - else s << "-"; - if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); - s << "}$"; - } - bool hasMore = cv.catCount.sum() > 0 || cv.lonePair > 0; - if(hasMore) s << "$"; - if(cv.catCount.sum() > 0) s << ", " << cv.catCount; - if(cv.lonePair > 0) s << ", e = " << cv.lonePair; - if(hasMore) s << "$"; - s << "};\n"; - s << "\\path[draw] (v-" << get(boost::vertex_index_t(), g, cv.geometry) << ") to (v-" << (i + n) << ");\n"; - } - s << "\\end{tikzpicture}"; - std::string f = s; - return std::string(f.begin(), f.end() - 4); - }(); - std::string fileTex = [&]() { - post::FileHandle s(IO::getUniqueFilePrefix() + "geometryGraphSummary.tex"); - s - << "\\begin{center}\n" - << "\\includegraphics{" << fileFig << "}\n\n" - << "File: \\texttt{" << escapeForLatex(fileFig) << "}\n" - << "\\end{center}\n" - << "\n" - << "\\section{Stereo, Chemically Valid Configurations}\n" - << "\\begin{center}\n" - << "\\begin{longtable}{@{}lllllll@{}}\n" - << "\\toprule\n" - << "Atom & S & D & T & A & LP & Geometry\\\\\n" - << "\\midrule\n"; - for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { - auto &cv = graph.chemValids[i]; - s << lib::Chem::symbolFromAtomId(cv.atomId); - if(cv.charge != 0) { - s << "$^{"; - if(cv.charge > 0) s << "+"; - else s << "-"; - if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); - s << "}$"; - } - for(auto cat :{lib::Stereo::EdgeCategory::Single, lib::Stereo::EdgeCategory::Double, - lib::Stereo::EdgeCategory::Triple, lib::Stereo::EdgeCategory::Aromatic}) { - s << " & "; - if(auto count = cv.catCount[cat]) { - s << int(count); - } - } - s << " & "; - if(cv.lonePair > 0) s << cv.lonePair; - s << " & " << g[cv.geometry].name << " \\\\\n"; - } - s - << "\\bottomrule\n" - << "\\end{longtable}\n" - << "\\end{center}\n"; - return std::string(s); - }(); - - IO::post() << "compileTikz \"" << fileFig << "\" \"" << fileCoords << "\"" << std::endl; - IO::post() << "summarySection \"Stereo, Geometry Graph\"" << std::endl; - IO::post() << "summaryInput \"" << fileTex << "\"" << std::endl; -} - -} // namesapce mod::lib::IO::Stereo::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Term.cpp b/libs/libmod/src/mod/lib/IO/Term.cpp deleted file mode 100644 index 6269894..0000000 --- a/libs/libmod/src/mod/lib/IO/Term.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include "Term.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include - -#include - -namespace mod::lib::IO::Term::Read { -namespace detail { - -struct Structure; - -struct Variable { - std::string name; -}; - -struct Term : x3::variant> { - using base_type::base_type; - using base_type::operator=; -}; - -struct Structure { - std::string name; - std::vector arguments; -}; - -namespace { -namespace parser { -#define FIRST "A-Za-z0-9=#:.+-" -#define SECOND "_" - -const x3::rule term = "term"; -const x3::rule function = "function"; -const x3::rule > termList = "term list"; -const x3::rule variable = "variable"; -const x3::rule identifier = "identifier"; - -const auto term_def = function | variable; -const auto function_def = identifier >> -('(' > termList > ')'); -const auto termList_def = term % ','; -const auto variable_def = (x3::lexeme['_' > identifier] >> x3::eps) | x3::string("*"); -const auto identifier_def = x3::lexeme[x3::char_(FIRST) > *x3::char_(SECOND FIRST)]; - -BOOST_SPIRIT_DEFINE(term, function, termList, variable, identifier) -} // namespace parser -} // namespace - -struct Converter : public boost::static_visitor { - Converter(const Converter &) = delete; - Converter &operator=(const Converter &) = delete; - - Converter(const StringStore &stringStore) : stringStore(stringStore) {} - - lib::Term::RawTerm operator()(const Variable &var) { - std::size_t stringId; - if(var.name == "*") { - for(;; ++nextVar) { - std::string name = "X" + std::to_string(nextVar) + "_"; - if(!stringStore.hasString(name)) { - stringId = stringStore.getIndex(name); - break; - } - } - } else { - stringId = stringStore.getIndex(var.name); - } - return lib::Term::RawVariable{stringId}; - } - - lib::Term::RawTerm operator()(const Structure &str) { - auto stringId = stringStore.getIndex(str.name); - std::vector arguments; - for(const auto &a : str.arguments) - arguments.push_back(boost::apply_visitor(*this, a)); - return lib::Term::RawStructure{stringId, std::move(arguments)}; - } -private: - const StringStore &stringStore; - std::size_t nextVar = 0; -}; - -} // namespace detail - -lib::Term::RawTerm rawTerm(const std::string &data, const StringStore &stringStore) { - detail::Term term; - parse(data.begin(), data.end(), detail::parser::term, term, x3::space); - detail::Converter converter(stringStore); - return boost::apply_visitor(converter, term); -} - -} // namespace mod::lib::IO::Term::Read -namespace mod::lib::IO::Term::Write { -namespace { - -std::ostream &rawVarFromCell(std::ostream &s, lib::Term::Cell cell) { - using namespace lib::Term; - assert(cell.tag == Cell::Tag::REF); - switch(cell.REF.addr.type) { - case AddressType::Heap: - return s << "_H" << cell.REF.addr.addr; - case AddressType::Temp: - return s << "_T" << cell.REF.addr.addr; - } - MOD_ABORT; -} - -} // namespace - -std::ostream &rawTerm(const lib::Term::RawTerm &term, const StringStore &strings, std::ostream &s) { - struct Printer { - void operator()(lib::Term::RawVariable v) const { - s << "_" << strings.getString(v.name); - } - - void operator()(const lib::Term::RawStructure &str) const { - s << strings.getString(str.name); - if(!str.args.empty()) { - s << '('; - std::visit(*this, str.args.front()); - for(int i = 1; i != str.args.size(); i++) { - s << ", "; - std::visit(*this, str.args[i]); - } - s << ')'; - } - } - public: - std::ostream &s; - const StringStore &strings; - }; - std::visit(Printer{s, strings}, term); - return s; -} - -std::ostream &element(lib::Term::Cell cell, const StringStore &strings, std::ostream &s) { - using namespace lib::Term; - switch(cell.tag) { - case Cell::Tag::STR: - return s << "STR " << cell.STR.addr; - case Cell::Tag::Structure: - s << strings.getString(cell.Structure.name); - if(cell.Structure.arity > 0) - s << "/" << cell.Structure.arity; - return s; - case Cell::Tag::REF: - return s << "REF " << cell.REF.addr; - } - __builtin_unreachable(); -} - -void wam(const lib::Term::Wam &machine, const StringStore &strings, std::ostream &s) { - wam(machine, strings, s, [](lib::Term::Address, std::ostream &) {}); -} - -void wam(const lib::Term::Wam &machine, const StringStore &strings, std::ostream &s, - std::function addressCallback) { - using namespace lib::Term; - s << "Heap:" << std::endl; - for(std::size_t i = 0; i < machine.getHeap().size(); i++) { - Cell cell = machine.getHeap()[i]; - s << std::setw(5) << std::left << i; - element(cell, strings, s); - addressCallback({AddressType::Heap, i}, s); - s << std::endl; - } - s << "-------------------------------------------------" << std::endl; - s << "Temp:" << std::endl; - for(std::size_t i = 0; i < machine.getTemp().size(); i++) { - Cell cell = machine.getTemp()[i]; - s << std::setw(5) << std::left << i; - element(cell, strings, s); - addressCallback({AddressType::Temp, i}, s); - s << std::endl; - } - s << "-------------------------------------------------" << std::endl; -} - -std::ostream & -term(const lib::Term::Wam &machine, lib::Term::Address addr, const StringStore &strings, std::ostream &s) { - using namespace lib::Term; - - struct Printer { - Printer(const Wam &machine, const StringStore &strings, std::ostream &s) - : machine(machine), strings(strings), s(s) { - occurred[0].resize(machine.getHeap().size(), 0); - occurred[1].resize(machine.getTemp().size(), 0); - } - - void operator()(Address addr) { - Cell cell = machine.getCell(addr); - switch(cell.tag) { - case Cell::Tag::REF: - if(cell.REF.addr == addr - || occurred[static_cast(cell.REF.addr.type)][cell.REF.addr.addr] != 0 - ) { - rawVarFromCell(s, cell); - } else (*this)(cell.REF.addr); - break; - case Cell::Tag::STR: - (*this)(cell.STR.addr); - break; - case Cell::Tag::Structure: - if(occurred[static_cast(addr.type)][addr.addr] != 0) { - wam(machine, strings, std::cout); - std::cout << "addr.addr = " << addr.addr << std::endl; - std::cout << "occurred:" << std::endl; - for(int aType : {0, 1}) { - for(const auto o : occurred[aType]) { - if(o == 0) continue; - std::cout << " [" << aType << "]: " << o << std::endl; - } - } - } - assert(occurred[static_cast(addr.type)][addr.addr] == 0); - s << strings.getString(cell.Structure.name); - if(cell.Structure.arity > 0) { - ++occurred[static_cast(addr.type)][addr.addr]; - s << "("; - (*this)(addr + 1); - for(std::size_t i = 2; i <= cell.Structure.arity; i++) { - s << ", "; - (*this)(addr + i); - } - s << ")"; - --occurred[static_cast(addr.type)][addr.addr]; - } - break; - } - } - private: - const Wam &machine; - const StringStore &strings; - std::ostream &s; - std::array, 2> occurred; - }; - Printer(machine, strings, s)(addr); - return s; -} - -std::ostream & -mgu(const lib::Term::Wam &machine, const lib::Term::MGU &mgu, const StringStore &strings, std::ostream &s) { - using namespace lib::Term; - switch(mgu.status) { - case MGU::Status::Exists: - s << "Exists: "; - break; - case MGU::Status::Fail: - term(machine, mgu.errorLeft, strings, s << "Fail(") << " != "; - term(machine, mgu.errorRight, strings, s) << ")"; - return s; - } - bool first = true; - for(auto binding : mgu.bindings) { - if(binding.type == AddressType::Heap && binding.addr >= mgu.preHeapSize) continue; - if(!first) s << ", "; - first = false; - Cell cell; - cell.tag = Cell::Tag::REF; - cell.REF.addr = binding; - rawVarFromCell(s, cell) << " = "; - term(machine, binding, strings, s); - } - return s; -} - -} // namespace mod::lib::IO::Term::Write -namespace mod::lib::Term { - -std::ostream &operator<<(std::ostream &s, Address addr) { - switch(addr.type) { - case AddressType::Heap: - s << "H"; - break; - case AddressType::Temp: - s << "T"; - break; - } - return s << "[" << addr.addr << "]"; -} - -} // namespace mod::lib::Term - -BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::Term::Read::detail::Variable, - (std::string, name)) -BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::Term::Read::detail::Structure, - (std::string, name) - (std::vector, arguments)) \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/Term.hpp b/libs/libmod/src/mod/lib/IO/Term.hpp deleted file mode 100644 index b7362cf..0000000 --- a/libs/libmod/src/mod/lib/IO/Term.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MOD_LIB_IO_TERM_HPP -#define MOD_LIB_IO_TERM_HPP - -#include -#include - -#include -#include - -namespace mod::lib { -struct StringStore; -} // namespace mod::lib -namespace mod::lib::IO::Term::Read { -// throws lib::IO::ParsingError on error -lib::Term::RawTerm rawTerm(const std::string &data, const StringStore &stringStore); -} // namespace mod::lib::IO::Term::Read -namespace mod::lib::IO::Term::Write { -std::ostream &rawTerm(const lib::Term::RawTerm &term, const StringStore &strings, std::ostream &s); -std::ostream &element(lib::Term::Cell cell, const StringStore &strings, std::ostream &s); -void wam(const lib::Term::Wam &machine, const StringStore &strings, std::ostream &s); -void wam(const lib::Term::Wam &machine, const StringStore &strings, std::ostream &s, - std::function addressCallback); -std::ostream &term(const lib::Term::Wam &machine, lib::Term::Address addr, const StringStore &strings, std::ostream &s); -std::ostream &mgu(const lib::Term::Wam &machine, const lib::Term::MGU &mgu, const StringStore &strings, std::ostream &s); -} // namespace mod::lib::IO::Term::Write - -#endif // MOD_LIB_IO_TERM_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/LabelledGraph.hpp b/libs/libmod/src/mod/lib/LabelledGraph.hpp index 90b6178..4346563 100644 --- a/libs/libmod/src/mod/lib/LabelledGraph.hpp +++ b/libs/libmod/src/mod/lib/LabelledGraph.hpp @@ -10,6 +10,7 @@ struct LabelledGraphTraits { using GraphType = typename G::GraphType; using PropStringType = typename G::PropStringType; using PropTermType = typename G::PropTermType; + using PropStereoType = typename G::PropStereoType; }; template @@ -17,16 +18,16 @@ struct LabelledGraphConcept { using GraphType = typename LabelledGraphTraits::GraphType; using PropStringType = typename LabelledGraphTraits::PropStringType; using PropTermType = typename LabelledGraphTraits::PropTermType; + using PropStereoType = typename LabelledGraphTraits::PropStereoType; public: BOOST_CONCEPT_USAGE(LabelledGraphConcept) { const G &gOuterConst = gOuter; - const GraphType &gConst = get_graph(gOuterConst); - (void) gConst; - const PropStringType &pString = get_string(gOuterConst); - (void) pString; - const PropTermType &pTerm = get_term(gOuterConst); - (void) pTerm; + [[maybe_unused]] const GraphType &gConst = get_graph(gOuterConst); + [[maybe_unused]] const PropStringType &pString = get_string(gOuterConst); + [[maybe_unused]] const PropTermType &pTerm = get_term(gOuterConst); + [[maybe_unused]] const bool hasStereo = has_stereo(gOuterConst); + [[maybe_unused]] const PropStereoType &pStereo = get_stereo(gOuterConst); } private: G gOuter; diff --git a/libs/libmod/src/mod/lib/RC/Compose.hpp b/libs/libmod/src/mod/lib/RC/Compose.hpp index a799beb..8d46179 100644 --- a/libs/libmod/src/mod/lib/RC/Compose.hpp +++ b/libs/libmod/src/mod/lib/RC/Compose.hpp @@ -6,28 +6,28 @@ // The graphs may not have parallel edges. #include +#include #include #include -#include -#include +#include namespace mod::lib::RC { -template -using DPOTraits = jla_boost::GraphDPO::PushoutRuleTraits; - template -std::optional compose(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, VisitorT visitor) { +bool compose(Result &result, + const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + VisitorT visitor) { // match must be rSecond -> rFirst - using RuleResult = typename Result::RuleResult; using WrappedVisitor = Visitor::Compound; - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::WritablePushoutRuleConcept)); - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::PushoutRuleConcept)); - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::PushoutRuleConcept)); + // TODO: enable these concepts again +// BOOST_CONCEPT_ASSERT((lib::DPO::WritableRuleConcept)); + BOOST_CONCEPT_ASSERT((lib::DPO::RuleConcept)); + BOOST_CONCEPT_ASSERT((lib::DPO::RuleConcept)); BOOST_CONCEPT_ASSERT((jla_boost::GraphMorphism::InvertibleVertexMapConcept)); return detail::CompositionHelper(rFirst, rSecond, match, Visitor::makeVisitor(std::move(visitor)))(); + InvertibleVertexMap, WrappedVisitor>(result, rFirst, rSecond, match, + Visitor::makeVisitor(std::move(visitor)))(); } } // namespace mod::lib::RC diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleReal.cpp b/libs/libmod/src/mod/lib/RC/ComposeRuleReal.cpp deleted file mode 100644 index 37a9d87..0000000 --- a/libs/libmod/src/mod/lib/RC/ComposeRuleReal.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "ComposeRuleReal.hpp" - -#include -#include -#include -#include -#include - -namespace mod::lib::RC { - -#define MOD_RC_COMPOSE_BY_MATCH_MAKER(MM) \ - void composeRuleRealByMatchMaker(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, \ - const MM &mm, std::function) > rr, LabelSettings labelSettings) { \ - composeRuleRealByMatchMakerGeneric(rFirst, rSecond, mm, rr, labelSettings); \ - } - -MOD_RC_COMPOSE_BY_MATCH_MAKER(Common) -MOD_RC_COMPOSE_BY_MATCH_MAKER(Parallel) -MOD_RC_COMPOSE_BY_MATCH_MAKER(Sub) -MOD_RC_COMPOSE_BY_MATCH_MAKER(Super) -#undef MOD_RC_COMPOSE_BY_MATCH_MAKER - -} // namespace mod::lib::RC \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleReal.hpp b/libs/libmod/src/mod/lib/RC/ComposeRuleReal.hpp index c14ee45..73cd564 100644 --- a/libs/libmod/src/mod/lib/RC/ComposeRuleReal.hpp +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleReal.hpp @@ -28,6 +28,12 @@ MOD_RC_COMPOSE_BY_MATCH_MAKER(Sub) MOD_RC_COMPOSE_BY_MATCH_MAKER(Super) #undef MOD_RC_COMPOSE_BY_MATCH_MAKER +#define MOD_RC_COMPOSE_BY_MATCH_MAKER_IMPL(MM) \ + void composeRuleRealByMatchMaker(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, \ + const MM &mm, std::function) > rr, LabelSettings labelSettings) { \ + composeRuleRealByMatchMakerGeneric(rFirst, rSecond, mm, rr, labelSettings); \ + } + } // namespace mod::lib::RC #endif // MOD_LIB_RC_COMPOSE_RULE_REAL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleRealCommon.cpp b/libs/libmod/src/mod/lib/RC/ComposeRuleRealCommon.cpp new file mode 100644 index 0000000..d398011 --- /dev/null +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleRealCommon.cpp @@ -0,0 +1,10 @@ +#include "ComposeRuleReal.hpp" + +#include +#include + +namespace mod::lib::RC { + +MOD_RC_COMPOSE_BY_MATCH_MAKER_IMPL(Common) + +} // namespace mod::lib::RC \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleRealGeneric.hpp b/libs/libmod/src/mod/lib/RC/ComposeRuleRealGeneric.hpp index 0120fe3..082c6a3 100644 --- a/libs/libmod/src/mod/lib/RC/ComposeRuleRealGeneric.hpp +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleRealGeneric.hpp @@ -4,9 +4,9 @@ #include #include #include -#include -#include #include +#include +#include #include #include #include @@ -20,15 +20,23 @@ struct Sub; struct Super; template -auto composeRuleRealByMatch(const lib::Rules::Real &rFirst, - const lib::Rules::Real &rSecond, - InvertibleVertexMap &match) { - using Result = BaseResult; - auto visitor = Visitor::MatchConstraints(); - auto res = composeLabelled( - rFirst.getDPORule(), rSecond.getDPORule(), match, visitor); - if(res) res->rResult.initComponents(); // TODO: move to the visitor finalizer - return res; +std::optional composeRuleRealByMatch(const lib::Rules::Real &rFirst, + const lib::Rules::Real &rSecond, + InvertibleVertexMap &match) { + auto visitor = Visitor::MatchConstraints(rFirst.getDPORule(), rSecond.getDPORule()); + LabelledResult result(rFirst.getDPORule().getRule(), rSecond.getDPORule().getRule()); + const bool success = composeLabelled( + result, rFirst.getDPORule(), rSecond.getDPORule(), match, visitor); + if(success) { + assert(result.pString || result.pTerm); + auto rule = result.pString + ? lib::Rules::LabelledRule(std::move(result.rDPO), std::move(result.pString), + std::move(result.pStereo)) + : lib::Rules::LabelledRule(std::move(result.rDPO), std::move(result.pTerm), + std::move(result.pStereo)); + rule.leftData.matchConstraints = std::move(result.matchConstraints); + return rule; + } else return {}; } template @@ -47,7 +55,7 @@ std::unique_ptr composeRuleRealByMatch(const lib::Rules::Real if(verbose) logger.indent() << "Composition failed" << std::endl; return nullptr; } - auto rResult = std::make_unique(std::move(resultOpt->rResult), labelType); + auto rResult = std::make_unique(std::move(*resultOpt), labelType); if(verbose) logger.indent() << "Composition done, rNew is '" << rResult->getName() << "'" << std::endl; return rResult; @@ -74,7 +82,7 @@ struct MatchMakerCallback { << "\t= " << rFirst.getName() << "\t. " << rSecond.getName() << std::endl; if(getConfig().rc.printMatches.get()) { - IO::RC::Write::test(rFirst, rSecond, m, *rResult); + RC::Write::test(rFirst, rSecond, m, *rResult); } const bool cont = rr(std::move(rResult)); if(!cont) return false; diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleRealParallel.cpp b/libs/libmod/src/mod/lib/RC/ComposeRuleRealParallel.cpp new file mode 100644 index 0000000..50c6427 --- /dev/null +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleRealParallel.cpp @@ -0,0 +1,10 @@ +#include "ComposeRuleReal.hpp" + +#include +#include + +namespace mod::lib::RC { + +MOD_RC_COMPOSE_BY_MATCH_MAKER_IMPL(Parallel) + +} // namespace mod::lib::RC \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleRealSub.cpp b/libs/libmod/src/mod/lib/RC/ComposeRuleRealSub.cpp new file mode 100644 index 0000000..cfaa995 --- /dev/null +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleRealSub.cpp @@ -0,0 +1,10 @@ +#include "ComposeRuleReal.hpp" + +#include +#include + +namespace mod::lib::RC { + +MOD_RC_COMPOSE_BY_MATCH_MAKER_IMPL(Sub) + +} // namespace mod::lib::RC \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/ComposeRuleRealSuper.cpp b/libs/libmod/src/mod/lib/RC/ComposeRuleRealSuper.cpp new file mode 100644 index 0000000..0ffbb72 --- /dev/null +++ b/libs/libmod/src/mod/lib/RC/ComposeRuleRealSuper.cpp @@ -0,0 +1,10 @@ +#include "ComposeRuleReal.hpp" + +#include +#include + +namespace mod::lib::RC { + +MOD_RC_COMPOSE_BY_MATCH_MAKER_IMPL(Super) + +} // namespace mod::lib::RC \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Evaluator.cpp b/libs/libmod/src/mod/lib/RC/Evaluator.cpp index 4b82972..cb84b89 100644 --- a/libs/libmod/src/mod/lib/RC/Evaluator.cpp +++ b/libs/libmod/src/mod/lib/RC/Evaluator.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -226,7 +226,7 @@ std::vector> Evaluator::eval(const rule::RCExp::Expr } void Evaluator::print() const { - std::string fileNoExt = IO::RC::Write::pdf(*this); + std::string fileNoExt = RC::Write::pdf(*this); IO::post() << "summaryRC \"" << fileNoExt << "\"" << std::endl; } diff --git a/libs/libmod/src/mod/lib/IO/RC.cpp b/libs/libmod/src/mod/lib/RC/IO/Write.cpp similarity index 71% rename from libs/libmod/src/mod/lib/IO/RC.cpp rename to libs/libmod/src/mod/lib/RC/IO/Write.cpp index fa6321b..392d9f3 100644 --- a/libs/libmod/src/mod/lib/IO/RC.cpp +++ b/libs/libmod/src/mod/lib/RC/IO/Write.cpp @@ -1,49 +1,49 @@ -#include "RC.hpp" +#include "Write.hpp" #include #include #include -#include #include #include -#include +#include +#include #include -namespace mod::lib::IO::RC::Write { +namespace mod::lib::RC::Write { -std::string dot(const lib::RC::Evaluator &rc) { - typedef lib::RC::Evaluator::Vertex Vertex; - typedef lib::RC::Evaluator::Edge Edge; - const lib::RC::Evaluator::GraphType &rcg = rc.getGraph(); - post::FileHandle s(getUniqueFilePrefix() + "rc.dot"); +std::string dot(const Evaluator &rc) { + using Vertex = Evaluator::Vertex; + using Edge = Evaluator::Edge; + const Evaluator::GraphType &rcg = rc.getGraph(); + post::FileHandle s(IO::makeUniqueFilePrefix() + "rc.dot"); std::string fileNoExt = s; fileNoExt.erase(end(fileNoExt) - 4, end(fileNoExt)); s << "digraph g {" << std::endl; - for(Vertex v : asRange(vertices(rcg))) { + for(Vertex v: asRange(vertices(rcg))) { s << "\t" << get(boost::vertex_index_t(), rcg, v) << " ["; switch(rcg[v].kind) { - case lib::RC::Evaluator::VertexKind::Rule: + case Evaluator::VertexKind::Rule: s << " label=\"" << rcg[v].rule->getName() << "\""; break; - case lib::RC::Evaluator::VertexKind::Composition: + case Evaluator::VertexKind::Composition: s << " shape=point"; break; } s << " ];" << std::endl; } - for(Edge e : asRange(edges(rcg))) { + for(Edge e: asRange(edges(rcg))) { s << "\t" << get(boost::vertex_index_t(), rcg, source(e, rcg)) << " -> " << get(boost::vertex_index_t(), rcg, target(e, rcg)) << " ["; switch(rcg[e].kind) { - case lib::RC::Evaluator::EdgeKind::First: + case Evaluator::EdgeKind::First: s << " label=1"; break; - case lib::RC::Evaluator::EdgeKind::Second: + case Evaluator::EdgeKind::Second: s << " label=2"; break; - case lib::RC::Evaluator::EdgeKind::Result: + case Evaluator::EdgeKind::Result: break; } s << " ];" << std::endl; @@ -52,13 +52,13 @@ std::string dot(const lib::RC::Evaluator &rc) { return fileNoExt; } -std::string svg(const lib::RC::Evaluator &rc) { +std::string svg(const Evaluator &rc) { std::string fileNoExt = dot(rc); IO::post() << "gv rc \"" << fileNoExt << "\" svg" << std::endl; return fileNoExt; } -std::string pdf(const lib::RC::Evaluator &rc) { +std::string pdf(const Evaluator &rc) { std::string fileNoExt = svg(rc); IO::post() << "svgToPdf \"" << fileNoExt << "\"" << std::endl; return fileNoExt; @@ -70,13 +70,13 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const const auto &lg = get_labelled_left(rNew.getDPORule()); const auto &g = get_graph(lg); const auto &mol = get_molecule(lg); - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto ad = mol[v]; if(ad.getRadical()) continue; if(ad.getCharge() != 0) continue; if(ad.getIsotope() != Isotope()) continue; int valence = 0; - for(const auto e : asRange(out_edges(v, g))) { + for(const auto e: asRange(out_edges(v, g))) { switch(mol[e]) { case BondType::Single: valence += 1; @@ -107,7 +107,7 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const } using CoreVertex = lib::Rules::Vertex; using CoreEdge = lib::Rules::Edge; - IO::Rules::Write::Options options; + Rules::Write::Options options; options.CollapseHydrogens(true); options.EdgesAsBonds(true); if(getConfig().rc.matchesWithIndex.get()) @@ -125,29 +125,29 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const std::map vFirstToCommon, vSecondToCommon, vNewToCommon; lib::Rules::LabelledRule dpoCommon(rFirst.getDPORule(), false); lib::Rules::GraphType &gComon = get_graph(dpoCommon); - lib::Rules::PropStringCore &pStringCommon = *dpoCommon.pString; - for(const CoreVertex v : asRange(vertices(rFirst.getGraph()))) + lib::Rules::PropString &pStringCommon = *dpoCommon.pString; + for(const CoreVertex v: asRange(vertices(rFirst.getGraph()))) vFirstToCommon[v] = v; // TODO: this will completely break if vertices are deleted in the composed rule - for(const CoreVertex v : asRange(vertices(rNew.getGraph()))) + for(const CoreVertex v: asRange(vertices(rNew.getGraph()))) vNewToCommon[v] = v; // copy rSecond vertices - for(const CoreVertex v : asRange(vertices(rSecond.getGraph()))) { + for(const CoreVertex v: asRange(vertices(rSecond.getGraph()))) { const auto rightIter = match.right.find(v); if(rightIter != match.right.end()) { vSecondToCommon[v] = rightIter->second; } else { const CoreVertex vCommon = add_vertex(gComon); vSecondToCommon[v] = vCommon; - gComon[vCommon].membership = lib::Rules::Membership::Context; - const std::string &label = rSecond.getGraph()[v].membership == lib::Rules::Membership::Left - ? rSecond.getStringState().getLeft()[v] - : rSecond.getStringState().getRight()[v]; + gComon[vCommon].membership = lib::Rules::Membership::K; + const std::string &label = rSecond.getGraph()[v].membership == lib::Rules::Membership::L + ? get_string(rSecond.getDPORule()).getLeft()[v] + : get_string(rSecond.getDPORule()).getRight()[v]; pStringCommon.add(vCommon, label, label); } } // copy rSecond edges - for(const CoreEdge e : asRange(edges(rSecond.getGraph()))) { + for(const CoreEdge e: asRange(edges(rSecond.getGraph()))) { const CoreVertex vSrcSecond = source(e, rSecond.getGraph()); const CoreVertex vTarSecond = target(e, rSecond.getGraph()); const auto iterSrc = vSecondToCommon.find(vSrcSecond); @@ -159,10 +159,10 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const auto pEdge = edge(vSrc, vTar, gComon); if(pEdge.second) continue; pEdge = add_edge(vSrc, vTar, gComon); - gComon[pEdge.first].membership = lib::Rules::Membership::Context; - const std::string &label = rSecond.getGraph()[e].membership == lib::Rules::Membership::Left - ? rSecond.getStringState().getLeft()[e] - : rSecond.getStringState().getRight()[e]; + gComon[pEdge.first].membership = lib::Rules::Membership::K; + const std::string &label = rSecond.getGraph()[e].membership == lib::Rules::Membership::L + ? get_string(rSecond.getDPORule()).getLeft()[e] + : get_string(rSecond.getDPORule()).getRight()[e]; pStringCommon.add(pEdge.first, label, label); } lib::Rules::Real rCommon(std::move(dpoCommon), rFirst.getLabelType()); @@ -175,7 +175,7 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const const auto secondIdOffset = num_vertices(rFirst.getGraph()); std::set matchVerticesInCommon; - for(const CoreVertex v : asRange(vertices(rFirst.getGraph()))) { + for(const CoreVertex v: asRange(vertices(rFirst.getGraph()))) { if(match.left.find(v) == match.left.end()) continue; const auto iter = vFirstToCommon.find(v); assert(iter != end(vFirstToCommon)); @@ -196,23 +196,23 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const assert(iter != end(vNewToCommon)); return matchVerticesInCommon.find(iter->second) != end(matchVerticesInCommon); }; - const auto rawFilesFirst = IO::Rules::Write::tikz(rFirstCopy, 0, options, "L", "K", "R", - IO::Rules::Write::BaseArgs{visible, vColour, eColour}, - disallowCollapseFirst); - const auto rawFilesSecond = IO::Rules::Write::tikz(rSecondCopy, secondIdOffset, options, "L", "K", "R", - IO::Rules::Write::BaseArgs{visible, vColour, eColour}, - disallowCollapseSecond); - const auto rawFilesNew = IO::Rules::Write::tikz(rNewCopy, 0, options, "L", "K", "R", - IO::Rules::Write::BaseArgs{visible, vColour, eColour}, - disallowCollapseNew); - post::FileHandle s(getUniqueFilePrefix() + "rcMatch.tex"); + const auto rawFilesFirst = Rules::Write::tikz(rFirstCopy, 0, options, "L", "K", "R", + Rules::Write::BaseArgs{visible, vColour, eColour}, + disallowCollapseFirst); + const auto rawFilesSecond = Rules::Write::tikz(rSecondCopy, secondIdOffset, options, "L", "K", "R", + Rules::Write::BaseArgs{visible, vColour, eColour}, + disallowCollapseSecond); + const auto rawFilesNew = Rules::Write::tikz(rNewCopy, 0, options, "L", "K", "R", + Rules::Write::BaseArgs{visible, vColour, eColour}, + disallowCollapseNew); + post::FileHandle s(IO::makeUniqueFilePrefix() + "rcMatch.tex"); { s << "\\rcMatchFig"; s << '{' << rawFilesFirst.first << '}' << '{' << rFirst.getId() << '}'; s << '{'; bool first = true; - for(const auto[vFirst, vSecond] : match.left) { + for(const auto[vFirst, vSecond]: match.left) { const auto vIdFirst = get(boost::vertex_index_t(), rFirst.getGraph(), vFirst); auto vIdSecond = get(boost::vertex_index_t(), rSecond.getGraph(), vSecond); vIdSecond += num_vertices(rFirst.getGraph()); @@ -227,7 +227,7 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const << '{' << rNew.getId() << '}'; s << '\n'; } - post::FileHandle sAux(getUniqueFilePrefix() + "rcMatch_aux.tex"); + post::FileHandle sAux(IO::makeUniqueFilePrefix() + "rcMatch_aux.tex"); { sAux << "\\\\\n"; sAux << "Files:\\\\\n \\texttt{" << IO::escapeForLatex(rawFilesFirst.first) @@ -238,7 +238,7 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const << "}\\\\\n"; sAux << "Match: \n"; bool first = true; - for(const auto[vFirst, vSecond] : match.left) { + for(const auto[vFirst, vSecond]: match.left) { if(!first) sAux << ", "; sAux << "$" << vFirst << "\\rightarrow " << vSecond << "$\n"; first = false; @@ -249,4 +249,4 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const IO::post() << std::flush; } -} // namespace mod::lib::IO::RC::Write \ No newline at end of file +} // namespace mod::lib::RC::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/RC.hpp b/libs/libmod/src/mod/lib/RC/IO/Write.hpp similarity index 73% rename from libs/libmod/src/mod/lib/IO/RC.hpp rename to libs/libmod/src/mod/lib/RC/IO/Write.hpp index 31cb488..ebe140b 100644 --- a/libs/libmod/src/mod/lib/IO/RC.hpp +++ b/libs/libmod/src/mod/lib/RC/IO/Write.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_LIB_IO_RC_HPP -#define MOD_LIB_IO_RC_HPP +#ifndef MOD_LIB_RC_IO_WRITE_HPP +#define MOD_LIB_RC_IO_WRITE_HPP #include @@ -10,10 +10,10 @@ namespace mod::lib::RC { struct Evaluator; } // namespace mod::lib::RC -namespace mod::lib::IO::RC::Write { -std::string dot(const lib::RC::Evaluator &rc); -std::string svg(const lib::RC::Evaluator &rc); -std::string pdf(const lib::RC::Evaluator &rc); +namespace mod::lib::RC::Write { +std::string dot(const Evaluator &rc); +std::string svg(const Evaluator &rc); +std::string pdf(const Evaluator &rc); using CoreCoreMap = boost::bimap; @@ -21,7 +21,7 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const template void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const VertexMap &m, const lib::Rules::Real &rNew) { - using GraphDom = lib::Rules::LabelledRule::LeftGraphType; + using GraphDom = lib::Rules::LabelledRule::SideProjectedGraphType; const auto &gDom = get_graph(get_labelled_left(rSecond.getDPORule())); const auto &gCodom = get_graph(get_labelled_right(rFirst.getDPORule())); CoreCoreMap match; @@ -33,6 +33,6 @@ void test(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, const test(rFirst, rSecond, match, rNew); } -} // namespace mod::lib::IO::RC::Write +} // namespace mod::lib::RC::Write -#endif // MOD_LIB_IO_RC_HPP \ No newline at end of file +#endif // MOD_LIB_RC_IO_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/LabelledComposition.hpp b/libs/libmod/src/mod/lib/RC/LabelledComposition.hpp index 301fd22..bc62161 100644 --- a/libs/libmod/src/mod/lib/RC/LabelledComposition.hpp +++ b/libs/libmod/src/mod/lib/RC/LabelledComposition.hpp @@ -6,18 +6,21 @@ #include #include #include -#include #include #include #include #include +#include namespace mod::lib::RC { namespace detail { -template -std::optional composeLabelledFinallyDoIt(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, VisitorT visitor) { - return compose(rFirst, rSecond, match, std::move(visitor)); +template +bool composeLabelledFinallyDoIt(Result &result, + const lib::Rules::LabelledRule &rFirst, + const lib::Rules::LabelledRule &rSecond, + InvertibleVertexMap &match, VisitorT visitor) { + return compose(result, rFirst.getRule(), rSecond.getRule(), match, std::move(visitor)); } template @@ -25,12 +28,16 @@ struct LabelTypeToVisitor; template<> struct LabelTypeToVisitor { - using type = Visitor::String; + static Visitor::String make(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) { + return {rFirst, rSecond}; + } }; template<> struct LabelTypeToVisitor { - using type = Visitor::Term; + static Visitor::Term make(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) { + return {rFirst, rSecond}; + } }; template @@ -38,22 +45,31 @@ struct WithStereoVisitor; template<> struct WithStereoVisitor { - using type = Visitor::Stereo; + static Visitor::Stereo make(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) { + return {rFirst, rSecond}; + } }; template<> struct WithStereoVisitor { - using type = Visitor::Null; + static Visitor::Null make(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) { + return {}; + } }; } // namespace detail -template -std::optional composeLabelled(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, VisitorT visitor = Visitor::Null()) { - return detail::composeLabelledFinallyDoIt(rFirst, rSecond, match, Visitor::makeVisitor( - std::move(visitor), - typename detail::LabelTypeToVisitor::type(), - typename detail::WithStereoVisitor::type() +template +bool composeLabelled(Result &result, const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond, + InvertibleVertexMap &match, + VisitorT visitor = Visitor::Null()) { + return detail::composeLabelledFinallyDoIt( + result, rFirst, rSecond, match, + Visitor::makeVisitor( + std::move(visitor), + detail::LabelTypeToVisitor::make(rFirst, rSecond), + detail::WithStereoVisitor::make(rFirst, rSecond) )); } diff --git a/libs/libmod/src/mod/lib/RC/MatchBuilder.cpp b/libs/libmod/src/mod/lib/RC/MatchBuilder.cpp index 83a1c48..738e00c 100644 --- a/libs/libmod/src/mod/lib/RC/MatchBuilder.cpp +++ b/libs/libmod/src/mod/lib/RC/MatchBuilder.cpp @@ -13,7 +13,7 @@ bool MatchBuilder::VertexPred::operator()(lib::Rules::Vertex vSecond, lib::Rules // ---------------------------------------------------------------------------- -bool MatchBuilder::EdgePred::operator()(lib::Rules::Edge eFirst, lib::Rules::Edge eSecond) { +bool MatchBuilder::EdgePred::operator()(lib::Rules::Edge eSecond, lib::Rules::Edge eFirst) { return lib::GraphMorphism::predicateSelectByLabelSeetings( get_labelled_left(rSecond.getDPORule()), get_labelled_right(rFirst.getDPORule()), eSecond, eFirst, labelSettings); @@ -23,7 +23,7 @@ bool MatchBuilder::EdgePred::operator()(lib::Rules::Edge eFirst, lib::Rules::Edg MatchBuilder::MatchBuilder(const lib::Rules::Real &rFirst, const lib::Rules::Real &rSecond, LabelSettings labelSettings) : rFirst(rFirst), rSecond(rSecond), labelSettings(labelSettings), - match(get_left(rSecond.getDPORule()), get_right(rFirst.getDPORule()), + match(getL(rSecond.getDPORule().getRule()), getR(rFirst.getDPORule().getRule()), EdgePred{rFirst, rSecond, labelSettings}, VertexPred{rFirst, rSecond, labelSettings}) {} lib::Rules::Vertex MatchBuilder::getSecondFromFirst(lib::Rules::Vertex v) const { @@ -71,7 +71,6 @@ std::unique_ptr MatchBuilder::compose(bool verbose) const { [[maybe_unused]] const bool cont = lib::GraphMorphism::matchSelectByLabelSettings( get_labelled_left(rSecond.getDPORule()), get_labelled_right(rFirst.getDPORule()), match.getVertexMap(), ls, mr); - assert(cont == bool(res)); return res; } @@ -89,19 +88,20 @@ std::vector> MatchBuilder::composeAll(bool max return true; })(rFirst, rSecond, std::move(m), verbose, IO::Logger(std::cout)); }; - const auto &gLeft = get_labelled_left(rSecond.getDPORule()); - const auto &gRight = get_labelled_right(rFirst.getDPORule()); + const auto &lgLeft = get_labelled_left(rSecond.getDPORule()); + const auto &lgRight = get_labelled_right(rFirst.getDPORule()); if(maximum) { - auto mr = jla_boost::GraphMorphism::makeMaxmimumSubgraphCallback(get_graph(gLeft), get_graph(gRight), mrCompose); + auto mr = jla_boost::GraphMorphism::makeMaxmimumSubgraphCallback(get_graph(lgLeft), get_graph(lgRight), + mrCompose); // the non-extended match, and importantly send it through mr to make sure maximum is respected - lib::GraphMorphism::matchSelectByLabelSettings(gLeft, gRight, match.getSizedVertexMap(), ls, std::ref(mr)); + lib::GraphMorphism::matchSelectByLabelSettings(lgLeft, lgRight, match.getSizedVertexMap(), ls, std::ref(mr)); // enumerate the rest m(std::ref(mr)); // and now release the maximum ones mr.outputMatches(); } else { // the non-extended match - lib::GraphMorphism::matchSelectByLabelSettings(gLeft, gRight, match.getSizedVertexMap(), ls, mrCompose); + lib::GraphMorphism::matchSelectByLabelSettings(lgLeft, lgRight, match.getSizedVertexMap(), ls, mrCompose); // enumerate the rest m(mrCompose); } diff --git a/libs/libmod/src/mod/lib/RC/MatchBuilder.hpp b/libs/libmod/src/mod/lib/RC/MatchBuilder.hpp index 5e427d8..c411384 100644 --- a/libs/libmod/src/mod/lib/RC/MatchBuilder.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchBuilder.hpp @@ -3,7 +3,6 @@ #include #include -#include namespace mod::lib::RC { @@ -37,8 +36,9 @@ struct MatchBuilder { const LabelSettings labelSettings; private: // note: the match has rSecond as domain and rFirst as codomain, i.e., <-, - // so everything todo with match is somewhat "backwards" - jla_boost::GraphMorphism::CommonSubgraphEnumerator match; }; diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/Common.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/Common.hpp index b6ac6d4..9198b96 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/Common.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/Common.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include namespace mod::lib::RC { @@ -20,7 +20,7 @@ struct Common { const lib::Rules::Real &rSecond, Callback callback, LabelSettings labelSettings) { - using MapImpl = std::vector; + using MapImpl = std::vector; std::vector maps; const auto mr = [&rFirst, &rSecond, &callback, this, &maps] (auto &&m, const auto &gSecond, const auto &gFirst) -> bool { diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/ComponentWiseUtil.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/ComponentWiseUtil.hpp index 6824c61..95357a0 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/ComponentWiseUtil.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/ComponentWiseUtil.hpp @@ -41,7 +41,6 @@ struct WrappedComponentGraph { using PropTermType = typename Rule::PropTermType; using PropStereoType = typename Rule::PropStereoType; public: - WrappedComponentGraph(const ComponentGraph &g, std::size_t i, const Rule &r) : g(jla_boost::makeFilteredWrapper(g)), i(i), r(r) {} @@ -69,7 +68,6 @@ struct WrappedComponentGraph { get_vertex_order(const WrappedComponentGraph &g) { return get_vertex_order_component(g.i, g.r); } - private: GraphType g; std::size_t i; @@ -87,7 +85,6 @@ template struct RuleRuleComponentMonomorphism { using Morphism = GM::VectorVertexMap; public: - RuleRuleComponentMonomorphism(const RuleSideDom &rsDom, const RuleSideCodom &rsCodom, bool enforceConstraints, diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/LabelledMatch.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/LabelledMatch.hpp index 4e0d022..0a2b157 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/LabelledMatch.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/LabelledMatch.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include @@ -16,35 +16,38 @@ constexpr int V_MorphismGen = 2; constexpr int V_Composition = 12; namespace detail { -template -void initTermLabel(const RFirst &rFirst, const RSecond &rSecond) { +inline void initTermLabel(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond) { const auto &termFirst = get_term(rFirst.getDPORule()); const auto &termSecond = get_term(rSecond.getDPORule()); if(!isValid(termFirst)) { std::string msg = "Term state of rFirst is invalid:\n" + termFirst.getParsingError(); - lib::IO::Rules::Write::summary(rFirst, true); + lib::Rules::Write::summary(rFirst, true); throw mod::FatalError(std::move(msg)); } if(!isValid(termSecond)) { std::string msg = "Term state of rSecond is invalid:\n" + termSecond.getParsingError(); - lib::IO::Rules::Write::summary(rSecond, true); + lib::Rules::Write::summary(rSecond, true); throw mod::FatalError(std::move(msg)); } } -template -void initStereoLabel(const RFirst &rFirst, const RSecond &rSecond) { +inline void initStereoLabel(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond) { get_stereo(rFirst.getDPORule()); get_stereo(rSecond.getDPORule()); } } // namespace detail -template -void initByLabelSettings(const RFirst &rFirst, const RSecond &rSecond, LabelSettings labelSettings) { +inline void initByLabelSettings(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond, + LabelSettings labelSettings) { switch(labelSettings.type) { - case LabelType::String: break; - case LabelType::Term: detail::initTermLabel(rFirst, rSecond); + case LabelType::String: + break; + case LabelType::Term: + detail::initTermLabel(rFirst, rSecond); break; } if(labelSettings.withStereo) detail::initStereoLabel(rFirst, rSecond); @@ -57,11 +60,12 @@ namespace detail { // return mr(rFirst, rSecond, std::move(m)); //} -template -bool handleMapToStereo(const RFirst &rFirst, const RSecond &rSecond, - VertexMap &&m, MR &&mr, - LabelSettings labelSettings, - const int verbosity, IO::Logger logger) { +template +bool handleMapToStereo(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond, + VertexMap &&m, MR &&mr, + LabelSettings labelSettings, + const int verbosity, IO::Logger logger) { if(!labelSettings.withStereo) { return mr(rFirst, rSecond, std::move(m), verbosity >= V_Composition, logger); } @@ -85,11 +89,12 @@ bool handleMapToStereo(const RFirst &rFirst, const RSecond &rSecond, MOD_ABORT; } -template -bool handleMapToTerm(const RFirst &rFirst, const RSecond &rSecond, - VertexMap &&m, MR &&mr, - LabelSettings labelSettings, - const int verbosity, IO::Logger logger) { +template +bool handleMapToTerm(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond, + VertexMap &&m, MR &&mr, + LabelSettings labelSettings, + const int verbosity, IO::Logger logger) { namespace GM = jla_boost::GraphMorphism; using GraphDom = typename GM::VertexMapTraits::GraphDom; using GraphCodom = typename GM::VertexMapTraits::GraphCodom; @@ -105,7 +110,7 @@ bool handleMapToTerm(const RFirst &rFirst, const RSecond &rSecond, machine.verify(); lib::Term::MGU mgu(machine.getHeap().size()); // first unify mapped vertices and edges - for(const auto vCodom : asRange(vertices(gCodom))) { + for(const auto vCodom: asRange(vertices(gCodom))) { const auto vDom = get_inverse(m, gDom, gCodom, vCodom); if(vDom == vNullDom) continue; const auto addrCodom = termCodom[vCodom]; @@ -114,7 +119,7 @@ bool handleMapToTerm(const RFirst &rFirst, const RSecond &rSecond, if(mgu.status != lib::Term::MGU::Status::Exists) break; machine.verify(); } - for(const auto eCodom : asRange(edges(gCodom))) { + for(const auto eCodom: asRange(edges(gCodom))) { if(mgu.status != lib::Term::MGU::Status::Exists) break; machine.verify(); const auto vSrcCodom = source(eCodom, gCodom); @@ -146,11 +151,12 @@ bool handleMapToTerm(const RFirst &rFirst, const RSecond &rSecond, return handleMapToStereo(rFirst, rSecond, std::move(mTerm), mr, labelSettings, verbosity, logger); } -template -bool handleMapToStringOrTerm(const RFirst &rFirst, const RSecond &rSecond, - VertexMap &&m, MR &&mr, - LabelSettings labelSettings, - const int verbosity, IO::Logger logger) { +template +bool handleMapToStringOrTerm(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond, + VertexMap &&m, MR &&mr, + LabelSettings labelSettings, + const int verbosity, IO::Logger logger) { switch(labelSettings.type) { case LabelType::String: return handleMapToStereo(rFirst, rSecond, std::move(m), std::move(mr), labelSettings, verbosity, logger); @@ -162,13 +168,14 @@ bool handleMapToStringOrTerm(const RFirst &rFirst, const RSecond &rSecond, } // namesapce detail -template -bool handleMapByLabelSettings(const RFirst &rFirst, const RSecond &rSecond, - VertexMap &&m, MR &&mr, - LabelSettings labelSettings, - const int verbosity, IO::Logger logger) { +template +bool handleMapByLabelSettings(const lib::Rules::Real &rFirst, // TODO: make rules generic, when needed + const lib::Rules::Real &rSecond, + VertexMap &&m, MR &&mr, + LabelSettings labelSettings, + const int verbosity, IO::Logger logger) { return detail::handleMapToStringOrTerm(rFirst, rSecond, std::forward(m), std::forward(mr), - labelSettings, verbosity, logger); + labelSettings, verbosity, logger); } } // namespace mod::lib::RC diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/Parallel.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/Parallel.hpp index 592e2d0..494d79b 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/Parallel.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/Parallel.hpp @@ -1,14 +1,12 @@ -#ifndef MOD_LIB_RC_MATCH_MAKER_PARALLEL_H -#define MOD_LIB_RC_MATCH_MAKER_PARALLEL_H +#ifndef MOD_LIB_RC_MATCH_MAKER_PARALLEL_HPP +#define MOD_LIB_RC_MATCH_MAKER_PARALLEL_HPP #include #include -#include +#include -namespace mod { -namespace lib { -namespace RC { +namespace mod::lib::RC { struct Parallel { Parallel(int verbosity, IO::Logger logger) : verbosity(verbosity), logger(logger) {} @@ -18,8 +16,8 @@ struct Parallel { const lib::Rules::Real &rSecond, Callback callback, LabelSettings labelSettings) { - using GraphDom = lib::Rules::LabelledRule::LeftGraphType; - using GraphCodom = lib::Rules::LabelledRule::RightGraphType; + using GraphDom = lib::Rules::LabelledRule::SideGraphType; + using GraphCodom = lib::Rules::LabelledRule::SideGraphType; using Map = jla_boost::GraphMorphism::InvertibleVectorVertexMap; const auto &gDom = get_graph(get_labelled_left(rSecond.getDPORule())); const auto &gCodom = get_graph(get_labelled_right(rFirst.getDPORule())); @@ -31,9 +29,6 @@ struct Parallel { IO::Logger logger; }; -} // namespace RC -} // namespace lib -} // namespace mod +} // namespace mod::lib::RC - -#endif /* MOD_LIB_RC_MATCH_MAKER_PARALLEL_H */ \ No newline at end of file +#endif // MOD_LIB_RC_MATCH_MAKER_PARALLEL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/Sub.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/Sub.hpp index 0657957..3369090 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/Sub.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/Sub.hpp @@ -3,24 +3,22 @@ #include #include -#include +#include +#include #include #include -#include #include #include #include #include -#include - #include namespace mod::lib::RC { struct Sub { - using GraphDom = lib::Rules::LabelledRule::LeftGraphType; - using GraphCodom = lib::Rules::LabelledRule::RightGraphType; + using GraphDom = lib::Rules::LabelledRule::SideGraphType; + using GraphCodom = lib::Rules::LabelledRule::SideGraphType; using VertexMapType = jla_boost::GraphMorphism::InvertibleVectorVertexMap; public: explicit Sub(int verbosity, IO::Logger logger, bool allowPartial) @@ -85,7 +83,7 @@ Sub::matchFromPosition(const lib::Rules::Real &rFirst, auto map = VertexMapType(gDom, gCodom); for(std::size_t pId = 0; pId < position.size(); pId++) { if(position[pId].disabled) continue; - if(position[pId].host == rSecond.getDPORule().numLeftComponents) { + if(position[pId].host == get_num_connected_components(get_labelled_left(rSecond.getDPORule()))) { if(!allowPartial) MOD_ABORT; // we should have gotten this continue; } diff --git a/libs/libmod/src/mod/lib/RC/MatchMaker/Super.hpp b/libs/libmod/src/mod/lib/RC/MatchMaker/Super.hpp index 4da11a3..616be02 100644 --- a/libs/libmod/src/mod/lib/RC/MatchMaker/Super.hpp +++ b/libs/libmod/src/mod/lib/RC/MatchMaker/Super.hpp @@ -3,25 +3,23 @@ #include #include -#include +#include +#include #include #include -#include #include #include #include #include -#include - #include #include namespace mod::lib::RC { struct Super { - using GraphDom = lib::Rules::LabelledRule::LeftGraphType; - using GraphCodom = lib::Rules::LabelledRule::RightGraphType; + using GraphDom = lib::Rules::LabelledRule::SideGraphType; + using GraphCodom = lib::Rules::LabelledRule::SideGraphType; using VertexMapType = jla_boost::GraphMorphism::InvertibleVectorVertexMap; public: Super(int verbosity, IO::Logger logger, bool allowPartial, bool enforceConstraints) @@ -129,7 +127,7 @@ Super::matchFromPosition(const lib::Rules::Real &rFirst, auto map = VertexMapType(gDom, gCodom); for(std::size_t pId = 0; pId < position.size(); ++pId) { if(position[pId].disabled) continue; - if(position[pId].host == rFirst.getDPORule().numRightComponents) { + if(position[pId].host == get_num_connected_components(get_labelled_right(rFirst.getDPORule()))) { if(!allowPartial) MOD_ABORT; // we should have gotten this continue; } diff --git a/libs/libmod/src/mod/lib/RC/Result.hpp b/libs/libmod/src/mod/lib/RC/Result.hpp index 0491d7c..d6afeac 100644 --- a/libs/libmod/src/mod/lib/RC/Result.hpp +++ b/libs/libmod/src/mod/lib/RC/Result.hpp @@ -1,12 +1,11 @@ -#ifndef MOD_LIB_RC_RESULT_H -#define MOD_LIB_RC_RESULT_H +#ifndef MOD_LIB_RC_RESULT_HPP +#define MOD_LIB_RC_RESULT_HPP -#include -#include +#include -namespace mod { -namespace lib { -namespace RC { +#include + +namespace mod::lib::RC { //------------------------------------------------------------------------------ // ResultConcept @@ -33,34 +32,40 @@ namespace RC { // Result result; //}; -//------------------------------------------------------------------------------ -// BaseResult -//------------------------------------------------------------------------------ +struct Result { + using Rule = lib::DPO::CombinedRule; + using CombinedGraph = Rule::CombinedGraphType; + + struct RuleResult { // TODO: temporary hax + using GraphType = CombinedGraph; + using SideGraphType = Rule::SideGraphType; + }; -template -struct BaseResult { - using RuleResult = RuleResultT; - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::PushoutRuleConcept)); - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::PushoutRuleConcept)); - BOOST_CONCEPT_ASSERT((jla_boost::GraphDPO::PushoutRuleConcept)); - using GraphResult = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphFirst = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphSecond = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + Result(const Rule &rFirst, const Rule &rSecond) : + rDPO(new Rule()), + mFirstToResult(rFirst.getCombinedGraph(), rDPO->getCombinedGraph()), + mSecondToResult(rSecond.getCombinedGraph(), rDPO->getCombinedGraph()) {} +public: + std::unique_ptr rDPO; + jla_boost::GraphMorphism::InvertibleVectorVertexMap mFirstToResult; + jla_boost::GraphMorphism::InvertibleVectorVertexMap mSecondToResult; +}; - template - BaseResult(const RuleFirst &rFirst, const RuleSecond &rSecond, Args &&... args) - : rResult(std::forward(args)...), - mFirstToResult(get_graph(rFirst), get_graph(rResult)), - mSecondToResult(get_graph(rSecond), get_graph(rResult)) {} + +struct LabelledResult : Result { + using MatchConstraint = GraphMorphism::Constraints::Constraint; + using PropStringType = lib::Rules::PropString; + using PropTermType = lib::Rules::PropTerm; +public: + using Result::Result; public: - RuleResult rResult; - jla_boost::GraphMorphism::InvertibleVectorVertexMap mFirstToResult; - jla_boost::GraphMorphism::InvertibleVectorVertexMap mSecondToResult; + std::unique_ptr pString; + std::unique_ptr pTerm; + std::unique_ptr pStereo; + std::vector> matchConstraints; }; -} // namespace RC -} // namespace lib -} // namespace mod +} // namespace mod::lib::RC -#endif /* MOD_LIB_RC_RESULT_H */ \ No newline at end of file +#endif // MOD_LIB_RC_RESULT_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Visitor/Compound.hpp b/libs/libmod/src/mod/lib/RC/Visitor/Compound.hpp index 9d4511b..877cf17 100644 --- a/libs/libmod/src/mod/lib/RC/Visitor/Compound.hpp +++ b/libs/libmod/src/mod/lib/RC/Visitor/Compound.hpp @@ -1,15 +1,9 @@ -#ifndef MOD_LIB_RC_VISITOR_COMPOUND_H -#define MOD_LIB_RC_VISITOR_COMPOUND_H - -#include -#include +#ifndef MOD_LIB_RC_VISITOR_COMPOUND_HPP +#define MOD_LIB_RC_VISITOR_COMPOUND_HPP #include -namespace mod { -namespace lib { -namespace RC { -namespace Visitor { +namespace mod::lib::RC::Visitor { template struct Compound { @@ -17,7 +11,6 @@ struct Compound { template<> struct Compound<> { - template bool init(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { return true; @@ -28,77 +21,86 @@ struct Compound<> { return true; } public: - template - void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexFirst &vFirst, const VertexResult &vResult) { } + typename VertexFirst, typename VertexResult> + void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const VertexFirst &vFirst, const VertexResult &vResult) {} template - void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexSecond &vSecond, const VertexResult &vResult) { } + typename VertexSecond, typename VertexResult> + void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const VertexSecond &vSecond, const VertexResult &vResult) {} template - void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeFirst &eFirst, const EdgeResult &eResult) { } + typename EdgeFirst, typename EdgeResult> + void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const EdgeFirst &eFirst, const EdgeResult &eResult) {} template - void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeSecond &eSecond, const EdgeResult &eResult) { } + typename EdgeSecond, typename EdgeResult> + void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const EdgeSecond &eSecond, const EdgeResult &eResult) {} public: - template - void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexFirst &vFirst) { } + void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexFirst &vFirst) {} template - void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexSecond &vSecond) { } + void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexSecond &vSecond) {} template - void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexResult &vResult) { } + void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexResult &vResult) {} template - void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeFirst &eFirst) { } + void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeFirst &eFirst) {} template - void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeSecond &eSecond) { } + void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeSecond &eSecond) {} public: - template - void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { } + typename VertexResult, typename VertexSecond> + void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) {} template - void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { } + typename VertexResult, typename VertexSecond> + void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) {} template - void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { } + typename VertexResult, typename VertexSecond> + void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) {} public: - template - void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - EdgeResult eResult, EdgeSecond eSecond) { } + typename EdgeResult, typename EdgeSecond> + void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, + const InvertibleVertexMap &match, const Result &result, + EdgeResult eResult, EdgeSecond eSecond) {} }; template struct Compound : Compound { using Base = Compound; - +public: Compound(Visitor visitor, Visitors ...visitors) - : Base(std::move(visitors)...), visitor(std::move(visitor)) { } + : Base(std::move(visitors)...), visitor(std::move(visitor)) {} template bool init(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { @@ -112,105 +114,114 @@ struct Compound : Compound { return res && Base::template finalize(rFirst, rSecond, match, result); } public: - template - void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexFirst &vFirst, const VertexResult &vResult) { + typename VertexFirst, typename VertexResult> + void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const VertexFirst &vFirst, const VertexResult &vResult) { visitor.template copyVertexFirst(rFirst, rSecond, match, result, vFirst, vResult); Base::template copyVertexFirst(rFirst, rSecond, match, result, vFirst, vResult); } template - void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexSecond &vSecond, const VertexResult &vResult) { + typename VertexSecond, typename VertexResult> + void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const VertexSecond &vSecond, const VertexResult &vResult) { visitor.template copyVertexSecond(rFirst, rSecond, match, result, vSecond, vResult); Base::template copyVertexSecond(rFirst, rSecond, match, result, vSecond, vResult); } template - void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeFirst &eFirst, const EdgeResult &eResult) { + typename EdgeFirst, typename EdgeResult> + void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const EdgeFirst &eFirst, const EdgeResult &eResult) { visitor.template copyEdgeFirst(rFirst, rSecond, match, result, eFirst, eResult); Base::template copyEdgeFirst(rFirst, rSecond, match, result, eFirst, eResult); } template - void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeSecond &eSecond, const EdgeResult &eResult) { + typename EdgeSecond, typename EdgeResult> + void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + const EdgeSecond &eSecond, const EdgeResult &eResult) { visitor.template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); Base::template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); } public: - template - void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexFirst &vFirst) { + void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexFirst &vFirst) { visitor.printVertexFirst(rFirst, rSecond, match, result, s, vFirst); Base::template printVertexFirst(rFirst, rSecond, match, result, s, vFirst); } template - void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexSecond &vSecond) { + void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexSecond &vSecond) { visitor.printVertexSecond(rFirst, rSecond, match, result, s, vSecond); Base::template printVertexSecond(rFirst, rSecond, match, result, s, vSecond); } template - void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexResult &vResult) { + void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexResult &vResult) { visitor.printVertexResult(rFirst, rSecond, match, result, s, vResult); Base::template printVertexResult(rFirst, rSecond, match, result, s, vResult); } template - void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeFirst &eFirst) { + void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeFirst &eFirst) { visitor.printEdgeFirst(rFirst, rSecond, match, result, s, eFirst); Base::template printEdgeFirst(rFirst, rSecond, match, result, s, eFirst); } template - void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeSecond &eSecond) { + void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeSecond &eSecond) { visitor.printEdgeSecond(rFirst, rSecond, match, result, s, eSecond); Base::template printEdgeSecond(rFirst, rSecond, match, result, s, eSecond); } public: - template - void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + typename VertexResult, typename VertexSecond> + void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { visitor.template composeVertexRvsLR(rFirst, rSecond, match, result, vResult, vSecond); Base::template composeVertexRvsLR(rFirst, rSecond, match, result, vResult, vSecond); } template - void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + typename VertexResult, typename VertexSecond> + void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { visitor.template composeVertexLRvsL(rFirst, rSecond, match, result, vResult, vSecond); Base::template composeVertexLRvsL(rFirst, rSecond, match, result, vResult, vSecond); } template - void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + typename VertexResult, typename VertexSecond> + void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { visitor.template composeVertexLRvsLR(rFirst, rSecond, match, result, vResult, vSecond); Base::template composeVertexLRvsLR(rFirst, rSecond, match, result, vResult, vSecond); } public: - template - void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - EdgeResult eResult, EdgeSecond eSecond) { + typename EdgeResult, typename EdgeSecond> + void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, + const InvertibleVertexMap &match, const Result &result, + EdgeResult eResult, EdgeSecond eSecond) { visitor.template setEdgeResultRightFromSecondRight(rFirst, rSecond, match, result, eResult, eSecond); Base::template setEdgeResultRightFromSecondRight(rFirst, rSecond, match, result, eResult, eSecond); } @@ -223,12 +234,9 @@ using Null = Compound<>; template Compound makeVisitor(Visitors... visitors) { - return Compound < Visitors...>(std::move(visitors)...); + return Compound(std::move(visitors)...); } -} // namespace Visitor -} // namespace RC -} // namespace lib -} // namespace mod +} // namespace mod::lib::RC::Visitor -#endif /* MOD_LIB_RC_VISITOR_COMPOUND_H */ \ No newline at end of file +#endif // MOD_LIB_RC_VISITOR_COMPOUND_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Visitor/MatchConstraints.hpp b/libs/libmod/src/mod/lib/RC/Visitor/MatchConstraints.hpp index 1f85ee1..b99b182 100644 --- a/libs/libmod/src/mod/lib/RC/Visitor/MatchConstraints.hpp +++ b/libs/libmod/src/mod/lib/RC/Visitor/MatchConstraints.hpp @@ -1,13 +1,11 @@ -#ifndef MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_H -#define MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_H +#ifndef MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_HPP +#define MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_HPP #include #include +#include -namespace mod { -namespace lib { -namespace RC { -namespace Visitor { +namespace mod::lib::RC::Visitor { namespace detail { template using ConstraintVisitor = lib::GraphMorphism::Constraints::AllVisitor; @@ -20,17 +18,17 @@ using ConstraintSP = lib::GraphMorphism::Constraints::ShortestPath; template struct ConvertFirst : public ConstraintVisitor::LeftGraphType +/* */ typename RuleFirst::SideGraphType /* */ > { - using GraphResult = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphFirst = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphSecond = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + using GraphResult = typename Result::RuleResult::GraphType; + using GraphFirst = typename RuleFirst::GraphType; + using GraphSecond = typename RuleSecond::GraphType; - using GraphResultLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; - using GraphFirstLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; - using GraphSecondLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; + using GraphResultLeft = typename Result::RuleResult::SideGraphType; + using GraphFirstLeft = typename RuleFirst::SideGraphType; + using GraphSecondLeft = typename RuleSecond::SideGraphType; - ConvertFirst(const RuleFirst &rFirst, Result &result) : rFirst(rFirst), result(result) { } + ConvertFirst(const RuleFirst &rFirst, Result &result) : rFirst(rFirst), result(result) {} virtual void operator()(const ConstraintAdj &c) override { auto cResult = std::make_unique >(c); @@ -45,15 +43,13 @@ struct ConvertFirst : public ConstraintVisitorvConstrained; - auto vResult = get(result.mFirstToResult, get_graph(rFirst), get_graph(result.rResult), vFirst); + auto vResult = get(result.mFirstToResult, get_graph(rFirst), result.rDPO->getCombinedGraph(), vFirst); assert(vResult != boost::graph_traits::null_vertex()); cResult->vConstrained = vResult; // std::cout << "WARNING: check transfered constraint for " << rFirst.getName() << " -> " << rSecond.getName() << std::endl; this->cResult = std::move(cResult); } - public: - virtual void operator()(const ConstraintSP &c) override { auto cResult = std::make_unique >(c); MOD_ABORT; @@ -66,35 +62,36 @@ struct ConvertFirst : public ConstraintVisitor struct ConvertSecond : public ConstraintVisitor::LeftGraphType +/* */ typename RuleSecond::SideGraphType /* */ > { - using GraphResult = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphFirst = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - using GraphSecond = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + using GraphResult = typename Result::RuleResult::GraphType; + using GraphFirst = typename RuleFirst::GraphType; + using GraphSecond = typename RuleSecond::GraphType; - using GraphResultLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; - using GraphFirstLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; - using GraphSecondLeft = typename jla_boost::GraphDPO::PushoutRuleTraits::LeftGraphType; + using GraphResultLeft = typename Result::RuleResult::SideGraphType; + using GraphFirstLeft = typename RuleFirst::SideGraphType; + using GraphSecondLeft = typename RuleSecond::SideGraphType; ConvertSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) - : rFirst(rFirst), rSecond(rSecond), match(match), result(result) { } + : rFirst(rFirst), rSecond(rSecond), match(match), result(result) {} virtual void operator()(const ConstraintAdj &c) override { const auto vSecond = c.vConstrained; - const auto vResult = get(result.mSecondToResult, get_graph(rSecond), get_graph(result.rResult), vSecond); + const auto vResult = get(result.mSecondToResult, get_graph(rSecond), result.rDPO->getCombinedGraph(), vSecond); // vResult may be null_vertex if(vResult == boost::graph_traits::null_vertex()) { // std::cout << "WARNING: constrained vertex " << vSecondId << " deleted in " << rFirst.getName() << " -> " << rSecond.getName() << std::endl; return; } - auto mResult = membership(result.rResult, vResult); - if(mResult == jla_boost::GraphDPO::Membership::Right) { + auto mResult = result.rDPO->getCombinedGraph()[vResult].membership; + if(mResult == lib::DPO::Membership::R) { // std::cout << "WARNING: constrained vertex " << vSecondId << " changed to right side in " << rFirst.getName() << " -> " << rSecond.getName() << std::endl; return; } - if(get(match, get_graph(get_labelled_left(rSecond)), get_graph(get_labelled_right(rFirst)), vSecond) != boost::graph_traits::null_vertex()) { + if(get(match, get_graph(get_labelled_left(rSecond)), get_graph(get_labelled_right(rFirst)), vSecond) != + boost::graph_traits::null_vertex()) { // std::cout << "WARNING: maybe missing constraint on " << vNew << " for " << rFirst.getName() << " -> " << rSecond.getName() << std::endl; return; } @@ -106,8 +103,7 @@ struct ConvertSecond : public ConstraintVisitorvertexTerms.clear(); cResult->edgeTerms.clear(); break; - case LabelType::Term: - { + case LabelType::Term: { cResult->vertexLabels.clear(); cResult->edgeLabels.clear(); // we need to make sure the terms referred to are actually in the result machine, @@ -116,7 +112,7 @@ struct ConvertSecond : public ConstraintVisitoredgeTerms); cResult->vertexTerms.clear(); cResult->edgeTerms.clear(); - auto &m = getMachine(*result.rResult.pTerm); + auto &m = getMachine(*result.pTerm); const auto handleTerm = [&m](const auto tSecond) { m.verify(); // lib::IO::Term::Write::wam(m, lib::Term::getStrings(), std::cout << "Copy " << addr << "\n"); @@ -125,8 +121,8 @@ struct ConvertSecond : public ConstraintVisitorvertexTerms.insert(handleTerm(tSecond)); - for(const auto tSecond : eTerms) cResult->edgeTerms.insert(handleTerm(tSecond)); + for(const auto tSecond: vTerms) cResult->vertexTerms.insert(handleTerm(tSecond)); + for(const auto tSecond: eTerms) cResult->edgeTerms.insert(handleTerm(tSecond)); break; } } @@ -134,10 +130,10 @@ struct ConvertSecond : public ConstraintVisitor " << rSecond.getName() << std::endl; } public: - virtual void operator()(const ConstraintSP &c) override { - auto vResultSrc = get(result.mSecondToResult, get_graph(rSecond), get_graph(result.rResult), c.vSrc); - auto vResultTar = get(result.mSecondToResult, get_graph(rSecond), get_graph(result.rResult), c.vTar); + const auto &gResult = result.rDPO->getCombinedGraph(); + auto vResultSrc = get(result.mSecondToResult, get_graph(rSecond), gResult, c.vSrc); + auto vResultTar = get(result.mSecondToResult, get_graph(rSecond), gResult, c.vTar); auto isSrcDeleted = vResultSrc == boost::graph_traits::null_vertex(); auto isTarDeleted = vResultTar == boost::graph_traits::null_vertex(); if(isSrcDeleted && isTarDeleted) { @@ -147,8 +143,8 @@ struct ConvertSecond : public ConstraintVisitor >(c); @@ -168,31 +164,40 @@ struct ConvertSecond : public ConstraintVisitor struct MatchConstraints : Null { + MatchConstraints(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) + : rFirst(rFirst), rSecond(rSecond) {} + + template + bool finalize(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { + using RuleFirst = lib::Rules::LabelledRule; + using RuleSecond = lib::Rules::LabelledRule; + + assert(&dpoFirst == &rFirst.getRule()); + assert(&dpoSecond == &rSecond.getRule()); - template - bool finalize(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { if(!getConfig().rc.composeConstraints.get()) return true; - for(const auto &cFirst : rFirst.leftMatchConstraints) { + for(const auto &cFirst: get_match_constraints(get_labelled_left(rFirst))) { detail::ConvertFirst visitor(rFirst, result); cFirst->accept(visitor); assert(visitor.cResult); - result.rResult.leftMatchConstraints.push_back(std::move(visitor.cResult)); + result.matchConstraints.push_back(std::move(visitor.cResult)); } - for(const auto &cSecond : rSecond.leftMatchConstraints) { - detail::ConvertSecond visitor(rFirst, rSecond, match, result); + for(const auto &cSecond: get_match_constraints(get_labelled_left(rSecond))) { + detail::ConvertSecond visitor( + rFirst, rSecond, match, result); cSecond->accept(visitor); if(visitor.cResult) - result.rResult.leftMatchConstraints.push_back(std::move(visitor.cResult)); + result.matchConstraints.push_back(std::move(visitor.cResult)); } return true; } +private: + const lib::Rules::LabelledRule &rFirst; + const lib::Rules::LabelledRule &rSecond; }; -} // namespace Visitor -} // namespace RC -} // namespace lib -} // namespace mod - +} // namespace mod::lib::RC::Visitor -#endif /* MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_H */ \ No newline at end of file +#endif // MOD_LIB_RC_VISITOR_MATCH_CONSTRAINTS_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Visitor/Stereo.hpp b/libs/libmod/src/mod/lib/RC/Visitor/Stereo.hpp index d3d7c8c..1775828 100644 --- a/libs/libmod/src/mod/lib/RC/Visitor/Stereo.hpp +++ b/libs/libmod/src/mod/lib/RC/Visitor/Stereo.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_LIB_RC_VISITOR_STEREO_H -#define MOD_LIB_RC_VISITOR_STEREO_H +#ifndef MOD_LIB_RC_VISITOR_STEREO_HPP +#define MOD_LIB_RC_VISITOR_STEREO_HPP #include #include @@ -10,10 +10,7 @@ // TODO: the edge categories are probably not instantiated correctly if the Any category is used -namespace mod { -namespace lib { -namespace RC { -namespace Visitor { +namespace mod::lib::RC::Visitor { // During the algorithm we: // - allocate space in the data arrays @@ -24,7 +21,7 @@ namespace Visitor { // - instantiate and compose embeddings struct Stereo { - using Membership = jla_boost::GraphDPO::Membership; + using Membership = lib::DPO::Membership; template static auto NullVertex() { @@ -36,43 +33,48 @@ struct Stereo { return boost::graph_traits::null_vertex(); } public: - std::vector vDataLeft, vDataRight; + std::vector vDataLeft, vDataRight; std::vector eDataLeft, eDataRight; std::vector vertexInContext, edgeInContext; private: - - template - auto getVertexFirstChecked(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const VertexSecond &vSecond) const { + template + auto getVertexFirstChecked(const InvertibleVertexMap &match, const VertexSecond &vSecond) const { const auto &gCodom = get_graph(get_labelled_right(rFirst)); const auto &gDom = get_graph(get_labelled_left(rSecond)); const auto m = membership(rSecond, vSecond); - if(m == Membership::Right) return NullVertex(gDom); + if(m == Membership::R) return NullVertex(gDom); else return get(match, gDom, gCodom, vSecond); } - template - auto getVertexSecondChecked(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const VertexFirst &vFirst) const { + template + auto getVertexSecondChecked(const InvertibleVertexMap &match, const VertexFirst &vFirst) const { const auto &gCodom = get_graph(get_labelled_right(rFirst)); const auto &gDom = get_graph(get_labelled_left(rSecond)); const auto m = membership(rFirst, vFirst); - if(m == Membership::Left) return NullVertex(gCodom); + if(m == Membership::L) return NullVertex(gCodom); else return get_inverse(match, gDom, gCodom, vFirst); } public: - - template - bool init(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { + Stereo(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) + : rFirst(rFirst), rSecond(rSecond) {} + + template + bool init(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { + assert(&dpoFirst == &rFirst.getRule()); + assert(&dpoSecond == &rSecond.getRule()); return true; } - template - bool finalize(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { + template + bool finalize(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { const auto &gFirst = get_graph(rFirst); const auto &gSecond = get_graph(rSecond); - const auto &gResult = get_graph(result.rResult); - using GraphFirst = typename std::decay < decltype(gFirst)>::type; - using GraphSecond = typename std::decay < decltype(gSecond)>::type; - const auto getGeoName = [](const auto &vGeo) -> const std::string& { + const auto &gResult = result.rDPO->getCombinedGraph(); + using GraphFirst = typename std::decay::type; + using GraphSecond = typename std::decay::type; + const auto getGeoName = [](const auto &vGeo) -> const std::string & { return lib::Stereo::getGeometryGraph().getGraph()[vGeo].name; }; const auto copyAllFromSide = [&]( @@ -86,7 +88,7 @@ struct Stereo { const auto &conf = *get_stereo(glSide)[vInput]; data.vGeometry = conf.getGeometryVertex(); if(Verbose) std::cout << "\tGeometry: " << getGeoName(data.vGeometry) << "\n"; - for(const auto &emb : conf) { + for(const auto &emb: conf) { switch(emb.type) { case lib::Stereo::EmbeddingEdge::Type::LonePair: // offsets should be corrected somewhere else @@ -102,8 +104,8 @@ struct Stereo { const auto vAdjInput = target(eInput, gInput); if(Verbose) { std::cout << "\tmapping edge: (" - << get(boost::vertex_index_t(), gInput, vInput) << ", " - << get(boost::vertex_index_t(), gInput, vAdjInput) << ")\n"; + << get(boost::vertex_index_t(), gInput, vInput) << ", " + << get(boost::vertex_index_t(), gInput, vAdjInput) << ")\n"; } const auto vAdjResult = get(mInputToResult, gInput, gResult, vAdjInput); if(safe) assert(vAdjResult != NullVertex(gResult)); @@ -124,9 +126,9 @@ struct Stereo { }(); if(Verbose) { std::cout << "\tto edge: (" - << get(boost::vertex_index_t(), gResult, vResult) << ", " - << get(boost::vertex_index_t(), gResult, vAdjResult) << "), offset = " - << eResultOffset << " (of " << out_degree(vResult, gResultSide) << ")\n"; + << get(boost::vertex_index_t(), gResult, vResult) << ", " + << get(boost::vertex_index_t(), gResult, vAdjResult) << "), offset = " + << eResultOffset << " (of " << out_degree(vResult, gResultSide) << ")\n"; } const auto eResult = out_edges(vResult, gResult).first[eResultOffset]; const auto eIdResult = get(boost::edge_index_t(), gResult, eResult); @@ -149,29 +151,33 @@ struct Stereo { const auto handleOnly = [&](auto vResult, auto vInput, const auto &rInput, const auto &mInputToResult) { const auto &gInput = get_graph(rInput); // yay, just copy the embedding, both in the right and left side - const auto m = membership(result.rResult, vResult); - if(m != Membership::Right) { + const auto m = gResult[vResult].membership; + if(m != Membership::R) { // copy left if(Verbose) std::cout << "\tLeft:\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rInput), vInput, gInput, mInputToResult, vResult, get_left(result.rResult), vDataLeft, eDataLeft); + const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rInput), vInput, gInput, + mInputToResult, vResult, result.rDPO->getLProjected(), vDataLeft, + eDataLeft); (void) partial; assert(!partial); } - if(m != Membership::Left) { + if(m != Membership::L) { // copy right if(Verbose) std::cout << "\tRight:\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rInput), vInput, gInput, mInputToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); - (void) partial; + const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rInput), vInput, gInput, + mInputToResult, vResult, result.rDPO->getRProjected(), vDataRight, + eDataRight); + (void) partial; assert(!partial); } }; const auto handleBoth = [&](auto vResult, auto vFirst, auto vSecond) { using EmbEdge = lib::Stereo::EmbeddingEdge; // const auto &geo = lib::Stereo::getGeometryGraph(); - const auto m = membership(result.rResult, vResult); + const auto m = gResult[vResult].membership; const auto vResultId = get(boost::vertex_index_t(), gResult, vResult); - const auto &gR1 = get_right(rFirst); - const auto &gL2 = get_left(rSecond); + const auto &gR1 = getR(rFirst.getRule()); + const auto &gL2 = getL(rSecond.getRule()); const auto &confR1 = *get_stereo(get_labelled_right(rFirst))[vFirst]; const auto &confL2 = *get_stereo(get_labelled_left(rSecond))[vSecond]; // Let's firs tmake a map of the offsets to each other. @@ -182,13 +188,15 @@ struct Stereo { offsetSecondToFirst(confL2.degree(), -1); { // heuristically we 'usually' have gSecondL as a subgraph of gFirstR std::size_t offsetFirst = 0; - for(auto oeFirst = out_edges(vFirst, gR1); oeFirst.first != oeFirst.second; ++oeFirst.first, (void) ++offsetFirst) { + for(auto oeFirst = out_edges(vFirst, gR1); + oeFirst.first != oeFirst.second; ++oeFirst.first, (void) ++offsetFirst) { const auto eFirst = *oeFirst.first; const auto vAdjFirst = target(eFirst, gR1); const auto vAdjSecond = get_inverse(match, gL2, gR1, vAdjFirst); if(vAdjSecond == NullVertex(gSecond)) continue; std::size_t offsetSecond = 0; - for(auto oeSecond = out_edges(vSecond, gL2); oeSecond.first != oeSecond.second; ++oeSecond.first, (void) ++offsetSecond) { + for(auto oeSecond = out_edges(vSecond, gL2); + oeSecond.first != oeSecond.second; ++oeSecond.first, (void) ++offsetSecond) { const auto eSecondCand = *oeSecond.first; if(target(eSecondCand, gL2) == vAdjSecond) { offsetFirstToSecond[offsetFirst] = offsetSecond; @@ -206,17 +214,19 @@ struct Stereo { // TODO: how do we merge virtuals? MOD_ABORT; } - const bool firstToSecondSubgraph = std::none_of(offsetFirstToSecond.begin(), offsetFirstToSecond.end(), [&](auto o) { - return o == -1; - }); - const bool secondToFirstSubgraph = std::none_of(offsetSecondToFirst.begin(), offsetSecondToFirst.end(), [&](auto o) { - return o == -1; - }); + const bool firstToSecondSubgraph = std::none_of(offsetFirstToSecond.begin(), offsetFirstToSecond.end(), + [&](auto o) { + return o == -1; + }); + const bool secondToFirstSubgraph = std::none_of(offsetSecondToFirst.begin(), offsetSecondToFirst.end(), + [&](auto o) { + return o == -1; + }); const auto geoR1 = confR1.getGeometryVertex(); const auto geoL2 = confL2.getGeometryVertex(); - if(m != Membership::Right) { - assert(membership(rFirst, vFirst) == Membership::Context); + if(m != Membership::R) { + assert(membership(rFirst, vFirst) == Membership::K); // auto &data = vDataLeft[vResultId]; // const auto &eData = eDataLeft; const auto firstInContext = get_stereo(rFirst).inContext(vFirst); @@ -224,14 +234,16 @@ struct Stereo { const auto geoL1 = confL1.getGeometryVertex(); if(Verbose) std::cout << "\tHandling L\n" - << "\t\tGeo L1: " << getGeoName(geoL1) << "\n" - << "\t\tGeo R1: " << getGeoName(geoR1) << "\n" - << "\t\tGeo L2: " << getGeoName(geoL2) << "\n"; + << "\t\tGeo L1: " << getGeoName(geoL1) << "\n" + << "\t\tGeo R1: " << getGeoName(geoR1) << "\n" + << "\t\tGeo L2: " << getGeoName(geoL2) << "\n"; if(firstInContext) { if(Verbose) std::cout << "\t\tFirst stereo in context\n"; if(secondToFirstSubgraph) { if(Verbose) std::cout << "\t\tSecond-to-first subgraph: copy and map L1/R1 to L\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rFirst), vFirst, gFirst, result.mFirstToResult, vResult, get_left(result.rResult), vDataLeft, eDataLeft); + const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rFirst), vFirst, gFirst, + result.mFirstToResult, vResult, result.rDPO->getLProjected(), + vDataLeft, eDataLeft); (void) partial; assert(!partial); } else if(firstToSecondSubgraph) { @@ -245,7 +257,9 @@ struct Stereo { if(Verbose) std::cout << "\t\tFirst stereo changes\n"; if(secondToFirstSubgraph) { if(Verbose) std::cout << "\t\tSecond-to-first subgraph: copy and map L1 to L\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rFirst), vFirst, gFirst, result.mFirstToResult, vResult, get_left(result.rResult), vDataLeft, eDataLeft); + const bool partial = copyAllFromSide(std::true_type(), get_labelled_left(rFirst), vFirst, gFirst, + result.mFirstToResult, vResult, result.rDPO->getLProjected(), + vDataLeft, eDataLeft); (void) partial; assert(!partial); } else if(firstToSecondSubgraph) { @@ -257,8 +271,8 @@ struct Stereo { } } } - if(m != Membership::Left) { - assert(membership(rSecond, vSecond) == Membership::Context); + if(m != Membership::L) { + assert(membership(rSecond, vSecond) == Membership::K); auto &data = vDataRight[vResultId]; const auto &eData = eDataRight; const auto secondInContext = get_stereo(rSecond).inContext(vSecond); @@ -266,20 +280,24 @@ struct Stereo { const auto geoR2 = confR2.getGeometryVertex(); if(Verbose) std::cout << "\tHandling R\n" - << "\t\tGeo R1: " << getGeoName(geoR1) << "\n" - << "\t\tGeo L2: " << getGeoName(geoL2) << "\n" - << "\t\tGeo R2: " << getGeoName(geoR2) << "\n"; + << "\t\tGeo R1: " << getGeoName(geoR1) << "\n" + << "\t\tGeo L2: " << getGeoName(geoL2) << "\n" + << "\t\tGeo R2: " << getGeoName(geoR2) << "\n"; if(secondInContext) { if(Verbose) std::cout << "\t\tSecond stereo in context\n"; if(firstToSecondSubgraph) { if(Verbose) std::cout << "\t\tFirst-to-second subgraph: copy and map L2/R2 to R\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, result.mSecondToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); + const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, + result.mSecondToResult, vResult, result.rDPO->getRProjected(), + vDataRight, eDataRight); (void) partial; assert(!partial); } else if(secondToFirstSubgraph) { if(Verbose) std::cout << "\t\tSecond-to-first subgraph: copy and map R1 to R\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rFirst), vFirst, gFirst, result.mFirstToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); - (void) partial; + const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rFirst), vFirst, gFirst, + result.mFirstToResult, vResult, result.rDPO->getRProjected(), + vDataRight, eDataRight); + (void) partial; assert(!partial); } else { if(Verbose) std::cout << "\t\tNon-subgraph: do a merge\n"; @@ -289,19 +307,22 @@ struct Stereo { if(Verbose) std::cout << "\t\tSecond stereo changes\n"; if(firstToSecondSubgraph) { if(Verbose) std::cout << "\t\tFirst-to-second subgraph: copy and map R2 to R\n"; - const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, result.mSecondToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); - (void) partial; + const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, + result.mSecondToResult, vResult, result.rDPO->getRProjected(), + vDataRight, eDataRight); + (void) partial; assert(!partial); } else if(secondToFirstSubgraph) { if(Verbose) { std::cout << "\t\tSecond-to-first subgraph:\n" - << "\t\t\t- Copy all from R2.\n" - << "\t\t\t- Copy unmatched from R1.\n" - << "\t\t\t- Match R2 stereo onto the result and check if the pushout is valid.\n" - ; + << "\t\t\t- Copy all from R2.\n" + << "\t\t\t- Copy unmatched from R1.\n" + << "\t\t\t- Match R2 stereo onto the result and check if the pushout is valid.\n"; std::cout << "\tCopying all from R2\n"; } - const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, result.mSecondToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); + const bool partial = copyAllFromSide(std::true_type(), get_labelled_right(rSecond), vSecond, gSecond, + result.mSecondToResult, vResult, result.rDPO->getRProjected(), + vDataRight, eDataRight); (void) partial; assert(!partial); const auto sizeAfterR2 = data.edges.size(); @@ -312,15 +333,15 @@ struct Stereo { const auto &gBaseInput = get_graph(rFirst); const auto &glSide = get_labelled_right(rFirst); const auto &glSideOther = get_labelled_left(rSecond); - const auto &gResultSide = get_right(result.rResult); + const auto &gResultSide = result.rDPO->getRProjected(); const auto &mInputToResult = result.mFirstToResult; const auto mapToOtherInput = [&](const auto &vFirst) { // the "this->" part is needed to get GCC to not seg. fault - return this->getVertexSecondChecked(rFirst, rSecond, match, vFirst); + return this->getVertexSecondChecked(match, vFirst); }; // the rest should be side invariant const auto &conf = *get_stereo(glSide)[vInput]; - for(const auto &emb : conf) { + for(const auto &emb: conf) { switch(emb.type) { case lib::Stereo::EmbeddingEdge::Type::LonePair: case lib::Stereo::EmbeddingEdge::Type::Radical: @@ -333,8 +354,8 @@ struct Stereo { const auto vAdjInput = target(eInput, gInput); if(Verbose) { std::cout << "\tmapping edge: (" - << get(boost::vertex_index_t(), gInput, vInput) << ", " - << get(boost::vertex_index_t(), gInput, vAdjInput) << ")\n"; + << get(boost::vertex_index_t(), gInput, vInput) << ", " + << get(boost::vertex_index_t(), gInput, vAdjInput) << ")\n"; } const auto vAdjResult = get(mInputToResult, gBaseInput, gResult, vAdjInput); if(vAdjResult == NullVertex(gResult)) { @@ -354,19 +375,21 @@ struct Stereo { if(Verbose) std::cout << "\tnot matched, due to vAdjInputOther = null\n"; return false; } - for(auto eInputOther : asRange(out_edges(vInputOther, gInputOther))) { + for(auto eInputOther: asRange(out_edges(vInputOther, gInputOther))) { if(target(eInputOther, gInputOther) != vAdjInputOther) { if(Verbose) { std::cout << "\tcand = (" - << get(boost::vertex_index_t(), gInputOther, vInputOther) << ", " - << get(boost::vertex_index_t(), gInputOther, vAdjInputOther) << ") not it\n"; + << get(boost::vertex_index_t(), gInputOther, vInputOther) << ", " + << get(boost::vertex_index_t(), gInputOther, vAdjInputOther) + << ") not it\n"; } continue; } if(Verbose) { std::cout << "\tcand = (" - << get(boost::vertex_index_t(), gInputOther, vInputOther) << ", " - << get(boost::vertex_index_t(), gInputOther, vAdjInputOther) << ") is it\n"; + << get(boost::vertex_index_t(), gInputOther, vInputOther) << ", " + << get(boost::vertex_index_t(), gInputOther, vAdjInputOther) + << ") is it\n"; } // TODO: shouldn't we check the membership as well? return true; @@ -382,17 +405,18 @@ struct Stereo { // now find the corresponding edge in result const auto eResultOffset = [&]() { const auto oeResult = out_edges(vResult, gResultSide); - const auto oeIter = std::find_if(oeResult.first, oeResult.second, [&](const auto &eResultCand) { - return target(eResultCand, gResult) == vAdjResult; - }); + const auto oeIter = std::find_if(oeResult.first, oeResult.second, + [&](const auto &eResultCand) { + return target(eResultCand, gResult) == vAdjResult; + }); assert(oeIter != oeResult.second); return std::distance(oeResult.first, oeIter); }(); if(Verbose) { std::cout << "\tto edge: (" - << get(boost::vertex_index_t(), gResult, vResult) << ", " - << get(boost::vertex_index_t(), gResult, vAdjResult) << "), offset = " - << eResultOffset << " (of " << out_degree(vResult, gResultSide) << ")\n"; + << get(boost::vertex_index_t(), gResult, vResult) << ", " + << get(boost::vertex_index_t(), gResult, vAdjResult) << "), offset = " + << eResultOffset << " (of " << out_degree(vResult, gResultSide) << ")\n"; } const auto eResult = out_edges(vResult, gResult).first[eResultOffset]; const auto eIdResult = get(boost::edge_index_t(), gResult, eResult); @@ -413,12 +437,15 @@ struct Stereo { } } if(false) { - copyAllFromSide(std::false_type(), get_labelled_right(rSecond), vSecond, gSecond, result.mSecondToResult, vResult, get_right(result.rResult), vDataRight, eDataRight); + copyAllFromSide(std::false_type(), get_labelled_right(rSecond), vSecond, gSecond, result.mSecondToResult, + vResult, result.rDPO->getRProjected(), vDataRight, eDataRight); const auto prevEmbSize = data.edges.size(); if(data.edges.size() > prevEmbSize) { // we the fixation must be free - if(Verbose) std::cout << "\tfix: data.edges.size() = " << data.edges.size() << " > " << prevEmbSize << " = prevEmbSize, so set free (was " << data.fix << ")\n"; + if(Verbose) + std::cout << "\tfix: data.edges.size() = " << data.edges.size() << " > " << prevEmbSize + << " = prevEmbSize, so set free (was " << data.fix << ")\n"; data.fix = lib::Stereo::Fixation::free(); } else { if(Verbose) std::cout << "\tfix: not changing it (" << data.fix << ")\n"; @@ -428,9 +455,9 @@ struct Stereo { }; // handleBoth() if(Verbose) std::cout << "Stereo Finalization\n" << std::string(80, '-') << '\n'; const auto &gGeometry = lib::Stereo::getGeometryGraph().getGraph(); - for(auto vResult : asRange(vertices(gResult))) { + for(auto vResult: asRange(vertices(gResult))) { if(Verbose) std::cout << "Result vertex: " << get(boost::vertex_index_t(), gResult, vResult) << "\n"; - const auto m = membership(result.rResult, vResult); + const auto m = result.rDPO->getCombinedGraph()[vResult].membership; const auto vResultId = get(boost::vertex_index_t(), gResult, vResult); // If vResult is in only first or only second, we should be able to just copy the embedding. const auto vFirst = get_inverse(result.mFirstToResult, gFirst, gResult, vResult); @@ -440,7 +467,7 @@ struct Stereo { const auto instantiateConfs = [&]() { if(Verbose) std::cout << "\tinstantiating configurations\n"; // TODO: we should probably correct LonePair and Radical offsets here - if(m != Membership::Right) { + if(m != Membership::R) { auto &data = vDataLeft[vResultId]; data.configuration = gGeometry[data.vGeometry].constructor( data.edges.data(), data.edges.data() + data.edges.size(), data.fix, ssErr); @@ -450,7 +477,7 @@ struct Stereo { MOD_ABORT; } } - if(m != Membership::Left) { + if(m != Membership::L) { auto &data = vDataRight[vResultId]; data.configuration = gGeometry[data.vGeometry].constructor( data.edges.data(), data.edges.data() + data.edges.size(), data.fix, ssErr); @@ -476,16 +503,15 @@ struct Stereo { } } - using GraphResult = typename std::decay < decltype(gResult)>::type; + using GraphResult = typename std::decay::type; using ResultVertex = typename boost::graph_traits::vertex_descriptor; using ResultEdge = typename boost::graph_traits::edge_descriptor; struct Inf { const GraphResult &g; - std::vector vData; + std::vector vData; std::vector eData; public: - std::unique_ptr extractConfiguration(const ResultVertex &v) { auto vId = get(boost::vertex_index_t(), g, v); assert(vData[vId].configuration); @@ -504,18 +530,19 @@ struct Stereo { const auto edgeInContext = [this, &gResult](const auto &e) -> bool { return this->edgeInContext[get(boost::edge_index_t(), gResult, e)]; }; - result.rResult.pStereo.reset(new lib::Rules::PropStereoCore(gResult, - Inf{gResult, std::move(vDataLeft), std::move(eDataLeft)}, Inf{gResult, std::move(vDataRight), std::move(eDataRight)}, - vertexInContext, edgeInContext)); + result.pStereo.reset(new lib::Rules::PropStereo( + *result.rDPO, + Inf{gResult, std::move(vDataLeft), std::move(eDataLeft)}, + Inf{gResult, std::move(vDataRight), std::move(eDataRight)}, + vertexInContext, edgeInContext)); return true; } public: - - template - void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexFirst &vFirst, const VertexResult &vResult) { - const auto &gResult = get_graph(result.rResult); + template + void copyVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const VertexFirst &vFirst, const VertexResult &vResult) { + const auto &gResult = result.rDPO->getCombinedGraph(); const auto vResultId = get(boost::vertex_index_t(), gResult, vResult); (void) vResultId; assert(vDataLeft.size() + 1 == num_vertices(gResult)); @@ -527,11 +554,12 @@ struct Stereo { vertexInContext.push_back(get_stereo(rFirst).inContext(vFirst)); } - template - void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexSecond &vSecond, const VertexResult &vResult) { - const auto &gResult = get_graph(result.rResult); + template + void copyVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const VertexSecond &vSecond, const VertexResult &vResult) { + const auto &gResult = result.rDPO->getCombinedGraph(); const auto vResultId = get(boost::vertex_index_t(), gResult, vResult); (void) vResultId; assert(vDataLeft.size() + 1 == num_vertices(gResult)); @@ -543,15 +571,16 @@ struct Stereo { vertexInContext.push_back(get_stereo(rSecond).inContext(vSecond)); } - template - void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeFirst &eFirst, const EdgeResult &eResult) { - using GraphSecond = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + template + void copyEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const EdgeFirst &eFirst, const EdgeResult &eResult) { + using GraphSecond = lib::Rules::LabelledRule::GraphType; const auto m = membership(rFirst, eFirst); const auto &gFirst = get_graph(rFirst); const auto &gSecond = get_graph(rSecond); - const auto &gResult = get_graph(result.rResult); + const auto &gResult = result.rDPO->getCombinedGraph(); const auto eResultId = get(boost::edge_index_t(), gResult, eResult); assert(eDataLeft.size() + 1 == num_edges(gResult)); assert(eDataLeft.size() == eDataRight.size()); @@ -560,20 +589,20 @@ struct Stereo { eDataLeft.emplace_back(); eDataRight.emplace_back(); edgeInContext.push_back(get_stereo(rFirst).inContext(eFirst)); - if(m != Membership::Right) { + if(m != Membership::R) { // copy from rFirst left eDataLeft[eResultId] = get_stereo(get_labelled_left(rFirst))[eFirst]; } - if(m != Membership::Left) { + if(m != Membership::L) { // copy from the epimorphic overlap // if the edge is matched, get data from the unifier // otherwise from rFirst - const auto vSecondSrc = getVertexSecondChecked(rFirst, rSecond, match, source(eFirst, gFirst)); - const auto vSecondTar = getVertexSecondChecked(rFirst, rSecond, match, target(eFirst, gFirst)); + const auto vSecondSrc = getVertexSecondChecked(match, source(eFirst, gFirst)); + const auto vSecondTar = getVertexSecondChecked(match, target(eFirst, gFirst)); const bool isMatched = [&]() { if(vSecondSrc == NullVertex()) return false; if(vSecondTar == NullVertex()) return false; - for(auto eSecond : asRange(out_edges(vSecondSrc, gSecond))) { + for(auto eSecond: asRange(out_edges(vSecondSrc, gSecond))) { if(target(eSecond, gSecond) != vSecondTar) continue; // TODO: shouldn't we check the membership as well? return true; @@ -584,20 +613,22 @@ struct Stereo { eDataRight[eResultId] = get_stereo(get_labelled_right(rFirst))[eFirst]; } else { auto data = get_prop(lib::GraphMorphism::StereoDataT(), match); - eDataRight[eResultId] = get_edge_category(data, get_labelled_left(rSecond), get_labelled_right(rFirst), eFirst); + eDataRight[eResultId] = get_edge_category(data, get_labelled_left(rSecond), get_labelled_right(rFirst), + eFirst); } } } - template - void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeSecond &eSecond, const EdgeResult &eResult) { - using GraphFirst = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; - const auto m = membership(result.rResult, eResult); + template + void copyEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const EdgeSecond &eSecond, const EdgeResult &eResult) { + using GraphFirst = lib::Rules::LabelledRule::GraphType; const auto &gFirst = get_graph(rFirst); const auto &gSecond = get_graph(rSecond); - const auto &gResult = get_graph(result.rResult); + const auto &gResult = result.rDPO->getCombinedGraph(); + const auto m = gResult[eResult].membership; const auto eResultId = get(boost::edge_index_t(), gResult, eResult); assert(eDataLeft.size() + 1 == num_edges(gResult)); assert(eDataLeft.size() == eDataRight.size()); @@ -606,16 +637,16 @@ struct Stereo { eDataLeft.emplace_back(); eDataRight.emplace_back(); edgeInContext.push_back(get_stereo(rSecond).inContext(eSecond)); - if(m != Membership::Right) { + if(m != Membership::R) { // copy from the epimorphic overlap // if the edge is matched, get data from the unifier // otherwise from rSecond - const auto vFirstSrc = getVertexFirstChecked(rFirst, rSecond, match, source(eSecond, gSecond)); - const auto vFirstTar = getVertexFirstChecked(rFirst, rSecond, match, target(eSecond, gSecond)); + const auto vFirstSrc = getVertexFirstChecked(match, source(eSecond, gSecond)); + const auto vFirstTar = getVertexFirstChecked(match, target(eSecond, gSecond)); const bool isMatched = [&]() { if(vFirstSrc == NullVertex()) return false; if(vFirstTar == NullVertex()) return false; - for(auto eFirst : asRange(out_edges(vFirstSrc, gFirst))) { + for(auto eFirst: asRange(out_edges(vFirstSrc, gFirst))) { if(target(eFirst, gFirst) != vFirstTar) continue; // TODO: shouldn't we check the membership as well? return true; @@ -629,13 +660,12 @@ struct Stereo { eDataLeft[eResultId] = get_stereo(get_labelled_left(rSecond))[eSecond]; } } - if(m != Membership::Left) { + if(m != Membership::L) { // copy from rSecond right eDataRight[eResultId] = get_stereo(get_labelled_right(rSecond))[eSecond]; } } private: - template void printData(std::ostream &s, GeometryVertex geometryVertex, IterEmb firstEmb, IterEmb lastEmb) { const auto &gGeometry = lib::Stereo::getGeometryGraph().getGraph(); @@ -663,156 +693,163 @@ struct Stereo { } s << "]"; } - public: - - template - void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexFirst &vFirst) { + template + void printVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexFirst &vFirst) { const auto m = membership(rFirst, vFirst); s << ", Stereo("; - if(m != Membership::Right) { + if(m != Membership::R) { const auto &conf = *get_stereo(get_labelled_left(rFirst))[vFirst]; printData(s, conf.getGeometryVertex(), conf.begin(), conf.end()); } else s << "<>"; s << " ->"; if(!get_stereo(rFirst).inContext(vFirst)) s << "!"; s << " "; - if(m != Membership::Left) { + if(m != Membership::L) { const auto &conf = *get_stereo(get_labelled_right(rFirst))[vFirst]; printData(s, conf.getGeometryVertex(), conf.begin(), conf.end()); } else s << "<>"; s << ")"; } - template - void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexSecond &vSecond) { + template + void printVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexSecond &vSecond) { const auto m = membership(rSecond, vSecond); s << ", Stereo("; - if(m != Membership::Right) { + if(m != Membership::R) { const auto &conf = *get_stereo(get_labelled_left(rSecond))[vSecond]; printData(s, conf.getGeometryVertex(), conf.begin(), conf.end()); } else s << "<>"; s << " ->"; if(!get_stereo(rSecond).inContext(vSecond)) s << "!"; s << " "; - if(m != Membership::Left) { + if(m != Membership::L) { const auto &conf = *get_stereo(get_labelled_right(rSecond))[vSecond]; printData(s, conf.getGeometryVertex(), conf.begin(), conf.end()); } else s << "<>"; s << ")"; } - template - void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexResult &vResult) { - const auto m = membership(result.rResult, vResult); - const auto &gResult = get_graph(result.rResult); + template + void printVertexResult(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexResult &vResult) { + const auto &gResult = result.rDPO->getCombinedGraph(); + const auto m = gResult[vResult].membership; const auto vResultId = get(boost::vertex_index_t(), gResult, vResult); const auto &dLeft = vDataLeft[vResultId]; const auto &dRight = vDataRight[vResultId]; s << ", Stereo("; - if(m != Membership::Right) + if(m != Membership::R) printData(s, dLeft.vGeometry, dLeft.edges.begin(), dLeft.edges.end()); else s << "<>"; s << " ->"; if(!vertexInContext[vResultId]) s << "!"; s << " "; - if(m != Membership::Left) + if(m != Membership::L) printData(s, dRight.vGeometry, dRight.edges.begin(), dRight.edges.end()); else s << "<>"; s << ")"; } - template - void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeFirst &eFirst) { + template + void printEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const EdgeFirst &eFirst) { const auto m = membership(rFirst, eFirst); s << ", Stereo("; - if(m != Membership::Right) { + if(m != Membership::R) { s << get_stereo(get_labelled_left(rFirst))[eFirst]; } else s << "<>"; s << " ->"; if(!get_stereo(rFirst).inContext(eFirst)) s << "!"; s << " "; - if(m != Membership::Left) { + if(m != Membership::L) { s << get_stereo(get_labelled_right(rFirst))[eFirst]; } else s << "<>"; s << ")"; } - template - void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeSecond &eSecond) { + template + void printEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const EdgeSecond &eSecond) { const auto m = membership(rSecond, eSecond); s << ", Stereo("; - if(m != Membership::Right) { + if(m != Membership::R) { s << get_stereo(get_labelled_left(rSecond))[eSecond]; } else s << "<>"; s << " ->"; if(!get_stereo(rSecond).inContext(eSecond)) s << "!"; s << " "; - if(m != Membership::Left) { + if(m != Membership::L) { s << get_stereo(get_labelled_right(rSecond))[eSecond]; } else s << "<>"; s << ")"; } -public: - template - void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { +public: + template + void composeVertexRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // -> a | a -> b, maybe a == b // to // -> b - assert(!vertexInContext[get(boost::vertex_index_t(), get_graph(result.rResult), vResult)]); + assert(!vertexInContext[get(boost::vertex_index_t(), result.rDPO->getCombinedGraph(), vResult)]); } - template - void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsL(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // a -> a | a -> // b -> a | a -> // to // a -> // b -> - const auto vIdResult = get(boost::vertex_index_t(), get_graph(result.rResult), vResult); + const auto vIdResult = get(boost::vertex_index_t(), result.rDPO->getCombinedGraph(), vResult); vertexInContext[vIdResult] = false; } - template - void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // a != b, a != c, b =? c // a -> a | a -> a // a -> a | a -> c // b -> a | a -> a // b -> a | a -> c // if both vertices are in context, so should the result be - const auto vIdResult = get(boost::vertex_index_t(), get_graph(result.rResult), vResult); + const auto vIdResult = get(boost::vertex_index_t(), result.rDPO->getCombinedGraph(), vResult); vertexInContext[vIdResult] = vertexInContext[vIdResult] && get_stereo(rSecond).inContext(vSecond); } public: - - template - void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - EdgeResult eResult, EdgeSecond eSecond) { + template + void + setEdgeResultRightFromSecondRight(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + EdgeResult eResult, EdgeSecond eSecond) { // | -> vs. -> |, simply copy the R2 to R // | -> | vs. | -> |, do the copy, but also set inContext if both are in context - const auto eIdResult = get(boost::edge_index_t(), get_graph(result.rResult), eResult); + const auto eIdResult = get(boost::edge_index_t(), result.rDPO->getCombinedGraph(), eResult); eDataRight[eIdResult] = get_stereo(get_labelled_right(rSecond))[eSecond]; edgeInContext[eIdResult] = edgeInContext[eIdResult] && get_stereo(rSecond).inContext(eSecond); } +private: + const lib::Rules::LabelledRule &rFirst; + const lib::Rules::LabelledRule &rSecond; }; -} // namespace Composer -} // namespace RC -} // namespace lib -} // namespace mod +} // namespace mod::lib::RC::Visitor -#endif /* MOD_LIB_RC_VISITOR_STEREO_H */ +#endif // MOD_LIB_RC_VISITOR_STEREO_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Visitor/String.hpp b/libs/libmod/src/mod/lib/RC/Visitor/String.hpp index cb50260..511eb70 100644 --- a/libs/libmod/src/mod/lib/RC/Visitor/String.hpp +++ b/libs/libmod/src/mod/lib/RC/Visitor/String.hpp @@ -1,105 +1,112 @@ -#ifndef MOD_LIB_RC_VISITOR_STRING_H -#define MOD_LIB_RC_VISITOR_STRING_H +#ifndef MOD_LIB_RC_VISITOR_STRING_HPP +#define MOD_LIB_RC_VISITOR_STRING_HPP #include #include -namespace mod { -namespace lib { -namespace RC { -namespace Visitor { +namespace mod::lib::RC::Visitor { struct String { - using Membership = jla_boost::GraphDPO::Membership; + using Membership = lib::DPO::Membership; public: + String(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) + : rFirst(rFirst), rSecond(rSecond) {} - template - bool init(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, Result &result) { - result.rResult.pString = std::make_unique (get_graph(result.rResult)); + template + bool init(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, Result &result) { + assert(&dpoFirst == &rFirst.getRule()); + assert(&dpoSecond == &rSecond.getRule()); + + result.pString = std::make_unique(*result.rDPO); return true; } - template - bool finalize(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { - result.rResult.pString->verify(&get_graph(result.rResult)); + template + bool finalize(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { + result.pString->verify(); return true; } public: - - template - void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexFirst &vFirst, const VertexResult &vResult) { - copyVertex(rFirst, result.rResult, vFirst, vResult, *rFirst.pString); + template + void copyVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const VertexFirst &vFirst, const VertexResult &vResult) { + copyVertex(rFirst, result, vFirst, vResult, *rFirst.pString); } - template - void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const VertexSecond &vSecond, const VertexResult &vResult) { - copyVertex(rSecond, result.rResult, vSecond, vResult, *rSecond.pString); + template + void copyVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const VertexSecond &vSecond, const VertexResult &vResult) { + copyVertex(rSecond, result, vSecond, vResult, *rSecond.pString); } - template - void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeFirst &eFirst, const EdgeResult &eResult) { - copyEdge(rFirst, result.rResult, eFirst, eResult, *rFirst.pString); + template + void copyEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const EdgeFirst &eFirst, const EdgeResult &eResult) { + copyEdge(rFirst, result, eFirst, eResult, *rFirst.pString); } - template - void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - const EdgeSecond &eSecond, const EdgeResult &eResult) { - copyEdge(rSecond, result.rResult, eSecond, eResult, *rSecond.pString); + template + void copyEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + const EdgeSecond &eSecond, const EdgeResult &eResult) { + copyEdge(rSecond, result, eSecond, eResult, *rSecond.pString); } public: - - template - void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexFirst &vFirst) { + template + void printVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexFirst &vFirst) { rFirst.pString->print(s, vFirst); } - template - void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexSecond &vSecond) { + template + void printVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexSecond &vSecond) { rSecond.pString->print(s, vSecond); } - template - void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const VertexResult &vResult) { - result.rResult.pString->print(s, vResult); + template + void printVertexResult(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const VertexResult &vResult) { + result.pString->print(s, vResult); } - template - void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeFirst &eFirst) { + template + void printEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const EdgeFirst &eFirst) { rFirst.pString->print(s, eFirst); } - template - void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - std::ostream &s, const EdgeSecond &eSecond) { + template + void printEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + std::ostream &s, const EdgeSecond &eSecond) { rSecond.pString->print(s, eSecond); } -public: - template - void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { +public: + template + void composeVertexRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // -> a | a -> b, maybe a == b - result.rResult.pString->setRight(vResult, rSecond.pString->getRight()[vSecond]); + result.pString->setRight(vResult, rSecond.pString->getRight()[vSecond]); // -> b } - template - void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsL(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // vFirst is CONTEXT, so do nothing // a -> a | a -> // b -> a | a -> @@ -108,70 +115,70 @@ struct String { // b -> } - template - void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + VertexResult vResult, VertexSecond vSecond) { // the left label of vResult is ok, but the right label might have to change // a != b, a != c, b =? c // a -> a | a -> a // a -> a | a -> c // b -> a | a -> a // b -> a | a -> c - result.rResult.pString->setRight(vResult, rSecond.pString->getRight()[vSecond]); + result.pString->setRight(vResult, rSecond.pString->getRight()[vSecond]); } public: - - template - void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, const Result &result, - EdgeResult eResult, EdgeSecond eSecond) { - result.rResult.pString->setRight(eResult, rSecond.pString->getRight()[eSecond]); + template + void + setEdgeResultRightFromSecondRight(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + EdgeResult eResult, EdgeSecond eSecond) { + result.pString->setRight(eResult, rSecond.pString->getRight()[eSecond]); } private: - - template - void copyVertex(const RuleFrom &rFrom, const RuleResult &rResult, VertexFrom vFrom, VertexResult vResult, const Prop &pFrom) { - assert(rResult.pString); - auto &pString = *rResult.pString; + template + void copyVertex(const RuleFrom &rFrom, const Result &result, VertexFrom vFrom, VertexResult vResult, + const Prop &pFrom) { + assert(result.pString); + auto &pString = *result.pString; auto m = membership(rFrom, vFrom); - assert(m == membership(rResult, vResult)); + assert(m == result.rDPO->getCombinedGraph()[vResult].membership); switch(m) { - case Membership::Left: + case Membership::L: pString.add(vResult, pFrom.getLeft()[vFrom], ""); break; - case Membership::Right: + case Membership::R: pString.add(vResult, "", pFrom.getRight()[vFrom]); break; - case Membership::Context: + case Membership::K: pString.add(vResult, pFrom.getLeft()[vFrom], pFrom.getRight()[vFrom]); break; } } - template - void copyEdge(const RuleFrom &rFrom, const RuleResult &rResult, EdgeFrom eFrom, EdgeResult eResult, const Prop &pFrom) { + template + void copyEdge(const RuleFrom &rFrom, const Result &result, EdgeFrom eFrom, EdgeResult eResult, const Prop &pFrom) { // the membership of eFrom may be different from eResult - assert(rResult.pString); - auto &pString = *rResult.pString; - auto m = membership(rResult, eResult); + assert(result.pString); + auto &pString = *result.pString; + auto m = result.rDPO->getCombinedGraph()[eResult].membership; switch(m) { - case Membership::Left: + case Membership::L: pString.add(eResult, pFrom.getLeft()[eFrom], ""); break; - case Membership::Right: + case Membership::R: pString.add(eResult, "", pFrom.getRight()[eFrom]); break; - case Membership::Context: + case Membership::K: pString.add(eResult, pFrom.getLeft()[eFrom], pFrom.getRight()[eFrom]); break; } } +private: + const lib::Rules::LabelledRule &rFirst; + const lib::Rules::LabelledRule &rSecond; }; -} // namespace Composer -} // namespace RC -} // namespace lib -} // namespace mod +} // namespace mod::lib::RC::Visitor -#endif /* MOD_LIB_RC_VISITOR_STRING_H */ +#endif // MOD_LIB_RC_VISITOR_STRING_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/RC/Visitor/Term.hpp b/libs/libmod/src/mod/lib/RC/Visitor/Term.hpp index f68ca92..9e5a372 100644 --- a/libs/libmod/src/mod/lib/RC/Visitor/Term.hpp +++ b/libs/libmod/src/mod/lib/RC/Visitor/Term.hpp @@ -2,206 +2,217 @@ #define MOD_LIB_RC_VISITOR_TERM_HPP #include -#include #include #include #include - -#include +#include namespace mod::lib::RC::Visitor { + static constexpr std::size_t TERM_MAX = std::numeric_limits::max(); struct Term { - using Membership = jla_boost::GraphDPO::Membership; + using Membership = lib::DPO::Membership; using AddressType = lib::Term::AddressType; using Cell = lib::Term::Cell; using CellTag = lib::Term::Cell::Tag; public: - template - bool init(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { - auto &data = get_prop(lib::GraphMorphism::TermDataT(), match); + Term(const lib::Rules::LabelledRule &rFirst, const lib::Rules::LabelledRule &rSecond) + : rFirst(rFirst), rSecond(rSecond) {} + + template + bool init(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { + assert(&dpoFirst == &rFirst.getRule()); + assert(&dpoSecond == &rSecond.getRule()); + + auto &data = get_prop(GraphMorphism::TermDataT(), match); auto &machine = data.machine; machine.verify(); - result.rResult.pTerm = std::make_unique(get_graph(result.rResult), - std::move(machine)); + result.pTerm = std::make_unique(*result.rDPO, std::move(machine)); if(Verbose) { std::cout << "New machine:\n"; - lib::IO::Term::Write::wam(getMachine(*result.rResult.pTerm), lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(getMachine(*result.pTerm), lib::Term::getStrings(), std::cout); } return true; } - template - bool finalize(const RuleFirst &rFirst, const RuleSecond &rSecond, InvertibleVertexMap &match, Result &result) { - result.rResult.pTerm->verify(&get_graph(result.rResult)); + template + bool finalize(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + InvertibleVertexMap &match, Result &result) { + result.pTerm->verify(); return true; } public: - template - void copyVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - const VertexFirst &vFirst, const VertexResult &vResult) { - assert(result.rResult.pTerm); + template + void copyVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + const VertexFirst &vFirst, const VertexResult &vResult) { + assert(result.pTerm); const auto &pFirst = *rFirst.pTerm; - auto &pResult = *result.rResult.pTerm; + auto &pResult = *result.pTerm; auto m = membership(rFirst, vFirst); - assert(m == membership(result.rResult, vResult)); + assert(m == result.rDPO->getCombinedGraph()[vResult].membership); switch(m) { - case Membership::Left: + case Membership::L: pResult.add(vResult, pFirst.getLeft()[vFirst], TERM_MAX); break; - case Membership::Right: + case Membership::R: pResult.add(vResult, TERM_MAX, pFirst.getRight()[vFirst]); break; - case Membership::Context: + case Membership::K: pResult.add(vResult, pFirst.getLeft()[vFirst], pFirst.getRight()[vFirst]); break; } if(Verbose) { std::cout << "Cur machine:\n"; - lib::IO::Term::Write::wam(getMachine(*result.rResult.pTerm), lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(getMachine(*result.pTerm), lib::Term::getStrings(), std::cout); } } - template - void copyVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - const VertexSecond &vSecond, const VertexResult &vResult) { - assert(result.rResult.pTerm); + template + void copyVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + const VertexSecond &vSecond, const VertexResult &vResult) { + assert(result.pTerm); const auto &pSecond = *rSecond.pTerm; - auto &pResult = *result.rResult.pTerm; + auto &pResult = *result.pTerm; auto m = membership(rSecond, vSecond); - assert(m == membership(result.rResult, vResult)); - if(m != Membership::Left) fixSecondTerm(pSecond.getRight()[vSecond], result); - if(m != Membership::Right) fixSecondTerm(pSecond.getLeft()[vSecond], result); + assert(m == result.rDPO->getCombinedGraph()[vResult].membership); + if(m != Membership::L) fixSecondTerm(pSecond.getRight()[vSecond], result); + if(m != Membership::R) fixSecondTerm(pSecond.getLeft()[vSecond], result); switch(m) { - case Membership::Left: + case Membership::L: pResult.add(vResult, deref(pSecond.getLeft()[vSecond], result), TERM_MAX); break; - case Membership::Right: + case Membership::R: pResult.add(vResult, TERM_MAX, deref(pSecond.getRight()[vSecond], result)); break; - case Membership::Context: + case Membership::K: pResult.add(vResult, deref(pSecond.getLeft()[vSecond], result), deref(pSecond.getRight()[vSecond], result)); break; } if(Verbose) { std::cout << "Cur machine:\n"; - lib::IO::Term::Write::wam(getMachine(*result.rResult.pTerm), lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(getMachine(*result.pTerm), lib::Term::getStrings(), std::cout); } } - template - void copyEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - const EdgeFirst &eFirst, const EdgeResult &eResult) { + template + void copyEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + const EdgeFirst &eFirst, const EdgeResult &eResult) { // the membership of e may be different from eResult - assert(result.rResult.pTerm); + assert(result.pTerm); const auto &pFirst = *rFirst.pTerm; - auto &pResult = *result.rResult.pTerm; - auto m = membership(result.rResult, eResult); + auto &pResult = *result.pTerm; + auto m = result.rDPO->getCombinedGraph()[eResult].membership; switch(m) { - case Membership::Left: + case Membership::L: pResult.add(eResult, pFirst.getLeft()[eFirst], TERM_MAX); break; - case Membership::Right: + case Membership::R: pResult.add(eResult, TERM_MAX, pFirst.getRight()[eFirst]); break; - case Membership::Context: + case Membership::K: pResult.add(eResult, pFirst.getLeft()[eFirst], pFirst.getRight()[eFirst]); break; } if(Verbose) { std::cout << "Cur machine:\n"; - lib::IO::Term::Write::wam(getMachine(*result.rResult.pTerm), lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(getMachine(*result.pTerm), lib::Term::getStrings(), std::cout); } } - template - void copyEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - const EdgeSecond &eSecond, const EdgeResult &eResult) { + template + void copyEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + const EdgeSecond &eSecond, const EdgeResult &eResult) { // the membership of e may be different from eResult - assert(result.rResult.pTerm); + assert(result.pTerm); const auto &pSecond = *rSecond.pTerm; - auto &pResult = *result.rResult.pTerm; - auto m = membership(result.rResult, eResult); - if(m != Membership::Left) fixSecondTerm(pSecond.getRight()[eSecond], result); - if(m != Membership::Right) fixSecondTerm(pSecond.getLeft()[eSecond], result); + auto &pResult = *result.pTerm; + auto m = result.rDPO->getCombinedGraph()[eResult].membership; + if(m != Membership::L) fixSecondTerm(pSecond.getRight()[eSecond], result); + if(m != Membership::R) fixSecondTerm(pSecond.getLeft()[eSecond], result); switch(m) { - case Membership::Left: + case Membership::L: pResult.add(eResult, deref(pSecond.getLeft()[eSecond], result), TERM_MAX); break; - case Membership::Right: + case Membership::R: pResult.add(eResult, TERM_MAX, deref(pSecond.getRight()[eSecond], result)); break; - case Membership::Context: + case Membership::K: pResult.add(eResult, deref(pSecond.getLeft()[eSecond], result), deref(pSecond.getRight()[eSecond], result)); break; } if(Verbose) { std::cout << "Cur machine:\n"; - lib::IO::Term::Write::wam(getMachine(*result.rResult.pTerm), lib::Term::getStrings(), std::cout); + lib::Term::Write::wam(getMachine(*result.pTerm), lib::Term::getStrings(), std::cout); } } public: - template - void printVertexFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - std::ostream &s, const VertexFirst &vFirst) { + template + void printVertexFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexFirst &vFirst) { rFirst.pTerm->print(s, vFirst); } - template - void printVertexSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - std::ostream &s, const VertexSecond &vSecond) { + template + void printVertexSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexSecond &vSecond) { rSecond.pTerm->print(s, vSecond); } - template - void printVertexResult(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - std::ostream &s, const VertexResult &vResult) { - result.rResult.pTerm->print(s, vResult); + template + void printVertexResult(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const VertexResult &vResult) { + result.pTerm->print(s, vResult); } - template - void printEdgeFirst(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - std::ostream &s, const EdgeFirst &eFirst) { + template + void printEdgeFirst(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeFirst &eFirst) { rFirst.pTerm->print(s, eFirst); } - template - void printEdgeSecond(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - std::ostream &s, const EdgeSecond &eSecond) { + template + void printEdgeSecond(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + std::ostream &s, const EdgeSecond &eSecond) { rSecond.pTerm->print(s, eSecond); } public: - template - void composeVertexRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { // -> a | a -> b, maybe a == b auto addr = rSecond.pTerm->getRight()[vSecond]; fixSecondTerm(addr, result); - result.rResult.pTerm->setRight(vResult, deref(addr, result)); + result.pTerm->setRight(vResult, deref(addr, result)); // -> b } - template - void composeVertexLRvsL(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsL(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { // vFirst is CONTEXT, so do nothing // a -> a | a -> // b -> a | a -> @@ -210,11 +221,11 @@ struct Term { // b -> } - template - void composeVertexLRvsLR(const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, - const Result &result, - VertexResult vResult, VertexSecond vSecond) { + template + void composeVertexLRvsLR(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, + const Result &result, + VertexResult vResult, VertexSecond vSecond) { // the left label of vResult is ok, but the right label might have to change // a != b, a != c, b =? c // a -> a | a -> a @@ -223,38 +234,41 @@ struct Term { // b -> a | a -> c auto addr = rSecond.pTerm->getRight()[vSecond]; fixSecondTerm(addr, result); - result.rResult.pTerm->setRight(vResult, deref(addr, result)); + result.pTerm->setRight(vResult, deref(addr, result)); } public: - template - void setEdgeResultRightFromSecondRight(const RuleFirst &rFirst, const RuleSecond &rSecond, - const InvertibleVertexMap &match, const Result &result, - EdgeResult eResult, EdgeSecond eSecond) { + template + void + setEdgeResultRightFromSecondRight(const lib::DPO::CombinedRule &dpoFirst, const lib::DPO::CombinedRule &dpoSecond, + const InvertibleVertexMap &match, const Result &result, + EdgeResult eResult, EdgeSecond eSecond) { auto addr = rSecond.pTerm->getRight()[eSecond]; fixSecondTerm(addr, result); - result.rResult.pTerm->setRight(eResult, deref(addr, result)); + result.pTerm->setRight(eResult, deref(addr, result)); } private: template void fixSecondTerm(std::size_t addr, Result &result) { - auto &m = getMachine(*result.rResult.pTerm); + auto &m = getMachine(*result.pTerm); m.verify(); if(Verbose) - lib::IO::Term::Write::wam(m, lib::Term::getStrings(), std::cout << "Copy " << addr << "\n"); + lib::Term::Write::wam(m, lib::Term::getStrings(), std::cout << "Copy " << addr << "\n"); m.copyFromTemp(addr); if(Verbose) - lib::IO::Term::Write::wam(m, lib::Term::getStrings(), std::cout << "After copy " << addr << "\n"); + lib::Term::Write::wam(m, lib::Term::getStrings(), std::cout << "After copy " << addr << "\n"); m.verify(); } template std::size_t deref(std::size_t addrTemp, Result &result) { - getMachine(*result.rResult.pTerm).verify(); - auto addr = getMachine(*result.rResult.pTerm).deref({AddressType::Temp, addrTemp}); + getMachine(*result.pTerm).verify(); + auto addr = getMachine(*result.pTerm).deref({AddressType::Temp, addrTemp}); assert(addr.type == AddressType::Heap); return addr.addr; } +private: + const lib::Rules::LabelledRule &rFirst; + const lib::Rules::LabelledRule &rSecond; }; } // namespace mod::lib::RC::Visitor diff --git a/libs/libmod/src/mod/lib/RC/detail/CompositionHelper.hpp b/libs/libmod/src/mod/lib/RC/detail/CompositionHelper.hpp index 22414b7..f3bb799 100644 --- a/libs/libmod/src/mod/lib/RC/detail/CompositionHelper.hpp +++ b/libs/libmod/src/mod/lib/RC/detail/CompositionHelper.hpp @@ -1,58 +1,56 @@ #ifndef MOD_LIB_RC_DETAIL_COMPOSITIONHELPER_HPP #define MOD_LIB_RC_DETAIL_COMPOSITIONHELPER_HPP +#include +#include #include -#include - #include namespace mod::lib::RC::detail { -using jla_boost::GraphDPO::Membership; +using lib::DPO::Membership; template struct CompositionHelper { - using GraphResult = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + using GraphResult = typename Result::RuleResult::GraphType; using VertexResult = typename boost::graph_traits::vertex_descriptor; using EdgeResult = typename boost::graph_traits::edge_descriptor; - using GraphFirst = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + using GraphFirst = typename RuleFirst::CombinedGraphType; using VertexFirst = typename boost::graph_traits::vertex_descriptor; - using GraphSecond = typename jla_boost::GraphDPO::PushoutRuleTraits::GraphType; + using GraphSecond = typename RuleSecond::CombinedGraphType; using VertexSecond = typename boost::graph_traits::vertex_descriptor; using EdgeSecond = typename boost::graph_traits::edge_descriptor; public: - CompositionHelper(const RuleFirst &rFirst, - const RuleSecond &rSecond, - const InvertibleVertexMap &match, + CompositionHelper(Result &result, + const RuleFirst &rFirst, const RuleSecond &rSecond, const InvertibleVertexMap &match, Visitor visitor) - : rFirst(rFirst), rSecond(rSecond), match(match), visitor(std::move(visitor)) {} + : result(result), rFirst(rFirst), rSecond(rSecond), match(match), visitor(std::move(visitor)) {} - std::optional operator()() &&{ + bool operator()() &&{ if(Verbose) std::cout << std::string(80, '=') << std::endl; - Result result(rFirst, rSecond); bool resInit = visitor.template init(rFirst, rSecond, match, result); if(!resInit) { if(Verbose) std::cout << std::string(80, '=') << std::endl; - return {}; + return false; } // Vertices //-------------------------------------------------------------------------- - copyVerticesFirst(result); - composeVerticesSecond(result); + copyVerticesFirst(); + composeVerticesSecond(); // Edges //-------------------------------------------------------------------------- - bool resFirst = copyEdgesFirstUnmatched(result); + bool resFirst = copyEdgesFirstUnmatched(); if(!resFirst) { if(Verbose) std::cout << std::string(80, '=') << std::endl; - return {}; + return false; } - bool resSecond = composeEdgesSecond(result); + bool resSecond = composeEdgesSecond(); if(!resSecond) { if(Verbose) std::cout << std::string(80, '=') << std::endl; - return {}; + return false; } // Finish it @@ -60,10 +58,10 @@ struct CompositionHelper { bool resFinal = visitor.template finalize(rFirst, rSecond, match, result); if(!resFinal) { if(Verbose) std::cout << std::string(80, '=') << std::endl; - return {}; + return false; } if(Verbose) std::cout << std::string(80, '=') << std::endl; - return result; + return true; } private: VertexFirst getNullFirst() const { @@ -79,58 +77,57 @@ struct CompositionHelper { } VertexFirst getVertexFirstChecked(VertexSecond vSecond) const { - const auto &gCodom = get_graph(get_labelled_right(rFirst)); - const auto &gDom = get_graph(get_labelled_left(rSecond)); - const auto m = membership(rSecond, vSecond); - if(m == Membership::Right) return getNullFirst(); + const auto &gCodom = getR(rFirst); + const auto &gDom = getL(rSecond); + const auto m = rSecond.getCombinedGraph()[vSecond].membership; + if(m == Membership::R) return getNullFirst(); else return get(match, gDom, gCodom, vSecond); } VertexSecond getVertexSecond(VertexFirst vFirst) const { - const auto &gCodom = get_graph(get_labelled_right(rFirst)); - const auto &gDom = get_graph(get_labelled_left(rSecond)); - assert(membership(rFirst, vFirst) != Membership::Left); + const auto &gCodom = getR(rFirst); + const auto &gDom = getL(rSecond); + assert(rFirst.getCombinedGraph()[vFirst].membership != Membership::L); return get_inverse(match, gDom, gCodom, vFirst); } private: - void copyVerticesFirst(Result &result) { + void copyVerticesFirst() { if(Verbose) std::cout << "copyVerticesFirst\n" << std::string(80, '-') << std::endl; - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); - const auto &gFirst = get_graph(rFirst); - const auto &gSecond = get_graph(rSecond); - for(const auto vFirst : asRange(vertices(gFirst))) { + auto &gResult = result.rDPO->getCombinedGraph(); + const auto &gFirst = rFirst.getCombinedGraph(); + const auto &gSecond = rSecond.getCombinedGraph(); + for(const auto vFirst: asRange(vertices(gFirst))) { if(Verbose) { std::cout << "rFirst node:\t" << get(boost::vertex_index_t(), gFirst, vFirst) - << "(" << membership(rFirst, vFirst) << ")" + << "(" << rFirst.getCombinedGraph()[vFirst].membership << ")" << "("; visitor.template printVertexFirst(rFirst, rSecond, match, result, std::cout, vFirst); std::cout << ")" << std::endl; } const bool getsDeleted = [&]() { // must be only in R to be deleted - if(membership(rFirst, vFirst) != Membership::Right) return false; + if(rFirst.getCombinedGraph()[vFirst].membership != Membership::R) return false; // must be matched to be deleted const auto vSecond = getVertexSecond(vFirst); if(vSecond == getNullSecond()) return false; // and the matched must only be in L - return membership(rSecond, vSecond) == Membership::Left; + return rSecond.getCombinedGraph()[vSecond].membership == Membership::L; }(); if(getsDeleted) { put(result.mFirstToResult, gFirst, gResult, vFirst, getNullResult()); if(Verbose) std::cout << "gets deleted" << std::endl; } else { const auto vResult = add_vertex(gResult); - result.mFirstToResult.resizeRight(gFirst, gResult); - result.mSecondToResult.resizeRight(gSecond, gResult); + syncSize(result.mFirstToResult, gFirst, gResult); + syncSize(result.mSecondToResult, gSecond, gResult); put(result.mFirstToResult, gFirst, gResult, vFirst, vResult); - put_membership(rResult, vResult, membership(rFirst, vFirst)); + gResult[vResult].membership = rFirst.getCombinedGraph()[vFirst].membership; visitor.template copyVertexFirst(rFirst, rSecond, match, result, vFirst, vResult); if(Verbose) { std::cout << "new node:\t" << get(boost::vertex_index_t(), gResult, vResult) - << "(" << membership(rResult, vResult) << ")("; + << "(" << gResult[vResult].membership << ")("; visitor.template printVertexResult(rFirst, rSecond, match, result, std::cout, vResult); std::cout << ")" << std::endl; } @@ -138,18 +135,17 @@ struct CompositionHelper { } } - void composeVerticesSecond(Result &result) { + void composeVerticesSecond() { if(Verbose) std::cout << "composeVerticesSecond\n" << std::string(80, '-') << std::endl; - const auto &gFirst = get_graph(rFirst); - const auto &gSecond = get_graph(rSecond); - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); + const auto &gFirst = rFirst.getCombinedGraph(); + const auto &gSecond = rSecond.getCombinedGraph(); + auto &gResult = result.rDPO->getCombinedGraph(); // copy nodes from second, but compose the matched ones which has already been created - for(const auto vSecond : asRange(vertices(gSecond))) { + for(const auto vSecond: asRange(vertices(gSecond))) { if(Verbose) { std::cout << "rSecond node:\t" << get(boost::vertex_index_t(), gSecond, vSecond) - << "(" << membership(rSecond, vSecond) << ")" + << "(" << rSecond.getCombinedGraph()[vSecond].membership << ")" << "("; visitor.template printVertexSecond(rFirst, rSecond, match, result, std::cout, vSecond); std::cout << ")" << std::endl; @@ -159,14 +155,14 @@ struct CompositionHelper { if(vFirst == getNullFirst()) { // unmatched vertex, create it const auto vResult = add_vertex(gResult); - result.mFirstToResult.resizeRight(gFirst, gResult); - result.mSecondToResult.resizeRight(gSecond, gResult); + syncSize(result.mFirstToResult, gFirst, gResult); + syncSize(result.mSecondToResult, gSecond, gResult); put(result.mSecondToResult, gSecond, gResult, vSecond, vResult); - put_membership(rResult, vResult, membership(rSecond, vSecond)); + gResult[vResult].membership = rSecond.getCombinedGraph()[vSecond].membership; visitor.template copyVertexSecond(rFirst, rSecond, match, result, vSecond, vResult); if(Verbose) { std::cout << "new node:\t" << get(boost::vertex_index_t(), gResult, vResult) - << "(" << membership(rResult, vResult) << ")" + << "(" << gResult[vResult].membership << ")" << "("; visitor.template printVertexResult(rFirst, rSecond, match, result, std::cout, vResult); std::cout << ")" << std::endl; @@ -179,17 +175,17 @@ struct CompositionHelper { } else { if(Verbose) { std::cout << "match to:\t" << get(boost::vertex_index_t(), gResult, vResult) - << "(" << membership(rResult, vResult) << ")" + << "(" << gResult[vResult].membership << ")" << "("; visitor.template printVertexResult(rFirst, rSecond, match, result, std::cout, vResult); std::cout << ")" << std::endl; } // now we calculate the new membership for the node - const auto mFirst = membership(rResult, vResult); // should be a copy of the one from rFirst - const auto mSecond = membership(rSecond, vSecond); - if(mFirst == Membership::Right) { + const auto mFirst = gResult[vResult].membership; // should be a copy of the one from rFirst + const auto mSecond = rSecond.getCombinedGraph()[vSecond].membership; + if(mFirst == Membership::R) { // vFirst appears - if(mSecond == Membership::Left) { + if(mSecond == Membership::L) { // and vSecond disappears, so vResult should not exist at all // We should have caught that already. assert(false); @@ -199,16 +195,16 @@ struct CompositionHelper { visitor.template composeVertexRvsLR(rFirst, rSecond, match, result, vResult, vSecond); } } else { // vFirst is either LEFT or CONTEXT - assert(mFirst != Membership::Left); // we can't match a LEFT node in rRight + assert(mFirst != Membership::L); // we can't match a LEFT node in rRight // vFirst is CONTEXT - if(mSecond == Membership::Left) { + if(mSecond == Membership::L) { // vSecond is disappearing, so vResult is disappearing as well visitor.template composeVertexLRvsL(rFirst, rSecond, match, result, vResult, vSecond); - put_membership(rResult, vResult, Membership::Left); + gResult[vResult].membership = Membership::L; } else { // both vFirst and vSecond is in both of their sides // let the visitor do its thing visitor.template composeVertexLRvsLR(rFirst, rSecond, match, result, vResult, vSecond); - assert(membership(rResult, vResult) == Membership::Context); + assert(gResult[vResult].membership == Membership::K); } // end if vSecond is LEFT } // end if vFirst is RIGHT } // end if vResult is null_vertex @@ -216,17 +212,16 @@ struct CompositionHelper { } // end foreach vSecond } - bool copyEdgesFirstUnmatched(Result &result) { + bool copyEdgesFirstUnmatched() { if(Verbose) std::cout << "copyEdgesFirstUnmatched\n" << std::string(80, '-') << std::endl; - const auto &gFirst = get_graph(rFirst); - const auto &gSecond = get_graph(rSecond); - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); + const auto &gFirst = rFirst.getCombinedGraph(); + const auto &gSecond = rSecond.getCombinedGraph(); + auto &gResult = result.rDPO->getCombinedGraph(); const auto processEdge = [&](const auto eFirst) { // map source and target to core vertices const auto vSrcFirst = source(eFirst, gFirst); const auto vTarFirst = target(eFirst, gFirst); - const auto meFirst = membership(rFirst, eFirst); + const auto meFirst = gFirst[eFirst].membership; if(Verbose) { const auto vSrcResult = get(result.mFirstToResult, gFirst, gResult, vSrcFirst); const auto vTarResult = get(result.mFirstToResult, gFirst, gResult, vTarFirst); @@ -249,15 +244,13 @@ struct CompositionHelper { // adding shouldn't fail assert(peResult.second); const auto eResult = peResult.first; - put_membership(rResult, eResult, membership(rFirst, eFirst)); + gResult[eResult].membership = gFirst[eFirst].membership; visitor.template copyEdgeFirst(rFirst, rSecond, match, result, eFirst, eResult); - assert( - membership(rResult, vSrcResult) == Membership::Context || membership(rResult, vSrcResult) == meFirst); - assert( - membership(rResult, vTarResult) == Membership::Context || membership(rResult, vTarResult) == meFirst); + assert(gResult[vSrcResult].membership == Membership::K || gResult[vSrcResult].membership == meFirst); + assert(gResult[vTarResult].membership == Membership::K || gResult[vTarResult].membership == meFirst); }; // and now the actual case analysis - if(meFirst == Membership::Left) { + if(meFirst == Membership::L) { if(Verbose) std::cout << "\teFirst in LEFT, clean copy" << std::endl; makeCopy(); return true; @@ -271,23 +264,23 @@ struct CompositionHelper { if(Verbose) std::cout << "\tBoth ends matched" << std::endl; const auto oeSecond = out_edges(vSrcSecond, gSecond); const auto eSecondIter = std::find_if(oeSecond.first, oeSecond.second, - [&gSecond, vTarSecond, this](const auto &eSecond) { + [&gSecond, vTarSecond](const auto &eSecond) { return target(eSecond, gSecond) == vTarSecond && - membership(rSecond, eSecond) != Membership::Right; + gSecond[eSecond].membership != Membership::R; }); // TODO: why do we check the membership in the find_if? // There should be only one edge, so we can bail out here, right? const bool isEdgeMatched = eSecondIter != oeSecond.second; if(isEdgeMatched) { const auto eSecond = *eSecondIter; - if(meFirst == Membership::Right) { + if(meFirst == Membership::R) { if(Verbose) std::cout << "\teFirst matched and in RIGHT, skipping" << std::endl; return true; } if(Verbose) std::cout << "\teFirst matched and in CONTEXT, copying to LEFT or CONTEXT (depending on eSecond (" - << membership(rSecond, eSecond) << "))" << std::endl; + << gSecond[eSecond].membership << "))" << std::endl; const auto vSrcResult = get(result.mFirstToResult, gFirst, gResult, vSrcFirst); const auto vTarResult = get(result.mFirstToResult, gFirst, gResult, vTarFirst); // vResultSrc/vResultTar can not be null_vertex @@ -298,10 +291,10 @@ struct CompositionHelper { assert(peResult.second); const auto eResult = peResult.first; // we use the second membership, so do not use the common copy mechanism in the bottom of the function - put_membership(rResult, eResult, membership(rSecond, eSecond)); + gResult[eResult].membership = gSecond[eSecond].membership; visitor.template copyEdgeFirst(rFirst, rSecond, match, result, eFirst, eResult); - assert(membership(rResult, vSrcResult) != Membership::Right); - assert(membership(rResult, vTarResult) != Membership::Right); + assert(gResult[vSrcResult].membership != Membership::R); + assert(gResult[vTarResult].membership != Membership::R); return true; } @@ -316,9 +309,9 @@ struct CompositionHelper { } // check consistency - const auto mvSrcResult = membership(rResult, vSrcResult); - const auto mvTarResult = membership(rResult, vTarResult); - if(mvSrcResult == Membership::Left || mvTarResult == Membership::Left) { + const auto mvSrcResult = gResult[vSrcResult].membership; + const auto mvTarResult = gResult[vTarResult].membership; + if(mvSrcResult == Membership::L || mvTarResult == Membership::L) { if(Verbose) std::cout << "\tComposition failure: at least one matched vertex has inconsistent context (" << mvSrcResult << " and " << mvTarResult << "), eFirst is (" << meFirst << ")" << std::endl; @@ -343,8 +336,8 @@ struct CompositionHelper { return false; } // matched, but is it consistent? - const auto mvMatchedResult = membership(rResult, vResultMatched); - if(mvMatchedResult == Membership::Left) { + const auto mvMatchedResult = gResult[vResultMatched].membership; + if(mvMatchedResult == Membership::L) { if(Verbose) std::cout << "\tComposition failure: matched vertex has inconsistent context (" << mvMatchedResult << "), eFirst is (" << meFirst << ")" << std::endl; @@ -360,20 +353,19 @@ struct CompositionHelper { makeCopy(); return true; }; - for(const auto eFirst : asRange(edges(gFirst))) { + for(const auto eFirst: asRange(edges(gFirst))) { const bool ok = processEdge(eFirst); if(!ok) return false; } return true; } - bool composeEdgesSecond(Result &result) { + bool composeEdgesSecond() { if(Verbose) std::cout << "composeEdgesSecond\n" << std::string(80, '-') << std::endl; - // const auto &gFirst = get_graph(rFirst); - const auto &gSecond = get_graph(rSecond); - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); - for(auto eSecond : asRange(edges(gSecond))) { + // const auto &gFirst = rFirst.getCombinedGraph(); + const auto &gSecond = rSecond.getCombinedGraph(); + auto &gResult = result.rDPO->getCombinedGraph(); + for(auto eSecond: asRange(edges(gSecond))) { auto vSecondSrc = source(eSecond, gSecond); auto vSecondTar = target(eSecond, gSecond); auto vResultSrc = get(result.mSecondToResult, gSecond, gResult, vSecondSrc); @@ -382,7 +374,7 @@ struct CompositionHelper { // vSrcNew/vTarNew may be null_vertex std::cout << "Edge second:\t(" << vSecondSrc << ", " << vSecondTar << ")\tmapped to (" << vResultSrc << ", " << vResultTar << ")" - << ", (" << membership(rSecond, eSecond) << ")" + << ", (" << gSecond[eSecond].membership << ")" << "("; visitor.template printEdgeSecond(rFirst, rSecond, match, result, std::cout, eSecond); std::cout << ")" << std::endl; @@ -403,32 +395,28 @@ struct CompositionHelper { // adding shouldn't fail assert(peResult.second); auto eResult = peResult.first; - put_membership(rResult, eResult, membership(rSecond, eSecond)); + gResult[eResult].membership = gSecond[eSecond].membership; visitor.template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); } else { // one end matched assert(vResultSrc != boost::graph_traits::null_vertex() || vResultTar != boost::graph_traits::null_vertex()); - bool isOk = composeEdgeSecond_oneEndMatched(result, eSecond, vResultSrc, vResultTar, isSrcMatched, - isTarMatched); + bool isOk = composeEdgeSecond_oneEndMatched(eSecond, vResultSrc, vResultTar, isSrcMatched, isTarMatched); if(!isOk) return false; } // if 0 or 1 end matched } else { // maybe a matched edge - bool isOk = composeEdgeSecond_bothEndsMatched(result, eSecond, vFirstSrc, vFirstTar, vResultSrc, - vResultTar); + bool isOk = composeEdgeSecond_bothEndsMatched(eSecond, vFirstSrc, vFirstTar, vResultSrc, vResultTar); if(!isOk) return false; } // end if 0 or 1 matched endNodes } // end for each eSecond return true; } private: - bool composeEdgeSecond_oneEndMatched(Result &result, - EdgeSecond eSecond, + bool composeEdgeSecond_oneEndMatched(EdgeSecond eSecond, VertexResult vResultSrc, VertexResult vResultTar, bool isSrcMatched, bool isTarMatched) { - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); + auto &gResult = result.rDPO->getCombinedGraph(); if(Verbose) std::cout << "One end matched, eSecond should be copied" << std::endl; assert(isSrcMatched ^ isTarMatched); // at most one of vResultSrc and vResultTar may be null_vertex @@ -438,18 +426,18 @@ struct CompositionHelper { auto vResultOther = isSrcMatched ? vResultTar : vResultSrc; // vResultMatched may be null_vertex, if it's a RIGHT vs. LEFT vertex assert(vResultOther != boost::graph_traits::null_vertex()); - const auto meSecond = membership(rSecond, eSecond); - const auto mvResultOther = membership(rResult, vResultOther); + const auto meSecond = rSecond.getCombinedGraph()[eSecond].membership; + const auto mvResultOther = gResult[vResultOther].membership; (void) mvResultOther; - assert(mvResultOther == Membership::Context || mvResultOther == meSecond); + assert(mvResultOther == Membership::K || mvResultOther == meSecond); if(vResultMatched == boost::graph_traits::null_vertex()) { if(Verbose) std::cout << "\tComposition failure: matched vertex deleted" << std::endl; return false; } - auto mvResultMatched = membership(rResult, vResultMatched); - if(mvResultMatched != Membership::Context && mvResultMatched != meSecond) { + auto mvResultMatched = gResult[vResultMatched].membership; + if(mvResultMatched != Membership::K && mvResultMatched != meSecond) { if(Verbose) std::cout << "\tComposition failure: matched vertex has inconsistent context (" << mvResultMatched << "), eSecond is (" << meSecond << ")" << std::endl; @@ -461,36 +449,34 @@ struct CompositionHelper { // adding shouldn't fail assert(peResult.second); auto eResult = peResult.first; - put_membership(rResult, eResult, membership(rSecond, eSecond)); + gResult[eResult].membership = rSecond.getCombinedGraph()[eSecond].membership; visitor.template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); return true; } - bool composeEdgeSecond_bothEndsMatched(Result &result, - EdgeSecond eSecond, + bool composeEdgeSecond_bothEndsMatched(EdgeSecond eSecond, VertexFirst vFirstSrc, VertexFirst vFirstTar, VertexResult vResultSrc, VertexResult vResultTar) { - const auto &gFirst = get_graph(rFirst); - auto &rResult = result.rResult; - auto &gResult = get_graph(rResult); + const auto &gFirst = rFirst.getCombinedGraph(); + auto &gResult = result.rDPO->getCombinedGraph(); if(Verbose) std::cout << "Both ends matched" << std::endl; // vResultSrc/vResultTar may be null_vertex std::optional omeFirst; // we search in coreFirst, because it has the matched edges - for(const auto eFirst : asRange(out_edges(vFirstSrc, gFirst))) { + for(const auto eFirst: asRange(out_edges(vFirstSrc, gFirst))) { if(target(eFirst, gFirst) != vFirstTar) continue; - omeFirst = membership(rFirst, eFirst); + omeFirst = gFirst[eFirst].membership; break; } - auto meSecond = membership(rSecond, eSecond); + auto meSecond = rSecond.getCombinedGraph()[eSecond].membership; // possibilities: - // -> vs. | -> + // -> vs. | -> // -> vs. -> | // -> vs. | -> | - // | -> vs. | -> + // | -> vs. | -> // | -> vs. -> | // | -> vs. | -> | // -> | vs. | -> first not in result @@ -501,11 +487,11 @@ struct CompositionHelper { // | -> | vs. | -> | first in result as CONTEXT // can we do a simple copy? if(!omeFirst) { - // -> vs. | -> + // -> vs. | -> // -> vs. -> | // -> vs. | -> | if(Verbose) std::cout << "\tSimple copy of eSecond" << std::endl; - if(meSecond == Membership::Left) { + if(meSecond == Membership::L) { if(Verbose) std::cout << "\t\teSecond in LEFT, check ends" << std::endl; // vResultSrc/vResultTar may be null_vertex if(vResultSrc == boost::graph_traits::null_vertex()) { @@ -516,11 +502,11 @@ struct CompositionHelper { if(Verbose) std::cout << "\tComposition failure: vResultTar deleted" << std::endl; return false; } - if(membership(rResult, vResultSrc) == Membership::Right) { + if(gResult[vResultSrc].membership == Membership::R) { if(Verbose) std::cout << "\tComposition failure: vResultSrc not in LEFT or CONTEXT" << std::endl; return false; } - if(membership(rResult, vResultTar) == Membership::Right) { + if(gResult[vResultTar].membership == Membership::R) { if(Verbose) std::cout << "\tComposition failure: vResultTar not in LEFT or CONTEXT" << std::endl; return false; } @@ -532,7 +518,7 @@ struct CompositionHelper { // adding shouldn't fail assert(peResult.second); auto eResult = peResult.first; - put_membership(rResult, eResult, membership(rSecond, eSecond)); + gResult[eResult].membership = rSecond.getCombinedGraph()[eSecond].membership; visitor.template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); return true; } @@ -540,14 +526,14 @@ struct CompositionHelper { // check for parallel errors assert(omeFirst); auto meFirst = *omeFirst; - if(meFirst == Membership::Left && meSecond != Membership::Right) { + if(meFirst == Membership::L && meSecond != Membership::R) { // creating parallel in left // | -> vs. | -> // | -> vs. | -> | if(Verbose) std::cout << "\tComposition failure: duplicate edge in L" << std::endl; return false; } - if(meFirst != Membership::Left && meSecond == Membership::Right) { + if(meFirst != Membership::L && meSecond == Membership::R) { // creating parallel in right // -> | vs. -> | // | -> | vs. -> | @@ -563,7 +549,7 @@ struct CompositionHelper { // | -> | vs. | -> first in result as LEFT // | -> | vs. | -> | first in result as CONTEXT // check if we should just ignore the edge (matched or handled - if(meSecond == Membership::Left) { + if(meSecond == Membership::L) { // -> | vs. | -> first not in result // | -> | vs. | -> first in result as LEFT if(Verbose) @@ -578,16 +564,16 @@ struct CompositionHelper { // vResultSrc/vResultTar can not be null_vertex assert(vResultSrc != boost::graph_traits::null_vertex()); assert(vResultTar != boost::graph_traits::null_vertex()); - if(meFirst == Membership::Right) { + if(meFirst == Membership::R) { // copy eSecond as RIGHT to result - assert(meSecond == Membership::Context); + assert(meSecond == Membership::K); // -> | vs. | -> | if(Verbose) std::cout << "\t -> | vs. | -> |, copy to RIGHT" << std::endl; auto peResult = add_edge(vResultSrc, vResultTar, gResult); // adding shouldn't fail assert(peResult.second); auto eResult = peResult.first; - put_membership(rResult, eResult, Membership::Right); + gResult[eResult].membership = Membership::R; visitor.template copyEdgeSecond(rFirst, rSecond, match, result, eSecond, eResult); return true; } @@ -606,11 +592,12 @@ struct CompositionHelper { std::cout << "\t'| -> vs. -> |' or '| -> | vs. | -> |', promote eNew to CONTEXT and set right from second right" << std::endl; - put_membership(rResult, eResult, Membership::Context); + gResult[eResult].membership = Membership::K; visitor.template setEdgeResultRightFromSecondRight(rFirst, rSecond, match, result, eResult, eSecond); return true; } private: + Result &result; const RuleFirst &rFirst; const RuleSecond &rSecond; const InvertibleVertexMap &match; diff --git a/libs/libmod/src/mod/lib/Rules/ConnectedComponent.hpp b/libs/libmod/src/mod/lib/Rules/ConnectedComponent.hpp index 6a020aa..856e61f 100644 --- a/libs/libmod/src/mod/lib/Rules/ConnectedComponent.hpp +++ b/libs/libmod/src/mod/lib/Rules/ConnectedComponent.hpp @@ -1,11 +1,9 @@ -#ifndef MOD_LIB_RULES_CONNECTEDCOMPONENT_H -#define MOD_LIB_RULES_CONNECTEDCOMPONENT_H +#ifndef MOD_LIB_RULES_CONNECTEDCOMPONENT_HPP +#define MOD_LIB_RULES_CONNECTEDCOMPONENT_HPP #include -namespace mod { -namespace lib { -namespace Rules { +namespace mod::lib::Rules { template struct ConnectedComponentFilter { @@ -13,10 +11,10 @@ struct ConnectedComponentFilter { using Edge = typename boost::graph_traits::edge_descriptor; // because filters in boost::filetered_graph must be default constructible - ConnectedComponentFilter() : g(nullptr), componentMap(nullptr) { } + ConnectedComponentFilter() : g(nullptr), componentMap(nullptr) {} ConnectedComponentFilter(const Graph *g, const ComponentMap *componentMap, std::size_t component) - : g(g), componentMap(componentMap), component(component) { } + : g(g), componentMap(componentMap), component(component) {} bool operator()(Vertex v) const { auto vComponent = (*componentMap)[get(boost::vertex_index_t(), *g, v)]; @@ -33,8 +31,6 @@ struct ConnectedComponentFilter { std::size_t component; }; -} // namespace Rules -} // namespace lib -} // namespace mod +} // namespace mod::lib::Rules -#endif /* MOD_LIB_RULES_CONNECTEDCOMPONENT_H */ \ No newline at end of file +#endif // MOD_LIB_RULES_CONNECTEDCOMPONENT_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.cpp b/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.cpp index 9b3edda..85af19a 100644 --- a/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.cpp +++ b/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.cpp @@ -9,18 +9,18 @@ namespace mod::lib::Rules { std::shared_ptr GraphAsRuleCache::getBindRule(const lib::Graph::Single *g) { - return getRule(g, Membership::Right); + return getRule(g, Membership::R); } std::shared_ptr GraphAsRuleCache::getIdRule(const lib::Graph::Single *g) { - return getRule(g, Membership::Context); + return getRule(g, Membership::K); } std::shared_ptr GraphAsRuleCache::getUnbindRule(const lib::Graph::Single *g) { - return getRule(g, Membership::Left); + return getRule(g, Membership::L); } -std::shared_ptr GraphAsRuleCache::getRule(const lib::Graph::Single *g, jla_boost::GraphDPO::Membership m) { +std::shared_ptr GraphAsRuleCache::getRule(const lib::Graph::Single *g, lib::DPO::Membership m) { const auto iter = storage.find({g, m}); if(iter != end(storage)) return iter->second; auto r = rule::Rule::makeRule(graphToRule(g->getLabelledGraph(), m, g->getName())); diff --git a/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.hpp b/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.hpp index f80c00e..a5e81fc 100644 --- a/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.hpp +++ b/libs/libmod/src/mod/lib/Rules/GraphAsRuleCache.hpp @@ -2,8 +2,7 @@ #define MOD_LIB_RULES_GRAPHASRULECACHE_HPP #include - -#include +#include #include #include @@ -18,9 +17,9 @@ struct GraphAsRuleCache { std::shared_ptr getIdRule(const lib::Graph::Single *g); std::shared_ptr getUnbindRule(const lib::Graph::Single *g); private: - std::shared_ptr getRule(const lib::Graph::Single *g, jla_boost::GraphDPO::Membership m); + std::shared_ptr getRule(const lib::Graph::Single *g, lib::DPO::Membership m); private: - std::map, std::shared_ptr> storage; + std::map, std::shared_ptr> storage; }; } // namespace mod::lib::Rules diff --git a/libs/libmod/src/mod/lib/Rules/GraphDecl.hpp b/libs/libmod/src/mod/lib/Rules/GraphDecl.hpp index d62b9c2..b6732aa 100644 --- a/libs/libmod/src/mod/lib/Rules/GraphDecl.hpp +++ b/libs/libmod/src/mod/lib/Rules/GraphDecl.hpp @@ -1,50 +1,16 @@ -#ifndef MOD_LIB_RULES_GRAPHDECL_H -#define MOD_LIB_RULES_GRAPHDECL_H +#ifndef MOD_LIB_RULES_GRAPHDECL_HPP +#define MOD_LIB_RULES_GRAPHDECL_HPP -#include -#include -#include +#include +#include -namespace mod { -namespace lib { -namespace Rules { +namespace mod::lib::Rules { +using lib::DPO::Membership; -using jla_boost::GraphDPO::Membership; - -struct VProp { - Membership membership; -}; - -struct EProp { - Membership membership; -}; - -using GraphType = jla_boost::EdgeIndexedAdjacencyList; +using GraphType = lib::DPO::CombinedRule::CombinedGraphType; using Vertex = boost::graph_traits::vertex_descriptor; using Edge = boost::graph_traits::edge_descriptor; -using SideGraphType = jla_boost::GraphDPO::FilteredGraphProjection; - -struct MembershipPropertyMap { - - MembershipPropertyMap(const GraphType &g) : g(g) { } -public: - const GraphType &g; -}; - -inline MembershipPropertyMap makeMembershipPropertyMap(const GraphType &g) { - return MembershipPropertyMap(g); -} - -inline Membership get(MembershipPropertyMap m, Vertex v) { - return get(&VProp::membership, m.g, v); -} - -inline Membership get(MembershipPropertyMap m, Edge e) { - return get(&EProp::membership, m.g, e); -} -} // namespace Rules -} // namespace lib -} // namespace mod +} // namespace mod::lib::Rules -#endif /* MOD_LIB_RULES_GRAPHDECL_H */ +#endif // MOD_LIB_RULES_GRAPHDECL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/GraphToRule.hpp b/libs/libmod/src/mod/lib/Rules/GraphToRule.hpp index 20e797e..9184649 100644 --- a/libs/libmod/src/mod/lib/Rules/GraphToRule.hpp +++ b/libs/libmod/src/mod/lib/Rules/GraphToRule.hpp @@ -13,31 +13,31 @@ std::unique_ptr graphToRule(const LGraph &lg, Membership membership, const using EdgeCat = lib::Stereo::EdgeCategory; const auto &g = get_graph(lg); const auto &pStringGraph = get_string(lg); - assert(membership == Membership::Left || membership == Membership::Context || membership == Membership::Right); - lib::Rules::LabelledRule rule; - auto &gCore = get_graph(rule); - rule.pString = std::make_unique(gCore); - auto &pString = *rule.pString; + assert(membership == Membership::L || membership == Membership::K || membership == Membership::R); + auto cRule = std::make_unique(); + auto &gCore = cRule->getCombinedGraph(); + auto pStringPtr = std::make_unique(*cRule); + auto &pString = *pStringPtr; - for(const auto v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vCore = add_vertex(gCore); assert(get(boost::vertex_index_t(), g, v) == get(boost::vertex_index_t(), gCore, vCore)); gCore[vCore].membership = membership; const auto &label = pStringGraph[v]; switch(membership) { - case Membership::Left: + case Membership::L: pString.add(vCore, label, ""); break; - case Membership::Right: + case Membership::R: pString.add(vCore, "", label); break; - case Membership::Context: + case Membership::K: pString.add(vCore, label, label); break; } } - for(const auto e : asRange(edges(g))) { + for(const auto e: asRange(edges(g))) { const auto vSrcCore = vertex(get(boost::vertex_index_t(), g, source(e, g)), gCore); const auto vTarCore = vertex(get(boost::vertex_index_t(), g, target(e, g)), gCore); const auto eCore = add_edge(vSrcCore, vTarCore, gCore).first; @@ -45,25 +45,26 @@ std::unique_ptr graphToRule(const LGraph &lg, Membership membership, const gCore[eCore].membership = membership; const auto &label = pStringGraph[e]; switch(membership) { - case Membership::Left: + case Membership::L: pString.add(eCore, label, ""); break; - case Membership::Right: + case Membership::R: pString.add(eCore, "", label); break; - case Membership::Context: + case Membership::K: pString.add(eCore, label, label); break; } } + std::unique_ptr pStereoPtr; if(has_stereo(lg)) { const auto &pStereoGraph = get_stereo(lg); std::vector eStereo(num_edges(g)); - for(const auto e : asRange(edges(g))) { + for(const auto e: asRange(edges(g))) { const auto eId = get(boost::edge_index_t(), g, e); - const auto eCore = *(edges(gCore).first + eId); + const auto eCore = *std::next(edges(gCore).first, eId); // TODO: get rid of O(n) lookup const auto eIdCore = get(boost::edge_index_t(), gCore, eCore); assert(eId == eIdCore); eStereo[eIdCore] = pStereoGraph[e]; @@ -82,20 +83,19 @@ std::unique_ptr graphToRule(const LGraph &lg, Membership membership, const return e; }; const auto inf = Stereo::makeCloner(lg, gCore, vertexMap, edgeMap); - rule.pStereo = std::make_unique(gCore, inf, inf, jla_boost::AlwaysTrue(), - jla_boost::AlwaysTrue()); + pStereoPtr = std::make_unique(*cRule, inf, inf, jla_boost::AlwaysTrue(), + jla_boost::AlwaysTrue()); } - rule.initComponents(); std::string completeName; switch(membership) { - case Membership::Left: + case Membership::L: completeName += "unbind"; break; - case Membership::Context: + case Membership::K: completeName += "id"; break; - case Membership::Right: + case Membership::R: completeName += "bind"; break; default: @@ -105,7 +105,8 @@ std::unique_ptr graphToRule(const LGraph &lg, Membership membership, const completeName += "<"; completeName += name; completeName += ">"; - auto res = std::make_unique(std::move(rule), std::nullopt); + LabelledRule lRule(std::move(cRule), std::move(pStringPtr), std::move(pStereoPtr)); + auto res = std::make_unique(std::move(lRule), std::nullopt); res->setName(completeName); return res; } diff --git a/libs/libmod/src/mod/lib/Rules/IO/DepictionData.cpp b/libs/libmod/src/mod/lib/Rules/IO/DepictionData.cpp new file mode 100644 index 0000000..1311d98 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/DepictionData.cpp @@ -0,0 +1,614 @@ +#include "DepictionData.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace mod::lib::Rules::Write { +namespace { +constexpr bool VERBOSE = false; +} // namespace + +DepictionData::Side::Side(const DepictionData &depict, const SideData &data, + const Chem::OBMolHandle CoordData::*obSide, + const lib::DPO::CombinedRule::SideGraphType &g, + SideToCG mToCG, + PropMolecule::Side pMol, + std::function fStereo) + : depict(depict), data(data), obSide(obSide), g(g), mToCG(mToCG), pMol(pMol), fStereo(fStereo) {} + +AtomData DepictionData::Side::getAtomData(SideVertex vS) const { + return pMol[vS]; +} + +AtomId DepictionData::Side::getAtomId(SideVertex vS) const { + return pMol[vS].getAtomId(); +} + +Isotope DepictionData::Side::getIsotope(SideVertex vS) const { + return pMol[vS].getIsotope(); +} + +Charge DepictionData::Side::getCharge(SideVertex vS) const { + return pMol[vS].getCharge(); +} + +bool DepictionData::Side::getRadical(SideVertex vS) const { + return pMol[vS].getRadical(); +} + +std::string DepictionData::Side::getVertexLabelNoIsotopeChargeRadical(SideVertex vS) const { + const auto atomId = getAtomId(vS); + if(atomId != AtomIds::Invalid) + return Chem::symbolFromAtomId(atomId); + const auto &nonAtomToPhonyAtom = data.nonAtomToPhonyAtom; + const auto nonAtomIter = nonAtomToPhonyAtom.find(vS); + assert(nonAtomIter != end(nonAtomToPhonyAtom)); + const auto labelIter = depict.phonyAtomToString.find(nonAtomIter->second.getAtomId()); + assert(labelIter != end(depict.phonyAtomToString)); + return labelIter->second; +} + +BondType DepictionData::Side::getBondData(SideEdge eS) const { + return pMol[eS]; +} + +std::string DepictionData::Side::getEdgeLabel(SideEdge eS) const { + const auto bt = getBondData(eS); + if(bt != BondType::Invalid) + return std::string(1, Chem::bondToChar(bt)); + const auto &nonBondEdges = data.nonBondEdges; + const auto iter = nonBondEdges.find(eS); + assert(iter != end(nonBondEdges)); + return iter->second; +} + +bool DepictionData::Side::hasImportantStereo(SideVertex vS) const { + if(!has_stereo(depict.lr)) return false; + return !fStereo()[vS]->morphismDynamicOk(); +} + +lib::IO::Graph::Write::EdgeFake3DType +DepictionData::Side::getEdgeFake3DType(SideEdge eS, bool withHydrogen) const { + const auto vSrc = source(eS, g); + const auto vTar = target(eS, g); + if(!hasImportantStereo(vSrc) && !hasImportantStereo(vTar)) + return lib::IO::Graph::Write::EdgeFake3DType::None; +#ifndef MOD_HAVE_OPENBABEL + throw FatalError(MOD_NO_OPENBABEL_ERROR_STR); +#else + assert(depict.hasMoleculeEncoding); + const auto idSrc = get(boost::vertex_index_t(), g, vSrc); + const auto idTar = get(boost::vertex_index_t(), g, vTar); + const CoordData &cData = withHydrogen ? depict.cDataAll : depict.cDataNoHydrogen; + return (cData.*obSide).getBondFake3D(idSrc, idTar); +#endif +} + +bool DepictionData::Side::getHasCoordinates() const { + return depict.getHasCoordinates(); +} + +double DepictionData::Side::getX(SideVertex vS, bool withHydrogen) const { + const auto v = get(mToCG, g, depict.lr.getRule().getCombinedGraph(), vS); + return depict.getX(v, withHydrogen); +} + +double DepictionData::Side::getY(SideVertex vS, bool withHydrogen) const { + const auto v = get(mToCG, g, depict.lr.getRule().getCombinedGraph(), vS); + return depict.getY(v, withHydrogen); +} + +const AtomData &DepictionData::Side::operator()(SideVertex vS) const { + if(getAtomId(vS) != AtomIds::Invalid) + return pMol[vS]; + const auto iter = data.nonAtomToPhonyAtom.find(vS); + assert(iter != end(data.nonAtomToPhonyAtom)); + return iter->second; +} + +BondType DepictionData::Side::operator()(SideEdge eS) const { + const auto bt = getBondData(eS); + return bt == BondType::Invalid ? BondType::Single : bt; +} + +//bool DepictionDataCore::Side::isAtomIdInvalidContext(Vertex v) const { +// switch(membership) { +// case Membership::Left: +// case Membership::Right +// return true; +// } +// const auto &molState = depict.moleculeState; +// if(membership == Membership::Left) return true; +// else if(membership == Membership::Right) return true; +// else { +// auto m = depict.g[v].membership; +// // if(m != Membership::StateChange) return true; +// auto leftId = molState.getNormal(v).getAtomId(); +// auto rightId = molState.getChange(v).getAtomId(); +// if(leftId == rightId) return true; +// else return false; +// } +//} + +//------------------------------------------------------------------------------ +// K +//------------------------------------------------------------------------------ + +DepictionData::K::K(const DepictionData &depict) : depict(depict) {} + +AtomId DepictionData::K::getAtomId(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + const auto vR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), v); + const auto &pMol = get_molecule(depict.lr); + const auto l = pMol.getLeft()[vL].getAtomId(); + const auto r = pMol.getRight()[vR].getAtomId(); + if(l == r) return l; + else return AtomIds::Invalid; +} + +Isotope DepictionData::K::getIsotope(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + const auto vR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), v); + const auto &pMol = get_molecule(depict.lr); + const auto l = pMol.getLeft()[vL].getIsotope(); + const auto r = pMol.getRight()[vR].getIsotope(); + if(l == r) return l; + else return Isotope(); +} + +Charge DepictionData::K::getCharge(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + const auto vR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), v); + const auto &pMol = get_molecule(depict.lr); + const auto l = pMol.getLeft()[vL].getCharge(); + const auto r = pMol.getRight()[vR].getCharge(); + if(l == r) return l; + else return Charge(0); +} + +bool DepictionData::K::getRadical(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + const auto vR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), v); + const auto &pMol = get_molecule(depict.lr); + const auto l = pMol.getLeft()[vL].getRadical(); + const auto r = pMol.getRight()[vR].getRadical(); + if(l == r) return l; + else return false; +} + +std::string DepictionData::K::getVertexLabelNoIsotopeChargeRadical(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + const auto vR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), v); + const auto atomId = getAtomId(v); + if(atomId != AtomIds::Invalid) + return Chem::symbolFromAtomId(atomId); + const auto &pMol = get_molecule(depict.lr); + const auto nonAtomIterLeft = depict.leftData.nonAtomToPhonyAtom.find(vL); + const auto nonAtomIterRight = depict.rightData.nonAtomToPhonyAtom.find(vR); + std::string left, right; + if(nonAtomIterLeft == end(depict.leftData.nonAtomToPhonyAtom)) { + const auto a = pMol.getLeft()[vL].getAtomId(); + left = Chem::symbolFromAtomId(a); + } else { + auto labelIterLeft = depict.phonyAtomToString.find(nonAtomIterLeft->second.getAtomId()); + assert(labelIterLeft != end(depict.phonyAtomToString)); + left = labelIterLeft->second; + } + if(nonAtomIterRight == end(depict.rightData.nonAtomToPhonyAtom)) { + const auto a = pMol.getRight()[vR].getAtomId(); + right = Chem::symbolFromAtomId(a); + } else { + auto labelIterRight = depict.phonyAtomToString.find(nonAtomIterRight->second.getAtomId()); + assert(labelIterRight != end(depict.phonyAtomToString)); + right = labelIterRight->second; + } + if(left == right) return left; + else return R"X($\langle$)X" + left + ", " + right + R"X($\rangle$)X"; +} + +BondType DepictionData::K::getBondData(KEdge e) const { + const auto &rDPO = depict.lr.getRule(); + const auto &pMol = get_molecule(depict.lr); + const auto eL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), e); + const auto eR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), e); + const auto l = pMol.getLeft()[eL]; + const auto r = pMol.getRight()[eR]; + if(l == r) return l; + else return BondType::Invalid; +} + +std::string DepictionData::K::getEdgeLabel(KEdge e) const { + const auto &rDPO = depict.lr.getRule(); + const auto eL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), e); + const auto eR = get(getMorR(rDPO), getK(rDPO), getR(rDPO), e); + const auto bt = getBondData(e); + if(bt != BondType::Invalid) + return std::string(1, Chem::bondToChar(bt)); + std::string left, right; + const auto iterLeft = depict.leftData.nonBondEdges.find(eL); + const auto iterRight = depict.rightData.nonBondEdges.find(eR); + const auto &pMol = get_molecule(depict.lr); + if(iterLeft != end(depict.leftData.nonBondEdges)) left = iterLeft->second; + else left = std::string(1, Chem::bondToChar(pMol.getLeft()[eL])); + if(iterRight != end(depict.rightData.nonBondEdges)) right = iterRight->second; + else right = std::string(1, Chem::bondToChar(pMol.getRight()[eR])); + if(left == right) return left; + else return "$\\langle$" + left + ", " + right + "$\\rangle$"; +} + +bool DepictionData::K::hasImportantStereo(KVertex v) const { + if(!has_stereo(depict.lr)) return false; + return get_stereo(depict.lr).inContext(v) && depict.hasImportantStereo(v); +} + +lib::IO::Graph::Write::EdgeFake3DType DepictionData::K::getEdgeFake3DType(KEdge e, bool withHydrogen) const { + // TODO: translate v to vL and vR + const auto &g = depict.lr.getRule().getCombinedGraph(); + const auto vSrc = source(e, g); + const auto vTar = target(e, g); + if(!hasImportantStereo(vSrc) && !hasImportantStereo(vTar)) + return lib::IO::Graph::Write::EdgeFake3DType::None; +#ifndef MOD_HAVE_OPENBABEL + throw FatalError(MOD_NO_OPENBABEL_ERROR_STR); +#else + assert(depict.hasMoleculeEncoding); + const auto idSrc = get(boost::vertex_index_t(), g, vSrc); + const auto idTar = get(boost::vertex_index_t(), g, vTar); + const CoordData &cData = withHydrogen ? depict.cDataAll : depict.cDataNoHydrogen; + if(has_stereo(depict.lr) && get_stereo(depict.lr).inContext(vSrc) && get_stereo(depict.lr).inContext(vTar)) + return cData.obMolLeft.getBondFake3D(idSrc, idTar); + else + return lib::IO::Graph::Write::EdgeFake3DType::None; +#endif +} + +bool DepictionData::K::getHasCoordinates() const { + return depict.getHasCoordinates(); +} + +double DepictionData::K::getX(KVertex v, bool withHydrogen) const { + return depict.getX(v, withHydrogen); +} + +double DepictionData::K::getY(KVertex v, bool withHydrogen) const { + return depict.getY(v, withHydrogen); +} + +const AtomData &DepictionData::K::operator()(KVertex v) const { + const auto &rDPO = depict.lr.getRule(); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), v); + // for now, we just return whatever is in left, it's fake data anyway + return depict.getLeft()(vL); +} + +BondType DepictionData::K::operator()(KEdge e) const { + const auto bt = getBondData(e); + return bt == BondType::Invalid ? BondType::Single : bt; +} + +//------------------------------------------------------------------------------ +// Combined +//------------------------------------------------------------------------------ + +DepictionData::Combined::Combined(const DepictionData &depict) : depict(depict) {} + +const AtomData &DepictionData::Combined::operator()(CombinedVertex v) const { + if(VERBOSE) std::cout << "DepictionData::Combined(" << &depict << "): v=" << v << std::endl; + // fake data, for creating OBMol + const auto &rDPO = depict.lr.getRule(); + const auto &pMol = get_molecule(depict.lr); + // return whatever we find + const auto m = rDPO.getCombinedGraph()[v].membership; + if(m != Membership::R) { + const auto vL = get_inverse(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), v); + if(VERBOSE) std::cout << " vL=" << vL << std::endl; + const auto &atomData = pMol.getLeft()[vL]; + const auto atomId = atomData.getAtomId(); + if(atomId != AtomIds::Invalid) return atomData; + else { + const auto iter = depict.leftData.nonAtomToPhonyAtom.find(vL); + assert(iter != end(depict.leftData.nonAtomToPhonyAtom)); + return iter->second; + } + } else { + const auto vR = get_inverse(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), v); + if(VERBOSE) std::cout << " vR=" << vR << std::endl; + const auto &atomData = pMol.getRight()[vR]; + const auto atomId = atomData.getAtomId(); + if(atomId != AtomIds::Invalid) return atomData; + else { + const auto iter = depict.rightData.nonAtomToPhonyAtom.find(vR); + assert(iter != end(depict.rightData.nonAtomToPhonyAtom)); + return iter->second; + } + } +} + +BondType DepictionData::Combined::operator()(CombinedEdge e) const { + // fake data, for creating OBMol + const auto &rDPO = depict.lr.getRule(); + const auto &g = rDPO.getCombinedGraph(); + const auto &pMol = get_molecule(depict.lr); + // if there is agreement, return that, otherwise prefer invalid bonds + // this should give a bit more freedom in bond angles + const auto m = g[e].membership; + BondType l = BondType::Single, r = BondType::Single; + if(m != Membership::R) { + const auto eL = get_inverse(rDPO.getLtoCG(), getL(rDPO), g, e); + l = pMol.getLeft()[eL]; + } + if(m != Membership::L) { + const auto eR = get_inverse(rDPO.getRtoCG(), getR(rDPO), g, e); + r = pMol.getRight()[eR]; + } + if(l == r) return l; + else return BondType::Invalid; +} + +//------------------------------------------------------------------------------ +// DepictionData +//------------------------------------------------------------------------------ + +DepictionData::DepictionData(const LabelledRule &lr) + : lr(lr), hasMoleculeEncoding(true) { + if(VERBOSE) std::cout << "DepictionData(" << this << "):" << std::endl; + + const auto &rDPO = lr.getRule(); + const auto &g = get_graph(lr); + const auto &pString = get_string(lr); + const auto &pMol = get_molecule(lr); + { // vertexData + std::vector atomUsed(AtomIds::Max + 1, false); + Chem::markSpecialAtomsUsed(atomUsed); + const auto findNonChemicalVertices = [&atomUsed](const lib::DPO::CombinedRule::SideGraphType &g, + const PropMolecule::Side &pMol) { + std::vector res; + if(VERBOSE) std::cout << " |V|=" << num_vertices(g) << std::endl; + for(const auto v: asRange(vertices(g))) { + const auto atomId = pMol[v].getAtomId(); + if(atomId != AtomIds::Invalid) atomUsed[atomId] = true; + else { + if(VERBOSE) std::cout << " v=" << v << " invalid" << std::endl; + res.emplace_back(v); + } + } + return res; + }; + if(VERBOSE) std::cout << " findNonChemicalVertices(L):" << std::endl; + const auto vsToProcessL = findNonChemicalVertices(getL(rDPO), pMol.getLeft()); + if(VERBOSE) std::cout << " findNonChemicalVertices(R):" << std::endl; + const auto vsToProcessR = findNonChemicalVertices(getR(rDPO), pMol.getRight()); + + // map non-atom labels to atoms + std::map atomIdFromLabel; + auto atomIdIter = atomUsed.begin() + 1; // skip invalid + const auto processVertices = [this, &atomIdFromLabel, &atomUsed, &atomIdIter]( + const std::vector &vs, const PropString::Side &pString, const PropMolecule::Side &pMol, + SideData &data) { + if(VERBOSE) std::cout << " |vs|=" << vs.size() << std::endl; + for(const auto v: vs) { + std::string label = std::get<0>(Chem::extractIsotopeChargeRadical(pString[v])); + if(VERBOSE) std::cout << " v=" << v << ", label=" << label << std::endl; + const auto atomId = [this, &atomIdFromLabel, &label, &atomUsed, &atomIdIter]() { + const auto iter = atomIdFromLabel.find(label); + if(iter != end(atomIdFromLabel)) { + if(VERBOSE) std::cout << " already handled" << std::endl; + return iter->second; + } + atomIdIter = std::find(atomIdIter, atomUsed.end(), false); + if(atomIdIter == atomUsed.end()) { + if(VERBOSE) std::cout << " no more atomIds" << std::endl; + hasMoleculeEncoding = false; + return AtomIds::Invalid; + } + unsigned char atomId = atomIdIter - atomUsed.begin(); + if(VERBOSE) std::cout << " atomId=" << int(atomId) << std::endl; + atomUsed[atomId] = true; + phonyAtomToString[AtomId(atomId)] = label; + atomIdFromLabel.emplace(label, AtomId(atomId)); + return AtomId(atomId); + }(); + if(atomId == AtomIds::Invalid) break; + const auto charge = pMol[v].getCharge(); + const bool radical = pMol[v].getRadical(); + if(VERBOSE) std::cout << " nonAtomToPhonyAtom[" << v << "]" << std::endl; + data.nonAtomToPhonyAtom[v] = AtomData(atomId, charge, radical); + } + }; + if(VERBOSE) std::cout << " processVertices(L):" << std::endl; + processVertices(vsToProcessL, pString.getLeft(), pMol.getLeft(), leftData); + if(VERBOSE) std::cout << " processVertices(R):" << std::endl; + processVertices(vsToProcessR, pString.getRight(), pMol.getRight(), rightData); + } + { // edgeData + const auto handleEdges = [](const lib::DPO::CombinedRule::SideGraphType &g, + const PropString::Side &pString, const PropMolecule::Side &pMol, + SideData &data) { + for(const auto e: asRange(edges(g))) { + const auto bt = pMol[e]; + if(bt == BondType::Invalid) { + if(VERBOSE) + std::cout << " e=" << e << "(" << e.get_property() << "), label=" << pString[e] << std::endl; + data.nonBondEdges[e] = pString[e]; + } + } + }; + if(VERBOSE) std::cout << " handleEdges(L):" << std::endl; + handleEdges(getL(rDPO), pString.getLeft(), pMol.getLeft(), leftData); + if(VERBOSE) std::cout << " handleEdges(R):" << std::endl; + handleEdges(getR(rDPO), pString.getRight(), pMol.getRight(), rightData); + } + + if(hasMoleculeEncoding) { +#ifdef MOD_HAVE_OPENBABEL + const auto doIt = [&](CoordData &cData, const bool withHydrogen) { + std::tie(cData.obMol, cData.obMolLeft, cData.obMolRight) + = Chem::makeOBMol(lr, getCombined(), getCombined(), + getLeft(), getLeft(), + getRight(), getRight(), + [this](const lib::DPO::CombinedRule::CombinedVertex v) { + return mayCollapse(v); + }, withHydrogen); + cData.x.resize(num_vertices(g)); + cData.y.resize(num_vertices(g)); + for(const auto v: asRange(vertices(g))) { + const auto vId = get(boost::vertex_index_t(), g, v); + if(cData.obMol.hasAtom(vId)) { + cData.x[vId] = cData.obMol.getAtomX(vId); + cData.y[vId] = cData.obMol.getAtomY(vId); + } else { + assert(!withHydrogen); + cData.x[vId] = std::numeric_limits::quiet_NaN(); + cData.y[vId] = std::numeric_limits::quiet_NaN(); + } + } + }; + doIt(cDataAll, true); + doIt(cDataNoHydrogen, false); +#endif + } +} + +bool DepictionData::hasImportantStereo(CombinedVertex v) const { + if(!has_stereo(lr)) return false; + const auto &g = get_graph(lr); + const auto m = g[v].membership; + // TODO: map v to side vertices + //assert(false); + if(m != Membership::R && !get_stereo(get_labelled_left(lr))[v]->morphismDynamicOk()) return true; + if(m != Membership::L && !get_stereo(get_labelled_right(lr))[v]->morphismDynamicOk()) return true; + return false; +} + +bool DepictionData::mayCollapse(CombinedVertex v) const { + const auto &rDPO = lr.getRule(); + const auto &g = rDPO.getCombinedGraph(); + if(g[v].membership != lib::Rules::Membership::K) + return false; + for(const auto e: asRange(out_edges(v, g))) + if(g[e].membership != lib::Rules::Membership::K) + return false; + // Do not use cgAtom and cgBond, as they provide fake data. + // Instead, create some differently fake data specifically for hydrogen collapsing. + const auto atomData = [&](const CombinedVertex v) { + const auto vL = get_inverse(rDPO.getLtoCG(), getL(rDPO), g, v); + const auto vR = get_inverse(rDPO.getRtoCG(), getR(rDPO), g, v); + const auto l = getLeft().getAtomData(vL); + const auto r = getRight().getAtomData(vR); + if(l == r) return l; + else return AtomData(AtomIds::Max); + }; + const auto bondData = [&](const CombinedEdge e) { + const auto eL = get_inverse(rDPO.getLtoCG(), getL(rDPO), g, e); + const auto eR = get_inverse(rDPO.getRtoCG(), getR(rDPO), g, e); + const auto l = getLeft().getBondData(eL); + const auto r = getRight().getBondData(eR); + if(l == r) return l; + else return BondType::Invalid; + }; + return lib::Chem::isCollapsibleHydrogen(v, g, atomData, bondData, [this](const CombinedVertex v) { + return hasImportantStereo(v); + }); +} + +bool DepictionData::getHasCoordinates() const { +#ifdef MOD_HAVE_OPENBABEL + return hasMoleculeEncoding; +#else + return false; +#endif +} + +double DepictionData::getX(CombinedVertex v, bool withHydrogen) const { + if(!getHasCoordinates()) MOD_ABORT; + const auto &g = get_graph(lr); + const auto vId = get(boost::vertex_index_t(), g, v); + const CoordData &cData = withHydrogen ? cDataAll : cDataNoHydrogen; + assert(vId < cData.x.size()); + return cData.x[vId]; +} + +double DepictionData::getY(CombinedVertex v, bool withHydrogen) const { + if(!getHasCoordinates()) MOD_ABORT; + const auto &g = get_graph(lr); + const auto vId = get(boost::vertex_index_t(), g, v); + const CoordData &cData = withHydrogen ? cDataAll : cDataNoHydrogen; + assert(vId < cData.y.size()); + return cData.y[vId]; +} + +void DepictionData::copyCoords(const DepictionData &other, const std::map &vMap) { + if(!other.getHasCoordinates()) return; + const auto &g = get_graph(lr); + const auto doIt = [&](CoordData &cData, const bool withHydrogen) { + cData.x.resize(num_vertices(g)); + cData.y.resize(num_vertices(g)); + for(const auto v: asRange(vertices(g))) { + const auto iter = vMap.find(v); + if(iter == end(vMap)) { + std::cout << "Vertex " << v << " (id=" << get(boost::vertex_index_t(), g, v) << ") not mapped." + << std::endl; + std::cout << "Map:" << std::endl; + for(auto p: vMap) std::cout << "\t" << p.first << " => " << p.second << std::endl; + std::cout << "num_vertices: " << num_vertices(g) << std::endl; + std::cout << "other.num_vertices: " << num_vertices(get_graph(other.lr)) << std::endl; + MOD_ABORT; + } + const auto vOther = iter->second; + const auto vId = get(boost::vertex_index_t(), g, v); + cData.x[vId] = other.getX(vOther, withHydrogen); + cData.y[vId] = other.getY(vOther, withHydrogen); + } + if(hasMoleculeEncoding) { +#ifdef MOD_HAVE_OPENBABEL + cData.obMol.setCoordinates(cData.x, cData.y); + cData.obMolLeft.setCoordinates(cData.x, cData.y); + cData.obMolRight.setCoordinates(cData.x, cData.y); +#endif + } + }; + doIt(cDataAll, true); + doIt(cDataNoHydrogen, false); +} + +DepictionData::Side DepictionData::getLeft() const { + if(!hasMoleculeEncoding) MOD_ABORT; + return {*this, leftData, &CoordData::obMolLeft, getL(lr.getRule()), + lr.getRule().getLtoCG(), + get_molecule(lr).getLeft(), + [this]() { return get_stereo(lr).getLeft(); }}; +} + +DepictionData::K DepictionData::getContext() const { + if(!hasMoleculeEncoding) MOD_ABORT; + return {*this}; +} + +DepictionData::Side DepictionData::getRight() const { + if(!hasMoleculeEncoding) MOD_ABORT; + return {*this, rightData, &CoordData::obMolRight, getR(lr.getRule()), + lr.getRule().getRtoCG(), + get_molecule(lr).getRight(), + [this]() { return get_stereo(lr).getRight(); }}; +} + +DepictionData::Combined DepictionData::getCombined() const { + return {*this}; +} + +} // namespace mod::lib::Rules::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/IO/DepictionData.hpp b/libs/libmod/src/mod/lib/Rules/IO/DepictionData.hpp new file mode 100644 index 0000000..d30f787 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/DepictionData.hpp @@ -0,0 +1,138 @@ +#ifndef MOD_LIB_RULES_IO_DEPICTIONDATA_HPP +#define MOD_LIB_RULES_IO_DEPICTIONDATA_HPP + +#include +#include +#include +#include + +#include +#include + +namespace mod { +struct AtomId; +struct Charge; +struct AtomData; +enum class BondType; +} // namespace mod +namespace mod::lib::Rules { +struct PropString; +struct PropMolecule; +} // namespace mod::lib::Rules +namespace mod::lib::Rules::Write { + +struct DepictionData { + using CombinedVertex = lib::DPO::CombinedRule::CombinedVertex; + using CombinedEdge = lib::DPO::CombinedRule::CombinedEdge; + using SideVertex = lib::DPO::CombinedRule::SideVertex; + using SideEdge = lib::DPO::CombinedRule::SideEdge; + using KVertex = lib::DPO::CombinedRule::KVertex; + using KEdge = lib::DPO::CombinedRule::KEdge; + using SideToCG = lib::DPO::CombinedRule::ToCombinedMorphismSide; +private: + struct SideData; + struct CoordData; +public: + struct Side { + Side(const DepictionData &depict, const SideData &data, + const Chem::OBMolHandle CoordData::*obSide, + const lib::DPO::CombinedRule::SideGraphType &g, + SideToCG mToCG, + PropMolecule::Side pMol, // we need definitely need mol data + // we don't want to instantiate stereo data unless needed + std::function fStereo); + public: // used in GraphWriteGeneric + AtomData getAtomData(SideVertex vS) const; // shortcut to moleculeState + AtomId getAtomId(SideVertex vS) const; // shortcut to moleculeState + Isotope getIsotope(SideVertex vS) const; // shortcut to moleculeState + Charge getCharge(SideVertex vS) const; // shortcut to moleculeState + bool getRadical(SideVertex vS) const; // shortcut to moleculeState + std::string getVertexLabelNoIsotopeChargeRadical(SideVertex vS) const; + BondType getBondData(SideEdge eS) const; // shortcut to moleculeState + std::string getEdgeLabel(SideEdge eS) const; + bool hasImportantStereo(SideVertex vS) const; + lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(SideEdge eS, bool withHydrogen) const; + bool getHasCoordinates() const; + double getX(SideVertex vS, bool withHydrogen) const; + double getY(SideVertex vS, bool withHydrogen) const; + public: // for coordinates + const AtomData &operator()(SideVertex vS) const; // fake data + BondType operator()(SideEdge eS) const; // fake data + private: + const DepictionData &depict; + const SideData &data; + const Chem::OBMolHandle CoordData::*obSide; + const lib::DPO::CombinedRule::SideGraphType &g; + const SideToCG mToCG; + PropMolecule::Side pMol; + std::function fStereo; + }; + struct K { + K(const DepictionData &depict); + public: // used in GraphWriteGeneric + AtomId getAtomId(KVertex v) const; // shortcut to moleculeState + Isotope getIsotope(KVertex v) const; // shortcut to moleculeState + Charge getCharge(KVertex v) const; // shortcut to moleculeState + bool getRadical(KVertex v) const; // shortcut to moleculeState + std::string getVertexLabelNoIsotopeChargeRadical(KVertex v) const; + BondType getBondData(KEdge e) const; // shortcut to moleculeState + std::string getEdgeLabel(KEdge e) const; + bool hasImportantStereo(KVertex v) const; + lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(KEdge e, bool withHydrogen) const; + bool getHasCoordinates() const; + double getX(KVertex v, bool withHydrogen) const; + double getY(KVertex v, bool withHydrogen) const; + public: // for coordinates + const AtomData &operator()(KVertex v) const; // fake data + BondType operator()(KEdge e) const; // fake data + private: + const DepictionData &depict; + }; + struct Combined { + Combined(const DepictionData &depict); + public: // for coordinates + const AtomData &operator()(CombinedVertex v) const; // fake data + BondType operator()(CombinedEdge e) const; // fake data + private: + const DepictionData &depict; + }; +public: + DepictionData(const DepictionData &) = delete; + DepictionData &operator=(const DepictionData &) = delete; +public: + DepictionData(const LabelledRule &lr); + bool hasImportantStereo(CombinedVertex v) const; + bool mayCollapse(CombinedVertex v) const; + bool getHasCoordinates() const; + double getX(CombinedVertex v, bool withHydrogen) const; + double getY(CombinedVertex v, bool withHydrogen) const; + // vMap: this -> other + void copyCoords(const DepictionData &other, const std::map &vMap); +public: // projections + Side getLeft() const; + K getContext() const; + Side getRight() const; + Combined getCombined() const; +private: + const LabelledRule &lr; + bool hasMoleculeEncoding; + std::map phonyAtomToString; + struct SideData { + std::map nonAtomToPhonyAtom; + std::map nonBondEdges; + } leftData, rightData; + + struct CoordData { + std::vector x, y; +#ifdef MOD_HAVE_OPENBABEL + // the pushout, for generating coords + lib::Chem::OBMolHandle obMol; + // each side, for stereo, with coords copied from the pushout + lib::Chem::OBMolHandle obMolLeft, obMolRight; +#endif + } cDataAll, cDataNoHydrogen; +}; + +} // namespace mod::lib::Rules + +#endif // MOD_LIB_RULES_IO_DEPICTIONDATA_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/IO/Read.cpp b/libs/libmod/src/mod/lib/Rules/IO/Read.cpp new file mode 100644 index 0000000..e4be344 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/Read.cpp @@ -0,0 +1,984 @@ +#include "Read.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace mod::lib::Rules::Read { +using lib::IO::Result; +namespace GML = lib::IO::GML; +namespace { + +template +struct Label { + std::optional left, context, right; +}; + +struct VertexLabels { + bool inLeft = false, inContext = false, inRight = false; + Label string, stereo; + bool stereoInContext = false; +public: + lib::DPO::CombinedRule::CombinedVertex cgVertex; + std::optional parsedEmbeddingLeft, parsedEmbeddingRight; +}; + +struct EdgeLabels { + bool inLeft = false, inContext = false, inRight = false; + Label string, stereo; + bool stereoInContext = false; +public: + lib::DPO::CombinedRule::CombinedEdge cgEdge; +}; + +Result parseGML(std::string_view input) { + GML::Rule rule; + gml::ast::KeyValue ast; + try { + ast = gml::parser::parse(input); + } catch(const gml::parser::error &e) { + return Result<>::Error(e.what()); + } + using namespace gml::converter::edsl; + auto cVertex = GML::makeVertexConverter(0); + auto cEdge = GML::makeEdgeConverter(0); + auto nodeLabels = list("nodeLabels") + (string("label", &GML::AdjacencyConstraint::nodeLabels)); + auto edgeLabels = list("edgeLabels") + (string("label", &GML::AdjacencyConstraint::edgeLabels)); + auto constrainAdj = list("constrainAdj", &GML::Rule::matchConstraints) + (int_("id", &GML::AdjacencyConstraint::id), 1, 1) + (string("op", &GML::AdjacencyConstraint::op), 1, 1) + (int_("count", &GML::AdjacencyConstraint::count), 1, 1) + (nodeLabels)(edgeLabels); + auto constrainShortestPath = list("constrainShortestPath", + &GML::Rule::matchConstraints) + (int_("source", &GML::ShortestPathConstraint::source), 1, 1) + (int_("target", &GML::ShortestPathConstraint::target), 1, 1) + (string("op", &GML::ShortestPathConstraint::op), 1, 1) + (int_("length", &GML::ShortestPathConstraint::length), 1, 1); + auto makeSide = [&](std::string name, GML::Graph GML::Rule::*side) { + return list(name, side)(cVertex)(cEdge); + }; + auto cRule = list("rule") + (string("ruleID", &GML::Rule::id), 0, 1) + (string("labelType", &GML::Rule::labelType), 0, 1) + (makeSide("left", &GML::Rule::left), 0, 1) + (makeSide("context", &GML::Rule::context), 0, 1) + (makeSide("right", &GML::Rule::right), 0, 1) + (constrainAdj)(constrainShortestPath); + auto iterBegin = * + auto iterEnd = iterBegin + 1; + try { + gml::converter::convert(iterBegin, iterEnd, cRule, rule); + } catch(const gml::converter::error &e) { + return Result<>::Error(e.what()); + } + return rule; +} + +Result<> checkGraphDuplicatesAndLoops(const GML::Graph &side, std::string_view name) { + std::unordered_set vertexIds; + for(const GML::Vertex &v: side.vertices) { + if(vertexIds.find(v.id) != end(vertexIds)) + return Result<>::Error("Duplicate vertex " + std::to_string(v.id) + " in " + std::string(name) + " graph."); + vertexIds.insert(v.id); + } + std::set> edgeIds; + for(const GML::Edge &e: side.edges) { + if(e.source == e.target) + return Result<>::Error( + "Loop edge (on " + std::to_string(e.source) + ", in " + std::string(name) + ") is not allowed."); + auto eSorted = std::minmax(e.source, e.target); + if(edgeIds.find(eSorted) != end(edgeIds)) + return Result<>::Error( + "Duplicate edge (" + std::to_string(e.source) + ", " + std::to_string(e.target) + ") in " + + std::string(name) + " graph."); + edgeIds.insert(eSorted); + } + return Result<>(); +} + +auto extractDataFromGML(const GML::Rule &rule) { + std::map vLabelsFromId; + std::map, EdgeLabels> eLabelsFromIds; + + for(const GML::Vertex &vGML: rule.left.vertices) { + auto &v = vLabelsFromId[vGML.id]; + v.inLeft = true; + v.string.left = vGML.label; + v.stereo.left = vGML.stereo; + } + for(const GML::Vertex &vGML: rule.context.vertices) { + auto &v = vLabelsFromId[vGML.id]; + v.inContext = true; + v.string.context = vGML.label; + v.stereo.context = vGML.stereo; + } + for(const GML::Vertex &vGML: rule.right.vertices) { + auto &v = vLabelsFromId[vGML.id]; + v.inRight = true; + v.string.right = vGML.label; + v.stereo.right = vGML.stereo; + } + for(const GML::Edge &eGML: rule.left.edges) { + auto eSorted = std::minmax(eGML.source, eGML.target); + auto &e = eLabelsFromIds[eSorted]; + e.inLeft = true; + e.string.left = eGML.label; + e.stereo.left = eGML.stereo; + } + for(const GML::Edge &eGML: rule.context.edges) { + auto eSorted = std::minmax(eGML.source, eGML.target); + auto &e = eLabelsFromIds[eSorted]; + e.inContext = true; + e.string.context = eGML.label; + e.stereo.context = eGML.stereo; + } + for(const GML::Edge &eGML: rule.right.edges) { + auto eSorted = std::minmax(eGML.source, eGML.target); + auto &e = eLabelsFromIds[eSorted]; + e.inRight = true; + e.string.right = eGML.label; + e.stereo.right = eGML.stereo; + } + + return std::pair(std::move(vLabelsFromId), std::move(eLabelsFromIds)); +} + +struct MatchConstraintConverter { + MatchConstraintConverter(LabelledRule &dpoResult, const std::map &vLabelsFromId) + : dpoResult(dpoResult), vLabelsFromId(vLabelsFromId) {} + + Result<> operator()(const GML::AdjacencyConstraint &cGML) { + const auto iter = vLabelsFromId.find(cGML.id); + if(iter == end(vLabelsFromId)) + return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.id) + + " in adjacency constraint does not exist."); + const auto vConstrained = iter->second.cgVertex; + lib::GraphMorphism::Constraints::Operator op; + { + const auto &s = cGML.op; + using namespace lib::GraphMorphism::Constraints; + if(s == "<") op = Operator::LT; + else if(s == "<=") op = Operator::LEQ; + else if(s == "=") op = Operator::EQ; + else if(s == ">=") op = Operator::GEQ; + else if(s == ">") op = Operator::GT; + else return Result<>::Error("Error in rule GML. Unknown operator '" + s + "' in adjacency constraint."); + } + auto c = std::make_unique< + lib::GraphMorphism::Constraints::VertexAdjacency + >(vConstrained, op, cGML.count); + c->vertexLabels.insert(cGML.nodeLabels.begin(), cGML.nodeLabels.end()); + c->edgeLabels.insert(cGML.edgeLabels.begin(), cGML.edgeLabels.end()); + dpoResult.leftData.matchConstraints.push_back(std::move(c)); + return Result<>(); + } + + Result<> operator()(const GML::ShortestPathConstraint &cGML) { + const auto iterSrc = vLabelsFromId.find(cGML.source); + const auto iterTar = vLabelsFromId.find(cGML.target); + if(iterSrc == end(vLabelsFromId)) + return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.source) + + " in shortest path constraint does not exist."); + if(iterTar == end(vLabelsFromId)) + return Result<>::Error("Error in rule GML. Vertex " + std::to_string(cGML.target) + + " in shortest path constraint does not exist."); + const auto vSrc = iterSrc->second.cgVertex; + const auto vTar = iterTar->second.cgVertex; + lib::GraphMorphism::Constraints::Operator op; + { + const auto &s = cGML.op; + using namespace lib::GraphMorphism::Constraints; + if(s == "<") op = Operator::LT; + else if(s == "<=") op = Operator::LEQ; + else if(s == "=") op = Operator::EQ; + else if(s == ">=") op = Operator::GEQ; + else if(s == ">") op = Operator::GT; + else + return Result<>::Error( + "Error in rule GML. Unknown operator '" + s + "' in shortest path constraint."); + } + const auto compSrc = get_component(get_labelled_left(dpoResult))[ + get(boost::vertex_index_t(), get_graph(dpoResult), vSrc)]; + const auto compTar = get_component(get_labelled_left(dpoResult))[ + get(boost::vertex_index_t(), get_graph(dpoResult), vTar)]; + if(compSrc != compTar) + return Result<>::Error( + "Error in rule GML. Vertex " + std::to_string(cGML.source) + " and " + std::to_string(cGML.target) + + " are in different connected components of the left graph. " + + "This is currently not supported for the shortest path constraint."); + auto c = std::make_unique< + lib::GraphMorphism::Constraints::ShortestPath + >(vSrc, vTar, op, cGML.length); + dpoResult.leftData.matchConstraints.push_back(std::move(c)); + return Result<>(); + } +public: + LabelledRule &dpoResult; + const std::map &vLabelsFromId; +}; + +} // namespace + +Result gml(lib::IO::Warnings &warnings, std::string_view input) { + auto resRule = parseGML(input); + if(!resRule) return std::move(resRule); // TODO: remove std::move when C++20/P1825R0 is available + auto &rule = *resRule; + + if(auto res = checkGraphDuplicatesAndLoops(rule.left, "left"); !res) return res; + if(auto res = checkGraphDuplicatesAndLoops(rule.context, "context"); !res) return res; + if(auto res = checkGraphDuplicatesAndLoops(rule.right, "right"); !res) return res; + auto labelsFromIdPair = extractDataFromGML(rule); // TODO: (C++20) use structured binding directly + auto &[vLabelsFromId, eLabelsFromIds] = labelsFromIdPair; + + Data data; + data.rule = LabelledRule(); + data.name = rule.id; + if(rule.labelType) { + const std::string <String = *rule.labelType; + if(ltString == "string") data.labelType = LabelType::String; + else if(ltString == "term") data.labelType = LabelType::Term; + else return Result<>::Error("Error in rule GML. Unknown labelType '" + ltString + "'."); + } + + auto &dpoResult = *data.rule; + auto &rDPO = dpoResult.getRule(); + dpoResult.pString = std::make_unique(dpoResult.getRule()); + auto &pString = *dpoResult.pString; + + using CombinedVertex = DPO::CombinedRule::CombinedVertex; + std::map vIdFromCG; + for(auto &[id, vData]: vLabelsFromId) { + // First find the right membership: + // inContext <=> inLeft && inRight + if(vData.inContext) vData.inLeft = vData.inRight = true; + else if(vData.inLeft && vData.inRight) vData.inContext = true; + + // Check labels and make (left, right) the correct labels + if(vData.string.context) { + if(vData.string.left) + return Result<>::Error( + "Error in rule GML. Vertex " + std::to_string(id) + " has a label both in 'context' and 'left'."); + if(vData.string.right) + return Result<>::Error( + "Error in rule GML. Vertex " + std::to_string(id) + " has a label both in 'context' and 'right'."); + // Note: terms follow the same semantics as string, i.e., the same string in L and R becomes the exact same terms. + vData.string.left = vData.string.right = vData.string.context; + } + + // Check that there is a string in left/right when inLeft/inRight + if(vData.inLeft && !vData.string.left) + return Result<>::Error("Error in rule GML. Vertex " + std::to_string(id) + " is in L, but has no label."); + if(vData.inRight && !vData.string.right) + return Result<>::Error("Error in rule GML. Vertex " + std::to_string(id) + " is in R, but has no label."); + + if(vData.inContext) { + const auto vK = addVertexK(rDPO); + pString.addK(vK, *vData.string.left, *vData.string.right); + vData.cgVertex = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + } else if(vData.inLeft) { + assert(!vData.inRight); + const auto vL = addVertexL(rDPO); + pString.addL(vL, *vData.string.left); + vData.cgVertex = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), vL); + } else { + assert(vData.inRight); + const auto vR = addVertexR(rDPO); + pString.addR(vR, *vData.string.right); + vData.cgVertex = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vR); + } + vIdFromCG[vData.cgVertex] = id; + data.externalToInternalIds[id] = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vData.cgVertex); + } // for each vertex + + for(auto &[eIds, eData]: eLabelsFromIds) { + const auto[src, tar] = eIds; + if(vLabelsFromId.find(src) == end(vLabelsFromId)) + return Result<>::Error( + "Error in rule GML. Edge endpoint '" + std::to_string(src) + "' does not exist for edge (" + + std::to_string(src) + ", " + std::to_string(tar) + ")."); + if(vLabelsFromId.find(tar) == end(vLabelsFromId)) + return Result<>::Error( + "Error in rule GML. Edge endpoint '" + std::to_string(tar) + "' does not exist for edge (" + + std::to_string(src) + ", " + std::to_string(tar) + ")."); + const CombinedVertex vcSrc = vLabelsFromId[src].cgVertex, vcTar = vLabelsFromId[tar].cgVertex; + // First find the right membership: + // inContext <=> inLeft && inRight + if(eData.inContext) eData.inLeft = eData.inRight = true; + else if(eData.inLeft && eData.inRight) eData.inContext = true; + + // checking dangling in left/right + if(eData.inLeft) { + const auto &g = rDPO.getCombinedGraph(); + if(g[vcSrc].membership == Membership::R) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") dangling: edge is present in L but endpoint " + std::to_string(src) + + " only present in R."); + if(g[vcTar].membership == Membership::R) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") dangling: edge is present in L but endpoint " + std::to_string(tar) + + " only present in R."); + } + if(eData.inRight) { + const auto &g = rDPO.getCombinedGraph(); + if(g[vcSrc].membership == Membership::L) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") dangling: edge is present in R but endpoint " + std::to_string(src) + + " only present in L."); + if(g[vcTar].membership == Membership::L) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") dangling: edge is present in R but endpoint " + std::to_string(tar) + + " only present in L."); + } + + // Check labels and make (left, right) the correct labels + if(eData.string.context) { + if(eData.string.left) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") has a label both in 'context' and 'left'."); + if(eData.string.right) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") has a label both in 'context' and 'right'."); + // TODO: for term it matters if it's L+R or it's K + eData.string.left = eData.string.right = eData.string.context; + } + + // Check that there is a string in left/right when inLeft/inRight + if(eData.inLeft && !eData.string.left) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") is in L, but has no label."); + if(eData.inRight && !eData.string.right) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") is in R, but has no label."); + + if(eData.inContext) { + const auto eK = addEdgeK(rDPO, vcSrc, vcTar); + pString.addK(eK, *eData.string.left, *eData.string.right); + eData.cgEdge = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), eK); + } else if(eData.inLeft) { + assert(!eData.inRight); + const auto vLSrc = get_inverse(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), vcSrc); + const auto vLTar = get_inverse(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), vcTar); + const auto eL = addEdgeL(rDPO, vLSrc, vLTar); + pString.addL(eL, *eData.string.left); + eData.cgEdge = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), eL); + } else { + assert(eData.inRight); + const auto vRSrc = get_inverse(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vcSrc); + const auto vRTar = get_inverse(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vcTar); + const auto eR = addEdgeR(rDPO, vRSrc, vRTar); + pString.addR(eR, *eData.string.right); + eData.cgEdge = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), eR); + } + } // for each edge + // the graph is set, so initialise the component storage + dpoResult.initComponents(); + + // constraints + for(const GML::MatchConstraint &cGML: rule.matchConstraints) { + MatchConstraintConverter visitor(dpoResult, vLabelsFromId); + if(auto res = std::visit(visitor, cGML); !res) return res; + } // for each constraint + + { // perhaps we can stop now, if there is not stereo annotation + bool doStereo = false; + for(const auto &v: rule.left.vertices) doStereo = doStereo || v.stereo; + for(const auto &v: rule.context.vertices) doStereo = doStereo || v.stereo; + for(const auto &v: rule.right.vertices) doStereo = doStereo || v.stereo; + for(const auto &e: rule.left.edges) doStereo = doStereo || e.stereo; + for(const auto &e: rule.context.edges) doStereo = doStereo || e.stereo; + for(const auto &e: rule.right.edges) doStereo = doStereo || e.stereo; + if(!doStereo) return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available + } + + // ========================================================================================= + // Stereo + // ========================================================================================= + + for(auto &[id, vData]: vLabelsFromId) { + // Check labels and make (left, right) the correct labels + if(vData.stereo.context) { + if(vData.stereo.left) + return Result<>::Error( + "Error in rule GML. Vertex " + std::to_string(id) + " has stereo both in 'context' and 'left'."); + if(vData.stereo.right) + return Result<>::Error( + "Error in rule GML. Vertex " + std::to_string(id) + " has stereo both in 'context' and 'right'."); + vData.stereo.left = vData.stereo.right = vData.stereo.context; + } + } + + for(auto &[eIds, eData]: eLabelsFromIds) { + const auto[src, tar] = eIds; + if(eData.stereo.context) { + if(eData.stereo.left) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") has stereo both in 'context' and 'left'."); + if(eData.stereo.right) + return Result<>::Error("Error in rule GML. Edge (" + std::to_string(src) + ", " + std::to_string(tar) + + ") has stereo both in 'context' and 'right'."); + eData.stereo.left = eData.stereo.right = eData.stereo.context; + } + } + + PropMolecule mol(dpoResult.getRule(), pString); // temporary for doing the inference + auto molLeft = mol.getLeft(); + auto molRight = mol.getRight(); + auto leftInference = lib::Stereo::Inference(getL(rDPO), molLeft, true); + auto rightInference = lib::Stereo::Inference(getR(rDPO), molRight, true); + // Set the explicitly defined edge categories. + //---------------------------------------------------------------------------- + for(const auto &eIdLabelPair: eLabelsFromIds) { + const auto &[eIds, labels] = eIdLabelPair; + const auto handleSide = [&eIdLabelPair, &rDPO]( + const std::optional &os, const std::string &side, + auto &inference, const auto &mSideToCG, const auto &gSide) -> Result<> { + // TODO: (C++20) use structured binding in loop and capture them + const auto &[eIds, labels] = eIdLabelPair; + if(!os) return Result<>(); + const std::string &s = *os; + if(s.size() != 1) + return Result<>::Error("Error in stereo data for edge (" + std::to_string(eIds.first) + + ", " + std::to_string(eIds.second) + ") in " + side + + ". Parsing error in stereo data '" + s + "'."); + lib::Stereo::EdgeCategory cat; + switch(s.front()) { + case '*': + cat = lib::Stereo::EdgeCategory::Any; + break; + default: + return Result<>::Error("Error in stereo data for edge (" + std::to_string(eIds.first) + ", " + + std::to_string(eIds.second) + ") in " + side + + ". Parsing error in stereo data '" + s + "'."); + } + const auto eSide = get_inverse(mSideToCG, gSide, rDPO.getCombinedGraph(), labels.cgEdge); + auto res = inference.assignEdgeCategory(eSide, cat); + if(!res) { + res.setError("Error in stereo data for edge (" + std::to_string(eIds.first) + ", " + + std::to_string(eIds.second) + ") in " + side + ". " + res.extractError()); + return res; + } + return res; + }; + if(auto res = handleSide(labels.stereo.left, "L", leftInference, rDPO.getLtoCG(), getL(rDPO)); !res) return res; + if(auto res = handleSide(labels.stereo.right, "R", rightInference, rDPO.getRtoCG(), getR(rDPO)); !res) return res; + } // for each edge + // Set the explicitly defined vertex stereo data. + //---------------------------------------------------------------------------- + const auto &gGeometry = lib::Stereo::getGeometryGraph(); + for(auto &eVIdLabelsPair: vLabelsFromId) { + auto &[vId, vLabels] = eVIdLabelsPair; // TODO: (C++20) remove, structured binding capture + // TODO: (C++20) use structured binding in loop + const auto handleSide = [&](const std::optional &os, const std::string &side, auto &inference, + auto &parsedEmbedding, const auto &mSideToCG, const auto &gSide) { + auto &[vLabelsFromId, eLabelsFromIds] = labelsFromIdPair; // TODO: (C++20) remove, structured binding capture + const auto &[vId, vLabels] = eVIdLabelsPair; + if(!os) return Result<>(); + if(auto parsedEmbeddingRes = lib::Stereo::Read::parseEmbedding(*os)) { + parsedEmbedding = std::move(*parsedEmbeddingRes); + } else { + return Result<>::Error( + "Error in stereo data for vertex " + std::to_string(vId) + " in " + side + ". " + + parsedEmbeddingRes.extractError()); + } + const auto &vcg = vLabels.cgVertex; + const auto vSide = get_inverse(mSideToCG, gSide, rDPO.getCombinedGraph(), vcg); + // Geometry + //.......................................................................... + const auto &embGML = *parsedEmbedding; + if(embGML.geometry) { + const auto vGeo = gGeometry.findGeometry(*embGML.geometry); + if(vGeo == gGeometry.nullGeometry()) + return Result<>::Error("Error in stereo data for vertex " + std::to_string(vId) + " in " + side + + ". Invalid geometry '" + *embGML.geometry + "'."); + if(auto res = inference.assignGeometry(vSide, vGeo); !res) { + return Result<>::Error( + "Error in stereo data for vertex " + std::to_string(vId) + " in " + side + ". " + + res.extractError()); + } + } + // Edges + //.......................................................................... + if(embGML.edges) { + inference.initEmbedding(vSide); + for(const auto &e: *embGML.edges) { + if(const int *idPtr = std::get_if(&e)) { + const int idNeighbour = *idPtr; + if(vLabelsFromId.find(idNeighbour) == end(vLabelsFromId)) + return Result<>::Error("Error in graph GML. Neighbour vertex " + std::to_string(idNeighbour) + + " in stereo embedding for vertex " + + std::to_string(vId) + " in " + side + " does not exist."); + const auto vFromVertexId = [&labelsFromIdPair](int id) { + auto &[vLabelsFromId, eLabelsFromIds] = labelsFromIdPair; // TODO: (C++20) remove, structured binding capture + const auto iter = vLabelsFromId.find(id); + assert(iter != end(vLabelsFromId)); + return iter->second.cgVertex; + }; + auto epCG = edge(vcg, vFromVertexId(idNeighbour), rDPO.getCombinedGraph()); + if(!epCG.second) + return Result<>::Error("Error in graph GML. Vertex " + std::to_string(idNeighbour) + + " in stereo embedding for vertex " + + std::to_string(vId) + " in " + side + " is not a neighbour."); + const auto eSide = get_inverse(mSideToCG, gSide, rDPO.getCombinedGraph(), epCG.first); + inference.addEdge(vSide, eSide); + } else if(const char *virtPtr = std::get_if(&e)) { + switch(*virtPtr) { + case 'e': + inference.addLonePair(vSide); + break; + case 'r': + inference.addRadical(vSide); + break; + default: + return Result<>::Error( + "Error in graph GML. Virtual neighbour in stereo embedding for vertex " + + std::to_string(vId) + " in " + side + " has unknown type '" + *virtPtr + "'."); + } + } else { + MOD_ABORT; // the parser should know what is allowed + } + } + } + // Fixation + //.......................................................................... + if(embGML.fixation) { + // TODO: expand this when more complicated geometries are implemented + const bool isFixed = *embGML.fixation; + if(isFixed) inference.fixSimpleGeometry(vSide); + } + return Result<>(); + }; + if(auto res = handleSide(vLabels.stereo.left, "L", leftInference, vLabels.parsedEmbeddingLeft, + rDPO.getLtoCG(), getL(rDPO)); !res) + return res; + if(auto res = handleSide(vLabels.stereo.right, "R", rightInference, vLabels.parsedEmbeddingRight, + rDPO.getRtoCG(), getR(rDPO)); !res) + return res; + } // for each vertex + + const auto finalize = [&warnings, &rDPO, &vIdFromCG](auto &inference, const std::string &side, + const auto &gSide, const auto &mSideToCG) { + return inference.finalize(warnings, + [&rDPO, &vIdFromCG, &side, &gSide, &mSideToCG](lib::DPO::CombinedRule::SideVertex vS) { + const auto v = get(mSideToCG, gSide, rDPO.getCombinedGraph(), vS); + const auto iter = vIdFromCG.find(v); + assert(iter != vIdFromCG.end()); + return std::to_string(iter->second) + " in " + side; + }); + }; + if(auto resLeft = finalize(leftInference, "L", getL(rDPO), rDPO.getLtoCG()); !resLeft) return resLeft; + if(auto resRight = finalize(rightInference, "R", getR(rDPO), rDPO.getRtoCG()); !resRight) return resRight; + + const auto vertexInContext = [&](lib::DPO::CombinedRule::CombinedVertex v) -> bool { + auto &[vLabelsFromId, eLabelsFromIds] = labelsFromIdPair; // TODO: (C++20) remove, structured binding capture + const auto idIter = vIdFromCG.find(v); + assert(idIter != end(vIdFromCG)); + const auto lIter = vLabelsFromId.find(idIter->second); + assert(lIter != end(vLabelsFromId)); + assert(lIter->second.inContext); + const auto &stereo = lIter->second.stereo; + // TODO: this should be looked at at some point + // if there is any stereo data, maybe we are in context + if(stereo.left.has_value() || stereo.context.has_value() || stereo.right.has_value()) + return stereo.context.has_value(); + else // otherwise, default to be in context + return true; + }; + + const auto edgeInContext = [&](lib::DPO::CombinedRule::CombinedEdge e) -> bool { + auto &[vLabelsFromId, eLabelsFromIds] = labelsFromIdPair; // TODO: (C++20) remove, structured binding capture + const auto vSrc = source(e, rDPO.getCombinedGraph()); + const auto vTar = target(e, rDPO.getCombinedGraph()); + const auto idSrcIter = vIdFromCG.find(vSrc); + const auto idTarIter = vIdFromCG.find(vTar); + assert(idSrcIter != end(vIdFromCG)); + assert(idTarIter != end(vIdFromCG)); + assert(idSrcIter->second < idTarIter->second); + const auto lIter = eLabelsFromIds.find(std::pair(idSrcIter->second, idTarIter->second)); + assert(lIter != end(eLabelsFromIds)); + assert(lIter->second.inContext); + const auto &stereo = lIter->second.stereo; + // TODO: this should be looked at at some point + // if there is any stereo data, maybe we are in context + if(stereo.left.has_value() || stereo.context.has_value() || stereo.right.has_value()) + return stereo.context.has_value(); + else // otherwise, default to be in context + return true; + }; + dpoResult.pStereo = std::make_unique(dpoResult.getRule(), + std::move(leftInference), std::move(rightInference), + vertexInContext, edgeInContext); + return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available +} + +namespace { +//#define MOD_RULEDFS_DEBUG + +using CombinedVertex = DPO::CombinedRule::CombinedVertex; + +namespace dfsDetail { +using namespace IO::DFS; +using Vertex = IO::DFS::Vertex; +using Edge = IO::DFS::Edge; + +using LeftEdgeMapType = std::map, std::string>; + +struct ConvertLeft { + struct ConvertRes { + CombinedVertex next; + bool isRingClosure; + }; + + ConvertLeft(const IO::DFS::Read::RuleResult &res, DPO::CombinedRule &rDPO, PropString &pString) + : res(res), rDPO(rDPO), pString(pString) {} + + LeftEdgeMapType operator()(Chain &chain) &&{ + const auto sub = (*this)(chain.head, lib::Graph::GraphType::null_vertex()); + CombinedVertex vPrev = sub.next; + assert(!sub.isRingClosure); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + for(EVPair &ev: chain.tail) { + vPrev = (*this)(ev, vPrev); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + } + return std::move(edges); + } + + ConvertRes operator()(Vertex &vertex, CombinedVertex prev) { + const auto sub = boost::apply_visitor(*this, vertex.vertex); + assert(!(sub.isRingClosure && prev == lib::Graph::GraphType::null_vertex())); + const CombinedVertex branchRoot = sub.isRingClosure ? prev : sub.next; + for(Branch &branch: vertex.branches) { + CombinedVertex branchPrev = branchRoot; + for(EVPair &ev: branch.tail) { + branchPrev = (*this)(ev, branchPrev); + assert(branchPrev != lib::Graph::GraphType::null_vertex()); + } + } + return sub; + } + + CombinedVertex operator()(EVPair &ev, CombinedVertex prev) { + const auto res = (*this)(ev.second, prev); + makeEdge(prev, res.next, ev.first.label); + if(res.isRingClosure) return prev; + else return res.next; + } + + ConvertRes operator()(LabelVertex &vDFS) { + const auto vL = addVertexL(rDPO); + pString.addL(vL, vDFS.label); + const auto v = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), vL); + +#ifdef MOD_RULEDFS_DEBUG + std::cout << "RuleDFS: ConvertLeft, addVertexL("; + pString.print(std::cout, v); + std::cout << ")\n"; +#endif + + const auto vId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v); + vDFS.gVertexId = vId; + + std::optional rightLabel; + if(vDFS.id && !vDFS.ringClosure) { + const auto iterRight = res.right.vertexFromId.find(*vDFS.id); + if(iterRight != res.right.vertexFromId.end()) { + rightLabel = iterRight->second->label; + assert(iterRight->second->gVertexId == -1); + iterRight->second->gVertexId = vId; + } + } + if(rightLabel) { + const auto vR = promoteVertexL(rDPO, vL); + pString.promoteL(vL, vR, *rightLabel); + } + if(vDFS.ringClosure) { + const auto vRing = vertex(vDFS.ringClosure->gVertexId, rDPO.getCombinedGraph()); + makeEdge(v, vRing, "-"); + } + return {v, false}; + } + + ConvertRes operator()(RingClosure &rc) { + return {vertex(rc.other->gVertexId, rDPO.getCombinedGraph()), true}; + } +private: + void makeEdge(CombinedVertex vSrc, CombinedVertex vTar, const std::string &label) { + if(label.empty()) return; // a dot edge for no-edge + std::pair eSorted = std::minmax(vSrc, vTar); + std::tie(vSrc, vTar) = eSorted; + assert(edges.find({vSrc, vTar}) == edges.end()); + edges[{vSrc, vTar}] = label; + } +public: + const IO::DFS::Read::RuleResult &res; + DPO::CombinedRule &rDPO; + PropString &pString; + LeftEdgeMapType edges; +}; + +struct ConvertRight { + struct ConvertRes { + CombinedVertex next; + bool isRingClosure; + }; + + ConvertRight(const IO::DFS::Read::RuleResult &res, DPO::CombinedRule &rDPO, PropString &pString, + LeftEdgeMapType &leftEdges) + : res(res), rDPO(rDPO), pString(pString), leftEdges(leftEdges) {} + + void operator()(Chain &chain) &&{ + const auto sub = (*this)(chain.head, lib::Graph::GraphType::null_vertex()); + CombinedVertex vPrev = sub.next; + assert(!sub.isRingClosure); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + for(EVPair &ev: chain.tail) { + vPrev = (*this)(ev, vPrev); + assert(vPrev != lib::Graph::GraphType::null_vertex()); + } + } + + ConvertRes operator()(Vertex &vertex, CombinedVertex prev) { + const auto sub = boost::apply_visitor(*this, vertex.vertex); + assert(!(sub.isRingClosure && prev == lib::Graph::GraphType::null_vertex())); + const CombinedVertex branchRoot = sub.isRingClosure ? prev : sub.next; + for(Branch &branch: vertex.branches) { + CombinedVertex branchPrev = branchRoot; + for(EVPair &ev: branch.tail) { + branchPrev = (*this)(ev, branchPrev); + assert(branchPrev != lib::Graph::GraphType::null_vertex()); + } + } + return sub; + } + + CombinedVertex operator()(EVPair &ev, CombinedVertex prev) { + const auto res = (*this)(ev.second, prev); + makeEdge(prev, res.next, ev.first.label); + if(res.isRingClosure) return prev; + else return res.next; + } + + ConvertRes operator()(LabelVertex &vDFS) { + // ConvertLeft will set our gVertexId if it is a context vertex + CombinedVertex v; + if(vDFS.gVertexId == -1) { + assert(!vDFS.id || vDFS.ringClosure || res.left.vertexFromId.find(*vDFS.id) == res.left.vertexFromId.end()); + const auto vR = addVertexR(rDPO); + v = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vR); + const auto vId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v); + vDFS.gVertexId = vId; + pString.addR(vR, vDFS.label); +#ifdef MOD_RULEDFS_DEBUG + std::cout << "RuleDFS: ConvertRight, add_vertex("; + pString.print(std::cout, v); + std::cout << ")\n"; +#endif + } else { + assert(vDFS.id); + assert(!vDFS.ringClosure); + assert(res.left.vertexFromId.find(*vDFS.id) != res.left.vertexFromId.end()); + v = vertex(vDFS.gVertexId, rDPO.getCombinedGraph()); + } + if(vDFS.ringClosure) { + const auto vRing = vertex(vDFS.ringClosure->gVertexId, rDPO.getCombinedGraph()); + makeEdge(v, vRing, "-"); + } + return {v, false}; + } + + ConvertRes operator()(RingClosure &rc) { + return {vertex(rc.other->gVertexId, rDPO.getCombinedGraph()), true}; + } +private: + void makeEdge(CombinedVertex vSrc, CombinedVertex vTar, const std::string &label) { + if(label.empty()) return; // a dot edge for no-edge + std::pair eSorted = std::minmax(vSrc, vTar); + std::tie(vSrc, vTar) = eSorted; + const auto iterLeft = leftEdges.find({vSrc, vTar}); + if(iterLeft != leftEdges.end()) { + const auto eK = addEdgeK(rDPO, vSrc, vTar); + pString.addK(eK, iterLeft->second, label); + leftEdges.erase(iterLeft); +#ifdef MOD_RULEDFS_DEBUG + std::cout << "RuleDFS: ConvertRight, addEdgeK("; + pString.print(std::cout, eK); + std::cout << ")\n"; +#endif + } else { + const auto vRSrc = get_inverse(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vSrc); + const auto vRTar = get_inverse(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), vTar); + const auto eR = addEdgeR(rDPO, vRSrc, vRTar); + pString.addR(eR, label); +#ifdef MOD_RULEDFS_DEBUG + std::cout << "RuleDFS: ConvertRight, addEdgeR("; + pString.print(std::cout, get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), eR)); + std::cout << ")\n"; +#endif + } +#ifdef MOD_RULEDFS_DEBUG + pString.print(std::cout << "\tsrc=", vSrc); + std::cout << '\n'; + pString.print(std::cout << "\ttar=", vTar); + std::cout << '\n'; + std::cout << std::flush; +#endif + } +public: + const IO::DFS::Read::RuleResult &res; + DPO::CombinedRule &rDPO; + PropString &pString; + LeftEdgeMapType &leftEdges; +}; + +struct ImplicitLeft { + ImplicitLeft(const IO::DFS::Read::RuleResult &res) : res(res) {} + + Result<> operator()(const Chain &chain) &&{ + auto sub = (*this)(chain.head); + if(!sub) return sub; + for(const EVPair &ev: chain.tail) { + auto sub = (*this)(ev.second); + if(!sub) return sub; + } + return {}; + } + + Result<> operator()(const Vertex &vertex) { + auto sub = boost::apply_visitor(*this, vertex.vertex); + if(!sub) return sub; + for(const Branch &branch: vertex.branches) { + for(const EVPair &ev: branch.tail) { + auto sub = (*this)(ev.second); + if(!sub) return sub; + } + } + return {}; + } + + Result<> operator()(const LabelVertex &vDFS) { + if(!vDFS.implicit) return {}; + return Result<>::Error("Vertices with implicit hydrogen atoms currently not supported."); + } + + Result<> operator()(const RingClosure &rc) { + return {}; + } +public: + const IO::DFS::Read::RuleResult &res; +}; + +struct ImplicitRight { + ImplicitRight(const IO::DFS::Read::RuleResult &res) : res(res) {} + + Result<> operator()(const Chain &chain) &&{ + auto sub = (*this)(chain.head); + if(!sub) return sub; + for(const EVPair &ev: chain.tail) { + auto sub = (*this)(ev.second); + if(!sub) return sub; + } + return {}; + } + + Result<> operator()(const Vertex &vertex) { + auto sub = boost::apply_visitor(*this, vertex.vertex); + if(!sub) return sub; + for(const Branch &branch: vertex.branches) { + for(const EVPair &ev: branch.tail) { + auto sub = (*this)(ev.second); + if(!sub) return sub; + } + } + return {}; + } + + Result<> operator()(const LabelVertex &vDFS) { + if(!vDFS.implicit) return {}; + return Result<>::Error("Vertices with implicit hydrogen atoms currently not supported."); + } + + Result<> operator()(const RingClosure &rc) { + return {}; + } +public: + const IO::DFS::Read::RuleResult &res; +}; + +} // namespace dfsDetail +} // namespace + +Result dfs(lib::IO::Warnings &warnings, std::string_view input) { + auto astRes = lib::IO::DFS::Read::rule(input); + if(!astRes) return lib::IO::Result<>::Error(astRes.extractError()); + + Data data; + auto rDPO = std::make_unique(); + auto pString = std::make_unique(*rDPO); + + std::map, std::string> leftEdges; + if(astRes->left.ast) + leftEdges = dfsDetail::ConvertLeft(*astRes, *rDPO, *pString)(*astRes->left.ast); + if(astRes->right.ast) + dfsDetail::ConvertRight(*astRes, *rDPO, *pString, leftEdges)(*astRes->right.ast); + // add remaining leftEdges + for(const auto &[ep, label]: leftEdges) { + const auto vL1 = get_inverse(rDPO->getLtoCG(), getL(*rDPO), rDPO->getCombinedGraph(), ep.first); + const auto vL2 = get_inverse(rDPO->getLtoCG(), getL(*rDPO), rDPO->getCombinedGraph(), ep.second); + const auto eL = addEdgeL(*rDPO, vL1, vL2); + pString->addL(eL, label); +#ifdef MOD_RULEDFS_DEBUG + std::cout << "RuleDFS: remaining leftEdges, add_edge("; + pString->print(std::cout, get(rDPO->getLtoCG(), getL(*rDPO), rDPO->getCombinedGraph(), eL)); + std::cout << ")\n"; + pString->print(std::cout << "\tsrc=", ep.first); + std::cout << '\n'; + pString->print(std::cout << "\ttar=", ep.second); + std::cout << '\n'; + std::cout << std::flush; +#endif + } + + if(astRes->left.ast) + if(auto res = dfsDetail::ImplicitLeft(*astRes)(*astRes->left.ast); !res) + return res; + if(astRes->right.ast) + if(auto res = dfsDetail::ImplicitRight(*astRes)(*astRes->right.ast); !res) + return res; + + if(astRes->left.ast) { + for(const auto[id, vDFS]: astRes->left.vertexFromId) { + assert(vDFS->gVertexId != -1); + data.externalToInternalIds[id] = vDFS->gVertexId; + } + } + if(astRes->right.ast) { + for(const auto[id, vDFS]: astRes->right.vertexFromId) { + assert(vDFS->gVertexId != -1); + if(auto iter = data.externalToInternalIds.find(id); iter == data.externalToInternalIds.end()) { + data.externalToInternalIds[id] = vDFS->gVertexId; + } else { + assert(iter->second == vDFS->gVertexId); + } + } + } + + data.rule.emplace(std::move(rDPO), std::move(pString), nullptr); + return std::move(data); // TODO: remove std::move when C++20/P1825R0 is available +} + +} // namespace mod::lib::Rules::Read \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/IO/Read.hpp b/libs/libmod/src/mod/lib/Rules/IO/Read.hpp new file mode 100644 index 0000000..8ea42f5 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/Read.hpp @@ -0,0 +1,31 @@ +#ifndef MOD_LIB_RULE_IO_READ_HPP +#define MOD_LIB_RULE_IO_READ_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace mod::lib::Rules { +struct Real; +} // namespace mod::lib::Rules +namespace mod::lib::Rules::Read { + +struct Data { + std::optional rule; + std::optional name; + std::optional labelType; + // maps external IDs to vertex IDs in the combined graph + std::map externalToInternalIds; +}; + +lib::IO::Result gml(lib::IO::Warnings &warnings, std::string_view input); +lib::IO::Result dfs(lib::IO::Warnings &warnings, std::string_view input); + +} // namespace mod::lib::Rules::Read + +#endif // MOD_LIB_RULE_IO_READ_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/IO/Write.cpp b/libs/libmod/src/mod/lib/Rules/IO/Write.cpp new file mode 100644 index 0000000..75b4844 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/Write.cpp @@ -0,0 +1,984 @@ +#include "Write.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mod::lib::Rules::Write { +namespace { + +// returns the filename _without_ extension + +std::string getFilePrefix(const Real &r) { + return IO::makeUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()); +} + +void gmlSide(const Real &r, std::ostream &s, Membership printMembership, bool withCoords) { + if(withCoords) { + const auto &depict = r.getDepictionData(); + if(!depict.getHasCoordinates()) MOD_ABORT; + } + const auto &lr = r.getDPORule(); + const auto &rDPO = lr.getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto &pString = get_string(lr); + + for(const auto vCG: asRange(vertices(gCombined))) { + const auto vMembership = gCombined[vCG].membership; + if(printMembership == Membership::K) { + if(vMembership != Membership::K) continue; + if(pString.isChanged(vCG)) continue; + } else { + if(vMembership == Membership::K) { + if(!pString.isChanged(vCG)) continue; + } else { + if(printMembership != vMembership) continue; + } + } + s << "\t\tnode [ id " << get(boost::vertex_index_t(), gCombined, vCG) << " label \""; + switch(printMembership) { + case Membership::L: + case Membership::K: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)]; + break; + case Membership::R: + s << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)]; + break; + } + s << "\""; + if(withCoords) { + const auto &depict = r.getDepictionData(); + s << " vis2d [ x " << depict.getX(vCG, true) << " y " << depict.getY(vCG, true) << " ]"; + } + s << " ]\n"; + } + + // we want the GML to be sorted, so iterate based on the vertices + for(const auto vCGSrc: asRange(vertices(gCombined))) { + for(const auto eCG: asRange(out_edges(vCGSrc, gCombined))) { + const auto vCGTar = target(eCG, gCombined); + const auto vSrcId = get(boost::vertex_index_t(), gCombined, vCGSrc); + const auto vTarId = get(boost::vertex_index_t(), gCombined, vCGTar); + if(vSrcId > vTarId) continue; + const auto eMembership = gCombined[eCG].membership; + if(printMembership == Membership::K) { + if(eMembership != Membership::K) continue; + if(pString.isChanged(eCG)) continue; + } else { + if(eMembership == Membership::K) { + if(!pString.isChanged(eCG)) continue; + } else { + if(printMembership != eMembership) continue; + } + } + s << "\t\tedge [ source " << vSrcId << " target " << vTarId << " label \""; + switch(printMembership) { + case Membership::L: + case Membership::K: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)]; + break; + case Membership::R: + s << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)]; + break; + } + s << "\" ]\n"; + } + } +} + +void printEdgeStyle(std::ostream &s, Membership eSide, int src, int tar) { + s << "\t" << src << " -- " << tar << " [ "; + switch(eSide) { + case Membership::L: + s << "style=dashed "; + break; + case Membership::R: + s << "style=dotted "; + break; + default: + break; + } +} + +} // namespace + +void gml(const Real &r, bool withCoords, std::ostream &s) { + s << "rule [\n"; + s << "\truleID \"" << r.getName() << "\"\n"; + if(r.getLabelType()) { + s << "\tlabelType \""; + switch(*r.getLabelType()) { + case LabelType::String: + s << "string"; + break; + case LabelType::Term: + s << "term"; + break; + } + s << "\"\n"; + } + { + std::stringstream str; + gmlSide(r, str, Membership::L, withCoords); + if(str.str().size() > 0) s << "\tleft [\n" << str.str() << "\t]\n"; + } + { + std::stringstream str; + gmlSide(r, str, Membership::K, withCoords); + if(str.str().size() > 0) s << "\tcontext [\n" << str.str() << "\t]\n"; + } + { + std::stringstream str; + gmlSide(r, str, Membership::R, withCoords); + if(str.str().size() > 0) s << "\tright [\n" << str.str() << "\t]\n"; + } + for(const auto &c: get_match_constraints(get_labelled_left(r.getDPORule()))) + lib::GraphMorphism::Write::gmlConstraint(s, getL(r.getDPORule().getRule()), "\t", *c); + s << "]"; +} + +std::string gml(const Real &r, bool withCoords) { + post::FileHandle s(getFilePrefix(r) + ".gml"); + gml(r, withCoords, s); + return s; +} + +std::string dotCombined(const Real &r) { + std::stringstream fileName; + fileName << "r_" << r.getId() << "_combined.dot"; + post::FileHandle s(IO::makeUniqueFilePrefix() + fileName.str()); + std::string fileNoExt = s; + fileNoExt.erase(fileNoExt.end() - 4, fileNoExt.end()); + + const auto &rDPO = r.getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto &pString = get_string(r.getDPORule()); + s << "graph G {\n"; + for(const auto vCG: asRange(vertices(gCombined))) { + const auto membership = gCombined[vCG].membership; + s << "\t" << get(boost::vertex_index_t(), gCombined, vCG) << " [ label=\""; + switch(membership) { + case Membership::L: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)]; + break; + case Membership::K: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)] << " | " + << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)]; + break; + case Membership::R: + s << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)]; + break; + } + s << "\""; + switch(membership) { + case Membership::L: + s << " style=dashed"; + break; + case Membership::R: + s << " style=dotted"; + break; + default: + break; + } + s << " ]\n"; + } + + // we want the output to be sorted, so iterate based on the vertices + for(const auto vCGSrc: asRange(vertices(gCombined))) { + for(const auto eCG: asRange(out_edges(vCGSrc, gCombined))) { + const auto vCGTar = target(eCG, gCombined); + const auto vSrcId = get(boost::vertex_index_t(), gCombined, vCGSrc); + const auto vTarId = get(boost::vertex_index_t(), gCombined, vCGTar); + if(vSrcId > vTarId) continue; + const auto membership = gCombined[eCG].membership; + std::string label; + switch(membership) { + case Membership::L: + label = pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)]; + break; + case Membership::K: + label = pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)] + " | " + + pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)]; + break; + case Membership::R: + label = pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)]; + break; + } + switch(label[0]) { + // case '=': // fall through to make two edges + // // assert(false); + // printEdgeStyle(s, membership, vSrcId, vTarId); + // s << "]\n"; + // case '-': // print the rest of the label + // printEdgeStyle(s, membership, vSrcId, vTarId); + // s << "label=\"" << (label.c_str() + 1) << "\" ]\n"; + // break; + default: + printEdgeStyle(s, membership, vSrcId, vTarId); + s << "label=\"" << label << "\" ]\n"; + break; + } + } + } + s << "}\n"; + return fileNoExt; +} + +std::string svgCombined(const Real &r) { + std::string fileNoExt = dotCombined(r); + IO::post() << "gv ruleCombined \"" << fileNoExt << "\" svg\n"; + return fileNoExt; +} + +std::string pdfCombined(const Real &r) { + std::string fileNoExt = svgCombined(r); + IO::post() << "svgToPdf \"" << fileNoExt << "\"\n"; + return fileNoExt; +} + +namespace { + +struct DotCacheEntry { + std::size_t id; + std::string prefix; +public: + friend bool operator<(const DotCacheEntry &a, const DotCacheEntry &b) { + return std::tie(a.id, a.prefix) < std::tie(b.id, b.prefix); + } +}; + +} // namespace + +std::string dot(const Real &r, const Options &options) { + static std::map cache; + const auto iter = cache.find({r.getId(), options.graphvizPrefix}); + if(iter != end(cache)) return iter->second; + + std::string fileNoExt = getFilePrefix(r); + post::FileHandle s(fileNoExt + ".dot"); + + const auto &rDPO = r.getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto &pString = get_string(r.getDPORule()); + + s << "graph G {\n"; + if(!options.graphvizPrefix.empty()) + s << "\t" << options.graphvizPrefix << '\n'; + + for(const auto vCG: asRange(vertices(gCombined))) { + const auto vId = get(boost::vertex_index_t(), gCombined, vCG); + s << vId << " [ label=\""; + const auto membership = gCombined[vCG].membership; + switch(membership) { + case Membership::L: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)]; + break; + case Membership::K: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)] << " | " + << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)]; + break; + case Membership::R: + s << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)]; + break; + } + s << "\" ];\n"; + } + + // we want the output to be sorted, so iterate based on the vertices + for(const auto vCGSrc: asRange(vertices(gCombined))) { + for(const auto eCG: asRange(out_edges(vCGSrc, gCombined))) { + const auto vCGTar = target(eCG, gCombined); + const auto vSrcId = get(boost::vertex_index_t(), gCombined, vCGSrc); + const auto vTarId = get(boost::vertex_index_t(), gCombined, vCGTar); + if(vSrcId > vTarId) continue; + s << vSrcId << " -- " << vTarId << " [ label=\""; + const auto membership = gCombined[eCG].membership; + switch(membership) { + case Membership::L: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)]; + break; + case Membership::K: + s << pString.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)] << " | " + << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)]; + break; + case Membership::R: + s << pString.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)]; + break; + } + s << "\" ];\n"; + } + } + s << "}\n"; + + cache[{r.getId(), options.graphvizPrefix}] = fileNoExt; + return fileNoExt; +} + +namespace { + +struct OpenBabelCoordsCacheEntry { + std::size_t id; + bool collapseHydrogens; + int rotation; + bool mirror; + int idOffset; +public: + friend bool operator<(const OpenBabelCoordsCacheEntry &a, const OpenBabelCoordsCacheEntry &b) { + return std::tie(a.id, a.collapseHydrogens, a.rotation, a.mirror, a.idOffset) + < std::tie(b.id, b.collapseHydrogens, b.rotation, b.mirror, b.idOffset); + } +}; + +bool disallowHydrogenCollapseByChangeThenCallback(const Real &r, const CombinedVertex vCG, + std::function disallowCollapse) { + if(getConfig().rule.collapseChangedHydrogens.get()) + return disallowCollapse(vCG); + const auto &gCombined = r.getDPORule().getRule().getCombinedGraph(); + // if we are changed, then disallow + if(gCombined[vCG].membership != Membership::K) + return true; + // and if any incident edges are changed, then disallow + for(const auto eCG: asRange(out_edges(vCG, gCombined))) { + if(gCombined[eCG].membership != Membership::K) + return true; + } + return disallowCollapse(vCG); +} + +} // namespace + +std::string coords(const Real &r, int idOffset, const Options &options, + std::function disallowCollapse_) { + assert(idOffset >= 0); + const auto &depict = r.getDepictionData(); + if(options.withGraphvizCoords || !depict.getHasCoordinates()) { + if(idOffset != 0) + throw FatalError("Blame the lazy programmer. Offset other than 0 not yet supported in dot coords."); + + // we map 1-to-1 a dot file to a coord file, so cache by the dot filename + static std::map cache; + const auto fileNoExt = dot(r, options); + const auto iter = cache.find(fileNoExt); + if(iter != end(cache)) return iter->second; + + IO::post() << "coordsFromGV rule \"" << fileNoExt << "\" noOverlay\n"; + // the coord file is still for the tex coord file which is just then created in post + std::string file = fileNoExt + "_coord"; + cache[fileNoExt] = file; + return file; + } else { + static std::map cache; + const auto iter = cache.find({r.getId(), options.collapseHydrogens, options.rotation, options.mirror, idOffset}); + if(iter != end(cache)) return iter->second; + + const auto &gCombined = r.getDPORule().getRule().getCombinedGraph(); + const std::string fileNoExt = [&]() { + auto f = getFilePrefix(r); + if(options.collapseHydrogens) f += "_mol"; + if(options.rotation != 0) f += "_r" + std::to_string(options.rotation); + if(options.mirror) f += "_m" + std::to_string(options.mirror); + if(idOffset != 0) f += "_id" + std::to_string(idOffset); + f += "_coord"; + return f; + }(); + post::FileHandle s(fileNoExt + ".tex"); + s << "% dummy\n"; + const bool useCollapsedCoords = [&]() { + // if the options didn't request collapsing, don't use it + if(!options.collapseHydrogens) return false; + // if there is _any_ user-defined collapsing, then make all coordinates available + for(const auto vCG: asRange(vertices(gCombined))) + if(disallowCollapse_(vCG)) + return false; + return true; + }(); + + for(const auto vCG: asRange(vertices(gCombined))) { + const auto vId = get(boost::vertex_index_t(), gCombined, vCG); + // if we are in the collapsed case and the depictor collapsed it, don't print + if(useCollapsedCoords && depict.mayCollapse(vCG)) continue; + const auto[x, y] = pointTransform( + depict.getX(vCG, !useCollapsedCoords), + depict.getY(vCG, !useCollapsedCoords), + options.rotation, options.mirror); + s << "\\coordinate[overlay] (\\modIdPrefix v-coord-" << (vId + idOffset) << ") at (" + << std::fixed << x << ", " << y << ") {};\n"; + } + if(options.collapseHydrogens && !useCollapsedCoords) { + // don't cache these as the user predicate influences it + } else { + cache[{r.getId(), options.collapseHydrogens, options.rotation, options.mirror, idOffset}] = fileNoExt; + } + return fileNoExt; + } +} + +namespace { + +using SideGraph = lib::DPO::CombinedRule::SideGraphType; +using SideVertex = lib::DPO::CombinedRule::SideVertex; +using SideEdge = lib::DPO::CombinedRule::SideEdge; +using MorphismType = lib::DPO::CombinedRule::MorphismType; +using ToCombinedMorphismSide = lib::DPO::CombinedRule::ToCombinedMorphismSide; +using KVertex = lib::DPO::CombinedRule::KVertex; +using KEdge = lib::DPO::CombinedRule::KEdge; +using DepictSide = DepictionData::Side; +using LabelledSide = LabelledRule::Side; + +struct AdvOptionsSide { + AdvOptionsSide(const Real &r, int idOffset, const BaseArgs &args, + std::function disallowHydrogenCollapse, + std::string changeColour, + const SideGraph &g, const MorphismType &mToSide, const ToCombinedMorphismSide &mToCG, + DepictSide depict, LabelledSide lg) + : idOffset(idOffset), changeColour(std::move(changeColour)), r(r), rDPO(r.getDPORule().getRule()), + args(args), disallowHydrogenCollapse_(disallowHydrogenCollapse), + g(g), mToCG(mToCG), depict(depict), lg(lg) {} +public: + std::string getColour(SideVertex vS) const { + const auto vCG = get(mToCG, g, rDPO.getCombinedGraph(), vS); + const bool isChanged = r.getDPORule().getRule().getCombinedGraph()[vCG].membership != Membership::K + || get_string(r.getDPORule()).isChanged(vCG); + if(isChanged) return changeColour; + else return args.vColour(vCG); + } + + std::string getColour(SideEdge eS) const { + const auto eCG = get(mToCG, g, rDPO.getCombinedGraph(), eS); + const bool isChanged = r.getDPORule().getRule().getCombinedGraph()[eCG].membership != Membership::K + || get_string(r.getDPORule()).isChanged(eCG); + if(isChanged) return changeColour; + else return args.eColour(eCG); + } + + bool isVisible(SideVertex vS) const { + const auto v = get(mToCG, g, rDPO.getCombinedGraph(), vS); + return args.visible(v); + } + + std::string getShownId(SideVertex vS) const { + const auto v = get(mToCG, g, rDPO.getCombinedGraph(), vS); + return std::to_string(get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v)); + } + + bool overwriteWithIndex(SideVertex) const { + return false; + } + + lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(SideEdge eS, bool withHydrogen) const { + return depict.getEdgeFake3DType(eS, withHydrogen); + } + + std::string getOpts(SideVertex vS) const { + return std::string(); + } +private: + template + std::string getStereoStringVertex(SideVertex vS, const F f) const { +// assert(false); // TODO: map vS + const auto &conf = *get_stereo(lg)[vS]; + const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { + return get(boost::vertex_index_t(), g, target(emb.getEdge(vS, g), g)); + }; + std::string res = f(conf, getNeighbourId); + const auto v = get(mToCG, g, rDPO.getCombinedGraph(), vS); + if(!get_stereo(r.getDPORule()).inContext(v)) { + res = "{\\color{" + changeColour + "}" + res + "}"; + } + return res; + } +public: + std::string getRawStereoString(SideVertex vS) const { +// assert(false); // TODO: anything to map? + return getStereoStringVertex(vS, [&](const auto &conf, auto getNId) { + return conf.asRawString(getNId); + }); + } + + std::string getPrettyStereoString(SideVertex vS) const { +// assert(false); // TODO: anything to map? + return getStereoStringVertex(vS, [&](const auto &conf, auto getNId) { + return conf.asPrettyString(getNId); + }); + } + + std::string getStereoString(SideEdge eS) const { +// assert(false); // TODO: map eS + const auto cat = get_stereo(lg)[eS]; + std::string res = boost::lexical_cast(cat); + const auto e = get(mToCG, g, rDPO.getCombinedGraph(), eS); + if(!get_stereo(r.getDPORule()).inContext(e)) { + res = "{\\color{" + changeColour + "}" + res + "}"; + } + return res; + } + + std::string getEdgeAnnotation(SideEdge) const { + return {}; + } + + bool disallowHydrogenCollapse(SideVertex vS) const { + const auto v = get(mToCG, g, rDPO.getCombinedGraph(), vS); + return disallowHydrogenCollapseByChangeThenCallback(r, v, disallowHydrogenCollapse_); + } +public: + auto getOutputId(SideVertex vS) const { + const auto v = get(mToCG, g, rDPO.getCombinedGraph(), vS); + return idOffset + get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v); + } +private: + const int idOffset; + const std::string changeColour; + const Real &r; + const lib::DPO::CombinedRule &rDPO; + const BaseArgs &args; + std::function disallowHydrogenCollapse_; + const SideGraph &g; + const ToCombinedMorphismSide &mToCG; + const DepictSide depict; + const LabelledSide lg; +}; + +struct AdvOptionsK { + AdvOptionsK(const Real &r, int idOffset, const BaseArgs &args, + std::function disallowHydrogenCollapse, + std::string changeColour) + : idOffset(idOffset), changeColour(std::move(changeColour)), r(r), rDPO(r.getDPORule().getRule()), + args(args), disallowHydrogenCollapse_(disallowHydrogenCollapse) {} +public: + std::string getColour(KVertex vK) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + const bool isChanged = get_string(r.getDPORule()).isChanged(v); + if(isChanged) return changeColour; + else return args.vColour(v); + } + + std::string getColour(KEdge eK) const { + const auto eCG = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), eK); + assert(r.getDPORule().getRule().getCombinedGraph()[eCG].membership == Membership::K); + const bool isChanged = get_string(r.getDPORule()).isChanged(eCG); + if(isChanged) return changeColour; + else return args.eColour(eCG); + } + + bool isVisible(KVertex vK) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + return args.visible(v); + } + + std::string getShownId(KVertex vK) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + return std::to_string(get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v)); + } + + bool overwriteWithIndex(KVertex) const { + return false; + } + + lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(KEdge eK, bool withHydrogen) const { + const auto e = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), eK); + return r.getDepictionData().getContext().getEdgeFake3DType(e, withHydrogen); + } + + std::string getOpts(KVertex) const { + return {}; + } +private: + template + std::string getStereoStringVertex(const KVertex vK, const F f) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + const auto &lr = r.getDPORule(); + if(get_stereo(lr).inContext(v)) { + const auto &lgLeft = get_labelled_left(r.getDPORule()); + const auto vL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), vK); + const auto &conf = *get_stereo(lgLeft)[vL]; + const auto getNeighbourId = [&](const lib::Stereo::EmbeddingEdge &emb) { + const auto &gLeft = get_graph(lgLeft); + return get(boost::vertex_index_t(), gLeft, target(emb.getEdge(vL, gLeft), gLeft)); + }; + return f(conf, getNeighbourId); + } else return {}; + } +public: + std::string getRawStereoString(KVertex vK) const { + return getStereoStringVertex(vK, [&](const auto &conf, auto getNId) { + return conf.asRawString(getNId); + }); + } + + std::string getPrettyStereoString(KVertex vK) const { + return getStereoStringVertex(vK, [&](const auto &conf, auto getNId) { + return conf.asPrettyString(getNId); + }); + } + + std::string getStereoString(KEdge eK) const { + const auto e = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), eK); + const auto &lr = r.getDPORule(); + if(get_stereo(lr).inContext(e)) { + const auto &lgLeft = get_labelled_left(r.getDPORule()); + const auto eL = get(getMorL(rDPO), getK(rDPO), getL(rDPO), eK); + const auto cat = get_stereo(lgLeft)[eL]; + return boost::lexical_cast(cat); + } else return {}; + } + + std::string getEdgeAnnotation(KEdge) const { + return {}; + } + + bool disallowHydrogenCollapse(KVertex vK) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + return disallowHydrogenCollapseByChangeThenCallback(r, v, disallowHydrogenCollapse_); + } +public: + int getOutputId(KVertex vK) const { + const auto v = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), vK); + return idOffset + get(boost::vertex_index_t(), rDPO.getCombinedGraph(), v); + } +private: + const int idOffset; + const std::string changeColour; + const Real &r; + const lib::DPO::CombinedRule &rDPO; + const BaseArgs &args; + std::function disallowHydrogenCollapse_; +}; + +} // namespace + +std::pair +tikz(const std::string &fileCoordsNoExt, const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, const BaseArgs &args, + std::function disallowCollapse) { + std::string strOptions = options.getStringEncoding(); + std::string fileNoExt = IO::makeUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()); + fileNoExt += "_" + strOptions; + + const auto &rDPO = r.getDPORule().getRule(); + + std::string fileCoords = fileCoordsNoExt + ".tex"; + { // left + post::FileHandle s(fileNoExt + "_" + suffixL + ".tex"); + const auto &g = getL(rDPO); + const auto &depict = r.getDepictionData().getLeft(); + const auto adv = AdvOptionsSide(r, idOffset, args, disallowCollapse, + getConfig().rule.changeColourL.get(), + g, getMorL(rDPO), rDPO.getLtoCG(), + r.getDepictionData().getLeft(), + get_labelled_left(r.getDPORule())); + IO::Graph::Write::tikz(s, options, g, depict, fileCoords, adv, jla_boost::Nop<>(), ""); + } + { // context + post::FileHandle s(fileNoExt + "_" + suffixK + ".tex"); + const auto &g = getK(rDPO); + const auto &depict = r.getDepictionData().getContext(); + + struct EdgeVisible { + EdgeVisible() = default; + + EdgeVisible(const Real &r) : r(&r) {} + + bool operator()(const CombinedEdge e) const { + if(getConfig().rule.printChangedEdgesInContext.get()) return true; + return !get_string(r->getDPORule()).isChanged(e); + } + private: + const Real *r = nullptr; + }; + boost::filtered_graph gFiltered(g, EdgeVisible(r)); + const auto adv = AdvOptionsK(r, idOffset, args, disallowCollapse, + getConfig().rule.changeColourK.get()); + IO::Graph::Write::tikz(s, options, gFiltered, depict, fileCoords, adv, jla_boost::Nop<>(), ""); + } + { // right + post::FileHandle s(fileNoExt + "_" + suffixR + ".tex"); + const auto &g = getR(rDPO); + const auto &depict = r.getDepictionData().getRight(); + const auto adv = AdvOptionsSide(r, idOffset, args, disallowCollapse, + getConfig().rule.changeColourR.get(), + g, getMorR(rDPO), rDPO.getRtoCG(), + r.getDepictionData().getRight(), + get_labelled_right(r.getDPORule())); + IO::Graph::Write::tikz(s, options, g, depict, fileCoords, adv, jla_boost::Nop<>(), ""); + } + return std::make_pair(fileNoExt, fileCoordsNoExt); +} + +std::pair tikz(const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffixL, const std::string &suffixK, + const std::string &suffixR, const BaseArgs &args, + std::function disallowCollapse) { + std::string fileCoordsNoExt = coords(r, idOffset, options, disallowCollapse); + return tikz(fileCoordsNoExt, r, idOffset, options, suffixL, suffixK, suffixR, args, disallowCollapse); +} + +std::string pdf(const Real &r, const Options &options, + const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, + const BaseArgs &args) { + std::string fileNoExt, fileCoordsNoExt; + const unsigned int idOffset = 0; + std::tie(fileNoExt, fileCoordsNoExt) = tikz(r, idOffset, options, suffixL, suffixK, suffixR, args, + jla_boost::AlwaysFalse()); + IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixL << "\" \"" << fileCoordsNoExt << "\"\n"; + IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixK << "\" \"" << fileCoordsNoExt << "\"\n"; + IO::post() << "compileTikz \"" << fileNoExt << "_" << suffixR << "\" \"" << fileCoordsNoExt << "\"\n"; + IO::post().flush(); + return fileNoExt; +} + +std::pair +tikzTransitionState(const std::string &fileCoordsNoExt, const Real &r, unsigned int idOffset, + const Options &options, + const std::string &suffix, const BaseArgs &args) { + MOD_ABORT; +} + +std::pair +tikzTransitionState(const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffix, const BaseArgs &args) { + MOD_ABORT; +} + +std::string pdfTransitionState(const Real &r, const Options &options, + const std::string &suffix, const BaseArgs &args) { + std::string fileNoExt, fileCoordsNoExt; + const unsigned int idOffset = 0; + std::tie(fileNoExt, fileCoordsNoExt) = tikzTransitionState(r, idOffset, options, suffix, args); + IO::post() << "compileTikz \"" << fileNoExt << "_" << suffix << "\" \"" << fileCoordsNoExt << "\"\n"; + return fileNoExt; +} + +std::string pdfCombined(const Real &r, const Options &options) { + MOD_ABORT; +} + +std::pair summary(const Real &r, bool printCombined) { + graph::Printer first; + graph::Printer second; + second.setReactionDefault(); + return summary(r, first.getOptions(), second.getOptions(), printCombined); +} + +std::pair +summary(const Real &r, const Options &first, const Options &second, bool printCombined) { + auto visible = jla_boost::AlwaysTrue(); + auto vColour = jla_boost::Nop(); + auto eColour = jla_boost::Nop(); + const BaseArgs args{visible, vColour, eColour}; + std::string graphLike = pdf(r, first, "L", "K", "R", args); + std::string molLike = first == second ? "" : pdf(r, second, "L", "K", "R", args); + std::string combined = printCombined + ? pdfCombined(r /*, Options().EdgesAsBonds().RaiseCharges()*/) + : ""; + std::string constraints = + IO::makeUniqueFilePrefix() + "r_" + boost::lexical_cast(r.getId()) + "_constraints.tex"; + { + post::FileHandle s(constraints); + for(const auto &c: get_match_constraints(get_labelled_left(r.getDPORule()))) { + lib::GraphMorphism::Write::texConstraint(s, getL(r.getDPORule().getRule()), *c); + s << "\n"; + } + } + IO::post() << "summaryRule \"" << r.getName() << "\" \"" << graphLike << "\" \"" << molLike << "\" \"" << combined + << "\" \"" << constraints << "\"\n"; + if(molLike.empty()) + return std::pair(graphLike, graphLike); + else + return std::pair(graphLike, molLike); +} + +void termState(const Real &r) { + using namespace lib::Term; + using CombinedVertex = lib::DPO::CombinedRule::CombinedVertex; + using CombinedEdge = lib::DPO::CombinedRule::CombinedEdge; + IO::post() << "summarySubsection \"Term State for " << r.getName() << "\"\n"; + post::FileHandle s(IO::makeUniqueFilePrefix() + "termState.tex"); + + const auto &rDPO = r.getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto &pTerm = get_term(r.getDPORule()); + + s << "\\begin{verbatim}\n"; + if(isValid(pTerm)) { + std::unordered_map>> addrToVertex; + std::unordered_map>> addrToEdge; + std::unordered_map> addrToConstraintInfo; + const auto insertVertex = [&addrToVertex](std::size_t addr, CombinedVertex v, Membership membership) { + Address a{AddressType::Heap, addr}; + addrToVertex[a].insert({v, membership}); + }; + const auto insertEdge = [&addrToEdge](std::size_t addr, CombinedEdge e, Membership membership) { + Address a{AddressType::Heap, addr}; + addrToEdge[a].insert({e, membership}); + }; + for(const auto vCG: asRange(vertices(gCombined))) { + switch(gCombined[vCG].membership) { + case Membership::L: + insertVertex(pTerm.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)], vCG, Membership::L); + break; + case Membership::K: + insertVertex(pTerm.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG)], vCG, Membership::L); + insertVertex(pTerm.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)], vCG, + Membership::R); + break; + case Membership::R: + insertVertex(pTerm.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG)], vCG, + Membership::R); + break; + } + } + for(const auto eCG: asRange(edges(r.getDPORule().getRule().getCombinedGraph()))) { + switch(gCombined[eCG].membership) { + case Membership::L: + insertEdge(pTerm.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)], eCG, Membership::L); + break; + case Membership::K: + insertEdge(pTerm.getLeft()[get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG)], eCG, Membership::L); + insertEdge(pTerm.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)], eCG, Membership::R); + break; + case Membership::R: + insertEdge(pTerm.getRight()[get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG)], eCG, Membership::R); + break; + } + } + + using SideGraphType = lib::DPO::CombinedRule::SideGraphType; + + struct Visitor : lib::GraphMorphism::Constraints::AllVisitor { + Visitor(std::unordered_map> &addrMap, + const lib::DPO::CombinedRule::CombinedGraphType &gCombined) + : addrMap(addrMap), gCombined(gCombined) {} + + virtual void operator()(const lib::GraphMorphism::Constraints::VertexAdjacency &c) override { + const auto vStr = boost::lexical_cast(get(boost::vertex_index_t(), gCombined, c.vConstrained)); + for(const auto a: c.vertexTerms) { + Address addr{AddressType::Heap, a}; + std::string msg = "VertexAdj(" + vStr + ", " + side + ", V)"; + addrMap[addr].insert(std::move(msg)); + } + for(const auto a: c.edgeTerms) { + Address addr{AddressType::Heap, a}; + std::string msg = "VertexAdj(" + vStr + ", " + side + ", E)"; + addrMap[addr].insert(std::move(msg)); + } + } + + virtual void operator()(const lib::GraphMorphism::Constraints::ShortestPath &c) override {} + public: + std::unordered_map> &addrMap; + const lib::DPO::CombinedRule::CombinedGraphType &gCombined; + std::string side; + }; + Visitor vis(addrToConstraintInfo, gCombined); + vis.side = "L"; + for(const auto &c: get_match_constraints(get_labelled_left(r.getDPORule()))) + c->accept(vis); + vis.side = "R"; + for(const auto &c: get_match_constraints(get_labelled_right(r.getDPORule()))) + c->accept(vis); + + Term::Write::wam(getMachine(pTerm), lib::Term::getStrings(), s, [&](Address addr, std::ostream &s) { + s << " "; + bool first = true; + for(auto vm: addrToVertex[addr]) { + if(!first) s << ", "; + first = false; + s << "v(" << get(boost::vertex_index_t(), gCombined, vm.first) << ", "; + switch(vm.second) { + case Membership::L: + s << "L"; + break; + case Membership::R: + s << "R"; + break; + case Membership::K: + s << "K"; + break; + } + s << ")"; + } + for(auto em: addrToEdge[addr]) { + if(!first) s << ", "; + first = false; + s << "e(" + << get(boost::vertex_index_t(), gCombined, source(em.first, gCombined)) + << ", " + << get(boost::vertex_index_t(), gCombined, target(em.first, gCombined)) + << ", "; + switch(em.second) { + case Membership::L: + s << "L"; + break; + case Membership::R: + s << "R"; + break; + case Membership::K: + s << "K"; + break; + } + s << ")"; + } + for(auto &msg: addrToConstraintInfo[addr]) { + if(!first) s << ", "; + first = false; + s << msg; + } + }); + } else { + std::string msg = "Parsing failed for rule '" + r.getName() + "'. " + pTerm.getParsingError(); + throw TermParsingError(std::move(msg)); + } + s << "\\end{verbatim}\n"; + IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; +} + +std::string stereoSummary(const Real &r, lib::DPO::CombinedRule::CombinedVertex vcg, Membership m, + const IO::Graph::Write::Options &options) { + assert(m != Membership::K); + const auto &lr = r.getDPORule(); + const auto &rDPO = lr.getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + if(m == Membership::L) assert(gCombined[vcg].membership != Membership::R); + if(m == Membership::R) assert(gCombined[vcg].membership != Membership::L); + const std::string side = m == Membership::L ? "L" : "R"; + std::string name = "r_" + std::to_string(r.getId()) + "_" + side + "_stereo_" + + std::to_string(get(boost::vertex_index_t(), gCombined, vcg)); + IO::post() << "summarySubsection \"Stereo, r " << r.getId() << ", v " << get(boost::vertex_index_t(), gCombined, vcg) + << " " << side << "\"\n"; + const auto handler = [&](const auto &lgSide, const auto &mSideToCG, const auto &depict) { + const auto &gSide = get_graph(lgSide); + const auto vSide = get_inverse(mSideToCG, gSide, gCombined, vcg); + return lib::Stereo::Write::pdf(gSide, vSide, *get_stereo(lgSide)[vSide], name, depict, options, + [](const auto &gSide, const auto vSide) { + return get(boost::vertex_index_t(), gSide, vSide); + }); + }; + std::string f = m == Membership::L + ? handler(get_labelled_left(lr), rDPO.getLtoCG(), r.getDepictionData().getLeft()) + : handler(get_labelled_right(lr), rDPO.getRtoCG(), r.getDepictionData().getRight()); + post::FileHandle s(IO::makeUniqueFilePrefix() + "stereo.tex"); + s << "\\begin{center}\n"; + s << "\\includegraphics{" << f << "}\\\\\n"; + s << "File: \\texttt{" << IO::escapeForLatex(f) << "}\n"; + s << "\\end{center}\n"; + IO::post() << "summaryInput \"" << std::string(s) << "\"\n"; + return f; +} + +} // namespace mod::lib::Rules::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/IO/Write.hpp b/libs/libmod/src/mod/lib/Rules/IO/Write.hpp new file mode 100644 index 0000000..b077ba3 --- /dev/null +++ b/libs/libmod/src/mod/lib/Rules/IO/Write.hpp @@ -0,0 +1,65 @@ +#ifndef MOD_LIB_RULE_IO_WRITE_HPP +#define MOD_LIB_RULE_IO_WRITE_HPP + +#include +#include + +namespace mod::lib::Rules { +struct Real; +} // namespace mod::lib::Rules +namespace mod::lib::Rules::Write { + +using Options = IO::Graph::Write::Options; +using CombinedVertex = lib::DPO::CombinedRule::CombinedVertex; +using CombinedEdge = lib::DPO::CombinedRule::CombinedEdge; + +struct BaseArgs { + std::function visible; + std::function vColour; + std::function eColour; +}; + +// returns the filename _with_ extension +void gml(const Real &r, bool withCoords, std::ostream &s); +std::string gml(const Real &r, bool withCoords); +// returns the filename without extension +std::string dotCombined(const Real &r); +std::string svgCombined(const Real &r); +std::string pdfCombined(const Real &r); +// returns the filename _without_ extension +std::string +dot(const Real &r, const Options &options); // does not handle labels correctly, is for coordinate generation +std::string coords(const Real &r, int idOffset, const Options &options, + std::function disallowCollapse_); +std::pair +tikz(const std::string &fileCoordsNoExt, const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, const BaseArgs &args, + std::function disallowCollapse); +std::pair tikz(const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffixL, const std::string &suffixK, + const std::string &suffixR, const BaseArgs &args, + std::function disallowCollapse); +std::string pdf(const Real &r, const Options &options, + const std::string &suffixL, const std::string &suffixK, const std::string &suffixR, + const BaseArgs &args); +std::pair +tikzTransitionState(const std::string &fileCoordsNoExt, const Real &r, unsigned int idOffset, + const Options &options, + const std::string &suffix, const BaseArgs &args); +std::pair +tikzTransitionState(const Real &r, unsigned int idOffset, const Options &options, + const std::string &suffix, const BaseArgs &args); +std::string pdfTransitionState(const Real &r, const Options &options, + const std::string &suffix, const BaseArgs &args); +//std::string pdfCombined(const Real &r, const Options &options); // TODO +std::pair summary(const Real &r, bool printCombined); +std::pair +summary(const Real &r, const Options &first, const Options &second, bool printCombined); +void termState(const Real &r); + +std::string stereoSummary(const Real &r, lib::DPO::CombinedRule::CombinedVertex v, Membership m, + const IO::Graph::Write::Options &options); + +} // namespace mod::lib::Rules::Write + +#endif // MOD_LIB_RULE_IO_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/LabelledRule.cpp b/libs/libmod/src/mod/lib/Rules/LabelledRule.cpp index 7e1e94d..e0a00cd 100644 --- a/libs/libmod/src/mod/lib/Rules/LabelledRule.cpp +++ b/libs/libmod/src/mod/lib/Rules/LabelledRule.cpp @@ -11,83 +11,133 @@ namespace mod::lib::Rules { +LabelledRule::LabelledRule(std::unique_ptr rule, + std::unique_ptr pString, + std::unique_ptr pStereo) + : rule(std::move(rule)), pString(std::move(pString)), pStereo(std::move(pStereo)) { + initComponents(); +} + +LabelledRule::LabelledRule(std::unique_ptr rule, + std::unique_ptr pTerm, + std::unique_ptr pStereo) + : rule(std::move(rule)), pTerm(std::move(pTerm)), pStereo(std::move(pStereo)) { + initComponents(); +} + // LabelledRule //------------------------------------------------------------------------------ -LabelledRule::LabelledRule() : g(new GraphType()) {} +LabelledRule::LabelledRule() : rule(new lib::DPO::CombinedRule()) {} LabelledRule::LabelledRule(const LabelledRule &other, bool withConstraints) : LabelledRule() { - auto &g = *this->g; - const auto &gOther = get_graph(other); - this->pString = std::make_unique(g); + const auto &cg = rule->getCombinedGraph(); + this->pString = std::make_unique(getRule()); auto &pString = *this->pString; - auto &pStringOther = get_string(other); - for(const auto vOther : asRange(vertices(gOther))) { - const auto v = add_vertex(g); - g[v].membership = membership(other, vOther); - switch(g[v].membership) { - case Membership::Left: - pString.add(v, pStringOther.getLeft()[vOther], ""); + const auto &ruleOther = other.getRule(); + const auto &cgOther = ruleOther.getCombinedGraph(); + const auto &pStringOther = get_string(other); + std::vector vNewFromOther(num_vertices(cgOther)); + for(const auto vcgOther: asRange(vertices(cgOther))) { + switch(cgOther[vcgOther].membership) { + case Membership::L: { + const auto vL = addVertexL(*rule); + pString.addL(vL, pStringOther.getLeft()[get_inverse(ruleOther.getLtoCG(), getL(ruleOther), + cgOther, vcgOther)]); + vNewFromOther[get(boost::vertex_index_t(), cgOther, vcgOther)] + = get(rule->getLtoCG(), getL(*rule), cg, vL); break; - case Membership::Right: - pString.add(v, "", pStringOther.getRight()[vOther]); + } + case Membership::R: { + const auto vR = addVertexR(*rule); + pString.addR(vR, pStringOther.getRight()[get_inverse(ruleOther.getRtoCG(), getR(ruleOther), + cgOther, vcgOther)]); + vNewFromOther[get(boost::vertex_index_t(), cgOther, vcgOther)] + = get(rule->getRtoCG(), getL(*rule), cg, vR); break; - case Membership::Context: - pString.add(v, pStringOther.getLeft()[vOther], pStringOther.getRight()[vOther]); + } + case Membership::K: { + const auto vK = addVertexK(*rule); + pString.addK(vK, + pStringOther.getLeft()[get_inverse(ruleOther.getLtoCG(), getL(ruleOther), + cgOther, vcgOther)], + pStringOther.getRight()[get_inverse(ruleOther.getRtoCG(), getR(ruleOther), + cgOther, vcgOther)]); + vNewFromOther[get(boost::vertex_index_t(), cgOther, vcgOther)] = vK; break; } + } } - for(const auto eOther : asRange(edges(gOther))) { - const auto e = add_edge(source(eOther, gOther), target(eOther, gOther), g).first; - g[e].membership = membership(other, eOther); - switch(g[e].membership) { - case Membership::Left: - pString.add(e, pStringOther.getLeft()[eOther], ""); + for(const auto ecgOther: asRange(edges(cgOther))) { + const auto vcgNewSrc = vNewFromOther[get(boost::vertex_index_t(), cgOther, source(ecgOther, cgOther))]; + const auto vcgNewTar = vNewFromOther[get(boost::vertex_index_t(), cgOther, target(ecgOther, cgOther))]; + switch(cgOther[ecgOther].membership) { + case Membership::L: { + const auto vLSrc = get_inverse(rule->getLtoCG(), getL(*rule), cg, vcgNewSrc); + const auto vLTar = get_inverse(rule->getLtoCG(), getL(*rule), cg, vcgNewTar); + const auto eL = addEdgeL(*rule, vLSrc, vLTar); + pString.addL(eL, pStringOther.getLeft()[get_inverse(ruleOther.getLtoCG(), getL(ruleOther), + cgOther, ecgOther)]); break; - case Membership::Right: - pString.add(e, "", pStringOther.getRight()[eOther]); + } + case Membership::R: { + const auto vRSrc = get_inverse(rule->getRtoCG(), getR(*rule), cg, vcgNewSrc); + const auto vRTar = get_inverse(rule->getRtoCG(), getR(*rule), cg, vcgNewTar); + const auto eR = addEdgeR(*rule, vRSrc, vRTar); + pString.addR(eR, pStringOther.getRight()[get_inverse(ruleOther.getRtoCG(), getR(ruleOther), + cgOther, ecgOther)]); break; - case Membership::Context: - pString.add(e, pStringOther.getLeft()[eOther], pStringOther.getRight()[eOther]); + } + case Membership::K: { + const auto eK = addEdgeK(*rule, vcgNewSrc, vcgNewTar); + pString.addK(eK, + pStringOther.getLeft()[get_inverse(ruleOther.getLtoCG(), getL(ruleOther), + cgOther, ecgOther)], + pStringOther.getRight()[get_inverse(ruleOther.getRtoCG(), getR(ruleOther), + cgOther, ecgOther)]); break; } + } } if(other.pStereo) { const auto &lgLeft = get_labelled_left(other); const auto &lgRight = get_labelled_right(other); - const auto infLeft = Stereo::makeCloner(lgLeft, get_left(*this), jla_boost::Identity(), jla_boost::Identity()); - const auto infRight = Stereo::makeCloner(lgRight, get_right(*this), jla_boost::Identity(), jla_boost::Identity()); + const auto infLeft = Stereo::makeCloner(lgLeft, get_L_projected(*this), jla_boost::Identity(), + jla_boost::Identity()); + const auto infRight = Stereo::makeCloner(lgRight, get_R_projected(*this), jla_boost::Identity(), + jla_boost::Identity()); const auto inContext = [&](const auto &ve) { return get_stereo(other).inContext(ve); }; - this->pStereo.reset(new PropStereoCore(g, infLeft, infRight, inContext, inContext)); + this->pStereo.reset(new PropStereo(getRule(), infLeft, infRight, inContext, inContext)); } if(withConstraints) { - leftMatchConstraints.reserve(other.leftMatchConstraints.size()); - rightMatchConstraints.reserve(other.rightMatchConstraints.size()); - for(auto &&c : other.leftMatchConstraints) - leftMatchConstraints.push_back(c->clone()); - for(auto &&c : other.rightMatchConstraints) - rightMatchConstraints.push_back(c->clone()); + leftData.matchConstraints.reserve(other.leftData.matchConstraints.size()); + rightData.matchConstraints.reserve(other.rightData.matchConstraints.size()); + for(auto &&c: other.leftData.matchConstraints) + leftData.matchConstraints.push_back(c->clone()); + for(auto &&c: other.rightData.matchConstraints) + rightData.matchConstraints.push_back(c->clone()); } } +lib::DPO::CombinedRule &LabelledRule::getRule() { return *rule; } +const lib::DPO::CombinedRule &LabelledRule::getRule() const { return *rule; } + void LabelledRule::initComponents() { // TODO: structure this better - if(numLeftComponents != std::numeric_limits::max()) MOD_ABORT; - leftComponents.resize(num_vertices(get_graph(*this)), -1); - rightComponents.resize(num_vertices(get_graph(*this)), -1); - numLeftComponents = boost::connected_components(get_graph(get_labelled_left(*this)), leftComponents.data()); - numRightComponents = boost::connected_components(get_graph(get_labelled_right(*this)), rightComponents.data()); + if(leftData.numComponents != -1) MOD_ABORT; + leftData.component.resize(num_vertices(get_graph(*this)), -1); + rightData.component.resize(num_vertices(get_graph(*this)), -1); + leftData.numComponents = boost::connected_components(get_graph(get_labelled_left(*this)), + leftData.component.data()); + rightData.numComponents = boost::connected_components(get_graph(get_labelled_right(*this)), + rightData.component.data()); } void LabelledRule::invert() { - // invert the underlying graph - // and not the actual inversion - auto &g = *this->g; - for(const auto v : asRange(vertices(g))) - g[v].membership = jla_boost::GraphDPO::invert(g[v].membership); - for(const auto e : asRange(edges(g))) - g[e].membership = jla_boost::GraphDPO::invert(g[e].membership); + // invert the underlying rule + using lib::DPO::invert; + invert(*rule); // invert props if(pString) pString->invert(); if(pTerm) pTerm->invert(); @@ -98,23 +148,25 @@ void LabelledRule::invert() { } // also invert the component stuff using std::swap; - swap(this->numLeftComponents, this->numRightComponents); - swap(this->leftComponents, this->rightComponents); - swap(this->leftMatchConstraints, this->rightMatchConstraints); - // clear cached stuff - this->projs.reset(); + swap(leftData, rightData); } GraphType &get_graph(LabelledRule &r) { - return *r.g; + return r.rule->getCombinedGraph(); } const GraphType &get_graph(const LabelledRule &r) { - return *r.g; + return r.rule->getCombinedGraph(); } const LabelledRule::PropStringType &get_string(const LabelledRule &r) { assert(r.pString || r.pTerm); + if(!r.pString) { + r.pString.reset(new LabelledRule::PropStringType( + r.getRule(), r.leftData.matchConstraints, r.rightData.matchConstraints, + get_term(r), lib::Term::getStrings() + )); + } return *r.pString; } @@ -122,7 +174,7 @@ const LabelledRule::PropTermType &get_term(const LabelledRule &r) { assert(r.pString || r.pTerm); if(!r.pTerm) { r.pTerm.reset(new LabelledRule::PropTermType( - get_graph(r), r.leftMatchConstraints, r.rightMatchConstraints, + r.getRule(), r.leftData.matchConstraints, r.rightData.matchConstraints, get_string(r), lib::Term::getStrings() )); } @@ -139,8 +191,8 @@ const LabelledRule::PropStereoType &get_stereo(const LabelledRule &r) { auto gRight = get_labelled_right(r); auto pMoleculeLeft = get_molecule(gLeft); auto pMoleculeRight = get_molecule(gRight); - auto leftInference = lib::Stereo::makeInference(get_graph(gLeft), pMoleculeLeft, true); - auto rightInference = lib::Stereo::makeInference(get_graph(gRight), pMoleculeRight, true); + auto leftInference = lib::Stereo::Inference(get_graph(gLeft), pMoleculeLeft, true); + auto rightInference = lib::Stereo::Inference(get_graph(gRight), pMoleculeRight, true); { lib::IO::Warnings warnings; @@ -161,187 +213,123 @@ const LabelledRule::PropStereoType &get_stereo(const LabelledRule &r) { res.throwIfError(); } - r.pStereo.reset(new PropStereoCore(get_graph(r), - std::move(leftInference), std::move(rightInference), jla_boost::AlwaysTrue(), - jla_boost::AlwaysTrue())); + r.pStereo.reset(new PropStereo(r.getRule(), + std::move(leftInference), std::move(rightInference), jla_boost::AlwaysTrue(), + jla_boost::AlwaysTrue())); } return *r.pStereo; } const LabelledRule::PropMoleculeType &get_molecule(const LabelledRule &r) { if(!r.pMolecule) { - r.pMolecule.reset(new LabelledRule::PropMoleculeType(get_graph(r), get_string(r))); + r.pMolecule.reset(new LabelledRule::PropMoleculeType(r.getRule(), get_string(r))); } return *r.pMolecule; } -const LabelledRule::LeftGraphType &get_left(const LabelledRule &r) { - if(!r.projs) r.projs.reset(new LabelledRule::Projections(r)); - return r.projs->left; +const LabelledRule::SideProjectedGraphType &get_L_projected(const LabelledRule &r) { + return r.rule->getLProjected(); } -const LabelledRule::ContextGraphType &get_context(const LabelledRule &r) { - if(!r.projs) r.projs.reset(new LabelledRule::Projections(r)); - return r.projs->context; +const LabelledRule::SideProjectedGraphType &get_R_projected(const LabelledRule &r) { + return r.rule->getRProjected(); } -const LabelledRule::RightGraphType &get_right(const LabelledRule &r) { - if(!r.projs) r.projs.reset(new LabelledRule::Projections(r)); - return r.projs->right; -} - -jla_boost::GraphDPO::Membership membership(const LabelledRule &r, const Vertex &v) { +lib::DPO::Membership membership(const LabelledRule &r, const Vertex &v) { return get_graph(r)[v].membership; } -jla_boost::GraphDPO::Membership membership(const LabelledRule &r, const Edge &e) { +lib::DPO::Membership membership(const LabelledRule &r, const Edge &e) { return get_graph(r)[e].membership; } -void put_membership(LabelledRule &r, const Vertex &v, jla_boost::GraphDPO::Membership m) { +void put_membership(LabelledRule &r, const Vertex &v, lib::DPO::Membership m) { get_graph(r)[v].membership = m; } -void put_membership(LabelledRule &r, const Edge &e, jla_boost::GraphDPO::Membership m) { +void put_membership(LabelledRule &r, const Edge &e, lib::DPO::Membership m) { get_graph(r)[e].membership = m; } -LabelledRule::LabelledLeftType get_labelled_left(const LabelledRule &r) { - return LabelledRule::LabelledLeftType(r); +LabelledRule::Side get_labelled_left(const LabelledRule &r) { + return LabelledRule::Side(r, getL(r.getRule()), + &PropString::getLeft, &PropTerm::getLeft, + &PropStereo::getLeft, &PropMolecule::getLeft, + r.leftData); } -LabelledRule::LabelledRightType get_labelled_right(const LabelledRule &r) { - return LabelledRule::LabelledRightType(r); +LabelledRule::Side get_labelled_right(const LabelledRule &r) { + return LabelledRule::Side(r, getR(r.getRule()), + &PropString::getRight, &PropTerm::getRight, + &PropStereo::getRight, &PropMolecule::getRight, + r.rightData); } -LabelledRule::Projections::Projections(const LabelledRule &r) - : left(get_graph(r), Membership::Left), - context(get_graph(r), Membership::Context), - right(get_graph(r), Membership::Right) {} - - -// LabelledSideGraph -//------------------------------------------------------------------------------ - -namespace detail { - -LabelledSideGraph::LabelledSideGraph(const LabelledRule &r, jla_boost::GraphDPO::Membership m) - : r(r), m(m) {} - -} // namespace detail // LabelledLeftGraph //------------------------------------------------------------------------------ -LabelledLeftGraph::LabelledLeftGraph(const LabelledRule &r) - : Base(r, jla_boost::GraphDPO::Membership::Left) {} +LabelledRule::Side::Side(const LabelledRule &r, const GraphType &g, + PropStringType (LabelledRule::PropStringType::*fString)() const, + PropTermType (LabelledRule::PropTermType::*fTerm)() const, + PropStereoType (LabelledRule::PropStereoType::*fStereo)() const, + PropMoleculeType (LabelledRule::PropMoleculeType::*fMol)() const, + const SideData &data) + : r(r), g(g), fString(fString), fTerm(fTerm), fStereo(fStereo), fMol(fMol), data(data) {} -const LabelledLeftGraph::GraphType &get_graph(const LabelledLeftGraph &g) { - return get_left(g.r); +const LabelledRule::Side::GraphType &get_graph(const LabelledRule::Side &g) { + return g.g; } -LabelledLeftGraph::PropStringType get_string(const LabelledLeftGraph &g) { - return get_string(g.r).getLeft(); +LabelledRule::Side::PropStringType get_string(const LabelledRule::Side &g) { + return (get_string(g.r).*g.fString)(); } -LabelledLeftGraph::PropTermType get_term(const LabelledLeftGraph &g) { - return get_term(g.r).getLeft(); +LabelledRule::Side::PropTermType get_term(const LabelledRule::Side &g) { + return (get_term(g.r).*g.fTerm)(); } -bool has_stereo(const LabelledLeftGraph &g) { +bool has_stereo(const LabelledRule::Side &g) { return has_stereo(g.r); } -LabelledLeftGraph::PropStereoType get_stereo(const LabelledLeftGraph &g) { - return get_stereo(g.r).getLeft(); +LabelledRule::Side::PropStereoType get_stereo(const LabelledRule::Side &g) { + return (get_stereo(g.r).*g.fStereo)(); } -const std::vector > & -get_match_constraints(const LabelledLeftGraph &g) { - return g.r.leftMatchConstraints; +LabelledRule::Side::PropMoleculeType get_molecule(const LabelledRule::Side &g) { + return (get_molecule(g.r).*g.fMol)(); } -std::size_t get_num_connected_components(const LabelledLeftGraph &g) { - return g.r.numLeftComponents; +const std::vector> & +get_match_constraints(const LabelledRule::Side &g) { + return g.data.matchConstraints; } -LabelledLeftGraph::PropMoleculeType get_molecule(const LabelledLeftGraph &g) { - return get_molecule(g.r).getLeft(); +std::size_t get_num_connected_components(const LabelledRule::Side &g) { + return g.data.numComponents; } -LabelledLeftGraph::Base::ComponentGraph -get_component_graph(std::size_t i, const LabelledLeftGraph &g) { - assert(i < get_num_connected_components(g)); - LabelledLeftGraph::Base::ComponentFilter filter(&get_graph(g), &g.r.leftComponents, i); - return LabelledLeftGraph::Base::ComponentGraph(get_graph(g), filter, filter); +const std::vector get_component(const LabelledRule::Side &g) { + return g.data.component; } -const std::vector::vertex_descriptor> & -get_vertex_order_component(std::size_t i, const LabelledLeftGraph &g) { +LabelledRule::Side::ComponentGraph +get_component_graph(std::size_t i, const LabelledRule::Side &g) { assert(i < get_num_connected_components(g)); - // the number of connected components is initialized externally after construction, so we have this annoying hax - if(g.vertex_orders.empty()) g.vertex_orders.resize(get_num_connected_components(g)); - if(g.vertex_orders[i].empty()) { - g.vertex_orders[i] = get_vertex_order(lib::GraphMorphism::DefaultFinderArgsProvider(), get_component_graph(i, g)); - } - return g.vertex_orders[i]; -} - -// LabelledRightGraph -//------------------------------------------------------------------------------ - -LabelledRightGraph::LabelledRightGraph(const LabelledRule &r) - : Base(r, jla_boost::GraphDPO::Membership::Right) {} - -const LabelledRightGraph::GraphType &get_graph(const LabelledRightGraph &g) { - return get_right(g.r); -} - -LabelledRightGraph::PropStringType get_string(const LabelledRightGraph &g) { - return get_string(g.r).getRight(); -} - -LabelledRightGraph::PropTermType get_term(const LabelledRightGraph &g) { - return get_term(g.r).getRight(); -} - -bool has_stereo(const LabelledRightGraph &g) { - return has_stereo(g.r); -} - -LabelledRightGraph::PropStereoType get_stereo(const LabelledRightGraph &g) { - return get_stereo(g.r).getRight(); -} - -const std::vector > & -get_match_constraints(const LabelledRightGraph &g) { - return g.r.rightMatchConstraints; -} - -std::size_t get_num_connected_components(const LabelledRightGraph &g) { - return g.r.numRightComponents; -} - -LabelledRightGraph::Base::ComponentGraph -get_component_graph(std::size_t i, const LabelledRightGraph &g) { - assert(i < get_num_connected_components(g)); - LabelledRightGraph::Base::ComponentFilter filter(&get_graph(g), &g.r.rightComponents, i); - return LabelledRightGraph::Base::ComponentGraph(get_graph(g), filter, filter); -} - -LabelledRightGraph::PropMoleculeType get_molecule(const LabelledRightGraph &g) { - return get_molecule(g.r).getRight(); + LabelledRule::Side::ComponentFilter filter(&get_graph(g), &g.data.component, i); + return LabelledRule::Side::ComponentGraph(get_graph(g), filter, filter); } const std::vector::vertex_descriptor> & -get_vertex_order_component(std::size_t i, const LabelledRightGraph &g) { +get_vertex_order_component(std::size_t i, const LabelledRule::Side &g) { assert(i < get_num_connected_components(g)); + auto &vertex_orders = g.data.vertex_orders; // the number of connected components is initialized externally after construction, so we have this annoying hax - if(g.vertex_orders.empty()) g.vertex_orders.resize(get_num_connected_components(g)); - if(g.vertex_orders[i].empty()) { - g.vertex_orders[i] = get_vertex_order(lib::GraphMorphism::DefaultFinderArgsProvider(), get_component_graph(i, g)); - } - return g.vertex_orders[i]; + if(vertex_orders.empty()) vertex_orders.resize(get_num_connected_components(g)); + if(vertex_orders[i].empty()) + vertex_orders[i] = get_vertex_order(lib::GraphMorphism::DefaultFinderArgsProvider(), get_component_graph(i, g)); + return vertex_orders[i]; } } // namespace mod::lib::Rules \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/LabelledRule.hpp b/libs/libmod/src/mod/lib/Rules/LabelledRule.hpp index a7461c3..872a0eb 100644 --- a/libs/libmod/src/mod/lib/Rules/LabelledRule.hpp +++ b/libs/libmod/src/mod/lib/Rules/LabelledRule.hpp @@ -1,6 +1,7 @@ #ifndef MOD_LIB_RULES_LABELLED_RULE_HPP #define MOD_LIB_RULES_LABELLED_RULE_HPP +#include #include #include #include @@ -12,147 +13,121 @@ #include namespace mod::lib::Rules { -struct LabelledLeftGraph; -struct LabelledRightGraph; -class LabelledRule { -public: // LabelledGraphConcept, PushoutRuleConcept - using GraphType = lib::Rules::GraphType; -public: // PushoutRuleConcept - using LeftGraphType = lib::Rules::SideGraphType; - using ContextGraphType = lib::Rules::SideGraphType; - using RightGraphType = lib::Rules::SideGraphType; +struct LabelledRule { + using RuleType = lib::DPO::CombinedRule; public: // LabelledGraphConcept - using PropStringType = PropStringCore; - using PropTermType = PropTermCore; - using PropStereoType = PropStereoCore; - using PropMoleculeType = PropMoleculeCore; + using GraphType = RuleType::CombinedGraphType; + using PropStringType = PropString; + using PropTermType = PropTerm; + using PropStereoType = PropStereo; +public: + using PropMoleculeType = PropMolecule; +public: // RuleConcept + using SideGraphType = RuleType::SideGraphType; + using KGraphType = RuleType::KGraphType; +public: // Projected view + using SideProjectedGraphType = RuleType::SideProjectedGraphType; public: // Other using Vertex = boost::graph_traits::vertex_descriptor; using Edge = boost::graph_traits::edge_descriptor; - using LeftMatchConstraint = GraphMorphism::Constraints::Constraint; - using RightMatchConstraint = GraphMorphism::Constraints::Constraint; - using LabelledLeftType = LabelledLeftGraph; - using LabelledRightType = LabelledRightGraph; + using MatchConstraint = GraphMorphism::Constraints::Constraint; +public: + struct SideData; +public: + struct Side { + using GraphType = SideGraphType; + using ComponentFilter = ConnectedComponentFilter>; + using ComponentGraph = boost::filtered_graph; + using PropStringType = LabelledRule::PropStringType::Side; + using PropTermType = LabelledRule::PropTermType::Side; + using PropStereoType = LabelledRule::PropStereoType::Side; + public: + using PropMoleculeType = LabelledRule::PropMoleculeType::Side; + public: + // We don't want to convert data unless needed, so instead of storing the + // side prop proxy objects, we store member function pointers to retrieve them. + explicit Side(const LabelledRule &r, const GraphType &g, + PropStringType (LabelledRule::PropStringType::*fString)() const, + PropTermType (LabelledRule::PropTermType::*fTerm)() const, + PropStereoType (LabelledRule::PropStereoType::*fStereo)() const, + PropMoleculeType (LabelledRule::PropMoleculeType::*fMol)() const, + const SideData &data); + public: + friend const GraphType &get_graph(const Side &g); + friend PropStringType get_string(const Side &g); + friend PropTermType get_term(const Side &g); + friend bool has_stereo(const Side &g); + friend PropStereoType get_stereo(const Side &g); + public: + friend PropMoleculeType get_molecule(const Side &g); + public: + friend const std::vector> & + get_match_constraints(const Side &g); + public: + friend std::size_t get_num_connected_components(const Side &g); + friend const std::vector get_component(const Side &g); + friend ComponentGraph get_component_graph(std::size_t i, const Side &g); + public: + friend const std::vector::vertex_descriptor> & + get_vertex_order_component(std::size_t i, const Side &g); + public: + const LabelledRule &r; + const GraphType &g; + PropStringType (LabelledRule::PropStringType::*fString)() const; + PropTermType (LabelledRule::PropTermType::*fTerm)() const; + PropStereoType (LabelledRule::PropStereoType::*fStereo)() const; + PropMoleculeType (LabelledRule::PropMoleculeType::*fMol)() const; + const SideData &data; + }; +public: + explicit LabelledRule(std::unique_ptr rule, + std::unique_ptr pString, + std::unique_ptr pStereo); + explicit LabelledRule(std::unique_ptr rule, + std::unique_ptr pTerm, + std::unique_ptr pStereo); + LabelledRule(); // TODO: remove + LabelledRule(const LabelledRule &other, bool withConstraints); // TODO: hmm + lib::DPO::CombinedRule &getRule(); // TODO: remove non-const version? + const lib::DPO::CombinedRule &getRule() const; public: - LabelledRule(); - LabelledRule(const LabelledRule &other, bool withConstraints); void initComponents(); // TODO: this is a huge hax void invert(); -public: // LabelledGraphConcept, PushoutRuleConcept +public: // LabelledGraphConcept friend GraphType &get_graph(LabelledRule &r); friend const GraphType &get_graph(const LabelledRule &r); -public: // LabelledGraphConcept friend const PropStringType &get_string(const LabelledRule &r); friend const PropTermType &get_term(const LabelledRule &r); friend bool has_stereo(const LabelledRule &r); friend const PropStereoType &get_stereo(const LabelledRule &r); public: friend const PropMoleculeType &get_molecule(const LabelledRule &r); -public: // PushoutRuleConcept - friend const LeftGraphType &get_left(const LabelledRule &r); - friend const ContextGraphType &get_context(const LabelledRule &r); - friend const RightGraphType &get_right(const LabelledRule &r); - friend jla_boost::GraphDPO::Membership membership(const LabelledRule &r, const Vertex &v); - friend jla_boost::GraphDPO::Membership membership(const LabelledRule &r, const Edge &e); - friend void put_membership(LabelledRule &r, const Vertex &v, jla_boost::GraphDPO::Membership m); - friend void put_membership(LabelledRule &r, const Edge &e, jla_boost::GraphDPO::Membership m); public: - friend LabelledLeftType get_labelled_left(const LabelledRule &r); - friend LabelledRightType get_labelled_right(const LabelledRule &r); + friend const SideProjectedGraphType &get_L_projected(const LabelledRule &r); + friend const SideProjectedGraphType &get_R_projected(const LabelledRule &r); + friend lib::DPO::Membership membership(const LabelledRule &r, const Vertex &v); + friend lib::DPO::Membership membership(const LabelledRule &r, const Edge &e); + friend void put_membership(LabelledRule &r, const Vertex &v, lib::DPO::Membership m); + friend void put_membership(LabelledRule &r, const Edge &e, lib::DPO::Membership m); +public: + friend Side get_labelled_left(const LabelledRule &r); + friend Side get_labelled_right(const LabelledRule &r); private: - struct Projections { - Projections(const LabelledRule &r); - public: - LeftGraphType left; - ContextGraphType context; - RightGraphType right; - }; - std::unique_ptr g; - mutable std::unique_ptr projs; + std::unique_ptr rule; public: mutable std::unique_ptr pString; mutable std::unique_ptr pTerm; mutable std::unique_ptr pStereo; - std::vector > leftMatchConstraints; - std::vector > rightMatchConstraints; private: mutable std::unique_ptr pMolecule; public: - std::size_t numLeftComponents = -1, numRightComponents = -1; - std::vector leftComponents, rightComponents; -}; - -namespace detail { - -struct LabelledSideGraph { - using LabelledRule = lib::Rules::LabelledRule; - using GraphType = lib::Rules::SideGraphType; - using ComponentFilter = ConnectedComponentFilter >; - using ComponentGraph = boost::filtered_graph; -public: - LabelledSideGraph(const LabelledRule &r, jla_boost::GraphDPO::Membership m); -public: - const LabelledRule &r; - const jla_boost::GraphDPO::Membership m; -protected: - mutable std::vector::vertex_descriptor> > vertex_orders; -}; - -} // namespace detail - -struct LabelledLeftGraph : detail::LabelledSideGraph { - using Base = detail::LabelledSideGraph; - using PropStringType = LabelledRule::PropStringType::LeftType; - using PropTermType = LabelledRule::PropTermType::LeftType; - using PropStereoType = LabelledRule::PropStereoType::LeftType; -public: - using PropMoleculeType = typename LabelledRule::PropMoleculeType::LeftType; -public: - explicit LabelledLeftGraph(const LabelledRule &r); - friend const Base::GraphType &get_graph(const LabelledLeftGraph &g); - friend PropStringType get_string(const LabelledLeftGraph &g); - friend PropTermType get_term(const LabelledLeftGraph &g); - friend bool has_stereo(const LabelledLeftGraph &g); - friend PropStereoType get_stereo(const LabelledLeftGraph &g); -public: - friend const std::vector > & - get_match_constraints(const LabelledLeftGraph &g); -public: - friend std::size_t get_num_connected_components(const LabelledLeftGraph &g); - friend Base::ComponentGraph get_component_graph(std::size_t i, const LabelledLeftGraph &g); -public: - friend PropMoleculeType get_molecule(const LabelledLeftGraph &g); -public: - friend const std::vector::vertex_descriptor> & - get_vertex_order_component(std::size_t i, const LabelledLeftGraph &g); -}; - -struct LabelledRightGraph : detail::LabelledSideGraph { - using Base = detail::LabelledSideGraph; - using PropStringType = LabelledRule::PropStringType::RightType; - using PropTermType = LabelledRule::PropTermType::RightType; - using PropStereoType = LabelledRule::PropStereoType::RightType; -public: - using PropMoleculeType = typename LabelledRule::PropMoleculeType::RightType; -public: - explicit LabelledRightGraph(const LabelledRule &r); - friend const Base::GraphType &get_graph(const LabelledRightGraph &g); - friend PropStringType get_string(const LabelledRightGraph &g); - friend PropTermType get_term(const LabelledRightGraph &g); - friend bool has_stereo(const LabelledRightGraph &g); - friend PropStereoType get_stereo(const LabelledRightGraph &g); -public: - friend const std::vector > & - get_match_constraints(const LabelledRightGraph &g); -public: - friend std::size_t get_num_connected_components(const LabelledRightGraph &g); - friend Base::ComponentGraph get_component_graph(std::size_t i, const LabelledRightGraph &g); -public: - friend PropMoleculeType get_molecule(const LabelledRightGraph &g); -public: - friend const std::vector::vertex_descriptor> & - get_vertex_order_component(std::size_t i, const LabelledRightGraph &g); + struct SideData { + std::size_t numComponents = -1; + std::vector component; + std::vector> matchConstraints; + mutable std::vector> vertex_orders; + } leftData, rightData; }; } // namespace mod::lib::Rules diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Depiction.cpp b/libs/libmod/src/mod/lib/Rules/Properties/Depiction.cpp deleted file mode 100644 index d9e61a2..0000000 --- a/libs/libmod/src/mod/lib/Rules/Properties/Depiction.cpp +++ /dev/null @@ -1,527 +0,0 @@ -#include "Depiction.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace mod::lib::Rules { - -//------------------------------------------------------------------------------ -// template DepictionData -//------------------------------------------------------------------------------ - -template -DepictionDataCore::DepictionData::DepictionData(const DepictionDataCore &depict) : depict(depict) {} - -template -AtomId DepictionDataCore::DepictionData::getAtomId(Vertex v) const { - const auto &pMol = get_molecule(depict.lr); - switch(membership) { - case Membership::Left: - return pMol.getLeft()[v].getAtomId(); - case Membership::Right: - return pMol.getRight()[v].getAtomId(); - case Membership::Context: - auto leftId = pMol.getLeft()[v].getAtomId(); - auto rightId = pMol.getRight()[v].getAtomId(); - if(leftId == rightId) return leftId; - else return AtomIds::Invalid; - } -} - -template -Isotope DepictionDataCore::DepictionData::getIsotope(Vertex v) const { - const auto &pMol = get_molecule(depict.lr); - switch(membership) { - case Membership::Left: - return pMol.getLeft()[v].getIsotope(); - case Membership::Right: - return pMol.getRight()[v].getIsotope(); - case Membership::Context: - auto leftId = pMol.getLeft()[v].getIsotope(); - auto rightId = pMol.getRight()[v].getIsotope(); - if(leftId == rightId) return leftId; - else return Isotope(); - } -} - -template -Charge DepictionDataCore::DepictionData::getCharge(Vertex v) const { - const auto &pMol = get_molecule(depict.lr); - switch(membership) { - case Membership::Left: - return pMol.getLeft()[v].getCharge(); - case Membership::Right: - return pMol.getRight()[v].getCharge(); - case Membership::Context: - auto leftCharge = pMol.getLeft()[v].getCharge(); - auto rightCharge = pMol.getRight()[v].getCharge(); - if(leftCharge == rightCharge) return leftCharge; - else return Charge(0); - } -} - -template -bool DepictionDataCore::DepictionData::getRadical(Vertex v) const { - const auto &pMol = get_molecule(depict.lr); - switch(membership) { - case Membership::Left: - return pMol.getLeft()[v].getRadical(); - case Membership::Right: - return pMol.getRight()[v].getRadical(); - case Membership::Context: - auto leftRadical = pMol.getLeft()[v].getRadical(); - auto rightRadical = pMol.getRight()[v].getRadical(); - if(leftRadical == rightRadical) return leftRadical; - else return false; - } -} - -template -BondType DepictionDataCore::DepictionData::getBondData(Edge e) const { - const auto &pMol = get_molecule(depict.lr); - switch(membership) { - case Membership::Left: - return pMol.getLeft()[e]; - case Membership::Right: - return pMol.getRight()[e]; - case Membership::Context: - auto btLeft = pMol.getLeft()[e]; - auto btRight = pMol.getRight()[e]; - if(btLeft == btRight) return btLeft; - else return BondType::Invalid; - } -} - -template -std::string DepictionDataCore::DepictionData::getVertexLabelNoIsotopeChargeRadical(Vertex v) const { - auto atomId = getAtomId(v); - if(atomId != AtomIds::Invalid) - return Chem::symbolFromAtomId(atomId); - switch(membership) { - case Membership::Left: - case Membership::Right: { - const auto &nonAtomToPhonyAtom = - membership == Membership::Left ? depict.nonAtomToPhonyAtomLeft : depict.nonAtomToPhonyAtomRight; - auto nonAtomIter = nonAtomToPhonyAtom.find(v); - assert(nonAtomIter != end(nonAtomToPhonyAtom)); - auto labelIter = depict.phonyAtomToString.find(nonAtomIter->second.getAtomId()); - assert(labelIter != end(depict.phonyAtomToString)); - return labelIter->second; - } - case Membership::Context: { - const auto &pMol = get_molecule(depict.lr); - auto nonAtomIterLeft = depict.nonAtomToPhonyAtomLeft.find(v); - auto nonAtomIterRight = depict.nonAtomToPhonyAtomRight.find(v); - std::string left, right; - if(nonAtomIterLeft == end(depict.nonAtomToPhonyAtomLeft)) { - auto atomId = pMol.getLeft()[v].getAtomId(); - left = Chem::symbolFromAtomId(atomId); - } else { - auto labelIterLeft = depict.phonyAtomToString.find(nonAtomIterLeft->second.getAtomId()); - assert(labelIterLeft != end(depict.phonyAtomToString)); - left = labelIterLeft->second; - } - if(nonAtomIterRight == end(depict.nonAtomToPhonyAtomRight)) { - auto atomId = pMol.getRight()[v].getAtomId(); - right = Chem::symbolFromAtomId(atomId); - } else { - auto labelIterRight = depict.phonyAtomToString.find(nonAtomIterRight->second.getAtomId()); - assert(labelIterRight != end(depict.phonyAtomToString)); - right = labelIterRight->second; - } - if(left == right) return left; - else return R"X($\langle$)X" + left + ", " + right + R"X($\rangle$)X"; - } - } -} - -template -std::string DepictionDataCore::DepictionData::getEdgeLabel(Edge e) const { - auto bt = getBondData(e); - if(bt != BondType::Invalid) - return std::string(1, Chem::bondToChar(bt)); - switch(membership) { - case Membership::Left: - case Membership::Right: { - const auto &nonBondEdges = membership == Membership::Left ? depict.nonBondEdgesLeft : depict.nonBondEdgesRight; - auto iter = nonBondEdges.find(e); - if(iter == end(nonBondEdges)) std::cout << "WTF: " << e << std::endl; - assert(iter != end(nonBondEdges)); - return iter->second; - } - case Membership::Context: { - std::string left, right; - auto iterLeft = depict.nonBondEdgesLeft.find(e); - auto iterRight = depict.nonBondEdgesRight.find(e); - const auto &pMol = get_molecule(depict.lr); - if(iterLeft != end(depict.nonBondEdgesLeft)) left = iterLeft->second; - else left = std::string(1, Chem::bondToChar(pMol.getLeft()[e])); - if(iterRight != end(depict.nonBondEdgesRight)) right = iterRight->second; - else right = std::string(1, Chem::bondToChar(pMol.getRight()[e])); - if(left == right) return left; - else return "$\\langle$" + left + ", " + right + "$\\rangle$"; - } - } -} - -template -const AtomData &DepictionDataCore::DepictionData::operator()(Vertex v) const { - switch(membership) { - case Membership::Left: - if(getAtomId(v) != AtomIds::Invalid) return get_molecule(depict.lr).getLeft()[v]; - else { - auto iter = depict.nonAtomToPhonyAtomLeft.find(v); - assert(iter != end(depict.nonAtomToPhonyAtomLeft)); - return iter->second; - } - case Membership::Right: - if(getAtomId(v) != AtomIds::Invalid) return get_molecule(depict.lr).getRight()[v]; - else { - auto iter = depict.nonAtomToPhonyAtomRight.find(v); - assert(iter != end(depict.nonAtomToPhonyAtomLeft)); - return iter->second; - } - case Membership::Context: - // for now we just return whatever is in left, it's fake data anyway - return depict.getLeft()(v); - } -} - -template -BondType DepictionDataCore::DepictionData::operator()(Edge e) const { - auto bt = getBondData(e); - return bt == BondType::Invalid ? BondType::Single : bt; -} - -template -bool DepictionDataCore::DepictionData::hasImportantStereo(Vertex v) const { - const auto &lr = depict.lr; - if(!has_stereo(lr)) return false; - switch(membership) { - case Membership::Left: { - const auto &lg = get_labelled_left(lr); - return !get_stereo(lg)[v]->morphismDynamicOk(); - } - case Membership::Right: { - const auto &lg = get_labelled_right(lr); - return !get_stereo(lg)[v]->morphismDynamicOk(); - } - case Membership::Context: - return get_stereo(lr).inContext(v) && depict.hasImportantStereo(v); - } - __builtin_unreachable(); -} - -template -bool DepictionDataCore::DepictionData::getHasCoordinates() const { - return depict.getHasCoordinates(); -} - -template -double DepictionDataCore::DepictionData::getX(Vertex v, bool withHydrogen) const { - return depict.getX(v, withHydrogen); -} - -template -double DepictionDataCore::DepictionData::getY(Vertex v, bool withHydrogen) const { - return depict.getY(v, withHydrogen); -} - -template -lib::IO::Graph::Write::EdgeFake3DType -DepictionDataCore::DepictionData::getEdgeFake3DType(Edge e, bool withHydrogen) const { -#ifndef MOD_HAVE_OPENBABEL - MOD_NO_OPENBABEL_ERROR -#else - assert(depict.hasMoleculeEncoding); - const auto &g = get_graph(depict.lr); - const auto vSrc = source(e, g); - const auto vTar = target(e, g); - const auto idSrc = get(boost::vertex_index_t(), g, vSrc); - const auto idTar = get(boost::vertex_index_t(), g, vTar); - const CoordData &cData = withHydrogen ? depict.cDataAll : depict.cDataNoHydrogen; - switch(membership) { - case Membership::Left: - return cData.obMolLeft.getBondFake3D(idSrc, idTar); - case Membership::Right: - return cData.obMolRight.getBondFake3D(idSrc, idTar); - case Membership::Context: - if(has_stereo(depict.lr) && get_stereo(depict.lr).inContext(vSrc) && get_stereo(depict.lr).inContext(vTar)) - return cData.obMolLeft.getBondFake3D(idSrc, idTar); - else - return lib::IO::Graph::Write::EdgeFake3DType::None; - } -#endif - MOD_ABORT; -} - -//template -//bool DepictionDataCore::DepictionData::isAtomIdInvalidContext(Vertex v) const { -// switch(membership) { -// case Membership::Left: -// case Membership::Right -// return true; -// } -// const auto &molState = depict.moleculeState; -// if(membership == Membership::Left) return true; -// else if(membership == Membership::Right) return true; -// else { -// auto m = depict.g[v].membership; -// // if(m != Membership::StateChange) return true; -// auto leftId = molState.getNormal(v).getAtomId(); -// auto rightId = molState.getChange(v).getAtomId(); -// if(leftId == rightId) return true; -// else return false; -// } -//} - -//------------------------------------------------------------------------------ -// DepictionDataCore -//------------------------------------------------------------------------------ - -DepictionDataCore::DepictionDataCore(const LabelledRule &lr) - : lr(lr), hasMoleculeEncoding(true), hasCoordinates(false) { - const auto &g = get_graph(lr); - const auto &pString = get_string(lr); - const auto &pMol = get_molecule(lr); - { // vertexData - std::vector atomUsed(AtomIds::Max + 1, false); - Chem::markSpecialAtomsUsed(atomUsed); - std::vector> verticesToProcess; // vertex x {Left, Right} - for(Vertex v : asRange(vertices(g))) { - auto m = g[v].membership; - if(m != Membership::Right) { - unsigned char atomId = pMol.getLeft()[v].getAtomId(); - if(atomId != AtomIds::Invalid) atomUsed[atomId] = true; - else verticesToProcess.emplace_back(v, Membership::Left); - } - if(m != Membership::Left) { - unsigned char atomId = pMol.getRight()[v].getAtomId(); - if(atomId != AtomIds::Invalid) atomUsed[atomId] = true; - else verticesToProcess.emplace_back(v, Membership::Right); - } - } - // map non-atom labels to atoms - std::map labelToAtomId; - for(auto p : verticesToProcess) { - Vertex v = p.first; - Membership m = p.second; - assert(m != Membership::Context); - std::string label = std::get<0>(Chem::extractIsotopeChargeRadical( - m == Membership::Left ? pString.getLeft()[v] : pString.getRight()[v] - )); - auto iter = labelToAtomId.find(label); - if(iter == end(labelToAtomId)) { - unsigned char atomId = 1; - for(; atomId <= AtomIds::Max; atomId++) { - if(atomUsed[atomId]) continue; - atomUsed[atomId] = true; - iter = labelToAtomId.emplace(label, AtomId(atomId)).first; - phonyAtomToString[AtomId(atomId)] = label; - break; - } - if(atomId > AtomIds::Max) { - hasMoleculeEncoding = false; - break; - } - } - auto atomId = iter->second; - if(m == Membership::Left) { - auto charge = pMol.getLeft()[v].getCharge(); - bool radical = pMol.getLeft()[v].getRadical(); - nonAtomToPhonyAtomLeft[v] = AtomData(atomId, charge, radical); - } else { - auto charge = pMol.getRight()[v].getCharge(); - bool radical = pMol.getRight()[v].getRadical(); - nonAtomToPhonyAtomRight[v] = AtomData(atomId, charge, radical); - } - } - } - { // edgeData - for(Edge e : asRange(edges(g))) { - auto m = g[e].membership; - if(m != Membership::Right) { - auto bt = pMol.getLeft()[e]; - if(bt == BondType::Invalid) nonBondEdgesLeft[e] = pString.getLeft()[e]; - } - if(m != Membership::Left) { - auto bt = pMol.getRight()[e]; - if(bt == BondType::Invalid) nonBondEdgesRight[e] = pString.getRight()[e]; - } - } - } - - if(hasMoleculeEncoding) { -#ifdef MOD_HAVE_OPENBABEL - const auto doIt = [&](CoordData &cData, const bool withHydrogen) { - std::tie(cData.obMol, cData.obMolLeft, cData.obMolRight) - = Chem::makeOBMol(lr, std::cref(*this), std::cref(*this), - getLeft(), getLeft(), - getRight(), getRight(), - withHydrogen); - cData.x.resize(num_vertices(g)); - cData.y.resize(num_vertices(g)); - for(const auto v : asRange(vertices(g))) { - const auto vId = get(boost::vertex_index_t(), g, v); - if(cData.obMol.hasAtom(vId)) { - cData.x[vId] = cData.obMol.getAtomX(vId); - cData.y[vId] = cData.obMol.getAtomY(vId); - } else { - assert(!withHydrogen); - cData.x[vId] = std::numeric_limits::quiet_NaN(); - cData.y[vId] = std::numeric_limits::quiet_NaN(); - } - } - }; - doIt(cDataAll, true); - doIt(cDataNoHydrogen, false); - hasCoordinates = true; -#endif - } -} - -const AtomData &DepictionDataCore::operator()(Vertex v) const { - const auto &g = get_graph(lr); - const auto &pMol = get_molecule(lr); - // return whatever we find - auto m = g[v].membership; - if(m != Membership::Right) { - auto atomId = pMol.getLeft()[v].getAtomId(); - if(atomId != AtomIds::Invalid) return pMol.getLeft()[v]; - else { - auto iter = nonAtomToPhonyAtomLeft.find(v); - assert(iter != end(nonAtomToPhonyAtomLeft)); - return iter->second; - } - } else { - auto atomId = pMol.getRight()[v].getAtomId(); - if(atomId != AtomIds::Invalid) return pMol.getRight()[v]; - else { - auto iter = nonAtomToPhonyAtomRight.find(v); - assert(iter != end(nonAtomToPhonyAtomRight)); - return iter->second; - } - } -} - -BondType DepictionDataCore::operator()(Edge e) const { - const auto &g = get_graph(lr); - const auto &pMol = get_molecule(lr); - // if there is agreement, return that, otherwise prefer invalid bonds - // this should give a bit more freedom in bond angles - const auto m = g[e].membership; - BondType l = BondType::Single, r = BondType::Single; - if(m != Membership::Right) - l = pMol.getLeft()[e]; - if(m != Membership::Left) - r = pMol.getRight()[e]; - if(l == r) return l; - else return BondType::Invalid; -} - -bool DepictionDataCore::hasImportantStereo(Vertex v) const { - if(!has_stereo(lr)) return false; - const auto &g = get_graph(lr); - const auto m = g[v].membership; - if(m != Membership::Right && !get_stereo(get_labelled_left(lr))[v]->morphismDynamicOk()) return true; - if(m != Membership::Left && !get_stereo(get_labelled_right(lr))[v]->morphismDynamicOk()) return true; - return false; -} - -bool DepictionDataCore::getHasCoordinates() const { -#ifdef MOD_HAVE_OPENBABEL - if(getConfig().io.useOpenBabelCoords.get()) - return hasMoleculeEncoding; - else return false; -#else - return false; -#endif -} - -double DepictionDataCore::getX(Vertex v, bool withHydrogen) const { - if(!getHasCoordinates()) MOD_ABORT; - const auto &g = get_graph(lr); - unsigned int vId = get(boost::vertex_index_t(), g, v); - const CoordData &cData = withHydrogen ? cDataAll : cDataNoHydrogen; - assert(vId < cData.x.size()); - return cData.x[vId]; -} - -double DepictionDataCore::getY(Vertex v, bool withHydrogen) const { - if(!getHasCoordinates()) MOD_ABORT; - const auto &g = get_graph(lr); - unsigned int vId = get(boost::vertex_index_t(), g, v); - const CoordData &cData = withHydrogen ? cDataAll : cDataNoHydrogen; - assert(vId < cData.y.size()); - return cData.y[vId]; -} - -void DepictionDataCore::copyCoords(const DepictionDataCore &other, const std::map &vMap) { - if(!other.getHasCoordinates()) return; - const auto &g = get_graph(lr); - const auto doIt = [&](CoordData &cData, const bool withHydrogen) { - cData.x.resize(num_vertices(g)); - cData.y.resize(num_vertices(g)); - for(const Vertex v : asRange(vertices(g))) { - const auto iter = vMap.find(v); - if(iter == end(vMap)) { - std::cout << "Vertex " << v << " (id=" << get(boost::vertex_index_t(), g, v) << ") not mapped." - << std::endl; - std::cout << "Map:" << std::endl; - for(auto p : vMap) std::cout << "\t" << p.first << " => " << p.second << std::endl; - std::cout << "num_vertices: " << num_vertices(g) << std::endl; - std::cout << "other.num_vertices: " << num_vertices(get_graph(other.lr)) << std::endl; - MOD_ABORT; - } - const Vertex vOther = iter->second; - const auto vId = get(boost::vertex_index_t(), g, v); - cData.x[vId] = other.getX(vOther, withHydrogen); - cData.y[vId] = other.getY(vOther, withHydrogen); - } - if(hasMoleculeEncoding) { -#ifdef MOD_HAVE_OPENBABEL - cData.obMol.setCoordinates(cData.x, cData.y); - cData.obMolLeft.setCoordinates(cData.x, cData.y); - cData.obMolRight.setCoordinates(cData.x, cData.y); -#endif - } - }; - doIt(cDataAll, true); - doIt(cDataNoHydrogen, false); - hasCoordinates = true; -} - -DepictionDataCore::DepictionData DepictionDataCore::getLeft() const { - if(!hasMoleculeEncoding) MOD_ABORT; - return DepictionData(*this); -} - -DepictionDataCore::DepictionData DepictionDataCore::getContext() const { - if(!hasMoleculeEncoding) MOD_ABORT; - return DepictionData(*this); -} - -DepictionDataCore::DepictionData DepictionDataCore::getRight() const { - if(!hasMoleculeEncoding) MOD_ABORT; - return DepictionData(*this); -} - -template -struct DepictionDataCore::DepictionData; -template -struct DepictionDataCore::DepictionData; -template -struct DepictionDataCore::DepictionData; - -} // namespace mod::lib::Rules \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Depiction.hpp b/libs/libmod/src/mod/lib/Rules/Properties/Depiction.hpp deleted file mode 100644 index 0bbfacc..0000000 --- a/libs/libmod/src/mod/lib/Rules/Properties/Depiction.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef MOD_LIB_RULES_STATE_DEPICTION_HPP -#define MOD_LIB_RULES_STATE_DEPICTION_HPP - -#include -#include -#include -#include - -#include -#include - -namespace mod { -struct AtomId; -struct Charge; -struct AtomData; -enum class BondType; -} // namespace mod -namespace mod::lib::Rules { -struct PropStringCore; -struct PropMoleculeCore; - -struct DepictionDataCore { - - template - struct DepictionData { // instantiated in the cpp file - DepictionData(const DepictionDataCore &depict); - AtomId getAtomId(Vertex v) const; // shortcut to moleculeState - Isotope getIsotope(Vertex v) const; // shortcut to moleculeState - Charge getCharge(Vertex v) const; // shortcut to moleculeState - bool getRadical(Vertex v) const; // shortcut to moleculeState - BondType getBondData(Edge e) const; // shortcut to moleculeState - std::string getVertexLabelNoIsotopeChargeRadical(Vertex v) const; - std::string getEdgeLabel(Edge e) const; - const AtomData &operator()(Vertex v) const; // fake data - BondType operator()(Edge e) const; // fake data - bool hasImportantStereo(Vertex v) const; - bool getHasCoordinates() const; - double getX(Vertex v, bool withHydrogen) const; - double getY(Vertex v, bool withHydrogen) const; - lib::IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(Edge e, bool withHydrogen) const; - // private: - // bool isAtomIdInvalidContext(Vertex v) const; - private: - const DepictionDataCore &depict; - }; -public: - DepictionDataCore(const LabelledRule &lr); - DepictionDataCore(const DepictionDataCore&) = delete; - DepictionDataCore &operator=(const DepictionDataCore&) = delete; - const AtomData &operator()(Vertex v) const; // fake data, for creating OBMol - BondType operator()(Edge e) const; // fake data, for creating OBMol - bool hasImportantStereo(Vertex v) const; - bool getHasCoordinates() const; - double getX(Vertex v, bool withHydrogen) const; - double getY(Vertex v, bool withHydrogen) const; - // vMap: this -> other - void copyCoords(const DepictionDataCore &other, const std::map &vMap); -public: // projections - DepictionData getLeft() const; - DepictionData getContext() const; - DepictionData getRight() const; -private: - const LabelledRule &lr; - bool hasMoleculeEncoding, hasCoordinates; - std::map nonAtomToPhonyAtomLeft, nonAtomToPhonyAtomRight; - std::map phonyAtomToString; - std::map nonBondEdgesLeft, nonBondEdgesRight; - - struct CoordData { - std::vector x, y; -#ifdef MOD_HAVE_OPENBABEL - lib::Chem::OBMolHandle obMol; // the pushout, for generating coords - lib::Chem::OBMolHandle obMolLeft, obMolRight; // each side, with copied coords, for stereo -#endif - } cDataAll, cDataNoHydrogen; -}; - -} // namespace mod::lib::Rules - -#endif // MOD_LIB_RULES_STATE_DEPICTION_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Molecule.cpp b/libs/libmod/src/mod/lib/Rules/Properties/Molecule.cpp index bc6c6df..31c786e 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Molecule.cpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Molecule.cpp @@ -7,56 +7,42 @@ #include -namespace mod { -namespace lib { -namespace Rules { +namespace mod::lib::Rules { -PropMoleculeCore::PropMoleculeCore(const GraphType &g, const PropStringCore &labelState) : Base(g), isReaction(true) { - vertexState.resize(num_vertices(g)); - for(Vertex v : asRange(vertices(g))) { - auto decodeLabel = [this](const std::string & label) { - auto p = Chem::decodeVertexLabel(label); - if(std::get<0>(p) == AtomIds::Invalid) isReaction = false; - return AtomData(std::get<0>(p), std::get<1>(p), std::get<2>(p), std::get<3>(p)); - }; - auto vId = get(boost::vertex_index_t(), g, v); - switch(g[v].membership) { - case Membership::Left: - vertexState[vId].left = decodeLabel(labelState.getLeft()[v]); - break; - case Membership::Right: - vertexState[vId].right = decodeLabel(labelState.getRight()[v]); - break; - case Membership::Context: - vertexState[vId].left = decodeLabel(labelState.getLeft()[v]); - vertexState[vId].right = decodeLabel(labelState.getRight()[v]); - break; - } +PropMolecule::PropMolecule(const RuleType &rule, const PropString &pString) + : Base(rule), isReaction(true) { + const auto decodeVLabel = [this](const std::string &label) { + const auto p = Chem::decodeVertexLabel(label); + if(std::get<0>(p) == AtomIds::Invalid) isReaction = false; + return AtomData(std::get<0>(p), std::get<1>(p), std::get<2>(p), std::get<3>(p)); + }; + const auto decodeELabel = [this](const std::string &label) { + const auto bondType = Chem::decodeEdgeLabel(label); + if(bondType == BondType::Invalid) isReaction = false; + return bondType; + }; + + vPropL.resize(num_vertices(getL(rule))); + vPropR.resize(num_vertices(getR(rule))); + for(const auto v: asRange(vertices(getL(rule)))) { + const auto vId = get(boost::vertex_index_t(), getL(rule), v); + vPropL[vId] = decodeVLabel(pString.getLeft()[v]); + } + for(const auto v: asRange(vertices(getR(rule)))) { + const auto vId = get(boost::vertex_index_t(), getR(rule), v); + vPropR[vId] = decodeVLabel(pString.getRight()[v]); } - edgeState.resize(num_edges(g)); - for(Edge e : asRange(edges(g))) { - auto decodeLabel = [this](const std::string & label) { - auto bondType = Chem::decodeEdgeLabel(label); - if(bondType == BondType::Invalid) isReaction = false; - return bondType; - }; - auto eId = get(boost::edge_index_t(), g, e); - switch(g[e].membership) { - case Membership::Left: - edgeState[eId].left = decodeLabel(labelState.getLeft()[e]); - break; - case Membership::Right: - edgeState[eId].right = decodeLabel(labelState.getRight()[e]); - break; - case Membership::Context: - edgeState[eId].left = decodeLabel(labelState.getLeft()[e]); - edgeState[eId].right = decodeLabel(labelState.getRight()[e]); - break; - } + ePropL.resize(num_edges(getL(rule))); + ePropR.resize(num_edges(getR(rule))); + for(const auto e: asRange(edges(getL(rule)))) { + const auto eId = get(boost::edge_index_t(), getL(rule), e); + ePropL[eId] = decodeELabel(pString.getLeft()[e]); + } + for(const auto e: asRange(edges(getR(rule)))) { + const auto eId = get(boost::edge_index_t(), getR(rule), e); + ePropR[eId] = decodeELabel(pString.getRight()[e]); } } -} // namespace Rules -} // namespace lib -} // namespace mod \ No newline at end of file +} // namespace mod::lib::Rules \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Molecule.hpp b/libs/libmod/src/mod/lib/Rules/Properties/Molecule.hpp index ae7e2fd..bdf74a2 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Molecule.hpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Molecule.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_LIB_RULES_PROP_MOLECULE_H -#define MOD_LIB_RULES_PROP_MOLECULE_H +#ifndef MOD_LIB_RULES_PROP_MOLECULE_HPP +#define MOD_LIB_RULES_PROP_MOLECULE_HPP #include #include @@ -8,27 +8,25 @@ namespace mod { struct AtomData; enum class BondType; -namespace lib { -namespace Rules { -struct PropStringCore; +} // namespace mod +namespace mod::lib::Rules { +struct PropString; -class PropMoleculeCore : private PropCore { - // read-only of data - using Base = PropCore; +struct PropMolecule : private PropBase { + // read-only of data, so do 'using' of things needed + using Base = PropBase; + using Base::Side; public: - using Base::LeftType; - using Base::RightType; + PropMolecule(const RuleType &rule, const PropString &pString); +public: // to be able to form pointers to getLeft and getRight it is not enough to 'using' them + Side getLeft() const { return {*this, vPropL, ePropL, getL(rule)}; } + Side getRight() const { return {*this, vPropR, ePropR, getR(rule)}; } public: - PropMoleculeCore(const GraphType &g, const PropStringCore &labelState); using Base::invert; - using Base::getLeft; - using Base::getRight; private: bool isReaction; }; -} // namespace Rules -} // namespace lib -} // namespace mod +} // namespace mod::lib::Rules -#endif /* MOD_LIB_RULES_STATE_MOLECULE_H */ +#endif // MOD_LIB_RULES_STATE_MOLECULE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Property.cpp b/libs/libmod/src/mod/lib/Rules/Properties/Property.cpp index cb3e3f0..b3ec297 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Property.cpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Property.cpp @@ -4,22 +4,41 @@ namespace mod::lib::Rules::detail { -void PropVerify(const void *g, const void *gOther, - std::size_t nGraph, std::size_t nOther, - std::size_t mGraph, std::size_t mOther) { - if(g != gOther) { - std::cout << "Different graphs: g = " << (std::uintptr_t) g - << ", &this->g = " << (std::uintptr_t) gOther << std::endl; +void PropVerify(const lib::DPO::CombinedRule &rule, + std::size_t vLSize, std::size_t vRSize, + std::size_t eLSize, std::size_t eRSize) { + const auto printStuff = [&]() { + std::cout << "|V(CG)| = " << num_vertices(rule.getCombinedGraph()) << '\t' + << "|E(CG)| = " << num_edges(rule.getCombinedGraph()) << std::endl; + std::cout << "|V(L)| = " << num_vertices(getL(rule)) << '\t' + << "|E(L)| = " << num_edges(getL(rule)) << std::endl; + std::cout << "|V(K)| = " << num_vertices(getK(rule)) << '\t' + << "|E(K)| = " << num_edges(getK(rule)) << std::endl; + std::cout << "|V(R)| = " << num_vertices(getR(rule)) << '\t' + << "|E(R)| = " << num_edges(getR(rule)) << std::endl; + std::cout << "|PV(L)| = " << vLSize << '\t' + << "|PE(L)| = " << eLSize << std::endl; + std::cout << "|PV(R)| = " << vRSize << '\t' + << "|PE(R)| = " << eRSize << std::endl; + }; + if(num_vertices(getL(rule)) != vLSize) { + std::cout << "Different |V(L)|:\n"; + printStuff(); MOD_ABORT; } - if(nGraph != nOther) { - std::cout << "Different sizes: num_vertices(this->g) = " << nGraph - << ", vertexLabels.size() = " << nOther << std::endl; + if(num_vertices(getR(rule)) != vRSize) { + std::cout << "Different |V(R)|:\n"; + printStuff(); MOD_ABORT; } - if(mGraph != mOther) { - std::cout << "Different sizes: num_edges(this->g) = " << mGraph - << ", edgeLabels.size() = " << mOther << std::endl; + if(num_edges(getL(rule)) != eLSize) { + std::cout << "Different |E(L)|:\n"; + printStuff(); + MOD_ABORT; + } + if(num_edges(getR(rule)) != eRSize) { + std::cout << "Different |E(R)|:\n"; + printStuff(); MOD_ABORT; } } diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Property.hpp b/libs/libmod/src/mod/lib/Rules/Properties/Property.hpp index 00ff229..8beb6a1 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Property.hpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Property.hpp @@ -2,94 +2,100 @@ #define MOD_LIB_RULES_PROP_HPP #include +#include +#include #include #include #include -#include #include +#include #include #include namespace mod::lib::Rules { -using jla_boost::GraphDPO::Membership; +using lib::DPO::Membership; -#define MOD_RULE_STATE_TEMPLATE_PARAMS \ - template -#define MOD_RULE_STATE_TEMPLATE_ARGS \ - Derived, Graph, LeftVertexType, LeftEdgeType, RightVertexType, RightEdgeType +#define MOD_RULE_PROP_TEMPLATE_PARAMS typename Derived, typename VertexProp, typename EdgeProp +#define MOD_RULE_PROP_TEMPLATE_ARGS Derived, VertexProp, EdgeProp -namespace detail { +template +struct PropBase { + using RuleType = lib::DPO::CombinedRule; +public: + using VertexProp = VertexPropT; + using EdgeProp = EdgePropT; +public: + struct Side { + using RuleType = lib::DPO::CombinedRule; + public: + const VertexProp &operator[](RuleType::SideVertex v) const; + const EdgeProp &operator[](RuleType::SideEdge e) const; -MOD_RULE_STATE_TEMPLATE_PARAMS -struct LeftState; + friend auto get(const Side &p, const RuleType::SideVertex &v) -> decltype(p[v]) { return p[v]; } -MOD_RULE_STATE_TEMPLATE_PARAMS -struct RightState; + friend auto get(const Side &p, const RuleType::SideEdge &e) -> decltype(p[e]) { return p[e]; } -} // namespace detail + public: + const PropBase &p; + const std::vector &pV; + const std::vector &pE; + const RuleType::SideGraphType &g; + public: + using Handler = IdentityPropertyHandler; + }; +public: + using ValueTypeVertex = std::pair, boost::optional>; + using ValueTypeEdge = std::pair, boost::optional>; +public: + void verify() const; + + explicit PropBase(const RuleType &rule) : rule(rule) {} + + Side getLeft() const { return {*this, vPropL, ePropL, getL(rule)}; } + + Side getRight() const { return {*this, vPropR, ePropR, getR(rule)}; } -template -struct PropCore { - using LeftVertexType = LeftVertexTypeT; - using LeftEdgeType = LeftEdgeTypeT; - using RightVertexType = RightVertexTypeT; - using RightEdgeType = RightEdgeTypeT; - using Vertex = typename boost::graph_traits::vertex_descriptor; - using Edge = typename boost::graph_traits::edge_descriptor; - using LeftType = detail::LeftState; - using RightType = detail::RightState; - friend class detail::LeftState; - friend class detail::RightState; - using ValueTypeVertex = std::pair, boost::optional >; - using ValueTypeEdge = std::pair, boost::optional >; public: - void verify(const Graph *g) const; - explicit PropCore(const Graph &g); - ValueTypeVertex operator[](Vertex v) const; - ValueTypeEdge operator[](Edge e) const; - LeftType getLeft() const; - RightType getRight() const; void invert(); - void add(Vertex v, const LeftVertexType &valueLeft, const RightVertexType &valueRight); - void add(Edge e, const LeftEdgeType &valueLeft, const RightEdgeType &valueRight); +public: // vertex + void addL(RuleType::SideVertex v, VertexProp p); + void addR(RuleType::SideVertex v, VertexProp p); + void addK(RuleType::KVertex v, VertexProp pL, VertexProp pR); + void promoteL(RuleType::SideVertex vL, RuleType::SideVertex vR, VertexProp pR); +public: // edge + void addL(RuleType::SideEdge e, EdgeProp p); + void addR(RuleType::SideEdge e, EdgeProp p); + void addK(RuleType::KEdge e, EdgeProp pL, EdgeProp pR); +public: + ValueTypeVertex operator[](RuleType::CombinedVertex v) const; + ValueTypeEdge operator[](RuleType::CombinedEdge e) const; +public: + friend auto get(const PropBase &p, RuleType::CombinedVertex v) -> decltype(p[v]) { return p[v]; } + + friend auto get(const PropBase &p, RuleType::CombinedEdge e) -> decltype(p[e]) { return p[e]; } + +public: // old stuff not yet fully evaluated + void add(RuleType::CombinedVertex v, const VertexProp &valueLeft, const VertexProp &valueRight); + void add(RuleType::CombinedEdge e, const EdgeProp &valueLeft, const EdgeProp &valueRight); // does not modify the other side - void setLeft(Vertex v, const LeftVertexType &value); - void setRight(Vertex v, const RightVertexType &value); - void setLeft(Edge e, const LeftEdgeType &value); - void setRight(Edge e, const RightEdgeType &value); - bool isChanged(Vertex v) const; - bool isChanged(Edge e) const; - void print(std::ostream &s, Vertex v) const; - void print(std::ostream &s, Edge e) const; + void setLeft(RuleType::CombinedVertex v, const VertexProp &value); + void setRight(RuleType::CombinedVertex v, const VertexProp &value); + void setLeft(RuleType::CombinedEdge e, const EdgeProp &value); + void setRight(RuleType::CombinedEdge e, const EdgeProp &value); +public: // is updated + bool isChanged(RuleType::CombinedVertex v) const; + bool isChanged(RuleType::CombinedEdge e) const; + void print(std::ostream &s, RuleType::CombinedVertex v) const; + void print(std::ostream &s, RuleType::CombinedEdge e) const; const Derived &getDerived() const; protected: - const Graph &g; + const RuleType &rule; protected: - struct VertexStore { - VertexStore() = default; - VertexStore(const LeftVertexType &left, const RightVertexType &right) : left(left), right(right) {} - VertexStore(LeftVertexType &&left, RightVertexType &&right) : left(std::move(left)), right(std::move(right)) {} - public: - LeftVertexType left; - RightVertexType right; - }; - - struct EdgeStore { - EdgeStore() = default; - EdgeStore(const LeftEdgeType &left, const RightEdgeType &right) : left(left), right(right) {} - public: - LeftEdgeType left; - RightEdgeType right; - }; - - std::vector vertexState; - std::vector edgeState; + std::vector vPropL, vPropR; + std::vector ePropL, ePropR; public: struct Handler { template @@ -119,230 +125,277 @@ struct PropCore { }; }; -template -auto get(const PropCore &p, VertexOrEdge ve) -> decltype(p[ve]) { - return p[ve]; -} +//------------------------------------------------------------------------------ +// Implementation +//------------------------------------------------------------------------------ -namespace detail { +template +const VertexProp &PropBase::Side::operator[](RuleType::SideVertex v) const { + const auto vId = get(boost::vertex_index_t(), g, v); + assert(vId < pV.size()); + return pV[vId]; +} -MOD_RULE_STATE_TEMPLATE_PARAMS -struct LeftState { - LeftState(const PropCore &state) : state(state) {} +template +const EdgeProp &PropBase::Side::operator[](RuleType::SideEdge e) const { + const auto eId = get(boost::edge_index_t(), g, e); + assert(eId < pE.size()); + return pE[eId]; +} - const LeftVertexType &operator[](typename boost::graph_traits::vertex_descriptor v) const { - auto vId = get(boost::vertex_index_t(), state.g, v); - assert(vId < state.vertexState.size()); - assert(state.g[v].membership != Membership::Right); - return state.vertexState[vId].left; - } +namespace detail { - const LeftEdgeType &operator[](typename boost::graph_traits::edge_descriptor e) const { - auto eId = get(boost::edge_index_t(), state.g, e); - assert(eId < state.edgeState.size()); - assert(state.g[e].membership != Membership::Right); - return state.edgeState[eId].left; - } -public: - const PropCore &state; -public: - using Handler = IdentityPropertyHandler; -}; +void PropVerify(const lib::DPO::CombinedRule &rule, + std::size_t vLSize, std::size_t vRSize, + std::size_t eLSize, std::size_t eRSize); -MOD_RULE_STATE_TEMPLATE_PARAMS -struct RightState { - RightState(const PropCore &state) : state(state) {} +} // namespace detail - const RightVertexType &operator[](typename boost::graph_traits::vertex_descriptor v) const { - auto vId = get(boost::vertex_index_t(), state.g, v); - assert(vId < state.vertexState.size()); - assert(state.g[v].membership != Membership::Left); - return state.vertexState[vId].right; - } +template +void PropBase::verify() const { + // dispatch to a non-templated function so the iostream stuff is not in a header + detail::PropVerify(rule, vPropL.size(), vPropR.size(), ePropL.size(), ePropR.size()); +} - const RightEdgeType &operator[](typename boost::graph_traits::edge_descriptor e) const { - auto eId = get(boost::edge_index_t(), state.g, e); - assert(eId < state.edgeState.size()); - assert(state.g[e].membership != Membership::Left); - return state.edgeState[eId].right; - } -public: - const PropCore &state; -public: - using Handler = IdentityPropertyHandler; -}; +// =============================================================================================== -template -auto get(const LeftState &p, const VertexOrEdge &ve) -> decltype(p[ve]) { - return p[ve]; +template +void PropBase::invert() { + using std::swap; + swap(vPropL, vPropR); + swap(ePropL, ePropR); } -template -auto get(const RightState &p, const VertexOrEdge &ve) -> decltype(p[ve]) { - return p[ve]; +template +void PropBase::addL(RuleType::SideVertex v, VertexProp p) { + assert(num_vertices(getL(rule)) == vPropL.size() + 1); + assert(get(boost::vertex_index_t(), getL(rule), v) == vPropL.size()); + vPropL.push_back(std::move(p)); + vPropR.emplace_back(); + verify(); } -} // namespace detail - -//------------------------------------------------------------------------------ -// Implementation -//------------------------------------------------------------------------------ +template +void PropBase::addR(RuleType::SideVertex v, VertexProp p) { + assert(num_vertices(getR(rule)) == vPropR.size() + 1); + assert(get(boost::vertex_index_t(), getR(rule), v) == vPropR.size()); + vPropL.emplace_back(); + vPropR.push_back(std::move(p)); + verify(); +} -namespace detail { -void PropVerify(const void *g, const void *gOther, - std::size_t nGraph, std::size_t nOther, - std::size_t mGraph, std::size_t mOther); -} // namespace detail +template +void PropBase::addK(RuleType::KVertex vK, VertexProp pL, VertexProp pR) { + assert(num_vertices(getL(rule)) == vPropL.size() + 1); + assert(num_vertices(getR(rule)) == vPropR.size() + 1); + assert(get(boost::vertex_index_t(), getL(rule), + get(getMorL(rule), getK(rule), getL(rule), vK)) + == vPropL.size()); + assert(get(boost::vertex_index_t(), getR(rule), + get(getMorR(rule), getK(rule), getR(rule), vK)) + == vPropR.size()); + vPropL.push_back(std::move(pL)); + vPropR.push_back(std::move(pR)); + verify(); +} -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::verify(const Graph *g) const { - detail::PropVerify(g, &this->g, num_vertices(*g), vertexState.size(), num_edges(*g), edgeState.size()); +template +void PropBase::promoteL(RuleType::SideVertex vL, RuleType::SideVertex vR, VertexProp pR) { + assert(vL == vR); + const auto vRId = get(boost::vertex_index_t(), getR(rule), vR); + vPropR[vRId] = std::move(pR); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -PropCore::PropCore(const Graph &g) : g(g) {} - -MOD_RULE_STATE_TEMPLATE_PARAMS -std::pair, boost::optional > -PropCore::operator[](typename boost::graph_traits::vertex_descriptor v) const { - assert(v != boost::graph_traits::null_vertex()); - boost::optional l; - boost::optional r; - if(g[v].membership != Membership::Right) l = getLeft()[v]; - if(g[v].membership != Membership::Left) r = getRight()[v]; - return std::make_pair(l, r); +template +void PropBase::addL(RuleType::SideEdge e, EdgeProp p) { + assert(num_edges(getL(rule)) == ePropL.size() + 1); + assert(get(boost::edge_index_t(), getL(rule), e) == ePropL.size()); + ePropL.push_back(std::move(p)); + ePropR.emplace_back(); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -std::pair, boost::optional > -PropCore::operator[](typename boost::graph_traits::edge_descriptor e) const { - boost::optional l; - boost::optional r; - if(g[e].membership != Membership::Right) l = getLeft()[e]; - if(g[e].membership != Membership::Left) r = getRight()[e]; - return std::make_pair(l, r); +template +void PropBase::addR(RuleType::SideEdge e, EdgeProp p) { + assert(num_edges(getR(rule)) == ePropR.size() + 1); + assert(get(boost::edge_index_t(), getR(rule), e) == ePropR.size()); + ePropL.emplace_back(); + ePropR.push_back(std::move(p)); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -typename detail::LeftState -PropCore::getLeft() const { - return detail::LeftState(*this); +template +void PropBase::addK(RuleType::KEdge eK, EdgeProp pL, EdgeProp pR) { + assert(num_edges(getL(rule)) == ePropL.size() + 1); + assert(num_edges(getR(rule)) == ePropR.size() + 1); + assert(get(boost::edge_index_t(), getL(rule), + get(getMorL(rule), getK(rule), getL(rule), eK)) + == ePropL.size()); + assert(get(boost::edge_index_t(), getR(rule), + get(getMorR(rule), getK(rule), getR(rule), eK)) + == ePropR.size()); + ePropL.push_back(std::move(pL)); + ePropR.push_back(std::move(pR)); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -typename detail::RightState -PropCore::getRight() const { - return detail::RightState(*this); +// =============================================================================================== + +template +std::pair, boost::optional> +PropBase::operator[](RuleType::CombinedVertex v) const { + assert(v != boost::graph_traits::null_vertex()); + boost::optional l; + boost::optional r; + if(rule.getCombinedGraph()[v].membership != Membership::R) + l = getLeft()[get_inverse(rule.getLtoCG(), getL(rule), rule.getCombinedGraph(), v)]; + if(rule.getCombinedGraph()[v].membership != Membership::L) + r = getRight()[get_inverse(rule.getRtoCG(), getR(rule), rule.getCombinedGraph(), v)]; + return {l, r}; } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::invert() { - using std::swap; - for(auto &vs : vertexState) swap(vs.left, vs.right); - for(auto &es : edgeState) swap(es.left, es.right); +template +std::pair, boost::optional> +PropBase::operator[](RuleType::CombinedEdge e) const { + boost::optional l; + boost::optional r; + const auto &cg = rule.getCombinedGraph(); + if(cg[e].membership != Membership::R) + l = getLeft()[get_inverse(rule.getLtoCG(), getL(rule), cg, e)]; + if(cg[e].membership != Membership::L) + r = getRight()[get_inverse(rule.getRtoCG(), getR(rule), cg, e)]; + return {l, r}; } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::add(Vertex v, - const LeftVertexType &valueLeft, - const RightVertexType &valueRight) { - assert(num_vertices(g) == vertexState.size() + 1); - assert(get(boost::vertex_index_t(), g, v) == vertexState.size()); - vertexState.emplace_back(valueLeft, valueRight); - verify(&g); + +template +void PropBase::add(RuleType::CombinedVertex v, const VertexProp &valueLeft, + const VertexProp &valueRight) { + assert(num_vertices(rule.getCombinedGraph()) == vPropL.size() + 1); + assert(num_vertices(rule.getCombinedGraph()) == vPropR.size() + 1); + assert(get(boost::vertex_index_t(), rule.getCombinedGraph(), v) == vPropL.size()); + assert(get(boost::vertex_index_t(), rule.getCombinedGraph(), v) == vPropR.size()); + vPropL.push_back(valueLeft); + vPropR.push_back(valueRight); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -void -PropCore::add(Edge e, const LeftEdgeType &valueLeft, const RightEdgeType &valueRight) { - assert(num_edges(g) == edgeState.size() + 1); - assert(get(boost::edge_index_t(), g, e) == edgeState.size()); - edgeState.emplace_back(valueLeft, valueRight); - verify(&g); +template +void PropBase::add(RuleType::CombinedEdge e, const EdgeProp &valueLeft, + const EdgeProp &valueRight) { + assert(num_edges(rule.getCombinedGraph()) == ePropL.size() + 1); + assert(num_edges(rule.getCombinedGraph()) == ePropR.size() + 1); + assert(get(boost::edge_index_t(), rule.getCombinedGraph(), e) == ePropL.size()); + assert(get(boost::edge_index_t(), rule.getCombinedGraph(), e) == ePropR.size()); + ePropL.push_back(valueLeft); + ePropR.push_back(valueRight); + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::setLeft(Vertex v, const LeftVertexType &value) { - auto vId = get(boost::vertex_index_t(), g, v); - assert(vId < vertexState.size()); - assert(g[v].membership != Membership::Right); - vertexState[vId].left = value; - verify(&g); +template +void PropBase::setLeft(RuleType::CombinedVertex v, const VertexProp &value) { + const auto vId = get(boost::vertex_index_t(), rule.getCombinedGraph(), v); + assert(vId < vPropL.size()); + assert(rule.getCombinedGraph()[v].membership != Membership::R); + vPropL[vId] = value; + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::setRight(Vertex v, const RightVertexType &value) { - auto vId = get(boost::vertex_index_t(), g, v); - assert(vId < vertexState.size()); - assert(g[v].membership != Membership::Left); - vertexState[vId].right = value; - verify(&g); +template +void PropBase::setRight(RuleType::CombinedVertex v, const VertexProp &value) { + const auto vId = get(boost::vertex_index_t(), rule.getCombinedGraph(), v); + assert(vId < vPropR.size()); + assert(rule.getCombinedGraph()[v].membership != Membership::L); + vPropR[vId] = value; + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::setLeft(Edge e, const LeftEdgeType &value) { - auto eId = get(boost::edge_index_t(), g, e); - assert(eId < edgeState.size()); - assert(g[e].membership != Membership::Right); - edgeState[eId].left = value; - verify(&g); +template +void PropBase::setLeft(RuleType::CombinedEdge e, const EdgeProp &value) { + const auto eId = get(boost::edge_index_t(), rule.getCombinedGraph(), e); + assert(eId < ePropL.size()); + assert(rule.getCombinedGraph()[e].membership != Membership::R); + ePropL[eId] = value; + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::setRight(Edge e, const RightEdgeType &value) { - auto eId = get(boost::edge_index_t(), g, e); - assert(eId < edgeState.size()); - assert(g[e].membership != Membership::Left); - edgeState[eId].right = value; - verify(&g); +template +void PropBase::setRight(RuleType::CombinedEdge e, const EdgeProp &value) { + const auto eId = get(boost::edge_index_t(), rule.getCombinedGraph(), e); + assert(eId < ePropR.size()); + assert(rule.getCombinedGraph()[e].membership != Membership::L); + ePropR[eId] = value; + verify(); } -MOD_RULE_STATE_TEMPLATE_PARAMS -bool PropCore::isChanged(Vertex v) const { - if(g[v].membership != Membership::Context) return false; - auto vId = get(boost::vertex_index_t(), g, v); - assert(vId < vertexState.size()); - return vertexState[vId].left != vertexState[vId].right; +template +bool PropBase::isChanged(RuleType::CombinedVertex v) const { + if(rule.getCombinedGraph()[v].membership != Membership::K) return false; + const auto vL = get_inverse(rule.getLtoCG(), getL(rule), rule.getCombinedGraph(), v); + const auto vR = get_inverse(rule.getRtoCG(), getR(rule), rule.getCombinedGraph(), v); + const auto vLid = get(boost::vertex_index_t(), getL(rule), vL); + const auto vRid = get(boost::vertex_index_t(), getR(rule), vR); + assert(vLid < vPropL.size()); + assert(vRid < vPropR.size()); + return vPropL[vLid] != vPropR[vRid]; } -MOD_RULE_STATE_TEMPLATE_PARAMS -bool PropCore::isChanged(Edge e) const { - if(g[e].membership != Membership::Context) return false; - auto eId = get(boost::edge_index_t(), g, e); - assert(eId < edgeState.size()); - return edgeState[eId].left != edgeState[eId].right; +template +bool PropBase::isChanged(RuleType::CombinedEdge e) const { + if(rule.getCombinedGraph()[e].membership != Membership::K) return false; + const auto eL = get_inverse(rule.getLtoCG(), getL(rule), rule.getCombinedGraph(), e); + const auto eR = get_inverse(rule.getRtoCG(), getR(rule), rule.getCombinedGraph(), e); + const auto eLid = get(boost::edge_index_t(), getL(rule), eL); + const auto eRid = get(boost::edge_index_t(), getR(rule), eR); + assert(eLid < ePropL.size()); + assert(eRid < ePropR.size()); + return ePropL[eLid] != ePropR[eRid]; } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::print(std::ostream &s, Vertex v) const { - auto vId = get(boost::vertex_index_t(), g, v); - assert(vId < vertexState.size()); - auto membership = g[v].membership; - if(membership == Membership::Right) s << "<>"; - else s << '\'' << vertexState[vId].left << '\''; +template +void PropBase::print(std::ostream &s, RuleType::CombinedVertex v) const { + const auto membership = rule.getCombinedGraph()[v].membership; + if(membership == Membership::R) s << "<>"; + else { + const auto vL = get_inverse(rule.getLtoCG(), getL(rule), rule.getCombinedGraph(), v); + const auto vLid = get(boost::vertex_index_t(), getL(rule), vL); + assert(vLid < vPropL.size()); + s << '\'' << vPropL[vLid] << '\''; + } s << " -> "; - if(membership == Membership::Left) s << "<>"; - else s << '\'' << vertexState[vId].right << '\''; + if(membership == Membership::L) s << "<>"; + else { + const auto vR = get_inverse(rule.getRtoCG(), getR(rule), rule.getCombinedGraph(), v); + const auto vRid = get(boost::vertex_index_t(), getR(rule), vR); + assert(vRid < vPropR.size()); + s << '\'' << vPropR[vRid] << '\''; + } } -MOD_RULE_STATE_TEMPLATE_PARAMS -void PropCore::print(std::ostream &s, Edge e) const { - auto eId = get(boost::edge_index_t(), g, e); - assert(eId < edgeState.size()); - auto membership = g[e].membership; - if(membership == Membership::Right) s << "<>"; - else s << '\'' << edgeState[eId].left << '\''; +template +void PropBase::print(std::ostream &s, RuleType::CombinedEdge e) const { + const auto membership = rule.getCombinedGraph()[e].membership; + if(membership == Membership::R) s << "<>"; + else { + const auto eL = get_inverse(rule.getLtoCG(), getL(rule), rule.getCombinedGraph(), e); + const auto eLid = get(boost::edge_index_t(), getL(rule), eL); + assert(eLid < ePropL.size()); + s << '\'' << ePropL[eLid] << '\''; + } s << " -> "; - if(membership == Membership::Left) s << "<>"; - else s << '\'' << edgeState[eId].right << '\''; + if(membership == Membership::L) s << "<>"; + else { + const auto eR = get_inverse(rule.getRtoCG(), getR(rule), rule.getCombinedGraph(), e); + const auto eRid = get(boost::edge_index_t(), getR(rule), eR); + assert(eRid < ePropR.size()); + s << '\'' << ePropR[eRid] << '\''; + } } -MOD_RULE_STATE_TEMPLATE_PARAMS -const Derived &PropCore::getDerived() const { - return static_cast (*this); +template +const Derived &PropBase::getDerived() const { + return static_cast(*this); } } // namespace mod::lib::Rules diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Stereo.hpp b/libs/libmod/src/mod/lib/Rules/Properties/Stereo.hpp index 26c816f..9fd684d 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Stereo.hpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Stereo.hpp @@ -10,52 +10,43 @@ namespace mod::lib::Rules { -struct PropStereoCore - : private PropCore, lib::Stereo::EdgeCategory> { +struct PropStereo + : private PropBase, lib::Stereo::EdgeCategory> { // read-only of data - using Base = PropCore, lib::Stereo::EdgeCategory>; - using Base::LeftVertexType; - using Base::LeftEdgeType; - using Base::RightVertexType; - using Base::RightEdgeType; - using Base::LeftType; - using Base::RightType; + using Base = PropBase, lib::Stereo::EdgeCategory>; + using Base::VertexProp; + using Base::EdgeProp; + using Base::Side; struct ValueTypeVertex { - boost::optional left; - boost::optional right; + boost::optional left, right; bool inContext; }; struct ValueTypeEdge { - std::optional left; - std::optional right; + std::optional left, right; bool inContext; }; public: template - PropStereoCore(const GraphType &g, InferenceLeft &&leftInference, InferenceRight &&rightInference, - VertexInContext vCallback, EdgeInContext eCallback) : Base(g) { + PropStereo(const RuleType &rule, InferenceLeft &&leftInference, InferenceRight &&rightInference, + VertexInContext vCallback, EdgeInContext eCallback) : Base(rule) { // The eCallback is only responsible for the information about being in context. // This function will ensure that it's valid to put in context as well. // However, the vCallback is also responsible for ensuring that it is valid to put it in context. // This function only checks what is easy, i.e., whether the vertex and its incident edges are in context. - vertexState.reserve(num_vertices(g)); - vertexInContext.reserve(num_vertices(g)); - for(const auto v : asRange(vertices(g))) { - assert(get(boost::vertex_index_t(), g, v) == vertexState.size()); - std::unique_ptr l, r; - if(g[v].membership != Membership::Right) l = leftInference.extractConfiguration(v); - if(g[v].membership != Membership::Left) r = rightInference.extractConfiguration(v); - { // verify + const auto handleVertices = [](const lib::DPO::CombinedRule::SideGraphType &g, + std::vector &vProp, auto &inference) { + for(const auto vS: asRange(vertices(g))) { + assert(get(boost::vertex_index_t(), g, vS) >= vProp.size()); + vProp.resize(get(boost::vertex_index_t(), g, vS)); + assert(get(boost::vertex_index_t(), g, vS) == vProp.size()); + auto p = inference.extractConfiguration(vS); + { // verify #ifndef NDEBUG - const auto verify = [&g, &v](const lib::Stereo::Configuration &conf, const auto m) { - const auto oe = out_edges(v, g); - const auto d = std::count_if(oe.first, oe.second, [&g, m](const auto &e) { - return g[e].membership != m; - }); + const auto d = degree(vS, g); int dConf = 0; - for(const auto &emb : conf) { + for(const auto &emb: *p) { if(emb.type != lib::Stereo::EmbeddingEdge::Type::Edge) { assert(emb.offset >= d); } else { // edge @@ -64,65 +55,94 @@ struct PropStereoCore }; } assert(dConf == d); - }; - if(l) verify(*l, Membership::Right); - if(r) verify(*r, Membership::Left); - assert(bool(l) || bool(r)); #endif - } // end verify - vertexState.emplace_back(std::move(l), std::move(r)); + } // end verify + vProp.push_back(std::move(p)); + } + }; + vPropL.reserve(num_vertices(getL(rule))); + vPropR.reserve(num_vertices(getR(rule))); + vertexInContext.reserve(num_vertices(rule.getCombinedGraph())); + handleVertices(getL(rule), vPropL, leftInference); + handleVertices(getR(rule), vPropR, rightInference); + assert(vPropL.size() <= num_vertices(rule.getCombinedGraph())); + assert(vPropR.size() <= num_vertices(rule.getCombinedGraph())); + vPropL.resize(num_vertices(rule.getCombinedGraph())); + vPropR.resize(num_vertices(rule.getCombinedGraph())); + + const auto &cg = rule.getCombinedGraph(); + for(const auto v: asRange(vertices(cg))) { const bool inContext = [&]() { - if(g[v].membership != Membership::Context) return false; + if(cg[v].membership != Membership::K) return false; if(!vCallback(v)) return false; - for(const auto eOut : asRange(out_edges(v, g))) { - if(g[eOut].membership != Membership::Context) return false; - } - assert(bool(vertexState.back().left)); - assert(bool(vertexState.back().right)); + for(const auto eOut: asRange(out_edges(v, rule.getCombinedGraph()))) + if(cg[eOut].membership != Membership::K) + return false; return true; }(); vertexInContext.push_back(inContext); } - edgeState.reserve(num_edges(g)); - edgeInContext.reserve(num_edges(g)); - for(const auto e : asRange(edges(g))) { - assert(get(boost::edge_index_t(), g, e) == edgeState.size()); - lib::Stereo::EdgeCategory - lCat = static_cast(-1), - rCat = static_cast(-1); - if(g[e].membership != Membership::Right) lCat = leftInference.getEdgeCategory(e); - if(g[e].membership != Membership::Left) rCat = rightInference.getEdgeCategory(e); - edgeState.emplace_back(lCat, rCat); - const auto m = g[e].membership; - const bool inContext = - m == Membership::Context - && lCat == rCat - && eCallback(e); + + const auto handleEdges = [](const lib::DPO::CombinedRule::SideGraphType &g, + std::vector &eProp, auto &inference) { + for(const auto eS: asRange(edges(g))) { + assert(get(boost::edge_index_t(), g, eS) >= eProp.size()); + eProp.resize(get(boost::edge_index_t(), g, eS)); + assert(get(boost::edge_index_t(), g, eS) == eProp.size()); + auto cat = inference.getEdgeCategory(eS); + eProp.push_back(cat); + } + }; + ePropL.reserve(num_edges(cg)); + ePropR.reserve(num_edges(cg)); + edgeInContext.reserve(num_edges(cg)); + handleEdges(getL(rule), ePropL, leftInference); + handleEdges(getR(rule), ePropR, rightInference); + assert(ePropL.size() <= num_edges(rule.getCombinedGraph())); + assert(ePropR.size() <= num_edges(rule.getCombinedGraph())); + ePropL.resize(num_edges(rule.getCombinedGraph())); + ePropR.resize(num_edges(rule.getCombinedGraph())); + + for(const auto e: asRange(edges(cg))) { + const bool inContext = [&]() { + const auto m = cg[e].membership; + if(m != Membership::K) return false; + const auto eL = get(getMorL(rule), getK(rule), getL(rule), e); + const auto eR = get(getMorR(rule), getK(rule), getR(rule), e); + const auto catL = ePropL[get(boost::edge_index_t(), getL(rule), eL)]; + const auto catR = ePropR[get(boost::edge_index_t(), getR(rule), eR)]; + if(catL != catR) return false; + return eCallback(e); + }(); edgeInContext.push_back(inContext); } - verify(&g); + verify(); } - ValueTypeVertex operator[](Vertex v) const { - assert(v != boost::graph_traits::null_vertex()); + ValueTypeVertex operator[](lib::DPO::CombinedRule::CombinedVertex v) const { + assert(v != boost::graph_traits::null_vertex()); ValueTypeVertex res; - if(g[v].membership != Membership::Right) res.left = getLeft()[v]; - if(g[v].membership != Membership::Left) res.right = getRight()[v]; + if(rule.getCombinedGraph()[v].membership != Membership::R) res.left = getLeft()[v]; + if(rule.getCombinedGraph()[v].membership != Membership::L) res.right = getRight()[v]; res.inContext = inContext(v); return res; } - ValueTypeEdge operator[](Edge e) const { + ValueTypeEdge operator[](lib::DPO::CombinedRule::CombinedEdge e) const { ValueTypeEdge res; - if(g[e].membership != Membership::Right) res.left = getLeft()[e]; - if(g[e].membership != Membership::Left) res.right = getRight()[e]; + if(rule.getCombinedGraph()[e].membership != Membership::R) res.left = getLeft()[e]; + if(rule.getCombinedGraph()[e].membership != Membership::L) res.right = getRight()[e]; res.inContext = inContext(e); return res; } +public: // to be able to form pointers to getLeft and getRight it is not enough to 'using' them + Side getLeft() const { return {*this, vPropL, ePropL, getL(rule)}; } + Side getRight() const { return {*this, vPropR, ePropR, getR(rule)}; } +public: // we have redefined operator[], so get should be redefined as well + friend auto get(const PropStereo &p, RuleType::CombinedVertex v) -> decltype(p[v]) { return p[v]; } + friend auto get(const PropStereo &p, RuleType::CombinedEdge e) -> decltype(p[e]) { return p[e]; } public: using Base::verify; - using Base::getLeft; - using Base::getRight; using Base::print; struct Handler { @@ -170,22 +190,17 @@ struct PropStereoCore } }; public: - bool inContext(Vertex v) const { - return vertexInContext[get(boost::vertex_index_t(), g, v)]; + bool inContext(lib::DPO::CombinedRule::CombinedVertex v) const { + return vertexInContext[get(boost::vertex_index_t(), rule.getCombinedGraph(), v)]; } - bool inContext(Edge e) const { - return edgeInContext[get(boost::edge_index_t(), g, e)]; + bool inContext(lib::DPO::CombinedRule::CombinedEdge e) const { + return edgeInContext[get(boost::edge_index_t(), rule.getCombinedGraph(), e)]; } private: std::vector vertexInContext, edgeInContext; }; -template -auto get(const PropStereoCore &p, const VertexOrEdge &ve) -> decltype(p[ve]) { - return p[ve]; -} - } // namespace mod::lib::Rules #endif // MOD_LIB_RULE_PROP_STEREO_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/String.cpp b/libs/libmod/src/mod/lib/Rules/Properties/String.cpp index 1a36999..0b8c1f8 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/String.cpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/String.cpp @@ -1,65 +1,67 @@ #include "String.hpp" #include -#include #include #include +#include -namespace mod { -namespace lib { -namespace Rules { +namespace mod::lib::Rules { -PropStringCore::PropStringCore(const GraphType &g) : PropCore(g) { - verify(&g); +PropString::PropString(const RuleType &rule) : PropBase(rule) { + verify(); } -PropStringCore::PropStringCore(const GraphType &g, - const std::vector &leftMatchConstraints, - const std::vector &rightMatchConstraints, - const PropTermCore &term, const StringStore &strings) : PropCore(g) { +PropString::PropString(const RuleType &rule, + const std::vector &leftMatchConstraints, + const std::vector &rightMatchConstraints, + const PropTerm &term, const StringStore &strings) + : PropBase(rule) { std::stringstream ss; const auto termToString = [&ss, &term, &strings](std::size_t addr) -> std::string { ss.str(std::string()); - lib::IO::Term::Write::term(getMachine(term), { - lib::Term::AddressType::Heap, addr + Term::Write::term(getMachine(term), { + Term::AddressType::Heap, addr }, strings, ss); return ss.str(); }; - vertexState.resize(num_vertices(g)); - for(auto v : asRange(vertices(g))) { - auto vId = get(boost::vertex_index_t(), g, v); - switch(g[v].membership) { - case Membership::Left: - vertexState[vId].left = termToString(term.getLeft()[v]); + vPropL.resize(num_vertices(rule.getCombinedGraph())); + vPropR.resize(num_vertices(rule.getCombinedGraph())); + for(auto v: asRange(vertices(rule.getCombinedGraph()))) { + auto vId = get(boost::vertex_index_t(), rule.getCombinedGraph(), v); + switch(rule.getCombinedGraph()[v].membership) { + case Membership::L: + vPropL[vId] = termToString(term.getLeft()[v]); break; - case Membership::Right: - vertexState[vId].right = termToString(term.getRight()[v]); + case Membership::R: + vPropR[vId] = termToString(term.getRight()[v]); break; - case Membership::Context: - vertexState[vId].left = termToString(term.getLeft()[v]); - vertexState[vId].right = termToString(term.getRight()[v]); + case Membership::K: + vPropL[vId] = termToString(term.getLeft()[v]); + vPropR[vId] = termToString(term.getRight()[v]); break; } } - edgeState.resize(num_edges(g)); - for(auto e : asRange(edges(g))) { - auto eId = get(boost::edge_index_t(), g, e); - switch(g[e].membership) { - case Membership::Left: - edgeState[eId].left = termToString(term.getLeft()[e]); + ePropL.resize(num_edges(rule.getCombinedGraph())); + ePropR.resize(num_edges(rule.getCombinedGraph())); + for(auto e: asRange(edges(rule.getCombinedGraph()))) { + auto eId = get(boost::edge_index_t(), rule.getCombinedGraph(), e); + switch(rule.getCombinedGraph()[e].membership) { + case Membership::L: + ePropL[eId] = termToString(term.getLeft()[e]); break; - case Membership::Right: - edgeState[eId].right = termToString(term.getRight()[e]); + case Membership::R: + ePropR[eId] = termToString(term.getRight()[e]); break; - case Membership::Context: - edgeState[eId].left = termToString(term.getLeft()[e]); - edgeState[eId].right = termToString(term.getRight()[e]); + case Membership::K: + ePropL[eId] = termToString(term.getLeft()[e]); + ePropR[eId] = termToString(term.getRight()[e]); break; } } using HandlerType = decltype(termToString); + using SideGraphType = lib::DPO::CombinedRule::SideGraphType; struct Visitor : lib::GraphMorphism::Constraints::AllVisitorNonConst { Visitor(HandlerType &termToString) : termToString(termToString) {} @@ -67,9 +69,9 @@ PropStringCore::PropStringCore(const GraphType &g, virtual void operator()(lib::GraphMorphism::Constraints::VertexAdjacency &c) override { assert(c.vertexLabels.size() == 0); assert(c.edgeLabels.size() == 0); - for(const auto &t : c.vertexTerms) + for(const auto &t: c.vertexTerms) c.vertexLabels.insert(termToString(t)); - for(const auto &t : c.edgeTerms) + for(const auto &t: c.edgeTerms) c.edgeLabels.insert(termToString(t)); } @@ -80,12 +82,10 @@ PropStringCore::PropStringCore(const GraphType &g, const auto handleConstraints = [&](const auto &cs) { Visitor vis(termToString); - for(auto &c : cs) c->accept(vis); + for(auto &c: cs) c->accept(vis); }; handleConstraints(leftMatchConstraints); handleConstraints(rightMatchConstraints); } -} // namespace Rules -} // namespace lib -} // namespace mod \ No newline at end of file +} // namespace mod::lib::Rules \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/String.hpp b/libs/libmod/src/mod/lib/Rules/Properties/String.hpp index b5c5472..4aaa568 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/String.hpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/String.hpp @@ -1,28 +1,26 @@ -#ifndef MOD_LIB_RULES_PROP_STRING_H -#define MOD_LIB_RULES_PROP_STRING_H +#ifndef MOD_LIB_RULES_PROP_STRING_HPP +#define MOD_LIB_RULES_PROP_STRING_HPP #include #include #include -namespace mod { -namespace lib { +namespace mod::lib { struct StringStore; -namespace Rules { -struct PropTermCore; +} // namespace mod::lib +namespace mod::lib::Rules { +struct PropTerm; -struct PropStringCore : PropCore { - using ConstraintPtr = std::unique_ptr >; +struct PropString : PropBase { + using ConstraintPtr = std::unique_ptr>; public: - explicit PropStringCore(const GraphType &g); - PropStringCore(const GraphType &g, - const std::vector &leftMatchConstraints, - const std::vector &rightMatchConstraints, - const PropTermCore &term, const StringStore &strings); + explicit PropString(const RuleType &rule); + PropString(const RuleType &rule, + const std::vector &leftMatchConstraints, + const std::vector &rightMatchConstraints, + const PropTerm &term, const StringStore &strings); }; -} // namespace Rules -} // namespace lib -} // namespace mod +} // namespace mod::lib::Rules -#endif /* MOD_LIB_RULES_PROP_STRING_H */ \ No newline at end of file +#endif // MOD_LIB_RULES_PROP_STRING_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Term.cpp b/libs/libmod/src/mod/lib/Rules/Properties/Term.cpp index 52c062a..563ae69 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Term.cpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Term.cpp @@ -4,21 +4,21 @@ #include #include #include -#include #include +#include #include namespace mod::lib::Rules { -PropTermCore::PropTermCore(const GraphType &core, - const std::vector &leftMatchConstraints, - const std::vector &rightMatchConstraints, - const PropStringCore &label, const StringStore &stringStore) : Base(core) { +PropTerm::PropTerm(const RuleType &rule, + const std::vector &leftMatchConstraints, + const std::vector &rightMatchConstraints, + const PropString &pString, const StringStore &stringStore) : Base(rule) { // take every unique string and parse it std::unordered_map labelToAddress; - lib::Term::RawAppendStore varToAddr; - const auto handleLabel = [&stringStore, &labelToAddress, &varToAddr, this](const std::string & label) { + Term::RawAppendStore varToAddr; + const auto handleLabel = [&stringStore, &labelToAddress, &varToAddr, this](const std::string &label) { // if there is a * in the label, then we can not just use label caching if(label.find('*') == std::string::npos) { auto iter = labelToAddress.find(label); @@ -26,107 +26,119 @@ PropTermCore::PropTermCore(const GraphType &core, return iter->second; } } - lib::Term::RawTerm rawTerm; + Term::RawTerm rawTerm; try { - rawTerm = lib::IO::Term::Read::rawTerm(label, stringStore); - } catch(const lib::IO::ParsingError &e) { + rawTerm = Term::Read::rawTerm(label, stringStore); + } catch(const IO::ParsingError &e) { parsingError = e.msg; return std::numeric_limits::max(); } - lib::Term::Address addr = lib::Term::append(machine, rawTerm, varToAddr); + Term::Address addr = Term::append(machine, rawTerm, varToAddr); labelToAddress[label] = addr.addr; return addr.addr; }; - vertexState.resize(num_vertices(core)); - for(Vertex v : asRange(vertices(core))) { - auto vId = get(boost::vertex_index_t(), g, v); - switch(core[v].membership) { - case Membership::Left: - vertexState[vId].left = handleLabel(label.getLeft()[v]); - break; - case Membership::Right: - vertexState[vId].right = handleLabel(label.getRight()[v]); - break; - case Membership::Context: - if(label.getLeft()[v] == label.getRight()[v]) { - // no label-change, so use the same for both (e.g., "*" -> "*" should be the _same_ variable) - vertexState[vId].left = vertexState[vId].right = handleLabel(label.getLeft()[v]); - } else { - vertexState[vId].left = handleLabel(label.getLeft()[v]); - vertexState[vId].right = handleLabel(label.getRight()[v]); + + vPropL.reserve(num_vertices(getL(rule))); + vPropR.reserve(num_vertices(getR(rule))); + for(const auto vL: asRange(vertices(getL(rule)))) { + assert(get(boost::vertex_index_t(), getL(rule), vL) >= vPropL.size()); + vPropL.resize(get(boost::vertex_index_t(), getL(rule), vL)); + assert(get(boost::vertex_index_t(), getL(rule), vL) == vPropL.size()); + vPropL.push_back(handleLabel(pString.getLeft()[vL])); + } + for(const auto vR: asRange(vertices(getR(rule)))) { + assert(get(boost::vertex_index_t(), getR(rule), vR) >= vPropR.size()); + vPropR.resize(get(boost::vertex_index_t(), getR(rule), vR)); + assert(get(boost::vertex_index_t(), getR(rule), vR) == vPropR.size()); + const auto vK = get_inverse(getMorR(rule), getK(rule), getR(rule), vR); + if(vK != lib::DPO::CombinedRule::KGraphType::null_vertex()) { + const auto vL = get(getMorL(rule), getK(rule), getL(rule), vK); + if(pString.getLeft()[vL] == pString.getRight()[vR]) { + // copy the term from left + vPropR.push_back(vPropL[get(boost::vertex_index_t(), getL(rule), vL)]); + continue; } - break; } + vPropR.push_back(handleLabel(pString.getRight()[vR])); } - edgeState.resize(num_edges(core)); - for(Edge e : asRange(edges(core))) { - auto eId = get(boost::edge_index_t(), g, e); - switch(core[e].membership) { - case Membership::Left: - edgeState[eId].left = handleLabel(label.getLeft()[e]); - break; - case Membership::Right: - edgeState[eId].right = handleLabel(label.getRight()[e]); - break; - case Membership::Context: - if(label.getLeft()[e] == label.getRight()[e]) { - // no label-change, so use the same for both (e.g., "*" -> "*" should be the _same_ variable) - auto convertedLabel = handleLabel(label.getLeft()[e]); - edgeState[eId].left = convertedLabel; - edgeState[eId].right = convertedLabel; - } else { - edgeState[eId].left = handleLabel(label.getLeft()[e]); - edgeState[eId].right = handleLabel(label.getRight()[e]); + assert(vPropL.size() <= num_vertices(rule.getCombinedGraph())); + assert(vPropR.size() <= num_vertices(rule.getCombinedGraph())); + vPropL.resize(num_vertices(rule.getCombinedGraph())); + vPropR.resize(num_vertices(rule.getCombinedGraph())); + + ePropL.reserve(num_edges(getL(rule))); + ePropR.reserve(num_edges(getR(rule))); + for(const auto eL: asRange(edges(getL(rule)))) { + assert(get(boost::edge_index_t(), getL(rule), eL) >= ePropL.size()); + ePropL.resize(get(boost::edge_index_t(), getL(rule), eL)); + assert(get(boost::edge_index_t(), getL(rule), eL) == ePropL.size()); + ePropL.push_back(handleLabel(pString.getLeft()[eL])); + } + for(const auto eR: asRange(edges(getR(rule)))) { + assert(get(boost::edge_index_t(), getR(rule), eR) >= ePropR.size()); + ePropR.resize(get(boost::edge_index_t(), getR(rule), eR)); + assert(get(boost::edge_index_t(), getR(rule), eR) == ePropR.size()); + const auto eK = get_inverse(getMorR(rule), getK(rule), getR(rule), eR); + if(eK != lib::DPO::CombinedRule::KGraphType::edge_descriptor()) { + const auto eL = get(getMorL(rule), getK(rule), getL(rule), eK); + if(pString.getLeft()[eL] == pString.getRight()[eR]) { + // copy the term from left + ePropR.push_back(ePropL[get(boost::edge_index_t(), getL(rule), eL)]); + continue; } - break; } + ePropR.push_back(handleLabel(pString.getRight()[eR])); } + assert(ePropL.size() <= num_edges(rule.getCombinedGraph())); + assert(ePropR.size() <= num_edges(rule.getCombinedGraph())); + ePropL.resize(num_edges(rule.getCombinedGraph())); + ePropR.resize(num_edges(rule.getCombinedGraph())); using HandlerType = decltype(handleLabel); + using SideGraphType = lib::DPO::CombinedRule::SideGraphType; - struct Visitor : lib::GraphMorphism::Constraints::AllVisitorNonConst { - - Visitor(HandlerType &handleLabel) : handleLabel(handleLabel) { } + struct Visitor : GraphMorphism::Constraints::AllVisitorNonConst { + Visitor(HandlerType &handleLabel) : handleLabel(handleLabel) {} - virtual void operator()(lib::GraphMorphism::Constraints::VertexAdjacency &c) override { + virtual void operator()(GraphMorphism::Constraints::VertexAdjacency &c) override { assert(c.vertexTerms.size() == 0); assert(c.edgeTerms.size() == 0); - for(const std::string &s : c.vertexLabels) + for(const std::string &s: c.vertexLabels) c.vertexTerms.insert(handleLabel(s)); - for(const std::string &s : c.edgeLabels) + for(const std::string &s: c.edgeLabels) c.edgeTerms.insert(handleLabel(s)); } - virtual void operator()(lib::GraphMorphism::Constraints::ShortestPath &c) override { } + virtual void operator()(GraphMorphism::Constraints::ShortestPath &c) override {} private: HandlerType handleLabel; }; const auto handleConstraints = [&](const auto &cs) { Visitor vis(handleLabel); - for(auto &c : cs) c->accept(vis); + for(auto &c: cs) c->accept(vis); }; handleConstraints(leftMatchConstraints); handleConstraints(rightMatchConstraints); - verify(&this->g); + verify(); } -PropTermCore::PropTermCore(const GraphType &core, lib::Term::Wam machine) : Base(core), machine(std::move(machine)) { } +PropTerm::PropTerm(const RuleType &rule, Term::Wam machine) : Base(rule), machine(std::move(machine)) {} -bool isValid(const PropTermCore &core) { +bool isValid(const PropTerm &core) { return !core.parsingError.has_value(); } -const std::string &PropTermCore::getParsingError() const { +const std::string &PropTerm::getParsingError() const { assert(!isValid(*this)); return *parsingError; } -lib::Term::Wam &getMachine(PropTermCore &core) { +Term::Wam &getMachine(PropTerm &core) { return core.machine; } -const lib::Term::Wam &getMachine(const PropTermCore &core) { +const Term::Wam &getMachine(const PropTerm &core) { return core.machine; } diff --git a/libs/libmod/src/mod/lib/Rules/Properties/Term.hpp b/libs/libmod/src/mod/lib/Rules/Properties/Term.hpp index 6aaafcd..e607f56 100644 --- a/libs/libmod/src/mod/lib/Rules/Properties/Term.hpp +++ b/libs/libmod/src/mod/lib/Rules/Properties/Term.hpp @@ -10,40 +10,32 @@ #include namespace mod::lib::Rules { -struct PropStringCore; +struct PropString; -struct PropTermCore : PropCore { - using Base = PropCore; - using ConstraintPtr = std::unique_ptr >; +struct PropTerm : PropBase { + using Base = PropBase; + using ConstraintPtr = std::unique_ptr>; public: - PropTermCore(const GraphType &core, - const std::vector &leftMatchConstraints, - const std::vector &rightMatchConstraints, - const PropStringCore &label, const StringStore &stringStore); // parse-construct - PropTermCore(const GraphType &core, lib::Term::Wam machine); // import a machine - friend bool isValid(const PropTermCore &core); + PropTerm(const RuleType &rule, + const std::vector &leftMatchConstraints, + const std::vector &rightMatchConstraints, + const PropString &pString, const StringStore &stringStore); // parse-construct + PropTerm(const RuleType &rule, lib::Term::Wam machine); // import a machine + friend bool isValid(const PropTerm &core); const std::string &getParsingError() const; - friend lib::Term::Wam &getMachine(PropTermCore &core); - friend const lib::Term::Wam &getMachine(const PropTermCore &core); + friend lib::Term::Wam &getMachine(PropTerm &core); + friend const lib::Term::Wam &getMachine(const PropTerm &core); private: std::optional parsingError; lib::Term::Wam machine; }; -inline const lib::Term::Wam &getMachine(const PropTermCore::LeftType &p) { - return getMachine(p.state.getDerived()); +inline const lib::Term::Wam &getMachine(const PropTerm::Side &p) { + return getMachine(p.p.getDerived()); } -inline bool isValid(const PropTermCore::LeftType &p) { - return isValid(p.state.getDerived()); -} - -inline const lib::Term::Wam &getMachine(const PropTermCore::RightType &p) { - return getMachine(p.state.getDerived()); -} - -inline bool isValid(const PropTermCore::RightType &p) { - return isValid(p.state.getDerived()); +inline bool isValid(const PropTerm::Side &p) { + return isValid(p.p.getDerived()); } } // namespace mod::lib::Rules diff --git a/libs/libmod/src/mod/lib/Rules/Real.cpp b/libs/libmod/src/mod/lib/Rules/Real.cpp index 10759b9..8323762 100644 --- a/libs/libmod/src/mod/lib/Rules/Real.cpp +++ b/libs/libmod/src/mod/lib/Rules/Real.cpp @@ -9,8 +9,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -23,48 +22,54 @@ namespace mod::lib::Rules { BOOST_CONCEPT_ASSERT((LabelledGraphConcept)); -BOOST_CONCEPT_ASSERT((LabelledGraphConcept)); -BOOST_CONCEPT_ASSERT((LabelledGraphConcept)); +BOOST_CONCEPT_ASSERT((LabelledGraphConcept)); //------------------------------------------------------------------------------ // Main class //------------------------------------------------------------------------------ -void printEdge(Edge e, const GraphType &core, const PropStringCore &labelState, std::ostream &s) { - Vertex vSrc = source(e, core), vTar = target(e, core); - s << "\t"; - labelState.print(s, vSrc); - s << " -- "; - labelState.print(s, e); - s << " -- "; - labelState.print(s, vTar); -} - -bool Real::sanityChecks(const GraphType &core, const PropStringCore &labelState, std::ostream &s) { +void printEdge(lib::DPO::CombinedRule::CombinedEdge eCG, const lib::DPO::CombinedRule::CombinedGraphType &gCombined, + const PropString &pString, std::ostream &s) { + const auto vSrc = source(eCG, gCombined); + const auto vTar = target(eCG, gCombined); + s << " vSrc (" << vSrc << "): "; + pString.print(s, vSrc); + s << "\n e: "; + pString.print(s, eCG); + s << "\n vTar (" << vTar << "): "; + pString.print(s, vTar); + s << std::endl; +} + +bool Real::sanityChecks(const lib::DPO::CombinedRule::CombinedGraphType &gCombined, + const PropString &pString, std::ostream &s) { // hmm, boost::edge_range is not supported for vecS, right? so we count in a rather stupid way - for(Edge e : asRange(edges(core))) { - Vertex vSrc = source(e, core), vTar = target(e, core); - auto eContext = core[e].membership, - srcContext = core[vSrc].membership, - tarContext = core[vTar].membership; + for(const auto eCG: asRange(edges(gCombined))) { + const auto vSrc = source(eCG, gCombined); + const auto vTar = target(eCG, gCombined); + const auto eContext = gCombined[eCG].membership, + srcContext = gCombined[vSrc].membership, + tarContext = gCombined[vTar].membership; // check danglingness - if(eContext != srcContext && srcContext != Membership::Context) { + if(eContext != srcContext && srcContext != Membership::K) { s << "Rule::sanityCheck\tdangling edge at source: " << std::endl; - printEdge(e, core, labelState, s); + printEdge(eCG, gCombined, pString, s); return false; } - if(eContext != tarContext && tarContext != Membership::Context) { + if(eContext != tarContext && tarContext != Membership::K) { s << "Rule::sanityCheck\tdangling edge at target: " << std::endl; - printEdge(e, core, labelState, s); + printEdge(eCG, gCombined, pString, s); return false; } // check parallelness - unsigned int count = 0; - for(Edge eOut : asRange(out_edges(vSrc, core))) if(target(eOut, core) == vTar) count++; + int count = 0; + for(const auto eOut: asRange(out_edges(vSrc, gCombined))) + if(target(eOut, gCombined) == vTar) + ++count; if(count > 1) { s << "Rule::sanityCheck\tcan't handle parallel edges in lib::Rules::GraphType" << std::endl; - printEdge(e, core, labelState, s); + printEdge(eCG, gCombined, pString, s); return false; } } @@ -73,16 +78,17 @@ bool Real::sanityChecks(const GraphType &core, const PropStringCore &labelState, namespace { std::size_t nextRuleNum = 0; -} // namespace +} // namespace Real::Real(LabelledRule &&rule, std::optional labelType) : id(nextRuleNum++), name("r_{" + boost::lexical_cast(id) + "}"), labelType(labelType), dpoRule(std::move(rule)) { - if(dpoRule.numLeftComponents == std::numeric_limits::max()) dpoRule.initComponents(); + if(get_num_connected_components(get_labelled_left(dpoRule)) == -1) + dpoRule.initComponents(); // only one of propString and propTerm should be defined assert(this->dpoRule.pString || this->dpoRule.pTerm); assert(!this->dpoRule.pString || !this->dpoRule.pTerm); - if(!sanityChecks(getGraph(), getStringState(), std::cout)) { + if(!sanityChecks(getDPORule().getRule().getCombinedGraph(), get_string(getDPORule()), std::cout)) { std::cout << "Rule::sanityCheck\tfailed in rule '" << getName() << "'" << std::endl; MOD_ABORT; } @@ -127,13 +133,13 @@ const lib::Rules::GraphType &Real::getGraph() const { return get_graph(dpoRule); } -DepictionDataCore &Real::getDepictionData() { - if(!depictionData) depictionData.reset(new DepictionDataCore(getDPORule())); +Write::DepictionData &Real::getDepictionData() { + if(!depictionData) depictionData.reset(new Write::DepictionData(getDPORule())); return *depictionData; } -const DepictionDataCore &Real::getDepictionData() const { - if(!depictionData) depictionData.reset(new DepictionDataCore(getDPORule())); +const Write::DepictionData &Real::getDepictionData() const { + if(!depictionData) depictionData.reset(new Write::DepictionData(getDPORule())); return *depictionData; } @@ -143,40 +149,16 @@ bool Real::isChemical() const { } bool Real::isOnlySide(Membership membership) const { - const GraphType &core = getGraph(); - for(Vertex v : asRange(vertices(core))) - if(core[v].membership != membership) return false; - for(Edge e : asRange(edges(core))) - if(core[e].membership != membership) return false; + const auto &gCombined = getDPORule().getRule().getCombinedGraph(); + for(const auto v: asRange(vertices(gCombined))) + if(gCombined[v].membership != membership) return false; + for(const auto e: asRange(edges(gCombined))) + if(gCombined[e].membership != membership) return false; return true; } bool Real::isOnlyRightSide() const { - return isOnlySide(Membership::Right); -} - -const PropStringCore &Real::getStringState() const { - assert(dpoRule.pString || dpoRule.pTerm); - if(!dpoRule.pString) { - dpoRule.pString.reset(new PropStringCore(get_graph(dpoRule), - dpoRule.leftMatchConstraints, dpoRule.rightMatchConstraints, - getTermState(), lib::Term::getStrings())); - } - return *dpoRule.pString; -} - -const PropTermCore &Real::getTermState() const { - assert(dpoRule.pString || dpoRule.pTerm); - if(!dpoRule.pTerm) { - dpoRule.pTerm.reset(new PropTermCore(get_graph(dpoRule), - dpoRule.leftMatchConstraints, dpoRule.rightMatchConstraints, - getStringState(), lib::Term::getStrings())); - } - return *dpoRule.pTerm; -} - -const PropMoleculeCore &Real::getMoleculeState() const { - return get_molecule(getDPORule()); + return isOnlySide(Membership::R); } namespace { diff --git a/libs/libmod/src/mod/lib/Rules/Real.hpp b/libs/libmod/src/mod/lib/Rules/Real.hpp index 9652db2..39f3265 100644 --- a/libs/libmod/src/mod/lib/Rules/Real.hpp +++ b/libs/libmod/src/mod/lib/Rules/Real.hpp @@ -17,14 +17,15 @@ struct PropString; struct Single; } // namespace mod::lib::Graph namespace mod::lib::Rules { -struct PropStringCore; -struct PropMoleculeCore; -struct DepictionDataCore; - -using DPOProjection = LabelledRule::LabelledLeftType::GraphType; +struct PropString; +struct PropMolecule; +namespace Write { +struct DepictionData; +} // namespace Write struct Real { - static bool sanityChecks(const GraphType &core, const PropStringCore &labelState, std::ostream &s); + static bool sanityChecks(const lib::DPO::CombinedRule::CombinedGraphType &gCombined, + const PropString &pString, std::ostream &s); public: Real(LabelledRule &&rule, std::optional labelType); ~Real(); @@ -35,18 +36,14 @@ struct Real { void setName(std::string name); std::optional getLabelType() const; const LabelledRule &getDPORule() const; -public: // shorthands +public: // shorthands, deprecated const GraphType &getGraph() const; - DepictionDataCore &getDepictionData(); // TODO: should not be available as non-const + Write::DepictionData &getDepictionData(); // TODO: should not be available as non-const public: - const DepictionDataCore &getDepictionData() const; + const Write::DepictionData &getDepictionData() const; bool isChemical() const; bool isOnlySide(Membership membership) const; bool isOnlyRightSide() const; // shortcut of above -public: // deprecated - const PropStringCore &getStringState() const; - const PropTermCore &getTermState() const; - const PropMoleculeCore &getMoleculeState() const; public: static std::size_t isomorphism(const Real &rDom, const Real &rCodom, @@ -66,7 +63,7 @@ struct Real { const std::optional labelType; private: LabelledRule dpoRule; - mutable std::unique_ptr depictionData; + mutable std::unique_ptr depictionData; }; struct LessById { @@ -85,6 +82,9 @@ struct IsomorphismPredicate { return 1 == Real::isomorphism(*rDom, *rCodom, 1, settings); } + bool operator()(const std::unique_ptr &rDom, const std::unique_ptr &rCodom) const { + return 1 == Real::isomorphism(*rDom, *rCodom, 1, settings); + } private: LabelSettings settings; }; @@ -96,11 +96,11 @@ inline detail::IsomorphismPredicate makeIsomorphismPredicate(LabelType labelType } struct MembershipPredWrapper { - template - auto operator()(const OuterGraph &gDomain, const OuterGraph &gCodomain, Pred pred) const { + template + auto operator()(const LabelledRule &gLabDomain, const LabelledRule &gLabCodomain, Pred pred) const { return jla_boost::GraphMorphism::makePropertyPredicateEq( - makeMembershipPropertyMap(get_graph(gDomain)), - makeMembershipPropertyMap(get_graph(gCodomain)), pred); + gLabDomain.getRule().makeMembershipPropertyMap(), + gLabCodomain.getRule().makeMembershipPropertyMap(), pred); } }; diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/Any.cpp b/libs/libmod/src/mod/lib/Stereo/Configuration/Any.cpp index 9e3502f..af87422 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/Any.cpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/Any.cpp @@ -2,9 +2,7 @@ #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { Any::Any(const GeometryGraph &g, const EmbeddingEdge *b, const EmbeddingEdge *e) : DynamicDegree(g.any, b, e) { } @@ -35,6 +33,4 @@ std::pair Any::asPrettyStringImpl(std::function -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { struct Any final : DynamicDegree { Any(const GeometryGraph &g, const EmbeddingEdge *b, const EmbeddingEdge *e); @@ -22,8 +20,6 @@ struct Any final : DynamicDegree { virtual std::pair asPrettyStringImpl(std::function getNeighbourId) const override; }; -} // namespace Stereo -} // namespace lib -} // namespace mod -#endif /* MOD_LIB_STEREO_CONFIGURATION_ANY_H */ +} // namespace mod::lib::Stereo +#endif // MOD_LIB_STEREO_CONFIGURATION_ANY_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.cpp b/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.cpp index 4e7c814..97026b9 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.cpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.cpp @@ -2,9 +2,7 @@ #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { // Fixation //------------------------------------------------------------------------------ @@ -133,6 +131,4 @@ DynamicDegree::DynamicDegree(GeometryGraph::Vertex vGeometry, const EmbeddingEdg std::copy(b, e, std::back_inserter(edges)); } -} // namespace Stereo -} // namespace lib -} // namespace mod \ No newline at end of file +} // namespace mod::lib::Stereo \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.hpp b/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.hpp index 9f18112..0f18c85 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.hpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/Configuration.hpp @@ -1,21 +1,15 @@ -#ifndef MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_H -#define MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_H +#ifndef MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_HPP +#define MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_HPP #include #include #include -namespace mod { -namespace lib { -namespace IO { -namespace Graph { -namespace Write { +namespace mod::lib::IO::Graph::Write { enum struct EdgeFake3DType; -} // namespace Write -} // namespace Graph -} // namespace IO -namespace Stereo { +} // namespace mod::lib::IO::Graph::Write +namespace mod::lib::Stereo { struct Fixation { explicit Fixation(bool f); // simple @@ -30,11 +24,9 @@ struct Fixation { static Fixation simpleFixed(); }; -class Configuration { +struct Configuration { Configuration(const Configuration&) = delete; - Configuration(Configuration&&) = delete; Configuration &operator=(const Configuration&) = delete; - Configuration &operator=(Configuration&&) = delete; protected: explicit Configuration(GeometryGraph::Vertex vGeometry, const EmbeddingEdge *first, const EmbeddingEdge *last); public: @@ -56,24 +48,21 @@ class Configuration { } public: // checking // pre: dynamic type of this and other is the same - virtual bool localPredIso(const Configuration &other) const { return true; } - // pre: dynamic type of this and other is the same + // pre: dynamic type of this and other is the same virtual bool localPredSpec(const Configuration &other) const { return true; } // TODO: this should be a kind of vtable constant - virtual bool morphismStaticOk() const { return true; } // E.g., influenced by fixedness. (TODO: only fixedness? then we could maybe pull it into the base class) - virtual bool morphismDynamicOk() const { return true; } @@ -106,7 +95,6 @@ struct DynamicDegree : Configuration { protected: DynamicDegree(GeometryGraph::Vertex vGeometry, const EmbeddingEdge *b, const EmbeddingEdge *e); public: - virtual const EmbeddingEdge *begin() const override final { return edges.data(); } @@ -121,7 +109,6 @@ struct DynamicDegree : Configuration { template struct StaticDegree : Configuration { protected: - StaticDegree(GeometryGraph::Vertex vGeometry, const std::array &edges) : Configuration(vGeometry, edges.begin(), edges.end()), edges(edges) { } public: @@ -137,8 +124,6 @@ struct StaticDegree : Configuration { std::array edges; }; -} // namespace Stereo -} // namespace lib -} // namespace mod +} // namespace mod::lib::Stereo -#endif /* MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_H */ \ No newline at end of file +#endif // MOD_LIB_STEREO_CONFIGURATION_CONFIGURATION_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/Linear.cpp b/libs/libmod/src/mod/lib/Stereo/Configuration/Linear.cpp index 0129d24..b8101ef 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/Linear.cpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/Linear.cpp @@ -2,9 +2,7 @@ #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { Linear::Linear(const GeometryGraph &g, const std::array &edges) : StaticDegree<2>(g.linear, edges) { } @@ -35,6 +33,4 @@ std::pair Linear::asPrettyStringImpl(std::function -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { struct Linear final : StaticDegree<2> { Linear(const GeometryGraph &g, const std::array &edges); @@ -22,8 +20,6 @@ struct Linear final : StaticDegree<2> { virtual std::pair asPrettyStringImpl(std::function getNeighbourId) const override; }; -} // namespace Stereo -} // namespace lib -} // namespace mod +} // namespace mod::lib::Stereo -#endif /* MOD_LIB_STEREO_CONFIGURATION_LINEAR_H */ \ No newline at end of file +#endif // MOD_LIB_STEREO_CONFIGURATION_LINEAR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/Tetrahedral.cpp b/libs/libmod/src/mod/lib/Stereo/Configuration/Tetrahedral.cpp index 3067b76..fcb5f97 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/Tetrahedral.cpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/Tetrahedral.cpp @@ -2,9 +2,7 @@ #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { Tetrahedral::Tetrahedral(const GeometryGraph &g, const std::array &edges_, bool fixed) : StaticDegree<4>(g.tetrahedral, edges_), fixed(fixed) { @@ -191,6 +189,4 @@ std::pair Tetrahedral::asPrettyStringImpl(std::function -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { struct Tetrahedral final : StaticDegree<4> { Tetrahedral(const GeometryGraph &g, const std::array &edges, bool fixed); @@ -30,8 +28,6 @@ struct Tetrahedral final : StaticDegree<4> { bool fixed; }; -} // namespace Stereo -} // namespace lib -} // namespace mod +} // namespace mod::lib::Stereo -#endif /* MOD_LIB_STEREO_CONFIGURATION_TETRAHEDRAL_H */ \ No newline at end of file +#endif // MOD_LIB_STEREO_CONFIGURATION_TETRAHEDRAL_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/Configuration/TrigonalPlanar.cpp b/libs/libmod/src/mod/lib/Stereo/Configuration/TrigonalPlanar.cpp index 1b53b99..20a453d 100644 --- a/libs/libmod/src/mod/lib/Stereo/Configuration/TrigonalPlanar.cpp +++ b/libs/libmod/src/mod/lib/Stereo/Configuration/TrigonalPlanar.cpp @@ -2,9 +2,7 @@ #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { TrigonalPlanar::TrigonalPlanar(const GeometryGraph &g, const std::array &edges, bool fixed) : StaticDegree<3>(g.trigonalPlanar, edges), fixed(fixed) { } @@ -68,6 +66,4 @@ std::pair TrigonalPlanar::asPrettyStringImpl(std::function -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { struct TrigonalPlanar final : StaticDegree<3> { TrigonalPlanar(const GeometryGraph &g, const std::array &edges, bool fixed); @@ -29,9 +27,6 @@ struct TrigonalPlanar final : StaticDegree<3> { bool fixed; }; -} // namespace Stereo -} // namespace lib -} // namespace mod - -#endif /* MOD_LIB_STEREO_CONFIGURATION_TRIGONAL_PLANAR_H */ +} // namespace mod::lib::Stereo +#endif // MOD_LIB_STEREO_CONFIGURATION_TRIGONAL_PLANAR_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.cpp b/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.cpp index d01561a..2a49c96 100644 --- a/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.cpp +++ b/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.cpp @@ -1,14 +1,10 @@ #include "EmbeddingEdge.hpp" -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { EmbeddingEdge::EmbeddingEdge(std::size_t offset, Type type, EdgeCategory cat) -: offset(offset), type(type), cat(cat) { + : offset(offset), type(type), cat(cat) { if(type != Type::Edge) assert(cat == EdgeCategory::Single); } -} // namespace Stereo -} // namespace lib -} // namespace mod \ No newline at end of file +} // namespace mod::lib::Stereo \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.hpp b/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.hpp index 807bc5e..1b48e6c 100644 --- a/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.hpp +++ b/libs/libmod/src/mod/lib/Stereo/EmbeddingEdge.hpp @@ -1,16 +1,13 @@ -#ifndef MOD_LIB_STEREO_EMBEDDINGEDGE_H -#define MOD_LIB_STEREO_EMBEDDINGEDGE_H +#ifndef MOD_LIB_STEREO_EMBEDDINGEDGE_HPP +#define MOD_LIB_STEREO_EMBEDDINGEDGE_HPP #include #include -namespace mod { -namespace lib { -namespace Stereo { +namespace mod::lib::Stereo { struct EmbeddingEdge { - enum class Type { Edge, LonePair, Radical }; @@ -32,8 +29,6 @@ struct EmbeddingEdge { EdgeCategory cat; }; -} // namespace Stereo -} // namespace lib -} // namespace mod +} // namespace mod::lib::Stereo -#endif /* MOD_LIB_STEREO_EMBEDDINGEDGE_H */ \ No newline at end of file +#endif // MOD_LIB_STEREO_EMBEDDINGEDGE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/IO/StereoRead.cpp b/libs/libmod/src/mod/lib/Stereo/IO/Read.cpp similarity index 54% rename from libs/libmod/src/mod/lib/IO/StereoRead.cpp rename to libs/libmod/src/mod/lib/Stereo/IO/Read.cpp index b5feae2..aeba5fa 100644 --- a/libs/libmod/src/mod/lib/IO/StereoRead.cpp +++ b/libs/libmod/src/mod/lib/Stereo/IO/Read.cpp @@ -1,8 +1,9 @@ -#include "Stereo.hpp" +#include "Read.hpp" -#include +#include #include + #include #include #include @@ -12,47 +13,46 @@ #include #include #include +#include #include -namespace mod::lib::IO::Stereo::Read { +namespace mod::lib::Stereo::Read { + namespace { namespace parser { const auto fixation = x3::rule{"fixation"} /* */ = x3::lit('!') >> x3::attr(true); const auto virtualEdge = x3::rule{"virtualEdge"} -/* */ = x3::char_("er"); // lone-pair or radical -const auto edge = x3::rule{"edge"} -/* */ = x3::int_ | virtualEdge; -const auto edges = x3::rule >{"edges"} -/* */ = x3::lit('[') > -(edge % ',') >> ']'; +/* */ = x3::char_("a-zA-Z"); +const auto neighbour = x3::rule{"neighbour"} +/* */ = x3::int_ | virtualEdge; +const auto neighboursInner = x3::rule>{ + "comma separated neighbour list"} +/* */ = -(neighbour % ',') >> ']'; +const auto neighbours = x3::lit('[') > neighboursInner; const auto geometry = x3::rule{"geometry"} /* */ = x3::lexeme[x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9")]; const auto start = x3::rule{"stereoEmbedding"} -/* */ = -geometry >> -edges >> -fixation >> x3::eoi; +/* */ = !x3::eoi >> -geometry >> -neighbours >> -fixation >> x3::eoi; } // namespace parser } // namesapce lib::IO::Result parseEmbedding(const std::string &str) { - using Iter = std::string::const_iterator; - Iter start = str.begin(); - ParsedEmbedding embedding; - bool res = x3::phrase_parse(start, str.end(), parser::start, x3::space, embedding); - if(!res || start != str.end()) { - std::string msg = "Error in stereo embedding at column "; - msg += std::to_string(start - str.begin()); - msg += "."; - if(start != str.end()) msg += " Expected end of input."; - return lib::IO::Result::Error(std::move(msg)); + try { + ParsedEmbedding embedding; + lib::IO::parse(str.begin(), str.end(), parser::start, embedding, false, x3::ascii::space); + return embedding; + } catch(const lib::IO::ParsingError &e) { + return lib::IO::Result<>::Error(e.msg); } - return embedding; } -} // namespace mod::lib::IO::Stereo::Read +} // namespace mod::lib::Stereo::Read -BOOST_FUSION_ADAPT_STRUCT(mod::lib::IO::Stereo::Read::ParsedEmbedding, +BOOST_FUSION_ADAPT_STRUCT(mod::lib::Stereo::Read::ParsedEmbedding, (std::optional, geometry) - (std::optional >, edges) + (std::optional>, edges) (std::optional, fixation) ) \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/IO/Read.hpp b/libs/libmod/src/mod/lib/Stereo/IO/Read.hpp new file mode 100644 index 0000000..899dbb4 --- /dev/null +++ b/libs/libmod/src/mod/lib/Stereo/IO/Read.hpp @@ -0,0 +1,25 @@ +#ifndef MOD_LIB_STEREO_READ_HPP +#define MOD_LIB_STEREO_READ_HPP + +#include + +#include +#include +#include +#include + +namespace mod::lib::Stereo::Read { + +using ParsedEmbeddingEdge = std::variant; + +struct ParsedEmbedding { + std::optional geometry; + std::optional> edges; + std::optional fixation; +}; + +IO::Result parseEmbedding(const std::string &str); + +} // namespace mod::lib::Stereo::Read + +#endif // MOD_LIB_STEREO_READ_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/IO/Write.cpp b/libs/libmod/src/mod/lib/Stereo/IO/Write.cpp new file mode 100644 index 0000000..ff62f51 --- /dev/null +++ b/libs/libmod/src/mod/lib/Stereo/IO/Write.cpp @@ -0,0 +1,133 @@ +#include "Write.hpp" + +#include +#include +#include +#include + +#include + +namespace mod::lib::Stereo::Write { + +void summary(const GeometryGraph &graph) { + const auto &g = graph.getGraph(); + const auto n = num_vertices(g); + std::string fileDot = [&]() { + post::FileHandle s(IO::makeUniqueFilePrefix() + "geometryGraph.dot"); + s << "digraph g {\nrankdir=LR;\n"; + for(auto v : asRange(vertices(g))) { + s << get(boost::vertex_index_t(), g, v) << " [ label=\"" << g[v].name << "\" ];\n"; + } + for(auto e : asRange(edges(g))) { + auto vSrc = source(e, g); + auto vTar = target(e, g); + s << get(boost::vertex_index_t(), g, vSrc) << " -> " << get(boost::vertex_index_t(), g, vTar); + s << " [];\n"; + } + for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { + auto &cv = graph.chemValids[i]; + s << (i + n) << " [ label=\"" << lib::Chem::symbolFromAtomId(cv.atomId); + if(cv.charge != 0) { + if(cv.charge > 0) s << "+"; + else s << "-"; + if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); + } + if(cv.catCount.sum() > 0) s << ", " << cv.catCount; + if(cv.lonePair > 0) s << ", e = " << cv.lonePair; + s << "\" ];\n"; + s << get(boost::vertex_index_t(), g, cv.geometry) << " -> " << (i + n) << " [];\n"; + } + s << "}\n"; + std::string f = s; + return std::string(f.begin(), f.end() - 4); + }(); + IO::post() << "coordsFromGV dgHyperDot \"" << fileDot << "\"\n"; + std::string fileCoords = fileDot + "_coord"; + std::string fileFig = [&]() { + post::FileHandle s(IO::makeUniqueFilePrefix() + "geometryGraph.tex"); + s << "\\begin{tikzpicture}[scale=\\modDGHyperScale]\n"; + s << "\\input{" << fileCoords << ".tex}\n"; + for(auto v : asRange(vertices(g))) { + auto vId = get(boost::vertex_index_t(), g, v); + s << "\\node[draw] (v-" << vId << ") at (v-coord-" << vId << "){"; + s << g[v].name; + s << "};\n"; + } + for(auto e : asRange(edges(g))) { + auto vSrc = source(e, g); + auto vTar = target(e, g); + s << "\\path[draw, ->, >=stealth] (v-" << get(boost::vertex_index_t(), g, vSrc) + << ") to (v-" << get(boost::vertex_index_t(), g, vTar) + << ");\n"; + } + for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { + auto &cv = graph.chemValids[i]; + s << "\\node[draw, ellipse] (v-" << (i + n) << ") at (v-coord-" << (i + n) << "){"; + s << lib::Chem::symbolFromAtomId(cv.atomId); + if(cv.charge != 0) { + s << "$^{"; + if(cv.charge > 0) s << "+"; + else s << "-"; + if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); + s << "}$"; + } + bool hasMore = cv.catCount.sum() > 0 || cv.lonePair > 0; + if(hasMore) s << "$"; + if(cv.catCount.sum() > 0) s << ", " << cv.catCount; + if(cv.lonePair > 0) s << ", e = " << cv.lonePair; + if(hasMore) s << "$"; + s << "};\n"; + s << "\\path[draw] (v-" << get(boost::vertex_index_t(), g, cv.geometry) << ") to (v-" << (i + n) << ");\n"; + } + s << "\\end{tikzpicture}"; + std::string f = s; + return std::string(f.begin(), f.end() - 4); + }(); + std::string fileTex = [&]() { + post::FileHandle s(IO::makeUniqueFilePrefix() + "geometryGraphSummary.tex"); + s + << "\\begin{center}\n" + << "\\includegraphics{" << fileFig << "}\n\n" + << "File: \\texttt{" << IO::escapeForLatex(fileFig) << "}\n" + << "\\end{center}\n" + << "\n" + << "\\section{Stereo, Chemically Valid Configurations}\n" + << "\\begin{center}\n" + << "\\begin{longtable}{@{}lllllll@{}}\n" + << "\\toprule\n" + << "Atom & S & D & T & A & LP & Geometry\\\\\n" + << "\\midrule\n"; + for(std::size_t i = 0; i < graph.chemValids.size(); ++i) { + auto &cv = graph.chemValids[i]; + s << lib::Chem::symbolFromAtomId(cv.atomId); + if(cv.charge != 0) { + s << "$^{"; + if(cv.charge > 0) s << "+"; + else s << "-"; + if(std::abs(cv.charge) != 1) s << std::abs(cv.charge); + s << "}$"; + } + for(auto cat :{EdgeCategory::Single, EdgeCategory::Double, + EdgeCategory::Triple, EdgeCategory::Aromatic}) { + s << " & "; + if(auto count = cv.catCount[cat]) { + s << int(count); + } + } + s << " & "; + if(cv.lonePair > 0) s << cv.lonePair; + s << " & " << g[cv.geometry].name << " \\\\\n"; + } + s + << "\\bottomrule\n" + << "\\end{longtable}\n" + << "\\end{center}\n"; + return std::string(s); + }(); + + IO::post() << "compileTikz \"" << fileFig << "\" \"" << fileCoords << "\"" << std::endl; + IO::post() << "summarySection \"Stereo, Geometry Graph\"" << std::endl; + IO::post() << "summaryInput \"" << fileTex << "\"" << std::endl; +} + +} // namespace mod::lib::Stereo::Write \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/IO/Write.hpp b/libs/libmod/src/mod/lib/Stereo/IO/Write.hpp new file mode 100644 index 0000000..87b608e --- /dev/null +++ b/libs/libmod/src/mod/lib/Stereo/IO/Write.hpp @@ -0,0 +1,13 @@ +#ifndef MOD_LIB_STEREO_WRITE_HPP +#define MOD_LIB_STEREO_WRITE_HPP + +namespace mod::lib::Stereo { +struct GeometryGraph; +} // namespace mod::lib::Stereo +namespace mod::lib::Stereo::Write { + +void summary(const GeometryGraph &g); + +} // namespace mod::lib::Stereo::Write + +#endif // MOD_LIB_STEREO_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.cpp b/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.cpp new file mode 100644 index 0000000..42d8f77 --- /dev/null +++ b/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.cpp @@ -0,0 +1,109 @@ +#include "WriteConfiguration.hpp" + +#include +#include +#include +#include + +#include + +namespace mod::lib::Stereo::Write { + +} // namespace mod::lib::Stereo::Write +namespace mod::lib::Stereo { +namespace { + +#define MOD_anyPrintCoords() \ +Configuration::printCoords(s, vIds); \ +if(vIds.size() == 1) return; \ +auto centerId = vIds.back(); \ +double angle = 360.0 / (vIds.size() - 1); \ +for(std::size_t i = 0; i < vIds.size() - 1; i++) \ +printSateliteCoord(s, centerId, angle * i + 90, vIds[i]); + +void printSateliteCoord(std::ostream &s, std::size_t centerId, double angle, std::size_t id) { + s << "\\coordinate[overlay, at=($(\\modIdPrefix v-coord-" << centerId << ")+(" << angle << ":1)"; + s << "$)] (\\modIdPrefix v-coord-" << id << ") {};\n"; +} + +} // namespace + +// Configuration +//------------------------------------------------------------------------------ + +IO::Graph::Write::EdgeFake3DType Configuration::getEdgeDepiction(std::size_t i) const { + return IO::Graph::Write::EdgeFake3DType::None; +} + +void Configuration::printCoords(std::ostream &s, const std::vector &vIds) const { + const std::string &name = getGeometryGraph().getGraph()[getGeometryVertex()].name; + s << "\\coordinate[overlay] (\\modIdPrefix v-coord-" << vIds.back() << ") {};\n"; + s << "\\node[at=($(\\modIdPrefix v-coord-" << vIds.back() << ")+(-90:1)+(-90:12pt)$)] {" << name << "};\n"; +} + +std::string Configuration::getEdgeAnnotation(std::size_t i) const { + return ""; +} + +// Any +//------------------------------------------------------------------------------ + +void Any::printCoords(std::ostream &s, const std::vector &vIds) const { + MOD_anyPrintCoords(); +} + +// Linear +//------------------------------------------------------------------------------ + +void Linear::printCoords(std::ostream &s, const std::vector &vIds) const { + Configuration::printCoords(s, vIds); + printSateliteCoord(s, vIds.back(), 180, vIds[0]); + printSateliteCoord(s, vIds.back(), 0, vIds[1]); +} + +// TrigonalPlanar +//------------------------------------------------------------------------------ + +void TrigonalPlanar::printCoords(std::ostream &s, const std::vector &vIds) const { + Configuration::printCoords(s, vIds); + printSateliteCoord(s, vIds.back(), 180, vIds[0]); + printSateliteCoord(s, vIds.back(), 300, vIds[1]); + printSateliteCoord(s, vIds.back(), 60, vIds[2]); +} + +std::string TrigonalPlanar::getEdgeAnnotation(std::size_t i) const { + if(fixed) return ""; + else return "?"; +} + +// Tetrahedral +//------------------------------------------------------------------------------ + +IO::Graph::Write::EdgeFake3DType Tetrahedral::getEdgeDepiction(std::size_t i) const { + switch(i) { + case 0: + case 1: + return lib::IO::Graph::Write::EdgeFake3DType::None; + case 2: + return lib::IO::Graph::Write::EdgeFake3DType::WedgeSL; + case 3: + return lib::IO::Graph::Write::EdgeFake3DType::HashSL; + default: + __builtin_unreachable(); + } +} + +void Tetrahedral::printCoords(std::ostream &s, const std::vector &vIds) const { + Configuration::printCoords(s, vIds); + printSateliteCoord(s, vIds.back(), 90, vIds[0]); + printSateliteCoord(s, vIds.back(), 210, vIds[1]); + printSateliteCoord(s, vIds.back(), -60, vIds[2]); + printSateliteCoord(s, vIds.back(), -10, vIds[3]); +} + +std::string Tetrahedral::getEdgeAnnotation(std::size_t i) const { + if(fixed) return ""; + else return "?"; +} + +} // namespace mod::lib::Stereo \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.hpp b/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.hpp new file mode 100644 index 0000000..6bce519 --- /dev/null +++ b/libs/libmod/src/mod/lib/Stereo/IO/WriteConfiguration.hpp @@ -0,0 +1,305 @@ +#ifndef MOD_LIG_STEREO_IO_WRITECONFIGURATION_HPP +#define MOD_LIG_STEREO_IO_WRITECONFIGURATION_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace mod::lib::Stereo::Write { + +template +std::string coords(const GraphInner &gStereo, const Configuration &conf, const std::string &name, + std::map::vertex_descriptor, int> &vMap) { + post::FileHandle s(IO::makeUniqueFilePrefix() + name + "_coord.tex"); + std::vector vIds(num_vertices(gStereo)); + using SVertex = typename boost::graph_traits::vertex_descriptor; + for(SVertex vStereo: asRange(vertices(gStereo))) { + auto vId = get(boost::vertex_index_t(), gStereo, vStereo); + auto iter = vMap.find(vStereo); + assert(iter != end(vMap)); + if(iter->second == -1) vIds[vIds.size() - 1] = vId; + else vIds[iter->second] = vId; + } + conf.printCoords(s, vIds); + return s; +} + +template +std::pair +tikz(const GraphPrint &g, typename boost::graph_traits::vertex_descriptor v, + const Configuration &conf, const std::string &name, const Depict &depict, + const IO::Graph::Write::Options &options, ShownId shownId) { + const bool printLonePairs = true; + using GVertex = typename boost::graph_traits::vertex_descriptor; + using GEdge = typename boost::graph_traits::edge_descriptor; + + using GraphStereo = jla_boost::EdgeIndexedAdjacencyList; + using SVertex = boost::graph_traits::vertex_descriptor; + using SEdge = boost::graph_traits::edge_descriptor; + + // we make a new graph with copies and then the extra lone pairs + GraphStereo gStereo; + std::map vMap; // the order id, though -1 => the center + SVertex vCenter = add_vertex(gStereo); + vMap[vCenter] = -1; + std::map, IO::Graph::Write::EdgeFake3DType> edgeDepiction; + for(std::size_t i = 0; i < conf.degree(); ++i) { + SVertex vStereo = add_vertex(gStereo); + vMap[vStereo] = i; + add_edge(vCenter, vStereo, gStereo); + auto edge3Dtype = conf.getEdgeDepiction(i); + edgeDepiction[std::make_pair(vCenter, vStereo)] = edge3Dtype; + edgeDepiction[std::make_pair(vStereo, vCenter)] = IO::Graph::Write::invertEdgeFake3DType(edge3Dtype); + } + + std::string coordFile = coords(gStereo, conf, name, vMap); + post::FileHandle s(IO::makeUniqueFilePrefix() + name + ".tex"); + + struct DepictorAndAdvOptions { + DepictorAndAdvOptions(const GraphPrint &gOuter, GVertex vOuterCenter, const GraphStereo &g, + const Depict &depict, bool printLonePairs, const std::map &vMap, + const Configuration &conf, + const std::map, IO::Graph::Write::EdgeFake3DType> &edgeDepiction, + ShownId shownId) + : gOuter(gOuter), vOuterCenter(vOuterCenter), + nullVertexOuter(boost::graph_traits::null_vertex()), gInner(g), + depict(depict), printLonePairs(printLonePairs), vMap(vMap), conf(conf), edgeDepiction(edgeDepiction), + shownId(shownId) {} + + GVertex getOuterVertexFromInnerVertex(SVertex vInner) const { + auto iter = vMap.find(vInner); + assert(iter != end(vMap)); + if(iter->second == -1) return vOuterCenter; + const auto &emb = conf.begin()[iter->second]; + if(emb.type != EmbeddingEdge::Type::Edge) return nullVertexOuter; + auto eOuter = emb.getEdge(vOuterCenter, gOuter); + return target(eOuter, gOuter); + } + + GEdge getOuterEdgeFromInnerEdge(SEdge eInner) const { + GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); + GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); + assert(vSrcOuter != nullVertexOuter); + assert(vTarOuter != nullVertexOuter); + auto p = edge(vSrcOuter, vTarOuter, gOuter); + assert(p.second); + return p.first; + } + + unsigned char getAtomId(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter == nullVertexOuter) return AtomIds::Invalid; + else return depict.getAtomId(vOuter); + } + + Isotope getIsotope(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter == nullVertexOuter) return Isotope(); + else return depict.getIsotope(vOuter); + } + + char getCharge(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter == nullVertexOuter) return 0; + else return depict.getCharge(vOuter); + } + + bool getRadical(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter == nullVertexOuter) return false; + else return depict.getRadical(vOuter); + } + + BondType getBondData(SEdge eInner) const { + GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); + GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); + if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return BondType::Invalid; + else return depict.getBondData(getOuterEdgeFromInnerEdge(eInner)); + } + + AtomData operator()(SVertex v) const { + GVertex vOuter = getOuterVertexFromInnerVertex(v); + if(vOuter == nullVertexOuter) return AtomData(); + else return depict(vOuter); + } + + BondType operator()(SEdge eInner) const { + GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); + GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); + if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return BondType::Invalid; + else return depict(getOuterEdgeFromInnerEdge(eInner)); + } + + std::string getVertexLabelNoIsotopeChargeRadical(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter != nullVertexOuter)return depict.getVertexLabelNoIsotopeChargeRadical(vOuter); + auto iter = vMap.find(vInner); + assert(iter != end(vMap)); + assert(iter->second != -1); + const auto &emb = conf.begin()[iter->second]; + switch(emb.type) { + case EmbeddingEdge::Type::Edge: + MOD_ABORT; + case EmbeddingEdge::Type::LonePair: + return "e"; + case EmbeddingEdge::Type::Radical: + return "r"; + } + MOD_ABORT; + } + + std::string getEdgeLabel(SEdge eInner) const { + GVertex vSrcOuter = getOuterVertexFromInnerVertex(source(eInner, gInner)); + GVertex vTarOuter = getOuterVertexFromInnerVertex(target(eInner, gInner)); + if(vSrcOuter == nullVertexOuter || vTarOuter == nullVertexOuter) return ""; + else return depict.getEdgeLabel(getOuterEdgeFromInnerEdge(eInner)); + } + + bool hasImportantStereo(SVertex vInner) const { + return true; + } + + bool getHasCoordinates() const { + return false; + } + + double getX(SVertex v, bool b) const { + return 0; + } + + double getY(SVertex v, bool b) const { + return 0; + } + + bool isVisible(SVertex v) const { + if(printLonePairs) return true; + else + MOD_ABORT; + return true; + } + + std::string getColour(SVertex) const { + return ""; + } + + std::string getColour(SEdge) const { + return ""; + } + + std::string getShownId(SVertex vInner) const { + GVertex vOuter = getOuterVertexFromInnerVertex(vInner); + if(vOuter == nullVertexOuter) MOD_ABORT; + else return boost::lexical_cast(shownId(gOuter, vOuter)); + } + + bool overwriteWithIndex(SVertex vInner) const { + auto vOuter = getOuterVertexFromInnerVertex(vInner); + return vOuter == nullVertexOuter; + } + + IO::Graph::Write::EdgeFake3DType getEdgeFake3DType(SEdge eInner, bool withHydrogen) const { + auto iter = edgeDepiction.find(std::make_pair(source(eInner, gInner), target(eInner, gInner))); + assert(iter != end(edgeDepiction)); + return iter->second; + } + + std::string getRawStereoString(SVertex vInner) const { + return ""; + } + + std::string getPrettyStereoString(SVertex vInner) const { + return ""; + } + + std::string getStereoString(SEdge eInner) const { + return ""; + } + + std::string getEdgeAnnotation(SEdge eInner) const { + SVertex vSrcInner = source(eInner, gInner), vTarInner = target(eInner, gInner); + auto iterSrc = vMap.find(vSrcInner), iterTar = vMap.find(vTarInner); + assert(iterSrc != end(vMap)); + assert(iterTar != end(vMap)); + assert(iterSrc->second == -1 || iterTar->second == -1); + bool swapped = false; + if(iterSrc->second != -1) { + std::swap(vSrcInner, vTarInner); + std::swap(iterSrc, iterTar); + swapped = true; + } + std::string res; + // if the edge category does not correspond to the bond type, then print it + const EmbeddingEdge &emb = conf.begin()[iterTar->second]; + if(emb.type == EmbeddingEdge::Type::Edge) { + auto eOuter = getOuterEdgeFromInnerEdge(eInner); + auto eCatFromBt = bondTypeToEdgeCategory(depict.getBondData(eOuter)); + if(eCatFromBt != emb.cat) { + res += " node[auto] {"; + res += boost::lexical_cast(emb.cat); + res += "}"; + } + } + + // print the offset and whatever the configuration wants + res += " node[auto, pos="; + if(swapped) res += "0.25"; + else res += "0.75"; + res += "] {{\\tiny "; + res += boost::lexical_cast(iterTar->second); + res += conf.getEdgeAnnotation(iterTar->second); + res += "}} "; + return res; + } + + bool disallowHydrogenCollapse(SVertex) const { + return false; + } + + std::string getOpts(SVertex v) const { + return std::string(); + } + public: + int getOutputId(SVertex vInner) const { + return get(boost::vertex_index_t(), gInner, vInner); + } + private: + const GraphPrint &gOuter; + GVertex vOuterCenter, nullVertexOuter; + const GraphStereo &gInner; + const Depict &depict; + bool printLonePairs; + const std::map &vMap; + const Configuration &conf; + const std::map, IO::Graph::Write::EdgeFake3DType> &edgeDepiction; + ShownId shownId; + } depictAndAdvOptions(g, v, gStereo, depict, printLonePairs, vMap, conf, edgeDepiction, shownId); + auto bonusWriter = [&](std::ostream &s) { + }; + lib::IO::Graph::Write::tikz(s, options, gStereo, depictAndAdvOptions, coordFile, depictAndAdvOptions, bonusWriter, + ""); + return std::pair(s, coordFile); +} + +template +std::string pdf(const Graph &g, typename boost::graph_traits::vertex_descriptor v, + const Configuration &conf, const std::string &name, const Depict &depict, + const lib::IO::Graph::Write::Options &options, ShownId shownId) { + const auto p = tikz(g, v, conf, name, depict, options, shownId); + std::string fileTikz = p.first, fileCoords = p.second; + std::string fileNoExt = fileTikz.substr(0, fileTikz.size() - 4); + std::string fileCoordsNoExt = fileCoords.substr(0, fileCoords.size() - 4); + IO::post() << "compileTikz \"" << fileNoExt << "\" \"" << fileCoordsNoExt << "\"" << std::endl; + return fileNoExt + ".pdf"; +} + +} // namespace mod::lib::Stereo::Write + +#endif // MOD_LIG_STEREO_IO_WRITECONFIGURATION_HPP diff --git a/libs/libmod/src/mod/lib/Stereo/Inference.hpp b/libs/libmod/src/mod/lib/Stereo/Inference.hpp index 82b02f7..e911c7d 100644 --- a/libs/libmod/src/mod/lib/Stereo/Inference.hpp +++ b/libs/libmod/src/mod/lib/Stereo/Inference.hpp @@ -15,49 +15,39 @@ #include namespace mod::lib::Stereo { -namespace detail { -template -struct makeInferenceHelper; - -struct InferenceBase { - struct VertexData { - GeometryGraph::Vertex vGeometry = GeometryGraph::nullGeometry(); - std::size_t nextAvailableVirtual; - std::vector edges; - bool explicitEmbedding = false; - Fixation fix = Fixation::free(); - public: - std::unique_ptr configuration; - }; - struct EdgeData { - EdgeCategorySubset valid = EdgeCategorySubset().all(); - EdgeCategory finalCategory; // will be set by finalize - }; +struct InferenceVertexData { + GeometryGraph::Vertex vGeometry = GeometryGraph::nullGeometry(); + int nextAvailableVirtual; + std::vector edges; + bool explicitEmbedding = false; + Fixation fix = Fixation::free(); +public: + std::unique_ptr configuration; }; -} // namespace detail +struct InferenceEdgeData { + EdgeCategorySubset valid = EdgeCategorySubset().all(); + EdgeCategory finalCategory; // will be set by finalize +}; template -struct Inference : private detail::InferenceBase { +struct Inference { using Vertex = typename boost::graph_traits::vertex_descriptor; using Edge = typename boost::graph_traits::edge_descriptor; public: - friend class detail::makeInferenceHelper; - Inference(const Graph &g, PropMolecule &&) = delete; Inference(const Graph &g, const PropMolecule &pMolecule, bool asPattern) : g(g), pMolecule(pMolecule), asPattern(asPattern), - hasFinalized(false), vertexData(num_vertices(g)), edgeData(num_edges(g)) { - for(Vertex v : asRange(vertices(g))) { + vertexData(num_vertices(g)), edgeData(num_edges(g)) { + for(const auto v: asRange(vertices(g))) vertexData[get(boost::vertex_index_t(), g, v)].nextAvailableVirtual = out_degree(v, g); - } } lib::IO::Result<> assignGeometry(Vertex v, GeometryGraph::Vertex vGeometry) { assert(vGeometry != GeometryGraph::nullGeometry()); - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; if(data.vGeometry != GeometryGraph::nullGeometry()) return lib::IO::Result<>::Error("Geometry already assigned."); @@ -66,7 +56,7 @@ struct Inference : private detail::InferenceBase { } lib::IO::Result<> assignEdgeCategory(Edge e, EdgeCategory cat) { - auto eId = get(boost::edge_index_t(), g, e); + const auto eId = get(boost::edge_index_t(), g, e); auto &data = edgeData[eId]; if(!data.valid(cat)) { return lib::IO::Result<>::Error( @@ -79,42 +69,42 @@ struct Inference : private detail::InferenceBase { } void initEmbedding(Vertex v) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; data.explicitEmbedding = true; } void addEdge(Vertex v, Edge eOut) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; data.explicitEmbedding = true; - auto outRange = out_edges(v, g); - auto iter = std::find(outRange.first, outRange.second, eOut); + const auto outRange = out_edges(v, g); + const auto iter = std::find(outRange.first, outRange.second, eOut); assert(iter != outRange.second); // Add the edge with undefined category. It will be assigned properly in finalization. data.edges.emplace_back(std::distance(outRange.first, iter), EmbeddingEdge::Type::Edge, EdgeCategory::Undefined); } void addLonePair(Vertex v) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; data.explicitEmbedding = true; - auto offset = data.nextAvailableVirtual; + const auto offset = data.nextAvailableVirtual; ++data.nextAvailableVirtual; data.edges.emplace_back(offset, EmbeddingEdge::Type::LonePair, EdgeCategory::Single); } void addRadical(Vertex v) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; data.explicitEmbedding = true; - auto offset = data.nextAvailableVirtual; + const auto offset = data.nextAvailableVirtual; ++data.nextAvailableVirtual; data.edges.emplace_back(offset, EmbeddingEdge::Type::Radical, EdgeCategory::Single); } void fixSimpleGeometry(Vertex v) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; assert(data.explicitEmbedding); // the embedding should have been initialised at this point data.fix = Fixation::simpleFixed(); @@ -127,8 +117,8 @@ struct Inference : private detail::InferenceBase { assert(!hasFinalized); // Finalize edge categories. //-------------------------------------------------------------------------- - for(Edge e : asRange(edges(g))) { - auto eId = get(boost::edge_index_t(), g, e); + for(const auto e: asRange(edges(g))) { + const auto eId = get(boost::edge_index_t(), g, e); auto &data = edgeData[eId]; const EdgeCategorySubset &eCat = data.valid; if(eCat.count() == 0) MOD_ABORT; // should have been handled earlier @@ -136,7 +126,7 @@ struct Inference : private detail::InferenceBase { data.finalCategory = eCat.selectFirst(); } else { // assume chemical - auto eCatChem = bondTypeToEdgeCategory(pMolecule[e]); + const auto eCatChem = bondTypeToEdgeCategory(pMolecule[e]); if(!eCat(eCatChem)) { MOD_ABORT; // TODO: implement } @@ -146,25 +136,24 @@ struct Inference : private detail::InferenceBase { // Assign edge categories to embedding edges. //-------------------------------------------------------------------------- - for(const Vertex v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { const auto vId = get(boost::vertex_index_t(), g, v); - auto &data = vertexData[vId]; - for(auto &emb : data.edges) { + for(auto &emb: vertexData[vId].edges) { if(emb.type != EmbeddingEdge::Type::Edge) continue; - auto e = emb.getEdge(v, g); + const auto e = emb.getEdge(v, g); emb.cat = edgeData[get(boost::edge_index_t(), g, e)].finalCategory; } } - for(const Vertex v : asRange(vertices(g))) { + for(const auto v: asRange(vertices(g))) { auto res = finalizeVertex(warnings, v, vertexPrinter); if(!res) return res; } // Construct the configurations. //-------------------------------------------------------------------------- - for(Vertex v : asRange(vertices(g))) { - auto vId = get(boost::vertex_index_t(), g, v); + for(const auto v: asRange(vertices(g))) { + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; assert(data.vGeometry != GeometryGraph::nullGeometry()); assert(!data.configuration); @@ -180,14 +169,14 @@ struct Inference : private detail::InferenceBase { } private: EdgeCategoryCount addEdgesFromGraph(Vertex v) { - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; - auto d = out_degree(v, g); + const auto d = out_degree(v, g); assert(data.edges.empty()); data.edges.reserve(d); std::size_t offset = 0; EdgeCategoryCount catCount; - for(Edge eOut : asRange(out_edges(v, g))) { + for(const auto eOut: asRange(out_edges(v, g))) { data.edges.emplace_back(offset, EmbeddingEdge::Type::Edge, edgeData[get(boost::edge_index_t(), g, eOut)].finalCategory); ++catCount[edgeData[get(boost::edge_index_t(), g, eOut)].finalCategory]; @@ -208,7 +197,7 @@ struct Inference : private detail::InferenceBase { template lib::IO::Result<> finalizeVertex(lib::IO::Warnings &warnings, Vertex v, VertexPrinter vertexPrinter) { const auto &geo = getGeometryGraph(); - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); auto &data = vertexData[vId]; AtomData ad = pMolecule[v]; EdgeCategoryCount catCount; @@ -222,40 +211,43 @@ struct Inference : private detail::InferenceBase { catCount = addEdgesFromGraph(v); // TODO: shouldn't we deduce radical? } else { - std::vector neighbourPresent(data.edges.size(), false); - if(data.edges.size() < out_degree(v, g)) { + // check if all real edges are there, once + std::vector offsetsUsed(data.edges.size(), false); + int edgeCount = 0; + for(const auto &emb: data.edges) { + if(emb.type != EmbeddingEdge::Type::Edge) continue; + assert(emb.offset < out_degree(v, g)); + if(offsetsUsed[emb.offset]) + return lib::IO::Result<>::Error( + "Duplicate edge in stereo embedding for vertex " + vertexPrinter(v) + "."); + offsetsUsed[emb.offset] = true; + ++edgeCount; + } + if(edgeCount != out_degree(v, g)) { + assert(edgeCount < out_degree(v, g)); return lib::IO::Result<>::Error( - "Too few edges in embedding for vertex " + vertexPrinter(v) + ". Got " + - std::to_string(data.edges.size()) - + ", but the degree is " + std::to_string(out_degree(v, g)) + "." + "Too few edges in stereo embedding for vertex " + vertexPrinter(v) + ". Got " + + std::to_string(edgeCount) + + " edges, but the degree is " + std::to_string(out_degree(v, g)) + "." ); } - for(const auto &emb : data.edges) { - if(emb.offset >= data.edges.size()) { - MOD_ABORT; // error, offset out of bounds - return lib::IO::Result<>::Error(""); - } - if(neighbourPresent[emb.offset]) { - MOD_ABORT; // error, duplicate neighbour - return lib::IO::Result<>::Error(""); - } - if(emb.offset < out_degree(v, g) && emb.type != EmbeddingEdge::Type::Edge) { - MOD_ABORT; // error, [0, d[ are reserved for the real edges - return lib::IO::Result<>::Error(""); - } - neighbourPresent[emb.offset] = true; + + // and now count stuff + for(const auto &emb: data.edges) { + assert(emb.offset < data.edges.size()); switch(emb.type) { case EmbeddingEdge::Type::Edge: ++catCount[emb.cat]; break; case EmbeddingEdge::Type::LonePair: + assert(emb.offset >= out_degree(v, g)); // should not happen, [0, d[ are reserved for the real edges ++numLonePairs; break; case EmbeddingEdge::Type::Radical: - if(radical) { - MOD_ABORT; // only one radical per vertex - return lib::IO::Result<>::Error(""); - } + assert(emb.offset >= out_degree(v, g)); // should not happen, [0, d[ are reserved for the real edges + if(radical) + return lib::IO::Result<>::Error( + "Multiple radicals in stereo embedding for vertex " + vertexPrinter(v) + "."); radical = true; break; } @@ -283,13 +275,14 @@ struct Inference : private detail::InferenceBase { public: // if hasFinalized std::unique_ptr extractConfiguration(Vertex v) { assert(hasFinalized); - auto vId = get(boost::vertex_index_t(), g, v); + const auto vId = get(boost::vertex_index_t(), g, v); assert(vertexData[vId].configuration); return std::move(vertexData[vId].configuration); } EdgeCategory getEdgeCategory(Edge e) const { - auto eId = get(boost::edge_index_t(), g, e); + assert(hasFinalized); + const auto eId = get(boost::edge_index_t(), g, e); assert(eId < num_edges(g)); return edgeData[eId].finalCategory; } @@ -298,33 +291,11 @@ struct Inference : private detail::InferenceBase { const PropMolecule &pMolecule; const bool asPattern; private: - bool hasFinalized; - std::vector vertexData; - std::vector edgeData; + bool hasFinalized = false; + std::vector vertexData; + std::vector edgeData; }; -namespace detail { - -// see http://stackoverflow.com/questions/27835925/overload-between-rvalue-reference-and-const-lvalue-reference-in-template - -template -struct makeInferenceHelper { - static Inference make(const Graph &g, PropMolecule &&pMolecule, bool asPattern) = delete; - - static Inference make(const Graph &g, const PropMolecule &pMolecule, bool asPattern) { - return Inference(g, pMolecule, asPattern); - } -}; - -} // detail - -template -Inference::type, typename std::decay::type> -makeInference(Graph &&g, PropMolecule &&pMolecule, bool asPattern) { - return detail::makeInferenceHelper::type, typename std::decay::type> - ::make(std::forward(g), std::forward(pMolecule), asPattern); -} - } // namespace mod::lib::Stereo #endif // MOD_LIB_STEREO_INFERENCE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Term/IO/Read.cpp b/libs/libmod/src/mod/lib/Term/IO/Read.cpp new file mode 100644 index 0000000..1270393 --- /dev/null +++ b/libs/libmod/src/mod/lib/Term/IO/Read.cpp @@ -0,0 +1,113 @@ +#include "Read.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +namespace mod::lib::Term::Read { +namespace detail { + +struct Structure; + +struct Variable { + std::string name; +}; + +struct Term : x3::variant> { + using base_type::base_type; + using base_type::operator=; +}; + +struct Structure { + std::string name; + std::vector arguments; +}; + +namespace { +namespace parser { +#define FIRST "A-Za-z0-9=#:.+-" +#define SECOND "_" + +const x3::rule term = "term"; +const x3::rule function = "function"; +const x3::rule > termList = "term list"; +const x3::rule variable = "variable"; +const x3::rule identifier = "identifier"; + +const auto term_def = function | variable; +const auto function_def = identifier >> -('(' > termList > ')'); +const auto termList_def = term % ','; +const auto variable_def = (x3::lexeme['_' > identifier] >> x3::eps) | x3::string("*"); +const auto identifier_def = x3::lexeme[x3::char_(FIRST) > *x3::char_(SECOND FIRST)]; + +BOOST_SPIRIT_DEFINE(term, function, termList, variable, identifier) +} // namespace parser +} // namespace + +struct Converter : public boost::static_visitor { + Converter(const Converter &) = delete; + Converter &operator=(const Converter &) = delete; + + Converter(const StringStore &stringStore) : stringStore(stringStore) {} + + RawTerm operator()(const Variable &var) { + std::size_t stringId; + if(var.name == "*") { + for(;; ++nextVar) { + std::string name = "X" + std::to_string(nextVar) + "_"; + if(!stringStore.hasString(name)) { + stringId = stringStore.getIndex(name); + break; + } + } + } else { + stringId = stringStore.getIndex(var.name); + } + return RawVariable{stringId}; + } + + RawTerm operator()(const Structure &str) { + auto stringId = stringStore.getIndex(str.name); + std::vector arguments; + for(const auto &a : str.arguments) + arguments.push_back(boost::apply_visitor(*this, a)); + return RawStructure{stringId, std::move(arguments)}; + } +private: + const StringStore &stringStore; + std::size_t nextVar = 0; +}; + +} // namespace detail + +RawTerm rawTerm(const std::string &data, const StringStore &stringStore) { + detail::Term term; + IO::parse(data.begin(), data.end(), detail::parser::term, term, true, x3::space); + detail::Converter converter(stringStore); + return boost::apply_visitor(converter, term); +} + +} // namespace mod::lib::Term::Read + +BOOST_FUSION_ADAPT_STRUCT(mod::lib::Term::Read::detail::Variable, + (std::string, name)) +BOOST_FUSION_ADAPT_STRUCT(mod::lib::Term::Read::detail::Structure, + (std::string, name) + (std::vector, arguments)) \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Term/IO/Read.hpp b/libs/libmod/src/mod/lib/Term/IO/Read.hpp new file mode 100644 index 0000000..8161de8 --- /dev/null +++ b/libs/libmod/src/mod/lib/Term/IO/Read.hpp @@ -0,0 +1,20 @@ +#ifndef MOD_LIB_TERM_IO_READ_HPP +#define MOD_LIB_TERM_IO_READ_HPP + +#include +#include + +#include +#include + +namespace mod::lib { +struct StringStore; +} // namespace mod::lib +namespace mod::lib::Term::Read { + +// throws lib::IO::ParsingError on error +RawTerm rawTerm(const std::string &data, const StringStore &stringStore); + +} // namespace mod::lib::Term::Read + +#endif // MOD_LIB_TERM_IO_READ_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Term/IO/Write.cpp b/libs/libmod/src/mod/lib/Term/IO/Write.cpp new file mode 100644 index 0000000..a22c3da --- /dev/null +++ b/libs/libmod/src/mod/lib/Term/IO/Write.cpp @@ -0,0 +1,192 @@ +#include "Write.hpp" + +#include + +#include +#include +#include + +#include + +namespace mod::lib::Term::Write { +namespace { + +std::ostream &rawVarFromCell(std::ostream &s, Cell cell) { + assert(cell.tag == Cell::Tag::REF); + switch(cell.REF.addr.type) { + case AddressType::Heap: + return s << "_H" << cell.REF.addr.addr; + case AddressType::Temp: + return s << "_T" << cell.REF.addr.addr; + } + MOD_ABORT; +} + +} // namespace + +std::ostream &rawTerm(const RawTerm &term, const StringStore &strings, std::ostream &s) { + struct Printer { + void operator()(RawVariable v) const { + s << "_" << strings.getString(v.name); + } + + void operator()(const RawStructure &str) const { + s << strings.getString(str.name); + if(!str.args.empty()) { + s << '('; + std::visit(*this, str.args.front()); + for(int i = 1; i != str.args.size(); i++) { + s << ", "; + std::visit(*this, str.args[i]); + } + s << ')'; + } + } + public: + std::ostream &s; + const StringStore &strings; + }; + std::visit(Printer{s, strings}, term); + return s; +} + +std::ostream &element(Cell cell, const StringStore &strings, std::ostream &s) { + switch(cell.tag) { + case Cell::Tag::STR: + return s << "STR " << cell.STR.addr; + case Cell::Tag::Structure: + s << strings.getString(cell.Structure.name); + if(cell.Structure.arity > 0) + s << "/" << cell.Structure.arity; + return s; + case Cell::Tag::REF: + return s << "REF " << cell.REF.addr; + } + __builtin_unreachable(); +} + +void wam(const Wam &machine, const StringStore &strings, std::ostream &s) { + wam(machine, strings, s, [](Address, std::ostream &) {}); +} + +void wam(const Wam &machine, const StringStore &strings, std::ostream &s, + std::function addressCallback) { + s << "Heap:" << std::endl; + for(std::size_t i = 0; i < machine.getHeap().size(); i++) { + Cell cell = machine.getHeap()[i]; + s << std::setw(5) << std::left << i; + element(cell, strings, s); + addressCallback({AddressType::Heap, i}, s); + s << std::endl; + } + s << "-------------------------------------------------" << std::endl; + s << "Temp:" << std::endl; + for(std::size_t i = 0; i < machine.getTemp().size(); i++) { + Cell cell = machine.getTemp()[i]; + s << std::setw(5) << std::left << i; + element(cell, strings, s); + addressCallback({AddressType::Temp, i}, s); + s << std::endl; + } + s << "-------------------------------------------------" << std::endl; +} + +std::ostream &term(const Wam &machine, Address addr, const StringStore &strings, std::ostream &s) { + struct Printer { + Printer(const Wam &machine, const StringStore &strings, std::ostream &s) + : machine(machine), strings(strings), s(s) { + occurred[0].resize(machine.getHeap().size(), 0); + occurred[1].resize(machine.getTemp().size(), 0); + } + + void operator()(Address addr) { + Cell cell = machine.getCell(addr); + switch(cell.tag) { + case Cell::Tag::REF: + if(cell.REF.addr == addr + || occurred[static_cast(cell.REF.addr.type)][cell.REF.addr.addr] != 0 + ) { + rawVarFromCell(s, cell); + } else (*this)(cell.REF.addr); + break; + case Cell::Tag::STR: + (*this)(cell.STR.addr); + break; + case Cell::Tag::Structure: + if(occurred[static_cast(addr.type)][addr.addr] != 0) { + wam(machine, strings, std::cout); + std::cout << "addr.addr = " << addr.addr << std::endl; + std::cout << "occurred:" << std::endl; + for(int aType : {0, 1}) { + for(const auto o : occurred[aType]) { + if(o == 0) continue; + std::cout << " [" << aType << "]: " << o << std::endl; + } + } + } + assert(occurred[static_cast(addr.type)][addr.addr] == 0); + s << strings.getString(cell.Structure.name); + if(cell.Structure.arity > 0) { + ++occurred[static_cast(addr.type)][addr.addr]; + s << "("; + (*this)(addr + 1); + for(std::size_t i = 2; i <= cell.Structure.arity; i++) { + s << ", "; + (*this)(addr + i); + } + s << ")"; + --occurred[static_cast(addr.type)][addr.addr]; + } + break; + } + } + private: + const Wam &machine; + const StringStore &strings; + std::ostream &s; + std::array, 2> occurred; + }; + Printer(machine, strings, s)(addr); + return s; +} + +std::ostream &mgu(const Wam &machine, const MGU &mgu, const StringStore &strings, std::ostream &s) { + switch(mgu.status) { + case MGU::Status::Exists: + s << "Exists: "; + break; + case MGU::Status::Fail: + term(machine, mgu.errorLeft, strings, s << "Fail(") << " != "; + term(machine, mgu.errorRight, strings, s) << ")"; + return s; + } + bool first = true; + for(auto binding : mgu.bindings) { + if(binding.type == AddressType::Heap && binding.addr >= mgu.preHeapSize) continue; + if(!first) s << ", "; + first = false; + Cell cell; + cell.tag = Cell::Tag::REF; + cell.REF.addr = binding; + rawVarFromCell(s, cell) << " = "; + term(machine, binding, strings, s); + } + return s; +} + +} // namespace mod::lib::Term::Write +namespace mod::lib::Term { + +std::ostream &operator<<(std::ostream &s, Address addr) { + switch(addr.type) { + case AddressType::Heap: + s << "H"; + break; + case AddressType::Temp: + s << "T"; + break; + } + return s << "[" << addr.addr << "]"; +} + +} // namespace mod::lib::Term \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Term/IO/Write.hpp b/libs/libmod/src/mod/lib/Term/IO/Write.hpp new file mode 100644 index 0000000..132bb32 --- /dev/null +++ b/libs/libmod/src/mod/lib/Term/IO/Write.hpp @@ -0,0 +1,25 @@ +#ifndef MOD_LIB_TERM_IO_WRITE_HPP +#define MOD_LIB_TERM_IO_WRITE_HPP + +#include +#include + +#include +#include + +namespace mod::lib { +struct StringStore; +} // namespace mod::lib +namespace mod::lib::Term::Write { + +std::ostream &rawTerm(const RawTerm &term, const StringStore &strings, std::ostream &s); +std::ostream &element(Cell cell, const StringStore &strings, std::ostream &s); +void wam(const Wam &machine, const StringStore &strings, std::ostream &s); +void wam(const Wam &machine, const StringStore &strings, std::ostream &s, + std::function addressCallback); +std::ostream &term(const Wam &machine, Address addr, const StringStore &strings, std::ostream &s); +std::ostream &mgu(const Wam &machine, const MGU &mgu, const StringStore &strings, std::ostream &s); + +} // namespace mod::lib::Term::Write + +#endif // MOD_LIB_TERM_IO_WRITE_HPP \ No newline at end of file diff --git a/libs/libmod/src/mod/lib/Term/WAM.hpp b/libs/libmod/src/mod/lib/Term/WAM.hpp index f750a08..f5cf11e 100644 --- a/libs/libmod/src/mod/lib/Term/WAM.hpp +++ b/libs/libmod/src/mod/lib/Term/WAM.hpp @@ -43,7 +43,7 @@ struct Address { return Address{addr.type, addr.addr + k}; } - // implemented in the IO cpp file + // implemented in IO/Write.cpp file friend std::ostream &operator<<(std::ostream &s, Address addr); }; diff --git a/libs/libmod/src/mod/rule/CompositionMatch.cpp b/libs/libmod/src/mod/rule/CompositionMatch.cpp index 3c6b1cd..3557c76 100644 --- a/libs/libmod/src/mod/rule/CompositionMatch.cpp +++ b/libs/libmod/src/mod/rule/CompositionMatch.cpp @@ -58,9 +58,12 @@ Rule::LeftGraph::Vertex CompositionMatch::operator[](Rule::RightGraph::Vertex vF const auto &gSecond = p->rSecond->getRule().getGraph(); const auto vFirstInner = vertices(gFirst).first[vFirst.getId()]; const auto vSecondInner = p->mb.getSecondFromFirst(vFirstInner); + if(vSecondInner == boost::graph_traits::null_vertex()) + return {}; const auto vCore = Rule::Vertex(p->rSecond, get(boost::vertex_index_t(), gSecond, vSecondInner)); - if(!vCore) return {}; - else return vCore.getLeft(); + assert(get(boost::vertex_index_t(), gSecond, vSecondInner) == vCore.getId()); + assert(vCore); + return vCore.getLeft(); } Rule::RightGraph::Vertex CompositionMatch::operator[](Rule::LeftGraph::Vertex vSecond) const { @@ -69,10 +72,13 @@ Rule::RightGraph::Vertex CompositionMatch::operator[](Rule::LeftGraph::Vertex vS const auto &gFirst = p->rFirst->getRule().getGraph(); const auto &gSecond = p->rSecond->getRule().getGraph(); const auto vSecondInner = vertices(gSecond).first[vSecond.getId()]; - const auto vFirstInner = p->mb.getSecondFromFirst(vSecondInner); + const auto vFirstInner = p->mb.getFirstFromSecond(vSecondInner); + if(vFirstInner == boost::graph_traits::null_vertex()) + return {}; const auto vCore = Rule::Vertex(p->rFirst, get(boost::vertex_index_t(), gFirst, vFirstInner)); - if(!vCore) return {}; - else return vCore.getRight(); + assert(get(boost::vertex_index_t(), gFirst, vFirstInner) == vCore.getId()); + assert(vCore); + return vCore.getRight(); } void CompositionMatch::push(Rule::RightGraph::Vertex vFirst, Rule::LeftGraph::Vertex vSecond) { diff --git a/libs/libmod/src/mod/rule/GraphInterface.cpp b/libs/libmod/src/mod/rule/GraphInterface.cpp index b54854d..9a59fbf 100644 --- a/libs/libmod/src/mod/rule/GraphInterface.cpp +++ b/libs/libmod/src/mod/rule/GraphInterface.cpp @@ -3,10 +3,10 @@ #include #include #include +#include #include -#include +#include #include -#include namespace mod::rule { @@ -20,15 +20,15 @@ std::ostream &operator<<(std::ostream &s, const Rule::LeftGraph &g) { std::size_t Rule::LeftGraph::numVertices() const { using boost::vertices; - const auto &graph = get_left(r->getRule().getDPORule()); - const auto &vs = vertices(graph); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &vs = vertices(getL(rDPO)); return std::distance(vs.first, vs.second); } std::size_t Rule::LeftGraph::numEdges() const { using boost::edges; - const auto &graph = get_left(r->getRule().getDPORule()); - const auto &es = edges(graph); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &es = edges(getL(rDPO)); return std::distance(es.first, es.second); } @@ -36,51 +36,60 @@ std::size_t Rule::LeftGraph::numEdges() const { // Vertex //------------------------------------------------------------------------------ -MOD_GRAPHPIMPL_Define_Vertex(Rule::LeftGraph, RuleLeftGraph, - get_left(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) +MOD_GRAPHPIMPL_Define_Vertex_noGraph_noId(Rule::LeftGraph, RuleLeftGraph, + getL(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) +MOD_GRAPHPIMPL_Define_Vertex_graph(Rule::LeftGraph, g) + +std::size_t Rule::LeftGraph::Vertex::getId() const { + return getCore().getId(); +} + MOD_GRAPHPIMPL_Define_Vertex_Undirected(Rule::LeftGraph, - get_left(this->g->getRule()->getRule().getDPORule()), g) + getL(this->g->getRule()->getRule().getDPORule().getRule()), g) const std::string &Rule::LeftGraph::Vertex::getStringLabel() const { if(!*this) throw LogicError("Can not get string label on a null vertex."); - const auto &graph = get_left(getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return g->getRule()->getRule().getStringState().getLeft()[v]; + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + return get_string(lr).getLeft()[v]; } AtomId Rule::LeftGraph::Vertex::getAtomId() const { if(!*this) throw LogicError("Can not get atom id on a null vertex."); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return g->getRule()->getRule().getMoleculeState().getLeft()[v].getAtomId(); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + return get_molecule(lr).getLeft()[v].getAtomId(); } Isotope Rule::LeftGraph::Vertex::getIsotope() const { if(!*this) throw LogicError("Can not get isotope on a null vertex."); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return g->getRule()->getRule().getMoleculeState().getLeft()[v].getIsotope(); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + return get_molecule(lr).getLeft()[v].getIsotope(); } Charge Rule::LeftGraph::Vertex::getCharge() const { if(!*this) throw LogicError("Can not get charge on a null vertex."); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto iter = vertices(graph).first; - std::advance(iter, vId); - auto v = *iter; - return g->getRule()->getRule().getMoleculeState().getLeft()[v].getCharge(); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + return get_molecule(g->getRule()->getRule().getDPORule()).getLeft()[v].getCharge(); } bool Rule::LeftGraph::Vertex::getRadical() const { if(!*this) throw LogicError("Can not get radical status on a null vertex."); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return g->getRule()->getRule().getMoleculeState().getLeft()[v].getRadical(); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + return get_molecule(lr).getLeft()[v].getRadical(); } std::string Rule::LeftGraph::Vertex::printStereo() const { @@ -94,19 +103,21 @@ std::string Rule::LeftGraph::Vertex::printStereo() const { std::string Rule::LeftGraph::Vertex::printStereo(const graph::Printer &p) const { if(!*this) throw LogicError("Can not print stereo on a null vertex."); - const auto &graph = get_graph(get_labelled_left(g->getRule()->getRule().getDPORule())); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return lib::IO::Stereo::Write::summary(g->getRule()->getRule(), v, lib::Rules::Membership::Left, p.getOptions()); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + const auto vCG = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), v); + return lib::Rules::Write::stereoSummary(g->getRule()->getRule(), vCG, lib::Rules::Membership::L, p.getOptions()); } Rule::Vertex Rule::LeftGraph::Vertex::getCore() const { if(isNull()) return Rule::Vertex(); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - return Rule::Vertex(g->getRule(), vCoreId); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + const auto vCG = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), v); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + return Rule::Vertex(g->getRule(), vCGId); } std::shared_ptr Rule::LeftGraph::Vertex::getRule() const { @@ -119,7 +130,7 @@ std::shared_ptr Rule::LeftGraph::Vertex::getRule() const { //------------------------------------------------------------------------------ MOD_GRAPHPIMPL_Define_Indices(Rule::LeftGraph, RuleLeftGraph, - get_left(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) + getL(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); @@ -127,32 +138,38 @@ BOOST_CONCEPT_ASSERT((boost::ForwardIteratorgetRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto e = *std::next(out_edges(v, graph).first, eId); - return g->getRule()->getRule().getStringState().getLeft()[e]; + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + const auto e = *std::next(out_edges(v, getL(rDPO)).first, eId); + return get_string(lr).getLeft()[e]; } BondType Rule::LeftGraph::Edge::getBondType() const { if(!*this) throw LogicError("Can not get bond type on a null edge."); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &lr = g->getRule()->getRule().getDPORule(); + const auto &rDPO = lr.getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto e = *std::next(out_edges(v, graph).first, eId); - return g->getRule()->getRule().getMoleculeState().getLeft()[e]; + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + const auto e = *std::next(out_edges(v, getL(rDPO)).first, eId); + return get_molecule(lr).getLeft()[e]; } Rule::Edge Rule::LeftGraph::Edge::getCore() const { if(isNull()) return Rule::Edge(); - const auto &graph = get_left(g->getRule()->getRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - auto e = *std::next(out_edges(v, graph).first, eId); - const auto &es = out_edges(v, get_graph(g->getRule()->getRule().getDPORule())); - auto eCoreId = std::distance(es.first, std::find(es.first, es.second, e)); - return Rule::Edge(g->getRule(), vCoreId, eCoreId); + const auto v = *std::next(vertices(getL(rDPO)).first, vId); + const auto e = *std::next(out_edges(v, getL(rDPO)).first, eId); + const auto vCG = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), v); + const auto eCG = get(rDPO.getLtoCG(), getL(rDPO), rDPO.getCombinedGraph(), e); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + const auto &esCG = out_edges(vCG, rDPO.getCombinedGraph()); + const auto eIter = std::find(esCG.first, esCG.second, eCG); + assert(eIter != esCG.second); + const auto eCGOffset = std::distance(esCG.first, eIter); + return Rule::Edge(g->getRule(), vCGId, eCGOffset); } std::shared_ptr Rule::LeftGraph::Edge::getRule() const { @@ -171,14 +188,14 @@ std::ostream &operator<<(std::ostream &s, const Rule::ContextGraph &g) { std::size_t Rule::ContextGraph::numVertices() const { using boost::vertices; - const auto &graph = get_context(r->getRule().getDPORule()); + const auto &graph = getK(r->getRule().getDPORule().getRule()); const auto &vs = vertices(graph); return std::distance(vs.first, vs.second); } std::size_t Rule::ContextGraph::numEdges() const { using boost::edges; - const auto &graph = get_context(r->getRule().getDPORule()); + const auto &graph = getK(r->getRule().getDPORule().getRule()); const auto &es = edges(graph); return std::distance(es.first, es.second); } @@ -188,17 +205,18 @@ std::size_t Rule::ContextGraph::numEdges() const { //------------------------------------------------------------------------------ MOD_GRAPHPIMPL_Define_Vertex(Rule::ContextGraph, RuleContextGraph, - get_context(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) + getK(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) MOD_GRAPHPIMPL_Define_Vertex_Undirected(Rule::ContextGraph, - get_context(this->g->getRule()->getRule().getDPORule()), g) + getK(this->g->getRule()->getRule().getDPORule().getRule()), g) Rule::Vertex Rule::ContextGraph::Vertex::getCore() const { if(isNull()) return Rule::Vertex(); - const auto &graph = get_context(g->getRule()->getRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - return Rule::Vertex(g->getRule(), vCoreId); + const auto v = *std::next(vertices(getK(rDPO)).first, vId); + const auto vCG = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), v); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + return Rule::Vertex(g->getRule(), vCGId); } std::shared_ptr Rule::ContextGraph::Vertex::getRule() const { @@ -211,7 +229,7 @@ std::shared_ptr Rule::ContextGraph::Vertex::getRule() const { //------------------------------------------------------------------------------ MOD_GRAPHPIMPL_Define_Indices(Rule::ContextGraph, RuleContextGraph, - get_context(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) + getK(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); @@ -219,14 +237,18 @@ BOOST_CONCEPT_ASSERT((boost::ForwardIteratorgetRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - auto e = *std::next(out_edges(v, graph).first, eId); - const auto &es = out_edges(v, get_graph(g->getRule()->getRule().getDPORule())); - auto eCoreId = std::distance(es.first, std::find(es.first, es.second, e)); - return Rule::Edge(g->getRule(), vCoreId, eCoreId); + const auto v = *std::next(vertices(getK(rDPO)).first, vId); + const auto e = *std::next(out_edges(v, getK(rDPO)).first, eId); + const auto vCG = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), v); + const auto eCG = get(rDPO.getKtoCG(), getK(rDPO), rDPO.getCombinedGraph(), e); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + const auto &esCG = out_edges(vCG, rDPO.getCombinedGraph()); + const auto eIter = std::find(esCG.first, esCG.second, eCG); + assert(eIter != esCG.second); + const auto eCGOffset = std::distance(esCG.first, eIter); + return Rule::Edge(g->getRule(), vCGId, eCGOffset); } std::shared_ptr Rule::ContextGraph::Edge::getRule() const { @@ -244,14 +266,14 @@ std::ostream &operator<<(std::ostream &s, const Rule::RightGraph &g) { std::size_t Rule::RightGraph::numVertices() const { using boost::vertices; - const auto &graph = get_right(r->getRule().getDPORule()); + const auto &graph = getR(r->getRule().getDPORule().getRule()); const auto &vs = vertices(graph); return std::distance(vs.first, vs.second); } std::size_t Rule::RightGraph::numEdges() const { using boost::edges; - const auto &graph = get_right(r->getRule().getDPORule()); + const auto &graph = getR(r->getRule().getDPORule().getRule()); const auto &es = edges(graph); return std::distance(es.first, es.second); } @@ -260,58 +282,65 @@ std::size_t Rule::RightGraph::numEdges() const { // Vertex //------------------------------------------------------------------------------ -MOD_GRAPHPIMPL_Define_Vertex(Rule::RightGraph, RuleRightGraph, - get_right(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) +MOD_GRAPHPIMPL_Define_Vertex_noGraph_noId(Rule::RightGraph, RuleRightGraph, + getR(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) +MOD_GRAPHPIMPL_Define_Vertex_graph(Rule::RightGraph, g) + +std::size_t Rule::RightGraph::Vertex::getId() const { + return getCore().getId(); +} + MOD_GRAPHPIMPL_Define_Vertex_Undirected(Rule::RightGraph, - get_right(this->g->getRule()->getRule().getDPORule()), g) + getR(this->g->getRule()->getRule().getDPORule().getRule()), g) const std::string &Rule::RightGraph::Vertex::getStringLabel() const { if(!g) throw LogicError("Can not get string label on a null vertex."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return getRule()->getRule().getStringState().getRight()[v]; + const auto v = *std::next(vertices(graph).first, vId); + return get_string(getRule()->getRule().getDPORule()).getRight()[v]; } AtomId Rule::RightGraph::Vertex::getAtomId() const { if(!g) throw LogicError("Can not get atom id on a null vertex."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return getRule()->getRule().getMoleculeState().getRight()[v].getAtomId(); + const auto v = *std::next(vertices(graph).first, vId); + return get_molecule(getRule()->getRule().getDPORule()).getRight()[v].getAtomId(); } Isotope Rule::RightGraph::Vertex::getIsotope() const { if(!g) throw LogicError("Can not get isotope on a null vertex."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return getRule()->getRule().getMoleculeState().getRight()[v].getIsotope(); + const auto v = *std::next(vertices(graph).first, vId); + return get_molecule(getRule()->getRule().getDPORule()).getRight()[v].getIsotope(); } Charge Rule::RightGraph::Vertex::getCharge() const { if(!g) throw LogicError("Can not get charge on a null vertex."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return getRule()->getRule().getMoleculeState().getRight()[v].getCharge(); + const auto v = *std::next(vertices(graph).first, vId); + return get_molecule(getRule()->getRule().getDPORule()).getRight()[v].getCharge(); } bool Rule::RightGraph::Vertex::getRadical() const { if(!g) throw LogicError("Can not get radical status on a null vertex."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return getRule()->getRule().getMoleculeState().getRight()[v].getRadical(); + const auto v = *std::next(vertices(graph).first, vId); + return get_molecule(getRule()->getRule().getDPORule()).getRight()[v].getRadical(); } Rule::Vertex Rule::RightGraph::Vertex::getCore() const { if(isNull()) return Rule::Vertex(); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - return Rule::Vertex(getRule(), vCoreId); + const auto v = *std::next(vertices(getR(rDPO)).first, vId); + const auto vCG = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), v); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + return Rule::Vertex(getRule(), vCGId); } std::shared_ptr Rule::RightGraph::Vertex::getRule() const { @@ -332,10 +361,12 @@ std::string Rule::RightGraph::Vertex::printStereo() const { std::string Rule::RightGraph::Vertex::printStereo(const graph::Printer &p) const { if(!g) throw LogicError("Can not print stereo on a null vertex."); - const auto &graph = get_graph(get_labelled_right(getRule()->getRule().getDPORule())); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - return lib::IO::Stereo::Write::summary(getRule()->getRule(), v, lib::Rules::Membership::Right, p.getOptions()); + const auto v = *std::next(vertices(graph).first, vId); + const auto &dpo = g->getRule()->getRule().getDPORule().getRule(); + const auto vCG = get(dpo.getRtoCG(), getR(dpo), dpo.getCombinedGraph(), v); + return lib::Rules::Write::stereoSummary(getRule()->getRule(), vCG, lib::Rules::Membership::R, p.getOptions()); } //------------------------------------------------------------------------------ @@ -343,7 +374,7 @@ std::string Rule::RightGraph::Vertex::printStereo(const graph::Printer &p) const //------------------------------------------------------------------------------ MOD_GRAPHPIMPL_Define_Indices(Rule::RightGraph, RuleRightGraph, - get_right(this->g->getRule()->getRule().getDPORule()), g, ->getRule()) + getR(this->g->getRule()->getRule().getDPORule().getRule()), g, ->getRule()) BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); @@ -351,36 +382,36 @@ BOOST_CONCEPT_ASSERT((boost::ForwardIteratorgetRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto iter = vertices(graph).first; - std::advance(iter, vId); - auto v = *iter; - auto e = *std::next(out_edges(v, graph).first, eId); - return getRule()->getRule().getStringState().getRight()[e]; + const auto v = *std::next(vertices(graph).first, vId); + const auto e = *std::next(out_edges(v, graph).first, eId); + return get_string(getRule()->getRule().getDPORule()).getRight()[e]; } BondType Rule::RightGraph::Edge::getBondType() const { if(!g) throw LogicError("Can not get bond type on a null edge."); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &graph = getR(getRule()->getRule().getDPORule().getRule()); using boost::vertices; - auto iter = vertices(graph).first; - std::advance(iter, vId); - auto v = *iter; - auto e = *std::next(out_edges(v, graph).first, eId); - return getRule()->getRule().getMoleculeState().getRight()[e]; + const auto v = *std::next(vertices(graph).first, vId); + const auto e = *std::next(out_edges(v, graph).first, eId); + return get_molecule(getRule()->getRule().getDPORule()).getRight()[e]; } Rule::Edge Rule::RightGraph::Edge::getCore() const { if(isNull()) return Rule::Edge(); - const auto &graph = get_right(getRule()->getRule().getDPORule()); + const auto &rDPO = g->getRule()->getRule().getDPORule().getRule(); using boost::vertices; - auto v = *std::next(vertices(graph).first, vId); - auto vCoreId = get(boost::vertex_index_t(), graph, v); - auto e = *std::next(out_edges(v, graph).first, eId); - const auto &es = out_edges(v, get_graph(getRule()->getRule().getDPORule())); - auto eCoreId = std::distance(es.first, std::find(es.first, es.second, e)); - return Rule::Edge(getRule(), vCoreId, eCoreId); + const auto v = *std::next(vertices(getR(rDPO)).first, vId); + const auto e = *std::next(out_edges(v, getR(rDPO)).first, eId); + const auto vCG = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), v); + const auto eCG = get(rDPO.getRtoCG(), getR(rDPO), rDPO.getCombinedGraph(), e); + const auto vCGId = get(boost::vertex_index_t(), rDPO.getCombinedGraph(), vCG); + const auto &esCG = out_edges(vCG, rDPO.getCombinedGraph()); + const auto eIter = std::find(esCG.first, esCG.second, eCG); + assert(eIter != esCG.second); + const auto eCGOffset = std::distance(esCG.first, eIter); + return Rule::Edge(g->getRule(), vCGId, eCGOffset); } std::shared_ptr Rule::RightGraph::Edge::getRule() const { @@ -402,62 +433,72 @@ MOD_GRAPHPIMPL_Define_Vertex_Undirected(Rule, r->getRule().getGraph(), r) Rule::LeftGraph::Vertex Rule::Vertex::getLeft() const { if(isNull()) return LeftGraph::Vertex(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto v = vertex(vId, get_graph(dpoRule)); - if(membership(dpoRule, v) != lib::Rules::Membership::Right) { + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + if(gCombined[vCG].membership != lib::Rules::Membership::R) { + const auto vL = get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG); using boost::vertices; - const auto &vs = vertices(get_left(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - return LeftGraph::Vertex(r, vSubId); + const auto &vs = vertices(getL(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vL); + assert(vIter != vs.second); + const auto vLOffset = std::distance(vs.first, vIter); + return LeftGraph::Vertex(r, vLOffset); } else return LeftGraph::Vertex(); } Rule::ContextGraph::Vertex Rule::Vertex::getContext() const { if(isNull()) return ContextGraph::Vertex(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto v = vertex(vId, get_graph(dpoRule)); - if(membership(dpoRule, v) == lib::Rules::Membership::Context) { + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + if(gCombined[vCG].membership == lib::Rules::Membership::K) { + const auto vK = get_inverse(rDPO.getKtoCG(), getK(rDPO), gCombined, vCG); using boost::vertices; - const auto &vs = vertices(get_context(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - return ContextGraph::Vertex(r, vSubId); + const auto &vs = vertices(getK(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vK); + assert(vIter != vs.second); + const auto vKOffset = std::distance(vs.first, vIter); + return ContextGraph::Vertex(r, vKOffset); } else return ContextGraph::Vertex(); } Rule::RightGraph::Vertex Rule::Vertex::getRight() const { if(isNull()) return RightGraph::Vertex(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto v = vertex(vId, get_graph(dpoRule)); - if(membership(dpoRule, v) != lib::Rules::Membership::Left) { + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + if(gCombined[vCG].membership != lib::Rules::Membership::L) { + const auto vR = get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG); using boost::vertices; - const auto &vs = vertices(get_right(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - return RightGraph::Vertex(r, vSubId); + const auto &vs = vertices(getR(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vR); + + assert(vIter != vs.second); + const auto vROffset = std::distance(vs.first, vIter); + return RightGraph::Vertex(r, vROffset); } else return RightGraph::Vertex(); } double Rule::Vertex::get2DX(bool withHydrogens) { - if(isNull()) throw LogicError("Can not get coordinaes on a null vertex."); - const auto &rule = r->getRule(); - const auto &dpoRule = rule.getDPORule(); - const auto v = vertex(vId, get_graph(dpoRule)); - const auto &depict = rule.getDepictionData(); - double x = depict.getX(v, withHydrogens); - if(std::isnan(x)) throw LogicError("Can not get coordinaes for this vertex."); + if(isNull()) throw LogicError("Can not get coordinates on a null vertex."); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + const auto &depict = r->getRule().getDepictionData(); + const double x = depict.getX(vCG, withHydrogens); + if(std::isnan(x)) throw LogicError("Can not get coordinates for this vertex."); return x; } double Rule::Vertex::get2DY(bool withHydrogens) { - if(isNull()) throw LogicError("Can not get coordinaes on a null vertex."); - const auto &rule = r->getRule(); - const auto &dpoRule = rule.getDPORule(); - const auto v = vertex(vId, get_graph(dpoRule)); - const auto &depict = rule.getDepictionData(); - double y = depict.getY(v, withHydrogens); - if(std::isnan(y)) throw LogicError("Can not get coordinaes for this vertex."); + if(isNull()) throw LogicError("Can not get coordinates on a null vertex."); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + const auto &depict = r->getRule().getDepictionData(); + const double y = depict.getY(vCG, withHydrogens); + if(std::isnan(y)) throw LogicError("Can not get coordinates for this vertex."); return y; } @@ -473,55 +514,67 @@ BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); Rule::LeftGraph::Edge Rule::Edge::getLeft() const { if(isNull()) return LeftGraph::Edge(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto &graph = get_graph(dpoRule); - using boost::vertices; - const auto v = *(vertices(graph).first + vId); - const auto e = *(out_edges(v, graph).first + eId); - if(membership(dpoRule, e) != lib::Rules::Membership::Right) { - const auto &vs = vertices(get_left(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - const auto &es = out_edges(v, get_left(r->getRule().getDPORule())); - auto eIter = std::find(es.first, es.second, e); - auto eSubId = std::distance(es.first, eIter); - return LeftGraph::Edge(r, vSubId, eSubId); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + const auto eCG = *std::next(out_edges(vCG, gCombined).first, eId); + if(gCombined[eCG].membership != lib::Rules::Membership::R) { + const auto vL = get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, vCG); + const auto eL = get_inverse(rDPO.getLtoCG(), getL(rDPO), gCombined, eCG); + using boost::vertices; + const auto &vs = vertices(getL(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vL); + assert(vIter != vs.second); + const auto vLId = std::distance(vs.first, vIter); + const auto &es = out_edges(vL, getL(rDPO)); + const auto eIter = std::find(es.first, es.second, eL); + assert(eIter != es.second); + const auto eLOffset = std::distance(es.first, eIter); + return LeftGraph::Edge(r, vLId, eLOffset); } else return LeftGraph::Edge(); } Rule::ContextGraph::Edge Rule::Edge::getContext() const { if(isNull()) return ContextGraph::Edge(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto &graph = get_graph(dpoRule); - using boost::vertices; - const auto v = *(vertices(graph).first + vId); - const auto e = *(out_edges(v, graph).first + eId); - if(membership(dpoRule, e) == lib::Rules::Membership::Context) { - const auto &vs = vertices(get_context(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - const auto &es = out_edges(v, get_context(r->getRule().getDPORule())); - auto eIter = std::find(es.first, es.second, e); - auto eSubId = std::distance(es.first, eIter); - return ContextGraph::Edge(r, vSubId, eSubId); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + const auto eCG = *std::next(out_edges(vCG, gCombined).first, eId); + if(gCombined[eCG].membership == lib::Rules::Membership::K) { + const auto vK = get_inverse(rDPO.getKtoCG(), getK(rDPO), gCombined, vCG); + const auto eK = get_inverse(rDPO.getKtoCG(), getK(rDPO), gCombined, eCG); + using boost::vertices; + const auto &vs = vertices(getK(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vK); + assert(vIter != vs.second); + const auto vKOffset = std::distance(vs.first, vIter); + const auto &es = out_edges(vK, getK(rDPO)); + const auto eIter = std::find(es.first, es.second, eK); + assert(eIter != es.second); + const auto eKOffset = std::distance(es.first, eIter); + return ContextGraph::Edge(r, vKOffset, eKOffset); } else return ContextGraph::Edge(); } Rule::RightGraph::Edge Rule::Edge::getRight() const { if(isNull()) return RightGraph::Edge(); - const auto &dpoRule = r->getRule().getDPORule(); - const auto &graph = get_graph(dpoRule); - using boost::vertices; - const auto v = *(vertices(graph).first + vId); - const auto e = *(out_edges(v, graph).first + eId); - if(membership(dpoRule, e) != lib::Rules::Membership::Left) { - const auto &vs = vertices(get_right(r->getRule().getDPORule())); - auto iter = std::find(vs.first, vs.second, v); - auto vSubId = std::distance(vs.first, iter); - const auto &es = out_edges(v, get_right(r->getRule().getDPORule())); - auto eIter = std::find(es.first, es.second, e); - auto eSubId = std::distance(es.first, eIter); - return RightGraph::Edge(r, vSubId, eSubId); + const auto &rDPO = r->getRule().getDPORule().getRule(); + const auto &gCombined = rDPO.getCombinedGraph(); + const auto vCG = vertex(vId, gCombined); + const auto eCG = *std::next(out_edges(vCG, gCombined).first, eId); + if(gCombined[eCG].membership != lib::Rules::Membership::L) { + const auto vR = get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, vCG); + const auto eR = get_inverse(rDPO.getRtoCG(), getR(rDPO), gCombined, eCG); + using boost::vertices; + const auto &vs = vertices(getR(rDPO)); + const auto vIter = std::find(vs.first, vs.second, vR); + assert(vIter != vs.second); + const auto vRId = std::distance(vs.first, vIter); + const auto &es = out_edges(vR, getR(rDPO)); + const auto eIter = std::find(es.first, es.second, eR); + assert(eIter != es.second); + const auto eROffset = std::distance(es.first, eIter); + return RightGraph::Edge(r, vRId, eROffset); } else return RightGraph::Edge(); } diff --git a/libs/libmod/src/mod/rule/Rule.cpp b/libs/libmod/src/mod/rule/Rule.cpp index 0d50678..64d54ed 100644 --- a/libs/libmod/src/mod/rule/Rule.cpp +++ b/libs/libmod/src/mod/rule/Rule.cpp @@ -4,9 +4,10 @@ #include #include #include -#include -#include #include +#include +#include +#include #include #include @@ -46,7 +47,7 @@ const lib::Rules::Real &Rule::getRule() const { //------------------------------------------------------------------------------ std::size_t Rule::numVertices() const { - return num_vertices(p->r->getGraph()); + return num_vertices(p->r->getDPORule().getRule().getCombinedGraph()); } Rule::VertexRange Rule::vertices() const { @@ -54,7 +55,7 @@ Rule::VertexRange Rule::vertices() const { } std::size_t Rule::numEdges() const { - return num_edges(p->r->getGraph()); + return num_edges(p->r->getDPORule().getRule().getCombinedGraph()); } Rule::EdgeRange Rule::edges() const { @@ -78,21 +79,21 @@ Rule::RightGraph Rule::getRight() const { std::shared_ptr Rule::makeInverse() const { lib::Rules::LabelledRule dpoRule(getRule().getDPORule(), true); if(getConfig().rule.ignoreConstraintsDuringInversion.get()) { - if(dpoRule.leftMatchConstraints.size() > 0 - || dpoRule.rightMatchConstraints.size() > 0) { + if(get_match_constraints(get_labelled_left(dpoRule)).size() > 0 + || get_match_constraints(get_labelled_right(dpoRule)).size() > 0) { std::cout << "WARNING: inversion of rule strips constraints.\n"; } } else { - if(dpoRule.leftMatchConstraints.size() > 0) { + if(get_match_constraints(get_labelled_left(dpoRule)).size() > 0) { throw LogicError("Can not invert rule with left-side component constraints."); } - if(dpoRule.rightMatchConstraints.size() > 0) { + if(get_match_constraints(get_labelled_right(dpoRule)).size() > 0) { throw LogicError("Can not invert rule with right-side component constraints."); } } dpoRule.invert(); - bool ignore = getConfig().rule.ignoreConstraintsDuringInversion.get(); - if(ignore) dpoRule.rightMatchConstraints.clear(); + const bool ignore = getConfig().rule.ignoreConstraintsDuringInversion.get(); + if(ignore) dpoRule.rightData.matchConstraints.clear(); auto rInner = std::make_unique(std::move(dpoRule), getLabelType()); rInner->setName(this->getName() + ", inverse"); return makeRule(std::move(rInner), *p->externalToInternalIds); @@ -116,25 +117,25 @@ Rule::print(const graph::Printer &first, const graph::Printer &second) const { std::pair Rule::print(const graph::Printer &first, const graph::Printer &second, bool printCombined) const { - return lib::IO::Rules::Write::summary(getRule(), first.getOptions(), second.getOptions(), printCombined); + return lib::Rules::Write::summary(getRule(), first.getOptions(), second.getOptions(), printCombined); } void Rule::printTermState() const { - lib::IO::Rules::Write::termState(getRule()); + lib::Rules::Write::termState(getRule()); } std::string Rule::getGMLString(bool withCoords) const { if(withCoords && !getRule().getDepictionData().getHasCoordinates()) throw LogicError("Coordinates are not available for this rule (" + getName() + ")."); std::stringstream ss; - lib::IO::Rules::Write::gml(getRule(), withCoords, ss); + lib::Rules::Write::gml(getRule(), withCoords, ss); return ss.str(); } std::string Rule::printGML(bool withCoords) const { if(withCoords && !getRule().getDepictionData().getHasCoordinates()) throw LogicError("Coordinates are not available for this rule (" + getName() + ")."); - return lib::IO::Rules::Write::gml(*p->r, withCoords); + return lib::Rules::Write::gml(*p->r, withCoords); } const std::string &Rule::getName() const { @@ -150,11 +151,11 @@ std::optional Rule::getLabelType() const { } std::size_t Rule::getNumLeftComponents() const { - return getRule().getDPORule().numLeftComponents; + return get_num_connected_components(get_labelled_left(getRule().getDPORule())); } std::size_t Rule::getNumRightComponents() const { - return getRule().getDPORule().numRightComponents; + return get_num_connected_components(get_labelled_right(getRule().getDPORule())); } namespace { @@ -212,7 +213,7 @@ int Rule::getMaxExternalId() const { namespace { -std::shared_ptr handleLoadedRule(lib::IO::Result dataRes, +std::shared_ptr handleLoadedRule(lib::IO::Result dataRes, lib::IO::Warnings warnings, bool invert, const std::string &dataSource) { @@ -223,7 +224,7 @@ std::shared_ptr handleLoadedRule(lib::IO::ResultpString); if(invert) { - if(!data.rule->leftMatchConstraints.empty()) { + if(!get_match_constraints(get_labelled_left(*data.rule)).empty()) { const bool ignore = getConfig().rule.ignoreConstraintsDuringInversion.get(); std::string msg = "The rule '"; if(data.name) msg += *data.name; @@ -238,7 +239,7 @@ std::shared_ptr handleLoadedRule(lib::IO::ResultleftMatchConstraints.clear(); + data.rule->leftData.matchConstraints.clear(); std::cout << "WARNING: " << msg << '\n'; } } @@ -254,7 +255,7 @@ std::shared_ptr handleLoadedRule(lib::IO::Result Rule::fromGMLString(const std::string &data, bool invert) { lib::IO::Warnings warnings; - auto res = lib::IO::Rules::Read::gml(warnings, data); + auto res = lib::Rules::Read::gml(warnings, data); return handleLoadedRule(std::move(res), std::move(warnings), invert, ""); } @@ -267,10 +268,16 @@ std::shared_ptr Rule::fromGMLFile(const std::string &file, bool invert) { } if(!ifs) throw InputError("Could not open rule GML file '" + file + "'.\n"); lib::IO::Warnings warnings; - auto res = lib::IO::Rules::Read::gml(warnings, {ifs.begin(), ifs.size()}); + auto res = lib::Rules::Read::gml(warnings, {ifs.begin(), ifs.size()}); return handleLoadedRule(std::move(res), std::move(warnings), invert, "file '" + file + "'"); } +std::shared_ptr Rule::fromDFS(const std::string &data, bool invert) { + lib::IO::Warnings warnings; + auto res = lib::Rules::Read::dfs(warnings, data); + return handleLoadedRule(std::move(res), std::move(warnings), invert, ""); +} + std::shared_ptr Rule::makeRule(std::unique_ptr r) { return makeRule(std::move(r), {}); } diff --git a/libs/libmod/src/mod/rule/Rule.hpp b/libs/libmod/src/mod/rule/Rule.hpp index db496b6..a6dbcf4 100644 --- a/libs/libmod/src/mod/rule/Rule.hpp +++ b/libs/libmod/src/mod/rule/Rule.hpp @@ -16,7 +16,12 @@ namespace mod::rule { // rst-class: rule::Rule // rst: -// rst: Model of a transformation rule in the Double Pushout formalism. +// rst: This class models a graph transformation rule in the Double Pushout formalism, +// rst: as the span :math:`L \leftarrow K \rightarrow R`. +// rst: The three graphs are referred to as respectively +// rst: the "left", "context", and "right" graphs of the rule. +// rst: See :ref:`graph-model` for more details. +// rst: // rst: See :ref:`cpp-rule/GraphInterface` for the documentation for the // rst: graph interface for this class. // rst: @@ -187,6 +192,15 @@ struct MOD_DECL Rule { // rst: :throws: :class:`InputError` on bad data and when inversion fails due to constraints. static std::shared_ptr fromGMLString(const std::string &data, bool invert); static std::shared_ptr fromGMLFile(const std::string &file, bool invert); + // rst: .. function:: static std::shared_ptr fromDFS(const std::string &data, bool invert) + // rst: + // rst: Load a rule from a :ref:`RuleDFS ` string, and store either that rule or its inverse. + // rst: The name of the rule is the one specified, though when ``invert=True`` + // rst: the string ", inverse" is appended to the name. + // rst: + // rst: :returns: the loaded (possibly inverted) rule. + // rst: :throws: :class:`InputError` on bad data and when inversion fails due to constraints. + static std::shared_ptr fromDFS(const std::string &data, bool invert); // rst: .. function:: static std::shared_ptr makeRule(std::unique_ptr r) // rst: static std::shared_ptr makeRule(std::unique_ptr r, std::map externalToInternalIds) // rst: diff --git a/libs/libmod/src/mod/rule/internal/Rule.cpp b/libs/libmod/src/mod/rule/internal/Rule.cpp index 863e999..11f6760 100644 --- a/libs/libmod/src/mod/rule/internal/Rule.cpp +++ b/libs/libmod/src/mod/rule/internal/Rule.cpp @@ -13,28 +13,27 @@ lib::Rules::LabelledRule::GraphType &getGraph(lib::Rules::LabelledRule &r) { return get_graph(r); } -std::unique_ptr makePropStringCore(const lib::Rules::GraphType &g) { - return std::make_unique(g); +std::unique_ptr makePropStringCore(const lib::Rules::LabelledRule &rule) { + return std::make_unique(rule.getRule()); } -void add(lib::Rules::PropStringCore &pString, boost::graph_traits::vertex_descriptor v, +void add(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedVertex v, const std::string &valueLeft, const std::string &valueRight) { pString.add(v, valueLeft, valueRight); } -void add(lib::Rules::PropStringCore &pString, boost::graph_traits::edge_descriptor e, +void add(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedEdge e, const std::string &valueLeft, const std::string &valueRight) { pString.add(e, valueLeft, valueRight); } -void setRight(lib::Rules::PropStringCore &pString, - boost::graph_traits::edge_descriptor e, const std::string &value) { +void setRight(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedEdge e, const std::string &value) { pString.setRight(e, value); } -MOD_DECL lib::Rules::PropMoleculeCore -makePropMoleculeCore(const lib::Rules::GraphType &g, const lib::Rules::PropStringCore &str) { - return lib::Rules::PropMoleculeCore(g, str); +lib::Rules::PropMolecule +makePropMoleculeCore(const lib::Rules::LabelledRule &rule, const lib::Rules::PropString &str) { + return lib::Rules::PropMolecule(rule.getRule(), str); } std::shared_ptr makeRule(lib::Rules::LabelledRule &&r) { @@ -42,4 +41,24 @@ std::shared_ptr makeRule(lib::Rules::LabelledRule &&r) { return mod::rule::Rule::makeRule(std::move(rLib)); } +const std::string &getStringLeft(lib::DPO::CombinedRule::CombinedVertex v, + const lib::Rules::PropString &str) { + return str.getLeft()[v]; +} + +const std::string &getStringRight(lib::DPO::CombinedRule::CombinedVertex v, + const lib::Rules::PropString &str) { + return str.getRight()[v]; +} + +BondType getMoleculeLeft(lib::DPO::CombinedRule::CombinedEdge e, + const lib::Rules::PropMolecule &mol) { + return mol.getLeft()[e]; +} + +BondType getMoleculeRight(lib::DPO::CombinedRule::CombinedEdge e, + const lib::Rules::PropMolecule &mol) { + return mol.getRight()[e]; +} + } // namespace mod::rule::internal \ No newline at end of file diff --git a/libs/libmod/src/mod/rule/internal/Rule.hpp b/libs/libmod/src/mod/rule/internal/Rule.hpp index c28e5a6..83f4f9f 100644 --- a/libs/libmod/src/mod/rule/internal/Rule.hpp +++ b/libs/libmod/src/mod/rule/internal/Rule.hpp @@ -10,18 +10,27 @@ namespace mod::rule::internal { MOD_DECL lib::Rules::LabelledRule makeLabelledRule(); MOD_DECL lib::Rules::GraphType &getGraph(lib::Rules::LabelledRule &r); -MOD_DECL std::unique_ptr makePropStringCore(const lib::Rules::GraphType &g); -MOD_DECL void add(lib::Rules::PropStringCore &pString, boost::graph_traits::vertex_descriptor v, +MOD_DECL std::unique_ptr makePropStringCore(const lib::Rules::LabelledRule &rule); +MOD_DECL void add(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedVertex v, const std::string &valueLeft, const std::string &valueRight); -MOD_DECL void add(lib::Rules::PropStringCore &pString, boost::graph_traits::edge_descriptor e, +MOD_DECL void add(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedEdge e, const std::string &valueLeft, const std::string &valueRight); -MOD_DECL void setRight(lib::Rules::PropStringCore &pString, - boost::graph_traits::edge_descriptor e, const std::string &value); -MOD_DECL lib::Rules::PropMoleculeCore -makePropMoleculeCore(const lib::Rules::GraphType &g, const lib::Rules::PropStringCore &str); +MOD_DECL void setRight(lib::Rules::PropString &pString, lib::DPO::CombinedRule::CombinedEdge e, + const std::string &value); +MOD_DECL lib::Rules::PropMolecule makePropMoleculeCore(const lib::Rules::LabelledRule &rule, + const lib::Rules::PropString &str); MOD_DECL std::shared_ptr makeRule(lib::Rules::LabelledRule &&r); +MOD_DECL const std::string &getStringLeft(lib::DPO::CombinedRule::CombinedVertex v, + const lib::Rules::PropString &str); +MOD_DECL const std::string &getStringRight(lib::DPO::CombinedRule::CombinedVertex v, + const lib::Rules::PropString &str); +MOD_DECL BondType getMoleculeLeft(lib::DPO::CombinedRule::CombinedEdge e, + const lib::Rules::PropMolecule &mol); +MOD_DECL BondType getMoleculeRight(lib::DPO::CombinedRule::CombinedEdge e, + const lib::Rules::PropMolecule &mol); + } // namespace mod::rule::internal #endif // MOD_RULE_INTERNAL_RULE_HPP diff --git a/libs/post_mod/bin/mod_post b/libs/post_mod/bin/mod_post index 543bb0a..2232802 100755 --- a/libs/post_mod/bin/mod_post +++ b/libs/post_mod/bin/mod_post @@ -57,6 +57,9 @@ else fi export MOD_PREFIX this="$MOD_PREFIX/bin/$self" +if [ "$this" -ef "$(which $self)" ]; then + this=$self +fi # Args if [ ! -n "$MOD_NUM_POST_THREADS" ]; then @@ -118,8 +121,15 @@ elif test "x$1" != "x--mode"; then exit $res fi - $this --mode invokeMake - exit $? + if [ -e out/disableInvokeMake ]; then + echo "Post-processing disabled by user. Run" + echo " $this --mode invokeMake" + echo "to manually invoke it." + exit 0 + else + $this --mode invokeMake + exit $? + fi else # $1 == --mode mode=$2 if test "x$mode" = "x"; then @@ -169,17 +179,21 @@ else # $1 == --mode echo "$texFile: gen" } function endMakefile { - echo "summary.pdf: $texFile" - if [ -e out/disableSummary ]; then - echo " echo ''" # each target prints a status message - echo " echo 'Summary disabled by user.' | tee summary/summary.pdf" + if [ -e out/disableCompileSummary ]; then + echo " echo ''" + echo " echo 'Summary compilation disabled by user. Run'" + echo " echo ' $this --mode compileSummary'" + echo " echo 'to manually invoke it.'" + echo " $texFile" >> $makefileAllTarget else - echo " $this --mode compileSummary" + echo " summary.pdf" >> $makefileAllTarget fi - echo "include $makefileImpl" >> $makefile - echo "include $makefileDep" >> $makefile - echo "include $makefileAllTarget" >> $makefile - echo "include $makefileClean" >> $makefile + echo "summary.pdf: $texFile" + echo " $this --mode compileSummary" + echo "include $makefileImpl" + echo "include $makefileDep" + echo "include $makefileAllTarget" + echo "include $makefileClean" } function initMakefileImpl { local p=$MOD_PREFIX/share/mod/commonPreamble @@ -217,16 +231,35 @@ else # $1 == --mode } #--------------------------------------------------------------------- - # Summary + # Invocation Control #--------------------------------------------------------------------- - function disableSummary { - touch out/disableSummary + function enableInvokeMake { + rm -f out/disableInvokeMake + } + function disableInvokeMake { + touch out/disableInvokeMake + } + + function enableCompileSummary { + rm -f out/disableCompileSummary + } + function disableCompileSummary { + touch out/disableCompileSummary } function enableSummary { - rm -f out/disableSummary + >&2 echo "WARNING: post command 'enableSummary' is deprecated', use 'ensableInvokeMake' instead." + enableInvokeMake } + function disableSummary { + >&2 echo "WARNING: post command 'disableSummary' is deprecated', use 'disableInvokeMake' instead." + disableInvokeMake + } + + #--------------------------------------------------------------------- + # Summary + #--------------------------------------------------------------------- function summaryChapter { echo " echo '\summaryChapter{$(latexEsc "$1")}' >> $texFile" >> $makefile @@ -371,14 +404,6 @@ else # $1 == --mode echo -n " $fileNoExt.$outType" >> $makefileDep return - extractGVImageDeps $inFile >> $makefileImpl - echo "" >> $makefileImpl - # dotFile - function extractGVImageDeps { - grep image $1 | sed "s/.* image=\"\([^\"]*\)\".*/\1/" | while read f; do - echo -n " $f" - done; - } } # graphType fileNoExt [noOverlay] @@ -388,7 +413,9 @@ else # $1 == --mode local noOverlay=$3 echo -n " $fileNoExt.plain ${fileNoExt}_coord.tex" >> $makefileClean function common { - grep image $fileNoExt.dot | sed "s/.* image=\"\([^\"]*\)\".*/\1/" | while read f; do + reGrep='\[.* image="\([^\"]*\)".*\]' + reSed='s/.*\[.* image="\([^\"]*\)".*\].*/\1/' + grep "$reGrep" $fileNoExt.dot | sed "$reSed" | while read f; do echo -n " $f" >> $makefileImpl done echo "" >> $makefileImpl @@ -451,7 +478,6 @@ else # $1 == --mode echo "" >> $makefileDep echo "" >> $makefileClean - echo " summary.pdf" >> $makefileAllTarget endMakefile >> $makefile return 0 } @@ -518,15 +544,13 @@ else # $1 == --mode break fi done - # hax to prevent files from changing - sed -i "s!/ID .*!/ID [<0> <0>]!" ${fileNoExt}.pdf return $res } gvArgs_base="" - #gvArgs_graph="$gvArgs_base -Kneato" + gvArgs_graph="$gvArgs_base -Kneato" #gvArgs_ruleSide="$gvArgs_base -Kneato -Nfontsize=28 -Epenwidth=3" - #gvArgs_rule="$gvArgs_base -Kneato" + gvArgs_rule="$gvArgs_base -Kneato" gvArgs_ruleCombined="$gvArgs_base -Kneato" gvArgs_dgNonHyper="$gvArgs_base -Kneato -Goverlap=false -Elen=1.1" gvArgs_dgHyper="$gvArgs_base -Kneato -Goverlap=false -Elen=1.5" @@ -592,9 +616,9 @@ BEGIN { if(abs(y) < 1e-4) y = 0 if(noOverlay == "xnoOverlay") { - printf "\\node (v-coord-%s) at (%s, %s) {};\n", name, x, y + printf "\\node (\\modIdPrefix v-coord-%s) at (%s, %s) {};\n", name, x, y } else { - printf "\\coordinate[overlay] (v-coord-%s) at (%s, %s) {};\n", name, x, y + printf "\\coordinate[overlay] (\\modIdPrefix v-coord-%s) at (%s, %s) {};\n", name, x, y } } else { # ignore diff --git a/libs/post_mod/share/mod/figureTemplate.tex b/libs/post_mod/share/mod/figureTemplate.tex index 952e966..73b8ad4 100644 --- a/libs/post_mod/share/mod/figureTemplate.tex +++ b/libs/post_mod/share/mod/figureTemplate.tex @@ -4,16 +4,11 @@ \usepackage{hyperref} % prevent the figures from changing when recompiling -% http://tex.stackexchange.com/questions/5401/how-to-prevent-different-checksums-for-different-typesets -% http://superuser.com/questions/130347/how-do-i-produce-bytewise-consistent-documents-with-pdflatex -\pdfinfo{/CreationDate (FixedForDiffing) -/ModDate (FixedForDiffing)} - -% in some future, use the following -% see http://tug.org/pipermail/pdftex/2015-July/008953.html -%\pdfinfoomitdate=1 -%\pdftrailerid{} - +% https://tex.stackexchange.com/questions/95080/making-an-anonymous-pdf-file-using-pdflatex +\pdfinfo{ /ModDate () /CreationDate () } +\hypersetup{pdfinfo={ Creator={}, Producer={} }} +\pdftrailerid{} %Remove ID +\pdfsuppressptexinfo15 %Suppress PTEX.Fullbanner and info of imported PDFs \begin{document} \begin{preview} diff --git a/libs/pymod/CMakeLists.txt b/libs/pymod/CMakeLists.txt index 4d954c0..446f159 100644 --- a/libs/pymod/CMakeLists.txt +++ b/libs/pymod/CMakeLists.txt @@ -16,7 +16,7 @@ target_include_directories(pymod PUBLIC $ $) -target_link_libraries(pymod PUBLIC mod::libmod Boost::${PYTHON_TARGET} Python3::Python) +target_link_libraries(pymod PUBLIC mod::pymodutils) target_link_libraries(pymod PRIVATE $<$:-Wl,--no-undefined> $<$:-Wl,--no-undefined> @@ -25,8 +25,7 @@ target_link_libraries(pymod PRIVATE set_target_properties(pymod PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") target_compile_options(pymod PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter - -Wno-comment - -Wno-unused-local-typedefs) + -Wno-comment) set_target_properties(pymod PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON) @@ -63,6 +62,19 @@ install(DIRECTORY lib/mod COMPONENT pymod_run FILES_MATCHING PATTERN "*.py" PATTERN "*.pyi") +# redirector +# ------------------------------------------------------------------------- + +set(exportDir ${CMAKE_CURRENT_BINARY_DIR}/redirector) +file(MAKE_DIRECTORY ${exportDir}/mod) +# TODO: should be file(COPY_FILE redirector/pyproject.toml ${exportDir}/pyproject.toml) when CMake 3.21 is required +configure_file(redirector/pyproject.toml ${exportDir}/pyproject.toml @ONLY) +configure_file(redirector/setup.cfg.in ${exportDir}/setup.cfg @ONLY) +configure_file(redirector/mod/__init__.py.in ${exportDir}/mod/__init__.py @ONLY) +if(${BUILD_PY_MOD_PIP}) + install(CODE "execute_process(COMMAND ${Python3_EXECUTABLE} -m pip install . WORKING_DIRECTORY ${exportDir})" + COMPONENT pymod_run) +endif() # mod # ------------------------------------------------------------------------- diff --git a/libs/pymod/bin/mod.in b/libs/pymod/bin/mod.in index d09ed47..90bb109 100755 --- a/libs/pymod/bin/mod.in +++ b/libs/pymod/bin/mod.in @@ -233,7 +233,7 @@ done # Create precommand (gdb/valgrind/...) precommand="" if [ $profile = "true" -o $memcheck = "true" ]; then - precommand="valgrind" + precommand="valgrind --error-exitcode=42" fi if [ $profile = "true" ]; then precommand="$precommand --tool=callgrind --dump-instr=yes --collect-jumps=yes $vgArgs" @@ -346,6 +346,18 @@ def include(fName, checkDup=True, putDup=True, skipDup=True): else: print("End of code from '", fName, "'", sep="") mod.include = include + +if "LD_PRELOAD" in os.environ and "valgrind" in os.environ["LD_PRELOAD"]: + import atexit + def _valgrindLeakHaxAtExit(): + toRemove = set(globals()) - _initialGlobals + toRemove.remove("_initialGlobals") + print("atexit hax to remove global variables for Valgrind leak check:") + for n in toRemove: + print(" ", n) + del globals()[n] + atexit.register(_valgrindLeakHaxAtExit) + _initialGlobals = set(globals()) EOF if [ ! -z "$MOD_NO_DEPRECATED" ]; then echo "config.common.ignoreDeprecation = False" diff --git a/libs/pymod/lib/mod/__init__.py b/libs/pymod/lib/mod/__init__.py index d84ed4c..1375006 100644 --- a/libs/pymod/lib/mod/__init__.py +++ b/libs/pymod/lib/mod/__init__.py @@ -1,14 +1,15 @@ -import collections +import collections.abc import ctypes import inspect -import math import sys from typing import ( Any, Callable, cast, Generic, Iterable, List, Optional, Sequence, TextIO, Tuple, Type, TypeVar, Union ) +_redirected = False + _oldFlags = sys.getdlopenflags() sys.setdlopenflags(_oldFlags | ctypes.RTLD_GLOBAL) from . import libpymod @@ -164,6 +165,13 @@ def __call__(self, *args: List[Any]) -> "T": return module._sharedToStd(res) +#---------------------------------------------------------- +# Common stuff +#---------------------------------------------------------- + +_lsString = LabelSettings(LabelType.String, LabelRelation.Isomorphism) + + ########################################################### # Chem ########################################################### @@ -179,6 +187,7 @@ def __call__(self, *args: List[Any]) -> "T": LabelRelation.__str__ = libpymod._LabelRelation__str__ # type: ignore IsomorphismPolicy.__str__ = libpymod._IsomorphismPolicy__str__ # type: ignore SmilesClassPolicy.__str__ = libpymod._SmilesClassPolicy__str__ # type: ignore +Action.__str__ = libpymod._Action__str__ # type: ignore config = getConfig() @@ -218,11 +227,11 @@ def dgDerivations(ders: Iterable[Derivation]) -> DG: dg = DG() with dg.build() as b: for d in ders: - b.addDerivation(d) # type: ignore + b.addDerivation(d) return dg def dgRuleComp(graphs: Iterable[Graph], strat: DGStrat, - labelSettings: LabelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism), + labelSettings: LabelSettings=_lsString, ignoreRuleLabelTypes: bool=False) -> DG: _deprecation("dgRuleComp is deprecated. Use the new build interface.") dg = DG(labelSettings=labelSettings, graphDatabase=graphs) @@ -234,6 +243,8 @@ def _DG_calc(dg: DG, printInfo: bool=True) -> None: _deprecation("DG.calc() is deprecated. Use the new build interface.") d = dg._ruleCompData # type: ignore dg.build().execute(d["strat"], ignoreRuleLabelTypes=d["ignoreRuleLabelTypes"]) + object.__setattr__(dg, "_ruleCompData", None) + object.__setattr__(dg, "calc", None) import types object.__setattr__(dg, "calc", types.MethodType(_DG_calc, dg)) return dg @@ -251,9 +262,7 @@ def _DG_load( _DG__init__old = DG.__init__ def _DG__init__(self: DG, *, - labelSettings: LabelSettings = LabelSettings( - LabelType.String, - LabelRelation.Isomorphism), + labelSettings: LabelSettings=_lsString, graphDatabase: List[Graph] = [], graphPolicy: IsomorphismPolicy = IsomorphismPolicy.Check) -> None: return _DG__init__old(self, # type: ignore @@ -318,28 +327,40 @@ def _DG__getattribute__(self: DG, name: str) -> Any: DG.__hash__ = lambda self: self.id # type: ignore -class DGBuildContextManager: - dg: Optional[DG] - _builder: Optional[DGBuilder] +class DGBuilder: + _builder: Optional[libpymod._DGBuilder] def __init__(self, dg: DG) -> None: assert dg is not None - self.dg = dg - self._builder = _DG_build_orig(self.dg) + self._builder = _DG_build_orig(dg) - def __enter__(self) -> "DGBuildContextManager": + def __enter__(self) -> "DGBuilder": return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: del self._builder - self.dg = None self._builder = None + @property + def dg(self) -> DG: + assert self._builder + return self._builder.dg + + @property + def isActive(self) -> bool: + assert self._builder + return self._builder.isActive + def addDerivation(self, d: Derivations, graphPolicy: IsomorphismPolicy = IsomorphismPolicy.Check) -> DGHyperEdge: assert self._builder return self._builder.addDerivation(d, graphPolicy) + def addHyperEdge(self, e: DGHyperEdge, + graphPolicy: IsomorphismPolicy = IsomorphismPolicy.Check) -> DGHyperEdge: + assert self._builder + return self._builder.addHyperEdge(e, graphPolicy) + def execute(self, strategy: DGStrat, *, verbosity: int=2, ignoreRuleLabelTypes: bool=False) -> DGExecuteResult: assert self._builder return self._builder.execute(dgStrat(strategy), verbosity, ignoreRuleLabelTypes) # type: ignore @@ -360,7 +381,7 @@ def load(self, ruleDatabase: List[Rule], f: str, verbosity: int = 2) -> None: prefixFilename(f), verbosity) _DG_build_orig = DG.build -DG.build = lambda self: DGBuildContextManager(self) # type: ignore +DG.build = lambda self: DGBuilder(self) # type: ignore #---------------------------------------------------------- @@ -396,7 +417,7 @@ def _makeGraphToVertexCallback(orig, name, func): def callback(self, f, *args, **kwargs): if hasattr(f, "__call__"): import inspect - spec = inspect.getargspec(f) + spec = inspect.getfullargspec(f) if len(spec.args) == 2: _deprecation("The callback for {} seems to take two arguments, a graph and a derivation graph. This is deprecated, the callback should take a single DGVertex argument.".format(name)) fOrig = f @@ -438,6 +459,16 @@ def callback(self, f, *args, **kwargs): DGPrinter.setMirrorOverwrite = ( # type: ignore lambda self, f: _DGPrinter_setMirrorOverwrite_orig(self, _funcWrap(libpymod._Func_BoolGraph, f))) +_DGPrinter_setImageOverwrite_orig = DGPrinter.setImageOverwrite +def _DGPrinter_setImageOverwrite(self, f): + if f is None: + wrapped = None + else: + wrapped = _funcWrap( + libpymod._Func_PairStringStringDGVertexInt, f) + return _DGPrinter_setImageOverwrite_orig(self, wrapped) +DGPrinter.setImageOverwrite = _DGPrinter_setImageOverwrite # type: ignore + #---------------------------------------------------------- # Strategy @@ -497,7 +528,7 @@ def _DGStrat_makeRightPredicate(pred: Callable[[Derivation], bool], strat: DGStr # DG Strategy Prettification #---------------------------------------------------------- -_DGStratType = Union[DGStrat, Rule, "_DGStrat_sequenceProxy", Iterable['_DGStratType']] # type: ignore +_DGStratType = Union[DGStrat, Rule, "_DGStrat_sequenceProxy", Iterable['_DGStratType']] def dgStrat(s: _DGStratType) -> DGStrat: if isinstance(s, DGStrat): @@ -506,7 +537,7 @@ def dgStrat(s: _DGStratType) -> DGStrat: return DGStrat.makeRule(s) elif isinstance(s, _DGStrat_sequenceProxy): return DGStrat.makeSequence(s.strats) - elif isinstance(s, collections.Iterable): + elif isinstance(s, collections.abc.Iterable): # do deep dgStrat l = [dgStrat(a) for a in s] return DGStrat.makeParallel(l) @@ -656,12 +687,15 @@ def _Graph_print(self: Graph, first: Optional[GraphPrinter]=None, second: Option Graph.print = _Graph_print # type: ignore _Graph_aut = Graph.aut -Graph.aut = lambda self, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism): _Graph_aut(self, labelSettings) # type: ignore +Graph.aut = lambda self, labelSettings=_lsString: _Graph_aut(self, labelSettings) # type: ignore _Graph_isomorphism = Graph.isomorphism -Graph.isomorphism = lambda self, g, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism): _Graph_isomorphism(self, g, maxNumMatches, labelSettings) # type: ignore +Graph.isomorphism = lambda self, g, maxNumMatches=1, labelSettings=_lsString: _Graph_isomorphism(self, g, maxNumMatches, labelSettings) # type: ignore _Graph_monomorphism = Graph.monomorphism -Graph.monomorphism = lambda self, g, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism): _Graph_monomorphism(self, g, maxNumMatches, labelSettings) # type: ignore +Graph.monomorphism = lambda self, g, maxNumMatches=1, labelSettings=_lsString: _Graph_monomorphism(self, g, maxNumMatches, labelSettings) # type: ignore +_Graph_enumerateMonomorphisms = Graph.enumerateMonomorphisms # type: ignore +Graph.enumerateMonomorphisms = lambda self, codomain, *, callback, labelSettings=_lsString: _Graph_enumerateMonomorphisms( # type: ignore + self, codomain, _funcWrap(libpymod._Func_BoolVertexMapGraphGraph, callback), labelSettings) # type: ignore _Graph_getGMLString = Graph.getGMLString Graph.getGMLString = lambda self, withCoords=False: _Graph_getGMLString(self, withCoords) # type: ignore @@ -678,47 +712,92 @@ def _graphLoad(a: Graph, name: Optional[str], add: bool) -> Graph: inputGraphs.append(a) return a -def _graphsLoad(gs, add): +def _graphsLoad(gs: List[Graph], add: bool) -> List[Graph]: us = _unwrap(gs) res = [_graphLoad(a, name=None, add=add) for a in us] return res +def _graphssLoad(gs: List[List[Graph]], add: bool) -> List[List[Graph]]: + us = _unwrap(gs) + res = [_graphsLoad(a, add=add) for a in us] + return res + _Graph_fromGMLString_orig = Graph.fromGMLString _Graph_fromGMLFile_orig = Graph.fromGMLFile _Graph_fromGMLStringMulti_orig = Graph.fromGMLStringMulti _Graph_fromGMLFileMulti_orig = Graph.fromGMLFileMulti _Graph_fromDFS_orig = Graph.fromDFS +_Graph_fromDFSMulti_orig = Graph.fromDFSMulti _Graph_fromSMILES_orig = Graph.fromSMILES _Graph_fromSMILESMulti_orig = Graph.fromSMILESMulti - -def _Graph_fromGMLString(s: str, name: Optional[str] = None, add: bool = True) -> Graph: - return _graphLoad(_Graph_fromGMLString_orig( s), name, add) -def _Graph_fromGMLFile( f: str, name: Optional[str] = None, add: bool = True) -> Graph: - return _graphLoad(_Graph_fromGMLFile_orig(prefixFilename(f)), name, add) -def _Graph_fromGMLStringMulti(s: str, add: bool = True): - return _graphsLoad(_Graph_fromGMLStringMulti_orig( s), add) -def _Graph_fromGMLFileMulti( f: str, add: bool = True): - return _graphsLoad(_Graph_fromGMLFileMulti_orig(prefixFilename(f)), add) -def _Graph_fromDFS( s: str, name: Optional[str] = None, add: bool = True) -> Graph: - return _graphLoad(_Graph_fromDFS_orig( s), name, add) -def _Graph_fromSMILES( s: str, name: Optional[str] = None, allowAbstract: bool = False, classPolicy: SmilesClassPolicy = SmilesClassPolicy.NoneOnDuplicate, add: bool = True) -> Graph: - return _graphLoad(_Graph_fromSMILES_orig( s, allowAbstract, classPolicy), name, add) -def _Graph_fromSMILESMulti(s: str, allowAbstract: bool = False, classPolicy: SmilesClassPolicy = SmilesClassPolicy.NoneOnDuplicate, add: bool = True): - return _graphsLoad(_Graph_fromSMILESMulti_orig( s, allowAbstract, classPolicy), add) +_Graph_fromMOLString_orig = Graph.fromMOLString +_Graph_fromMOLFile_orig = Graph.fromMOLFile +_Graph_fromMOLStringMulti_orig = Graph.fromMOLStringMulti +_Graph_fromMOLFileMulti_orig = Graph.fromMOLFileMulti +_Graph_fromSDString_orig = Graph.fromSDString +_Graph_fromSDFile_orig = Graph.fromSDFile +_Graph_fromSDStringMulti_orig = Graph.fromSDStringMulti +_Graph_fromSDFileMulti_orig = Graph.fromSDFileMulti + +def _Graph_fromGMLString( s: str, name: Optional[str] = None, add: bool = True) -> Graph: + return _graphLoad(_Graph_fromGMLString_orig( s ), name, add) +def _Graph_fromGMLFile( f: str, name: Optional[str] = None, add: bool = True) -> Graph: + return _graphLoad(_Graph_fromGMLFile_orig( prefixFilename(f) ), name, add) +def _Graph_fromGMLStringMulti(s: str, add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromGMLStringMulti_orig( s ), add) +def _Graph_fromGMLFileMulti( f: str, add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromGMLFileMulti_orig(prefixFilename(f) ), add) +def _Graph_fromDFS( s: str, name: Optional[str] = None, add: bool = True) -> Graph: + return _graphLoad(_Graph_fromDFS_orig( s ), name, add) +def _Graph_fromDFSMulti( s: str, add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromDFSMulti_orig( s ), add) +def _Graph_fromSMILES( s: str, name: Optional[str] = None, allowAbstract: bool = False, classPolicy: SmilesClassPolicy = SmilesClassPolicy.NoneOnDuplicate, + add: bool = True) -> Graph: + return _graphLoad(_Graph_fromSMILES_orig( s, allowAbstract, classPolicy ), name, add) +def _Graph_fromSMILESMulti( s: str, allowAbstract: bool = False, classPolicy: SmilesClassPolicy = SmilesClassPolicy.NoneOnDuplicate, + add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromSMILESMulti_orig( s, allowAbstract, classPolicy ), add) +def _Graph_fromMOLString( s: str, name: Optional[str] = None, options: MDLOptions = MDLOptions(), add: bool = True) -> Graph: + return _graphLoad(_Graph_fromMOLString_orig( s, options ), name, add) +def _Graph_fromMOLFile( f: str, name: Optional[str] = None, options: MDLOptions = MDLOptions(), add: bool = True) -> Graph: + return _graphLoad(_Graph_fromMOLFile_orig( prefixFilename(f), options ), name, add) +def _Graph_fromMOLStringMulti(s: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromMOLStringMulti_orig( s, options ), add) +def _Graph_fromMOLFileMulti( f: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromMOLFileMulti_orig(prefixFilename(f), options ), add) +def _Graph_fromSDString( s: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromSDString_orig( s, options ), add) +def _Graph_fromSDFile( f: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[Graph]: + return _graphsLoad(_Graph_fromSDFile_orig( prefixFilename(f), options ), add) +def _Graph_fromSDStringMulti( s: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[List[Graph]]: + return _graphssLoad(_Graph_fromSDStringMulti_orig( s, options ), add) +def _Graph_fromSDFileMulti( f: str, options: MDLOptions = MDLOptions(), add: bool = True) -> List[List[Graph]]: + return _graphssLoad(_Graph_fromSDFileMulti_orig(prefixFilename(f), options ), add) Graph.fromGMLString = _Graph_fromGMLString # type: ignore Graph.fromGMLFile = _Graph_fromGMLFile # type: ignore Graph.fromGMLStringMulti = _Graph_fromGMLStringMulti # type: ignore Graph.fromGMLFileMulti = _Graph_fromGMLFileMulti # type: ignore Graph.fromDFS = _Graph_fromDFS # type: ignore +Graph.fromDFSMulti = _Graph_fromDFSMulti # type: ignore Graph.fromSMILES = _Graph_fromSMILES # type: ignore Graph.fromSMILESMulti = _Graph_fromSMILESMulti # type: ignore +Graph.fromMOLString = _Graph_fromMOLString # type: ignore +Graph.fromMOLFile = _Graph_fromMOLFile # type: ignore +Graph.fromMOLStringMulti = _Graph_fromMOLStringMulti # type: ignore +Graph.fromMOLFileMulti = _Graph_fromMOLFileMulti # type: ignore +Graph.fromSDString = _Graph_fromSDString # type: ignore +Graph.fromSDFile = _Graph_fromSDFile # type: ignore +Graph.fromSDStringMulti = _Graph_fromSDStringMulti # type: ignore +Graph.fromSDFileMulti = _Graph_fromSDFileMulti # type: ignore graphGMLString = Graph.fromGMLString graphGML = Graph.fromGMLFile graphDFS = Graph.fromDFS smiles = Graph.fromSMILES +########################################################### + Graph.__repr__ = lambda self: str(self) + "(" + str(self.id) + ")" # type: ignore Graph.__eq__ = lambda self, other: self.id == other.id # type: ignore Graph.__lt__ = lambda self, other: self.id < other.id # type: ignore @@ -733,7 +812,35 @@ def _Graph__setattr__(self: Graph, name: str, value: Any) -> None: ########################################################### -# Rule +# Post +########################################################### + +def _post_command(self, cmd: str) -> None: + _deprecation("'post(cmd)' is deprecated. Use 'post.command(cmd)'.") + return post.command(cmd) +post.__init__ = _post_command # type: ignore + +def postFlush() -> None: + _deprecation("'postFlush()' is deprecated. Use 'post.flushCommands()'.") + return post.flushCommands() +def postDisable() -> None: + _deprecation("'postDisable()' is deprecated. Use 'post.disableCommands()'.") + return post.disableCommands() +def postEnable() -> None: + _deprecation("'postEnable()' is deprecated. Use 'post.enableCommands()'.") + return post.enableCommands() +def postReset() -> None: + _deprecation("'postReset()' is deprecated. Use 'post.reopenCommandFile()'.") + return post.reopenCommandFile() + +def postChapter(heading: str) -> None: + _deprecation("'postChapter(heading)' is deprecated. Use 'post.summaryChapter(heading)'.") + post.summaryChapter(heading) +def postSection(heading: str) -> None: + _deprecation("'postSection(heading)' is deprecated. Use 'post.summarySection(heading)'.") + post.summarySection(heading) + + ########################################################### inputRules = [] @@ -749,9 +856,9 @@ def _Rule_print(self: Rule, first: Optional[GraphPrinter]=None, second: Optional Rule.print = _Rule_print # type: ignore _Rule_isomorphism = Rule.isomorphism -Rule.isomorphism = lambda self, r, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism): _Rule_isomorphism(self, r, maxNumMatches, labelSettings) # type: ignore +Rule.isomorphism = lambda self, r, maxNumMatches=1, labelSettings=_lsString: _Rule_isomorphism(self, r, maxNumMatches, labelSettings) # type: ignore _Rule_monomorphism = Rule.monomorphism -Rule.monomorphism = lambda self, r, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism): _Rule_monomorphism(self, r, maxNumMatches, labelSettings) # type: ignore +Rule.monomorphism = lambda self, r, maxNumMatches=1, labelSettings=_lsString: _Rule_monomorphism(self, r, maxNumMatches, labelSettings) # type: ignore _Rule_getGMLString = Rule.getGMLString Rule.getGMLString = lambda self, withCoords=False: _Rule_getGMLString(self, withCoords) # type: ignore @@ -765,14 +872,18 @@ def _ruleLoad(a: Rule, add: bool) -> Rule: _Rule_fromGMLString_orig = Rule.fromGMLString _Rule_fromGMLFile_orig = Rule.fromGMLFile +_Rule_fromDFS_orig = Rule.fromDFS def _Rule_fromGMLString(s: str, invert: bool=False, add: bool=True) -> Rule: return _ruleLoad(_Rule_fromGMLString_orig(s, invert), add) def _Rule_fromGMLFile(f: str, invert: bool=False, add: bool=True) -> Rule: return _ruleLoad(_Rule_fromGMLFile_orig(prefixFilename(f), invert), add) +def _Rule_fromDFS(s: str, invert: bool=False, add: bool=True) -> Rule: + return _ruleLoad(_Rule_fromDFS_orig(s, invert), add) Rule.fromGMLString = _Rule_fromGMLString # type: ignore Rule.fromGMLFile = _Rule_fromGMLFile # type: ignore +Rule.fromDFS = _Rule_fromDFS # type: ignore ruleGMLString = Rule.fromGMLString ruleGML = Rule.fromGMLFile @@ -799,7 +910,7 @@ def _RCEvaluator__getattribute__(self: RCEvaluator, name: str) -> Any: _RCEvaluator_eval = RCEvaluator.eval RCEvaluator.eval = lambda self, exp, *, verbosity=2: _unwrap(_RCEvaluator_eval(self, exp, verbosity)) # type: ignore -def rcEvaluator(rules: Iterable[Rule], labelSettings: LabelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) -> RCEvaluator: +def rcEvaluator(rules: Iterable[Rule], labelSettings: LabelSettings=_lsString) -> RCEvaluator: return libpymod._rcEvaluator(_wrap(libpymod._VecRule, rules), labelSettings) @@ -823,7 +934,7 @@ def _RCMatch_composeAll(self, *, maximum=False, verbose=False): # RCExp prettification #---------------------------------------------------------- -_rcExpType = Union[RCExpExp, RCExpBind, RCExpComposeCommon, RCExpComposeParallel, RCExpComposeSub, RCExpComposeSuper, Iterable["_rcExpType"]] # type: ignore +_rcExpType = Union[RCExpExp, RCExpBind, RCExpComposeCommon, RCExpComposeParallel, RCExpComposeSub, RCExpComposeSuper, Iterable["_rcExpType"]] def rcExp(e: _rcExpType) -> RCExpExp: if isinstance(e, RCExpExp) or isinstance(e, Rule) or isinstance(e, RCExpUnion): @@ -832,7 +943,7 @@ def rcExp(e: _rcExpType) -> RCExpExp: return e elif isinstance(e, RCExpComposeCommon) or isinstance(e, RCExpComposeParallel) or isinstance(e, RCExpComposeSub) or isinstance(e, RCExpComposeSuper): return e - elif isinstance(e, collections.Iterable): + elif isinstance(e, collections.abc.Iterable): return RCExpUnion(_wrap(libpymod._VecRCExpExp, [rcExp(a) for a in e])) else: raise TypeError("Can not convert type '" + str(type(e)) + "' to RCExpExp") @@ -842,7 +953,7 @@ def rcExp(e: _rcExpType) -> RCExpExp: def _rcConvertGraph(g: _GraphOrGraphs, cls: Type[Union[RCExpBind, RCExpId, RCExpUnbind]], f: Callable[[Graph], RCExpExp]) -> RCExpExp: if isinstance(g, Graph): return cls(g) - elif isinstance(g, collections.Iterable): + elif isinstance(g, collections.abc.Iterable): l = [f(a) for a in g] return rcExp(l) else: diff --git a/libs/pymod/lib/mod/latex.py b/libs/pymod/lib/mod/latex.py index 559857b..8c2112e 100644 --- a/libs/pymod/lib/mod/latex.py +++ b/libs/pymod/lib/mod/latex.py @@ -15,14 +15,14 @@ def setTexFile(fName): global _texFile _texFile = open(fName, "w") - mod.post("disableSummary") + mod.post.disableCompileSummary() def setFigFolder(fName): global _figFolder _figFolder = fName _checkSettings() - mod.post("post \"mkdir -p '%s'\"" % _figFolder) + mod.post.command("post \"mkdir -p '%s'\"" % _figFolder) def _checkSettings(): @@ -39,7 +39,7 @@ def outputFile(f, inline=False): assert f.endswith(".pdf") f = f[:-4] f += ".tex" if inline else ".pdf" - mod.post("post \"cp '%s' '%s/'\"" % (f, _figFolder)) + mod.post.command("post \"cp '%s' '%s/'\"" % (f, _figFolder)) res = _figFolder + "/" + os.path.basename(f) return res[:-4] @@ -70,15 +70,15 @@ def graph(id, g, p, inline): def graphGML(id, data, printer, inline=False): - graph(id, mod.graphGML(data), printer, inline) + graph(id, mod.Graph.fromGMLFile(data), printer, inline) def smiles(id, data, printer, inline=False): - graph(id, mod.smiles(data.replace('##', '#')), printer, inline) + graph(id, mod.Graph.fromSMILES(data.replace('##', '#')), printer, inline) def graphDFS(id, data, printer, inline=False): - graph(id, mod.graphDFS(data.replace('##', '#')), printer, inline) + graph(id, mod.Graph.fromDFS(data.replace('##', '#')), printer, inline) # ------------------------------------------------------------------------------ @@ -102,4 +102,4 @@ def rule(id, r, p): def ruleGML(id, data, printer): - rule(id, mod.ruleGML(data), printer) + rule(id, mod.Rule.fromGMLFile(data), printer) diff --git a/libs/pymod/lib/mod/libpymod.pyi b/libs/pymod/lib/mod/libpymod.pyi index 20e5ad9..0d8e923 100644 --- a/libs/pymod/lib/mod/libpymod.pyi +++ b/libs/pymod/lib/mod/libpymod.pyi @@ -35,8 +35,12 @@ class _Func_BoolDerivation: class _Func_BoolDGVertex: def __call__(self, v: DGVertex) -> bool: ... +class _Func_DoubleDGVertex: + def __call__(self, v: DGVertex) -> float: ... class _Func_StringDGVertex: def __call__(self, v: DGVertex) -> str: ... +class _Func_StringDGVertexInt: + def __call__(self, v: DGVertex, dupNum: int) -> str: ... class _Func_BoolDGHyperEdge: def __call__(self, e: DGHyperEdge) -> bool: ... @@ -98,6 +102,7 @@ class SmilesClassPolicy(enum.Enum): MapUnique: int def _SmilesClassPolicy__str__(self: SmilesClassPolicy) -> str: ... +class MDLOptions: ... def _getAvailableILPSolvers() -> List[str]: ... @@ -131,47 +136,25 @@ class Derivations: ... def rngUniformReal() -> float: ... - -#----------------------------------------------------------------------------- -# causality -#----------------------------------------------------------------------------- - -# Petri -#----------------------------------------------------------------------------- - -class PetriNet: - def __init__(self, dg: DG) -> None: ... - def syncSize(self) -> None: ... - - -class PetriNetMarking: - def __init__(self, net: PetriNet) -> None: ... - def syncSize(self) -> None: ... - def add(self, v: DGVertex, c: int) -> int: ... - def remove(self, v: DGVertex, c: int) -> int: ... - def __getitem__(self, v: DGVertex) -> int: ... - def getAllEnabled(self) -> List[DGHyperEdge]: ... - def getNonZeroPlaces(self) -> List[DGVertex]: ... - def getEmptyPostPlaces(self, e: DGHyperEdge) -> List[DGVertex]: ... - def fire(self, e: DGHyperEdge) -> None: ... - - -class PetriNetMarkingSet: - def addIfNotSubset(self, m: PetriNetMarking) -> bool: ... - - -# Stochsim +# Post #----------------------------------------------------------------------------- -class MassActionKinetics: - def __init__(self, dg: DG, rate: Callable[[DGHyperEdge], float]) -> None: ... - def draw(self, possibles: List[DGHyperEdge], m: PetriNetMarking) -> Tuple[DGHyperEdge, float]: ... - +class post: + @staticmethod + def command(line: str) -> None: ... + @staticmethod + def flushCommands() -> None: ... + @staticmethod + def disableCommands() -> None: ... + @staticmethod + def enableCommands() -> None: ... + @staticmethod + def reopenCommandFile() -> None: ... -class EventTrace: - def __init__(self, initialState: PetriNetMarking) -> None: ... - def addEdge(self, time: float, e: DGHyperEdge) -> None: ... - def addTransfer(self, time: float, v: DGVertex, count: int) -> None: ... + @staticmethod + def summaryChapter(heading: str) -> None: ... + @staticmethod + def summarySection(heading: str) -> None: ... #----------------------------------------------------------------------------- @@ -187,17 +170,19 @@ class DG: def findEdge(self, sources: List[DGVertex], targets: List[DGVertex]) -> DGHyperEdge: ... @overload def findEdge(self, sourcesGraphs: List[Graph], targetGraphs: List[Graph]) -> DGHyperEdge: ... - def build(self) -> DGBuilder: ... + def build(self): ... def print(self, printer: DGPrinter=..., data: Optional[DGPrintData]=...) -> Tuple[str, str]: ... @staticmethod def load(graphDatabase: List[Graph], ruleDatabase: List[Rule], file: str, graphPolicy: IsomorphismPolicy=..., verbosity: int=...) -> DG: ... -class DGBuilder: - def __enter__(self) -> DGBuilder: ... - def __exit__(self, exc_type, exc_val, exc_tb) -> None: ... - +class _DGBuilder: + @property + def dg(self) -> DG: ... + @property + def isActive(self) -> bool: ... def addDerivation(self, d: Derivations, graphPolicy: IsomorphismPolicy = ...) -> DGHyperEdge: ... + def addHyperEdge(self, e: DGHyperEdge, graphPolicy: IsomorphismPolicy = ...) -> DGHyperEdge: ... def execute(self, strategy: DGStrat, *, verbosity: int=..., ignoreRuleLabelTypes: bool=...) -> DGExecuteResult: ... def apply(self, graphs: List[Graph], rule: Rule, onlyProper: bool = ..., verbosity: int = ..., graphPolicy: IsomorphismPolicy = ...) -> List[DGHyperEdge]: ... def addAbstract(self, description: str) -> None: ... @@ -225,6 +210,7 @@ class DGPrinter: def pushEdgeColour(self, f: Union[Callable[[DGHyperEdge], str], str]) -> None: ... def setRotationOverwrite(self, f: Union[Callable[[Graph], int], int]) -> None: ... def setMirrorOverwrite(self, f: Union[Callable[[Graph], bool], bool]) -> None: ... + def setImageOverwrite(self, f: Union[Callable[[DGVertex, int], str], str]) -> None: ... class DGPrintData: @@ -288,12 +274,40 @@ class Graph: def fromGMLFileMulti( f: str) -> List[Graph]: ... @staticmethod - def fromDFS(s: str) -> Graph: ... + def fromDFS( s: str) -> Graph: ... + @staticmethod + def fromDFSMulti( s: str) -> List[Graph]: ... @staticmethod - def fromSMILES( s: str, allowAbstract: bool = ..., classPolicy: SmilesClassPolicy = ...) -> Graph: ... + def fromSMILES( s: str, allowAbstract: bool = ..., classPolicy: SmilesClassPolicy = ...) -> Graph: ... @staticmethod - def fromSMILESMulti(s: str, allowAbstract: bool = ..., classPolicy: SmilesClassPolicy = ...) -> List[Graph]: ... + def fromSMILESMulti( s: str, allowAbstract: bool = ..., classPolicy: SmilesClassPolicy = ...) -> List[Graph]: ... + + @staticmethod + def fromMOLString( s: str, options: MDLOptions = ..., add: bool = ...) -> Graph: ... + @staticmethod + def fromMOLFile( f: str, options: MDLOptions = ..., add: bool = ...) -> Graph: ... + @staticmethod + def fromMOLStringMulti(s: str, options: MDLOptions = ..., add: bool = ...) -> List[Graph]: ... + @staticmethod + def fromMOLFileMulti( f: str, options: MDLOptions = ..., add: bool = ...) -> List[Graph]: ... + @staticmethod + def fromSDString( s: str, options: MDLOptions = ..., add: bool = ...) -> List[Graph]: ... + @staticmethod + def fromSDFile( f: str, options: MDLOptions = ..., add: bool = ...) -> List[Graph]: ... + @staticmethod + def fromSDStringMulti( s: str, options: MDLOptions = ..., add: bool = ...) -> List[List[Graph]]: ... + @staticmethod + def fromSDFileMulti( f: str, options: MDLOptions = ..., add: bool = ...) -> List[List[Graph]]: ... + + @staticmethod + def fromRXNString( s: str, options: MDLOptions = ..., add: bool = ...): ... + @staticmethod + def fromRXNFile( f: str, options: MDLOptions = ..., add: bool = ...): ... + @staticmethod + def fromRXNStringMulti(s: str, options: MDLOptions = ..., add: bool = ...): ... + @staticmethod + def fromRXNFileMulti( f: str, options: MDLOptions = ..., add: bool = ...): ... class GraphAutGroup: ... @@ -302,6 +316,15 @@ class GraphAutGroup: ... class GraphPrinter: ... +def graphGMLString(s: str) -> Graph: ... +def graphGML(f: str) -> Graph: ... +def graphDFS(s: str) -> Graph: ... +def smiles(s: str, allowAbstract: bool, classPolicy: SmilesClassPolicy) -> Graph: ... +def mdlMOLString(s: str, addHydrogens: bool) -> Graph: ... +def mdlMOL(f: str, addHydrogens: bool) -> Graph: ... +def mdlSDString(s: str, addHydrogens: bool) -> List[Graph]: ... +def mdlSD(f: str, addHydrogens: bool) -> List[Graph]: ... + #----------------------------------------------------------------------------- # rule @@ -357,6 +380,8 @@ class Rule(RCExpExp): def fromGMLString(s: str, invert: bool=...) -> Rule: ... @staticmethod def fromGMLFile(f: str, invert: bool=...) -> Rule: ... + @staticmethod + def fromDFS(s: str, invert: bool=...) -> Rule: ... def _rcEvaluator(rules: Iterable[Rule], labelSettings: LabelSettings=...) -> RCEvaluator: ... diff --git a/libs/pymod/redirector/mod/__init__.py.in b/libs/pymod/redirector/mod/__init__.py.in new file mode 100644 index 0000000..bd3a76e --- /dev/null +++ b/libs/pymod/redirector/mod/__init__.py.in @@ -0,0 +1,6 @@ +import sys +sys.path.insert(0, "@CMAKE_INSTALL_FULL_LIBDIR@") +del sys.modules['mod'] +import mod +assert not mod._redirected +mod._redirected = True \ No newline at end of file diff --git a/libs/pymod/redirector/pyproject.toml b/libs/pymod/redirector/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/libs/pymod/redirector/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/libs/pymod/redirector/setup.cfg.in b/libs/pymod/redirector/setup.cfg.in new file mode 100644 index 0000000..f39e9d1 --- /dev/null +++ b/libs/pymod/redirector/setup.cfg.in @@ -0,0 +1,10 @@ +[metadata] +name = mod-jakobandersen +version = @mod_VERSION@ +author = @mod_AUTHOR@ +author_email = @mod_AUTHOR_EMAIL@ +description = A package just for redirecting the mod import to the /lib folder. + +[options] +packages = + mod \ No newline at end of file diff --git a/libs/pymod/share/mod/python.supp b/libs/pymod/share/mod/python.supp index b9abc9b..dc36fee 100644 --- a/libs/pymod/share/mod/python.supp +++ b/libs/pymod/share/mod/python.supp @@ -15,6 +15,15 @@ # # See Misc/README.valgrind for more information. +{ + When using Open Babel from Python this appears. + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:_PyObject_GC_Alloc* +} + # all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif { ADDRESS_IN_RANGE/Invalid read of size 4 @@ -102,6 +111,14 @@ fun:_PyObject_GC_Resize #fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING } +{ + Handle PyMalloc confusing valgrind (possibly leaked), manually added + Memcheck:Leak + fun:realloc + ... + fun:_PyObject_GC_Resize + #fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} { Handle PyMalloc confusing valgrind (possibly leaked) diff --git a/libs/pymod/src/mod/py/Chem.cpp b/libs/pymod/src/mod/py/Chem.cpp index 7b7fc1e..71472c8 100644 --- a/libs/pymod/src/mod/py/Chem.cpp +++ b/libs/pymod/src/mod/py/Chem.cpp @@ -49,7 +49,7 @@ void Chem_doExport() { // rst: py::class_("Isotope", py::no_init) // rst: .. method:: __init__() - // rst: __init__(i) + // rst: __init__(i) // rst: // rst: Construct a representation of an isotope. // rst: If an isotope number is given, that specific one is constructed, @@ -93,7 +93,7 @@ void Chem_doExport() { // rst: Representation of basic data of an atom. // rst: py::class_("AtomData", py::no_init) - // rst: .. method:: __init__(atomId=AtomIds.Invalid, istotope=Isotope(), charge=Charge(), radical=False) + // rst: .. method:: __init__(atomId=AtomIds.Invalid, isotope=Isotope(), charge=Charge(), radical=False) // rst: // rst: Construct an atom data object. // rst: @@ -156,7 +156,6 @@ void Chem_doExport() { py::def("_bondTypeToString", &bondTypeToString); - // rst: .. class:: AtomIds // rst: // rst: This class contains constants for each chemical element, both as their abbreviations and their full names. diff --git a/libs/pymod/src/mod/py/Collections.cpp b/libs/pymod/src/mod/py/Collections.cpp index d73efae..46d91bb 100644 --- a/libs/pymod/src/mod/py/Collections.cpp +++ b/libs/pymod/src/mod/py/Collections.cpp @@ -54,6 +54,7 @@ bool listCompare(const std::vector &l, py::object r) { template void makePair() { py::to_python_converter, PairToTupleConverter>(); + TupleToPairConverter(); } void Collections_doExport() { @@ -63,6 +64,7 @@ void Collections_doExport() { makeVector(VecDGHyperEdge, dg::DG::HyperEdge); makeVector(VecDGStrat, std::shared_ptr); makeVector(VecGraph, std::shared_ptr); + makeVector(VecVecGraph, std::vector>); makeVector(VecRule, std::shared_ptr); using PairString = std::pair; makeVector(VecPairString, PairString); @@ -76,7 +78,6 @@ void Collections_doExport() { makePair(); makePair(); makePair(); - makePair(); // Optional py::to_python_converter, ToPythonOptionalValue>(); diff --git a/libs/pymod/src/mod/py/Config.cpp b/libs/pymod/src/mod/py/Config.cpp index 9bbe6ca..49db56a 100644 --- a/libs/pymod/src/mod/py/Config.cpp +++ b/libs/pymod/src/mod/py/Config.cpp @@ -23,6 +23,10 @@ std::string SmilesClassPolicy_str(SmilesClassPolicy p) { return boost::lexical_cast(p); } +std::string Action_str(Action a) { + return boost::lexical_cast(a); +} + } // namespace void Config_doExport() { @@ -154,6 +158,127 @@ void Config_doExport() { .value("MapUnique", SmilesClassPolicy::MapUnique); py::def("_SmilesClassPolicy__str__", &SmilesClassPolicy_str); + // rst: .. class:: Action + // rst: + // rst: Utility enum for deciding what to do in certain cases. + // rst: + py::enum_("Action") + // rst: .. attribute:: Error + // rst: + // rst: Abort the function and produce an error message, e.g., through and exception. + .value("Error", Action::Error) + // rst: .. attribute:: Warn + // rst: + // rst: Write a warning, but otherwise do as if it was `Ignore`. + .value("Warn", Action::Warn) + // rst: .. attribute:: Ignore + // rst: + // rst: Ignore the case. The function taking the action as argument should describe what this means. + .value("Ignore", Action::Ignore); + py::def("_Action__str__", &Action_str); + + // rst: .. class:: MDLOptions + // rst: + // rst: An aggregation of options for the various loading functions for MDL formats. + // rst: Generally each option is defaulted to follow the specification of the formats, + // rst: unless it is harmless to deviate (e.g., relaxed white-space parsing). + // rst: + py::class_("MDLOptions") + // rst: .. attribute:: addHydrogens = True + // rst: + // rst: Use the MDL valence model to add hydrogens to atoms with default valence, or disable all hydrogen addition. + // rst: + // rst: :type: bool + .def_readwrite("addHydrogens", &MDLOptions::addHydrogens) + // rst: .. attribute:: allowAbstract = False + // rst: + // rst: Allow non-standard atom symbols. The standard symbols are the element symbols and those specifying wildcard atoms. + // rst: + // rst: :type: bool + .def_readwrite("allowAbstract", &MDLOptions::allowAbstract) + // rst: .. attribute:: applyV2000AtomAliases = True + // rst: + // rst: In MOL V2000 CTAB blocks, replace atom labels by their aliases. + // rst: After application, the atom is considered abstract without errors, and hydrogen addition is suppressed. + // rst: + // rst: :type: bool + .def_readwrite("applyV2000AtomAliases", &MDLOptions::applyV2000AtomAliases) + // rst: .. attribute:: Action onPatternIsotope = Action::Error + // rst: Action onPatternCharge = Action::Error; + // rst: Action onPatternRadical = Action::Error; + // rst: + // rst: What to do when an atom with symbol ``*`` has an isotope, charge, or radical. + // rst: ``Action.Ignore`` means assuming the isotope, charge, or radical was not there. + .def_readwrite("onPatternIsotope", &MDLOptions::onPatternIsotope) + .def_readwrite("onPatternCharge", &MDLOptions::onPatternCharge) + .def_readwrite("onPatternRadical", &MDLOptions::onPatternRadical) + // rst: .. attribute:: onImplicitValenceOnAbstract = Action.Error + // rst: + // rst: What to do when ``addHydrogens and allowAbstract`` and an abstract atom is encountered with implicit valence. + // rst: ``Action.Ignore`` means adding no hydrogens. + // rst: + // rst: :type: Action + .def_readwrite("onImplicitValenceOnAbstract", &MDLOptions::onImplicitValenceOnAbstract) + // rst: .. attribute:: onV2000UnhandledProperty = Action.Warn + // rst: + // rst: What to do when a property line in a V2000 MOL file is not recognized. + // rst: ``Action.Ignore`` means simply ignoring that particular line. + // rst: + // rst: :type: Action + .def_readwrite("onV2000UnhandledProperty", &MDLOptions::onV2000UnhandledProperty) + // rst: .. attribute:: fullyIgnoreV2000UnhandledKnownProperty = False + // rst: + // rst: Warnings are usually stored as "loading warnings", even when they are ignored as during parsing. + // rst: Setting this to ``True`` will act as if ``onV2000UnhandledProperty = Actions::Ignore`` and + // rst: skip the storage, but only for a pre-defined known subset of properties. + // rst: + // rst: :type: bool + .def_readwrite("fullyIgnoreV2000UnhandledKnownProperty", &MDLOptions::fullyIgnoreV2000UnhandledKnownProperty) + // rst: .. attribute:: onV3000UnhandledAtomProperty = Action.Warn + // rst: + // rst: What to do when a property in atom line in a V3000 MOL file is not recognized. + // rst: ``Action.Ignore`` means simply ignoring that particular property. + // rst: + // rst: :type: Action + .def_readwrite("onV3000UnhandledAtomProperty", &MDLOptions::onV3000UnhandledAtomProperty) + // rst: .. attribute:: onV2000Charge4 = Action.Warn + // rst: + // rst: What to do when an atom in a V2000 MOL file has the charge 4 (doublet radical). + // rst: ``Action.Ignore`` means assuming it was charge 0. + // rst: + // rst: :type: Action + .def_readwrite("onV2000Charge4", &MDLOptions::onV2000Charge4) + // rst: .. attribute:: onV2000AbstractISO = Action.Warn + // rst: + // rst: What to do when an abstract atom in a V2000 MOL file has a non-default ISO or mass difference value. + // rst: ``Action.Ignore`` means assuming it had no ISO or mass difference value. + // rst: + // rst: :type: Action + .def_readwrite("onV2000AbstractISO", &MDLOptions::onV2000AbstractISO) + // rst: .. attribute:: onRAD1 = Action.Error + // rst: onRAD3 = Action.Error + // rst: onRAD4 = Action.Error + // rst: onRAD5 = Action.Error + // rst: onRAD6 = Action.Error + // rst: + // rst: What to do when an atom has assigned the indicated radical state. + // rst: ``Action.Ignore`` means pretending the atom has no radical state assigned. + // rst: + // rst: :type: Action + .def_readwrite("onRAD1", &MDLOptions::onRAD1) + .def_readwrite("onRAD3", &MDLOptions::onRAD3) + .def_readwrite("onRAD4", &MDLOptions::onRAD4) + .def_readwrite("onRAD5", &MDLOptions::onRAD5) + .def_readwrite("onRAD6", &MDLOptions::onRAD6) + // rst: .. attribute:: onUnsupportedQueryBondType = Action.Error + // rst: + // rst: What to do when a bond type 5, 6, or 7 are encountered (constrained query bond types). + // rst: ``Action.Ignore`` means assigning a term variable, as if the type was 8. + // rst: + // rst: :type: Action + .def_readwrite("onUnsupportedQueryBondType", &MDLOptions::onUnsupportedQueryBondType); + + #define NSIter(rNS, dataNS, tNS) \ BOOST_PP_SEQ_FOR_EACH_I(SettingIter, ~, \ BOOST_PP_TUPLE_ELEM(MOD_CONFIG_DATA_NS_SIZE(), 2, tNS)) diff --git a/libs/pymod/src/mod/py/Function.cpp b/libs/pymod/src/mod/py/Function.cpp index c02d20e..3a464a2 100644 --- a/libs/pymod/src/mod/py/Function.cpp +++ b/libs/pymod/src/mod/py/Function.cpp @@ -1,9 +1,9 @@ -#include - -#include "Function.hpp" +#include #include +#include #include +#include #include #include #include @@ -19,7 +19,11 @@ void Function_doExport() { exportFunc("_Func_StringDerivation"); // DG::Vertex -> X exportFunc("_Func_BoolDGVertex"); + exportFunc("_Func_DoubleDGVertex"); exportFunc("_Func_StringDGVertex"); + // DG::Vertex x int -> X + exportFunc(dg::DG::Vertex, int)>( + "_Func_PairStringStringDGVertexInt"); // DG::HyperEdge -> X exportFunc("_Func_BoolDGHyperEdge"); exportFunc("_Func_StringDGHyperEdge"); @@ -28,10 +32,6 @@ void Function_doExport() { exportFunc)>("_Func_BoolGraph"); exportFunc)>("_Func_IntGraph"); exportFunc)>("_Func_StringGraph"); - // Graph x DG -> X - exportFunc, std::shared_ptr)>("_Func_StringGraphDG"); - // Graph x DG x bool -> X - exportFunc, std::shared_ptr, bool)>("_Func_StringGraphDGBool"); // Graph x Strategy::GraphState -> X exportFunc, const dg::Strategy::GraphState &)>( "_Func_BoolGraphDGStratGraphState"); @@ -43,6 +43,8 @@ void Function_doExport() { "_Func_BoolGraphGraphDGStratGraphState"); // Strategy::GraphState -> X exportFunc("_Func_VoidDGStratGraphState"); + + exportFunc)>("_Func_BoolVertexMapGraphGraph"); } } // namespace mod::Py diff --git a/libs/pymod/src/mod/py/Post.cpp b/libs/pymod/src/mod/py/Post.cpp index 2b5d562..8feb73b 100644 --- a/libs/pymod/src/mod/py/Post.cpp +++ b/libs/pymod/src/mod/py/Post.cpp @@ -5,19 +5,90 @@ namespace mod::post::Py { void Post_doExport() { - py::def("post", &command); - py::def("postReset", &reset); - py::def("postFlush", &flush); - py::def("postDisable", &disable); - - py::def("postChapter", &summaryChapter); - py::def("postSection", &summarySection); - // rst: .. function:: makeUniqueFilePrefix() // rst: - // rst: :returns: a unique file prefix from the ``out/`` folder. + // rst: :returns: a string on the form ``out/iii_`` where ```iii``` is the next zero-padded integer + // rst: from an internal counter. // rst: :rtype: str py::def("makeUniqueFilePrefix", &makeUniqueFilePrefix); + + struct Post { + // a fake class to hax a "submodule" + }; + // rst: .. class:: post + // rst: + // rst: This class (which will be a submodule in the future) + // rst: contains various functions to manipulate post-processing (:ref:`mod_post`). + // rst: Commands for the post-processor are written to the command file ``out/post.sh`` + // rst: which the post-processor executes internally as a Bash script. + // rst: + py::class_("post", py::no_init) + // rst: .. staticmethod:: command(line) + // rst: + // rst: Write the given text to the command file and write a newline character. + // rst: + // rst: :param str line: the text to be written. + // rst: + // rst: .. warning:: The contents of the command file is executed without any security checks. + .def("command", &command).staticmethod("command") + // rst: .. staticmethod:: flushCommands() + // rst: + // rst: Flush the command file buffer. + .def("flushCommands", &flushCommands).staticmethod("flushCommands") + // rst: .. staticmethod:: disableCommands() + // rst: + // rst: Disable command writing and flushing, also for commands emitted internally in the library. + .def("disableCommands", &disableCommands).staticmethod("disableCommands") + // rst: .. staticmethod:: enableCommands() + // rst: + // rst: Enable command writing and flushing, also for commands emitted internally in the library. + .def("enableCommands", &enableCommands).staticmethod("enableCommands") + // rst: .. staticmethod:: reopenCommandFile() + // rst: + // rst: Reopen the command file, which may be useful if it was modified externally while open by the library. + .def("reopenCommandFile", &reopenCommandFile).staticmethod("reopenCommandFile") + // rst: .. staticmethod:: summaryChapter(heading) + // rst: + // rst: Command the post-processor to insert a ``\chapter`` macro in the summary. + // rst: + // rst: :param str heading: the chapter heading to insert. + .def("summaryChapter", &summaryChapter).staticmethod("summaryChapter") + // rst: .. staticmethod:: summarySection(heading) + // rst: + // rst: Command the post-processor to insert a ``\section`` macro in the summary. + // rst: + // rst: :param str heading: the section heading to insert. + .def("summarySection", &summarySection).staticmethod("summarySection") + // rst: .. staticmethod:: summaryRaw(latexCode, file=...) + // rst: + // rst: Command the post-processor to insert the given code verbatim in the summary. + // rst: + // rst: :param str latexCode: the code to insert. + // rst: :param str file: if given then that will be appended to a unique prefix for the final filename the code is stored in. + .def("summaryRaw", static_cast(&summaryRaw)) + .def("summaryRaw", static_cast(&summaryRaw)) + .staticmethod("summaryRaw") + // rst: .. staticmethod:: summaryInput(filename) + // rst: + // rst: Command the post-processor to insert a ``\input`` macro in the summary. + // rst: + // rst: :param str filename: the filename to input. + .def("summaryInput", &summaryInput).staticmethod("summaryInput") + // rst: .. staticmethod:: disableInvokeMake() + // rst: enableInvokeMake() + // rst: + // rst: Disable/enable the invocation of Make in the post-processor. + // rst: The processing of commands and generation of Makefiles will still be carried out, + // rst: and Make invocation can be done manually afterwards through the post-processor + .def("disableInvokeMake", &disableInvokeMake).staticmethod("disableInvokeMake") + .def("enableInvokeMake", &enableInvokeMake).staticmethod("enableInvokeMake") + // rst: .. staticmethod:: disableCompileSummary() + // rst: enableCompileSummary() + // rst: + // rst: Disable/enable the compilation of the final summary during post-processing. + // rst: The compilation can be invoked manually afterwards through the post-processor. + .def("disableCompileSummary", &disableCompileSummary).staticmethod("disableCompileSummary") + .def("enableCompileSummary", &enableCompileSummary).staticmethod("enableCompileSummary"); } } // namespace mod::post::Py \ No newline at end of file diff --git a/libs/pymod/src/mod/py/dg/Builder.cpp b/libs/pymod/src/mod/py/dg/Builder.cpp index 01e1bf1..e6fe2e6 100644 --- a/libs/pymod/src/mod/py/dg/Builder.cpp +++ b/libs/pymod/src/mod/py/dg/Builder.cpp @@ -18,6 +18,7 @@ Builder_execute(std::shared_ptr b, std::shared_ptr strategy, void Builder_doExport() { using AddDerivation = DG::HyperEdge (Builder::*)(const Derivations &, IsomorphismPolicy); + using AddHyperEdge = DG::HyperEdge (Builder::*)(const DG::HyperEdge &, IsomorphismPolicy); using Apply = std::vector (Builder::*)(const std::vector > &, std::shared_ptr, bool, int, IsomorphismPolicy); @@ -38,22 +39,49 @@ void Builder_doExport() { // rst: // rst: Otherwise one can manually use ``del`` on the obtained builder to trigger the destruction. // rst: - py::class_, boost::noncopyable>("DGBuilder", py::no_init) - // rst: .. method:: addDerivation(d, graphPolicy=IsomorphismPolicy.Check) + py::class_, boost::noncopyable>("_DGBuilder", py::no_init) + // rst: .. attribute:: dg // rst: - // rst: Adds a hyperedge corresponding to the given derivation to the associated :class:`DG`. - // rst: If it already exists, only add the given rules to the edge. + // rst: The derivation graph this builder can modify. // rst: - // rst: :param Derivations d: a derivation to add a hyperedge for. - // rst: :param IsomorphismPolicy graphPolicy: the isomorphism policy for adding the given graphs. - // rst: :returns: the hyperedge corresponding to the given derivation. - // rst: :rtype: DGHyperEdge - // rst: :raises: :class:`LogicError` if ``d.left`` or ``d.right`` is empty. - // rst: :raises: :class:`LogicError` if a ``None``is in ``d.left``, ``d.right``, or ``d.rules``. - // rst: :raises: :class:`LogicError` if ``graphPolicy == IsomorphismPolicy.Check`` and a given graph object - // rst: is different but isomorphic to another given graph object or to a graph object already - // rst: in the internal graph database in the associated derivation graph. + // rst: :type: DG + .add_property("dg", &Builder::getDG) + // rst: .. attribute:: isActive + // rst: + // rst: Whether this object is associated with a :py:class:`DG`. + // rst: + // rst: :type: bool + .add_property("isActive", &Builder::isActive) + // rst: .. method:: addDerivation(d, graphPolicy=IsomorphismPolicy.Check) + // rst: + // rst: Adds a hyperedge corresponding to the given derivation to the associated :class:`DG`. + // rst: If it already exists, only add the given rules to the edge. + // rst: + // rst: :param Derivations d: a derivation to add a hyperedge for. + // rst: :param IsomorphismPolicy graphPolicy: the isomorphism policy for adding the given graphs. + // rst: :returns: the hyperedge corresponding to the given derivation. + // rst: :rtype: DGHyperEdge + // rst: :raises: :class:`LogicError` if ``d.left`` or ``d.right`` is empty. + // rst: :raises: :class:`LogicError` if a ``None``is in ``d.left``, ``d.right``, or ``d.rules``. + // rst: :raises: :class:`LogicError` if ``graphPolicy == IsomorphismPolicy.Check`` and a given graph object + // rst: is different but isomorphic to another given graph object or to a graph object already + // rst: in the internal graph database in the associated derivation graph. .def("addDerivation", static_cast(&Builder::addDerivation)) + // rst: .. method:: addHyperEdge(e, graphPolicy=IsomorphismPolicy.Check) + // rst: + // rst: Adds a hyperedge to the associated :class:`DG` from a copy of the given hyperedge + // rst: (from a different :class:`DG`). + // rst: If it already exists, only add the rules to the edge. + // rst: + // rst: :param DGHyperEdge e: a hyperedge to copy. + // rst: :param IsomorphismPolicy graphPolicy: the isomorphism policy for adding the given graphs. + // rst: :returns: the hyperedge corresponding to the copy of the given hyperedge. + // rst: :rtype: DGHyperEdge + // rst: :raises: :class:`LogicError` if ``e`` is a null edge. + // rst: :raises: :class:`LogicError` if ``graphPolicy == IsomorphismPolicy.Check`` and a given graph object + // rst: is different but isomorphic to another given graph object or to a graph object already + // rst: in the internal graph database in the associated derivation graph. + .def("addHyperEdge", static_cast(&Builder::addHyperEdge)) // rst: .. method:: execute(strategy, *, verbosity=2, ignoreRuleLabelTypes=False) // rst: // rst: Execute the given strategy (:ref:`dgStrat`) and as a side-effect add diff --git a/libs/pymod/src/mod/py/dg/DG.cpp b/libs/pymod/src/mod/py/dg/DG.cpp index d75e20b..81b1284 100644 --- a/libs/pymod/src/mod/py/dg/DG.cpp +++ b/libs/pymod/src/mod/py/dg/DG.cpp @@ -210,7 +210,7 @@ void DG_doExport() { // rst: // rst: :raises: :class:`LogicError` if the DG has not been calculated. .def("listStats", &DG::listStats) - // rst: .. method:: load(graphDatabase, ruleDatabase, f, graphPolicy=IsomorphismPolicy.Check, verbosity=2) + // rst: .. staticmethod:: load(graphDatabase, ruleDatabase, f, graphPolicy=IsomorphismPolicy.Check, verbosity=2) // rst: // rst: Load a derivation graph dump as a locked object. // rst: Use :func:`DGBuilder.load` to load a dump into a derivation graph under construction. diff --git a/libs/pymod/src/mod/py/dg/Printer.cpp b/libs/pymod/src/mod/py/dg/Printer.cpp index 5a6b532..38dae1b 100644 --- a/libs/pymod/src/mod/py/dg/Printer.cpp +++ b/libs/pymod/src/mod/py/dg/Printer.cpp @@ -44,6 +44,12 @@ Printer_setMirrorOverwrite(Printer &printer, std::shared_ptr( + DG::Vertex, int)>> f) { + printer.setImageOverwrite(mod::toStdFunction(f)); +} + } // namespace void Printer_doExport() { @@ -322,6 +328,29 @@ void Printer_doExport() { // rst: :param f: the function called on each graph to retrieve the mirror to render it with. // rst: :type f: Callable[[Graph], bool] or bool .def("setMirrorOverwrite", &Printer_setMirrorOverwrite) + // rst: .. method:: setImageOverwrite(f) + // rst: + // rst: Overwrite the image generation for graphs depicted in the vertices of the printed derivation graph. + // rst: For each duplicate of each vertex to be depicted, the given callback is called. + // rst: It must then return two strings: + // rst: + // rst: 1. Either + // rst: + // rst: - an empty string if the standard depiction should be used, in which case the second string is ignored, or + // rst: - the filename of the PDF that should be included in the final compiled figure. + // rst: + // rst: 2. Either + // rst: + // rst: - an empty string if no additional post-processing command is needed, or + // rst: - a string of Bash code which will be inserted in the post-processing instructions. + // rst: For example, one can write out source code in the callback, and then return a command that compiles + // rst: that code into the PDF needed by inclusion. + // rst: + // rst: The image overwrite can be removed by calling with ``None``. + // rst: + // rst: :param f: the callback to use, or ``None`` to remove an existing callback. + // rst: :type f: Callable[[DGVertex, int], tuple[str, str]] or None + .def("setImageOverwrite", &Printer_setImageOverride) // rst: .. attribute:: graphvizPrefix // rst: // rst: The string that will be inserted into generated DOT files, diff --git a/libs/pymod/src/mod/py/graph/Graph.cpp b/libs/pymod/src/mod/py/graph/Graph.cpp index d6dbf34..cc40d5c 100644 --- a/libs/pymod/src/mod/py/graph/Graph.cpp +++ b/libs/pymod/src/mod/py/graph/Graph.cpp @@ -1,10 +1,13 @@ #include +#include #include #include #include #include +#include + #include namespace mod::graph::Py { @@ -19,8 +22,7 @@ void Graph_doExport() { // rst: // rst: This class models an undirected graph with labels on vertices and edges, // rst: without loops and without parallel edges. - // rst: Certain labels are regarded as models of chemical atoms and bonds. - // rst: See :ref:`mol-enc` for more information on this. + // rst: See :ref:`graph-model` for more details. // rst: // rst: The class implements the :class:`protocols.LabelledGraph`. // rst: See :ref:`py-Graph/GraphInterface` for additional guarantees. @@ -109,14 +111,14 @@ void Graph_doExport() { py::make_function(&Graph::getSmilesWithIds, py::return_value_policy())) // rst: .. attribute:: graphDFS // rst: - // rst: (Read-only) This is a :ref:`GraphDFS ` of the graph. + // rst: (Read-only) This is a :ref:`GraphDFS ` of the graph. // rst: // rst: :type: str .add_property("graphDFS", py::make_function(&Graph::getGraphDFS, py::return_value_policy())) // rst: .. attribute:: graphDFSWithIds // rst: - // rst: (Read-only) This is a :ref:`GraphDFS ` of the graph, where each vertices have an explicit id, + // rst: (Read-only) This is a :ref:`GraphDFS ` of the graph, where each vertices have an explicit id, // rst: corresponding to its internal vertex id. // rst: // rst: :type: str @@ -124,7 +126,7 @@ void Graph_doExport() { py::return_value_policy())) // rst: .. attribute:: linearEncoding // rst: - // rst: (Read-only) If the graph models a molecule this is the :ref:`SMILES string ` string, otherwise it is the :ref:`GraphDFS ` string. + // rst: (Read-only) If the graph models a molecule this is the :ref:`SMILES string ` string, otherwise it is the :ref:`GraphDFS ` string. // rst: // rst: :type: str .add_property("linearEncoding", py::make_function(&Graph::getLinearEncoding, @@ -170,22 +172,39 @@ void Graph_doExport() { // rst: :returns: the number of edges in the graph with the given label. // rst: :rtype: int .def("eLabelCount", &Graph::eLabelCount) - // rst: .. method:: isomorphism(other, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) + // rst: .. method:: isomorphism(codomain, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) // rst: - // rst: :param Graph other: the codomain :class:`Graph` for finding morphisms. + // rst: :param Graph codomain: the codomain graph for finding morphisms. // rst: :param int maxNumMatches: the maximum number of isomorphisms to search for. // rst: :param LabelSettings labelSettings: the label settings to use during the search. // rst: :returns: the number of isomorphisms from this graph to ``other``, but at most ``maxNumMatches``. // rst: :rtype: int + // rst: :raises LogicError: if ``codomain`` is null. .def("isomorphism", &Graph::isomorphism) - // rst: .. method:: monomorphism(other, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) + // rst: .. method:: monomorphism(codomain, maxNumMatches=1, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) // rst: - // rst: :param Graph other: the codomain :class:`Graph` for finding morphisms. + // rst: :param Graph codomain: the codomain graph for finding morphisms. // rst: :param int maxNumMatches: the maximum number of monomorphisms to search for. // rst: :param LabelSettings labelSettings: the label settings to use during the search. // rst: :returns: the number of monomorphisms from this graph to ``other``, though at most ``maxNumMatches``. // rst: :rtype: int + // rst: :raises LogicError: if ``codomain`` is null. .def("monomorphism", &Graph::monomorphism) + // rst: .. method:: enumerateMonomorphisms(codomain, callback, labelSettings=LabelSettings(LabelType.String, LabelRelation.Isomorphism)) + // rst: + // rst: Perform substructure search of this graph into the given codomain graph. + // rst: Whenever a match is found, the corresponding monomorphism is copied into a vertex map + // rst: and the given callback is invoked with it. + // rst: + // rst: :param Graph codomain: the codomain graph for finding morphisms. + // rst: :param callback: the function to call with each found monomorphism. + // rst: If ``False`` is returned from it, then the search is stopped. + // rst: :type callback: Callable[[protocols.VertexMap], bool] + // rst: :param LabelSettings labelSettings: the label settings to use during the search. + // rst: + // rst: :raises LogicError: if ``codomain`` is null. + // rst: :raises LogicError: if ``callback`` is null. + .def("enumerateMonomorphisms", &Graph::enumerateMonomorphisms) // rst: .. method:: makePermutation() // rst: // rst: :returns: a graph isomorphic to this, but with the vertex indices randomly permuted. @@ -261,7 +280,7 @@ void Graph_doExport() { // rst: Load a graph in :ref:`GML ` format from a given string, ``s``, // rst: or given file ``f``. // rst: The graph must be connected. - // rst: If not, use :meth:`Graph.fromGMLStringMulti` or :meth:`Graph.fromGMLFileMulti`. + // rst: If not, use :meth:`fromGMLStringMulti` or :meth:`fromGMLFileMulti`. // rst: // rst: :param str s: the string with the :ref:`GML ` data to load from. // rst: :param f: name of the :ref:`GML ` file to be loaded. @@ -282,7 +301,7 @@ void Graph_doExport() { // rst: or given file ``f``, // rst: with each graph being a connected component of the graph specified in the GML data. // rst: - // rst: See :meth:`Graph.fromGMLString` and :meth:`Graph.fromGMLFile` + // rst: See :meth:`fromGMLString` and :meth:`fromGMLFile` // rst: for a description of the parameters and exceptions. // rst: // rst: :returns: a list of the loaded graphs. @@ -293,9 +312,11 @@ void Graph_doExport() { .staticmethod("fromGMLFileMulti") // rst: .. staticmethod:: Graph.fromDFS(s, name=None, add=True) // rst: - // rst: Load a graph from a :ref:`GraphDFS ` string. + // rst: Load a graph from a :ref:`GraphDFS ` string. + // rst: The graph must be connected. + // rst: If not, use :meth:`Graph.fromDFSMulti`. // rst: - // rst: :param str s: the :ref:`GraphDFS ` string to parse. + // rst: :param str s: the :ref:`GraphDFS ` string to parse. // rst: :param str name: the name of the graph. If none is given the default name is used. // rst: :param bool add: whether to append the graph to :data:`inputGraphs` or not. // rst: :returns: the loaded graph. @@ -303,10 +324,22 @@ void Graph_doExport() { // rst: :raises: :class:`InputError` on bad input. .def("fromDFS", &Graph::fromDFS) .staticmethod("fromDFS") + // rst: .. staticmethod:: Graph.fromDFSMulti(s, add=True) + // rst: + // rst: Load a set of graphs from a :ref:`GraphDFS ` string, + // rst: with each graph being a connected component of the graph specified in the DFS data. + // rst: + // rst: :param str s: the :ref:`GraphDFS ` string to parse. + // rst: :param bool add: whether to append the graphs to :data:`inputGraphs` or not. + // rst: :returns: the loaded graphs. + // rst: :rtype: list[Graph] + // rst: :raises: :class:`InputError` on bad input. + .def("fromDFSMulti", &Graph::fromDFSMulti) + .staticmethod("fromDFSMulti") // rst: .. staticmethod:: Graph.fromSMILES(s, name=None, allowAbstract=False, classPolicy=SmilesClassPolicy.NoneOnDuplicate, add=True) // rst: // rst: Load a molecule from a :ref:`SMILES ` string. - // rst: The molecule must be a connected graph. If not, use :meth:`Graph.fromSMILESMulti`. + // rst: The molecule must be a connected graph. If not, use :meth:`fromSMILESMulti`. // rst: // rst: :param str s: the :ref:`SMILES ` string to parse. // rst: :param str name: the name of the graph. If none is given the default name is used. @@ -323,13 +356,87 @@ void Graph_doExport() { // rst: Load a set of molecules from a :ref:`SMILES ` string, // rst: with each molecule being a connected component of the graph specified in the SMILES string. // rst: - // rst: See :meth:`Graph.fromSMILES` for a description of the parameters and exceptions. + // rst: See :meth:`fromSMILES` for a description of the parameters and exceptions. // rst: // rst: :returns: a list of the loaded molecules. // rst: :rtype: list[Graph] .def("fromSMILESMulti", static_cast>(*)(const std::string &, bool, SmilesClassPolicy)>(&Graph::fromSMILESMulti)) - .staticmethod("fromSMILESMulti"); + .staticmethod("fromSMILESMulti") + // rst: .. staticmethod:: Graph.fromMOLString(s, name=None, options=MDLOptions(), add=True) + // rst: Graph.fromMOLFile(f, name=None, options=MDLOptions(), add=True) + // rst: + // rst: Load a molecule in :ref:`MOL ` format from a given string or file. + // rst: The molecule must be a connected graph. + // rst: If not, use :meth:`fromMOLStringMulti` and :meth:`fromMOLFileMulti`. + // rst: + // rst: :param str s: the string to parse. + // rst: :param f: name of the file to load. + // rst: :type f: str or CWDPath + // rst: :param str name: the name of the graph. If none is given the default name is used. + // rst: :param MDLOptions options: the options to use for loading. + // rst: :param bool add: whether to append the graph to :data:`inputGraphs` or not. + // rst: :returns: the loaded molecule. + // rst: :rtype: :class:`Graph` + // rst: :raises: :class:`InputError` on bad input. + .def("fromMOLString", &Graph::fromMOLString) + .staticmethod("fromMOLString") + .def("fromMOLFile", &Graph::fromMOLFile) + .staticmethod("fromMOLFile") + // rst: .. staticmethod:: Graph.fromMOLStringMulti(s, options=MDLOptions(), add=True) + // rst: Graph.fromMOLFileMulti(f, options=MDLOptions(), add=True) + // rst: + // rst: Load a set of molecules from a given string or file with :ref:`MOL ` data, + // rst: with each molecule being a connected component of the graph specified in the data. + // rst: + // rst: See :meth:`fromMOLString` and :meth:`fromMOLFile` + // rst: for a description of the parameters and exceptions. + // rst: + // rst: :returns: a list of the loaded molecules. + // rst: :rtype: list[Graph] + .def("fromMOLStringMulti", &Graph::fromMOLStringMulti) + .staticmethod("fromMOLStringMulti") + .def("fromMOLFileMulti", &Graph::fromMOLFileMulti) + .staticmethod("fromMOLFileMulti") + // rst: .. staticmethod:: Graph.fromSDString(s, options=MDLOptions(), add=True) + // rst: Graph.fromSDFile(f, options=MDLOptions(), add=True) + // rst: + // rst: Load a list of molecules in :ref:`SD ` format from a given string or file, + // rst: with each molecule being a connected component of each of the the graphs specified in the data. + // rst: If any graph is not connected, use :meth:`fromSDStringMulti` and :meth:`fromSDFileMulti` instead. + // rst: + // rst: :param str s: the string to parse. + // rst: :param f: name of the file to load. + // rst: :type f: str or CWDPath + // rst: :param MDLOptions options: the options to use for loading. + // rst: :param bool add: whether to append the graphs to :data:`inputGraphs` or not. + // rst: :returns: a list of the loaded molecules. + // rst: :rtype: list of :class:`Graph` + // rst: :raises: :class:`InputError` on bad input. + .def("fromSDString", &Graph::fromSDString) + .staticmethod("fromSDString") + .def("fromSDFile", &Graph::fromSDFile) + .staticmethod("fromSDFile") + // rst: .. staticmethod:: Graph.fromSDStringMulti(s, options=MDLOptions(), add=True) + // rst: Graph.fromSDFileMulti(f, options=MDLOptions(), add=True) + // rst: + // rst: Load a list of molecules in :ref:`SD ` format from a given string or file. + // rst: Each molecule is returned as a list of graphs, with each corresponding to a connected + // rst: component of the MOL entry. + // rst: + // rst: See :meth:`fromSDString` and :meth:`fromSDFile` + // rst: for a description of the parameters and exceptions. + // rst: + // rst: :returns: a list of lists of the loaded molecules. + // rst: The items of the outer list correspond to each MOL entry in the SD data. + // rst: :rtype: list[list[Graph]] + .def("fromSDStringMulti", &Graph::fromSDStringMulti) + .staticmethod("fromSDStringMulti") + .def("fromSDFileMulti", &Graph::fromSDFileMulti) + .staticmethod("fromSDFileMulti"); + + mod::Py::exportVertexMap>("VertexMapGraphGraph"); + // rst: .. method:: graphGMLString(s, name=None, add=True) // rst: diff --git a/libs/pymod/src/mod/py/graph/Printer.cpp b/libs/pymod/src/mod/py/graph/Printer.cpp index 4c54998..3b5445f 100644 --- a/libs/pymod/src/mod/py/graph/Printer.cpp +++ b/libs/pymod/src/mod/py/graph/Printer.cpp @@ -21,95 +21,121 @@ void Printer_doExport() { py::class_("GraphPrinter") .def(py::self == py::self) .def(py::self != py::self) - // rst: .. method:: setMolDefault() - // rst: - // rst: Shortcut for enabling all but thickening and index printing. + // rst: .. method:: setMolDefault() + // rst: + // rst: Shortcut for enabling all but thickening and index printing. .def("setMolDefault", &Printer::setMolDefault) - // rst: .. method:: setReactionDefault() - // rst: - // rst: Shortcut for enabling all but thickening, index printing and simplification of carbon atoms. + // rst: .. method:: setReactionDefault() + // rst: + // rst: Shortcut for enabling all but thickening, index printing and simplification of carbon atoms. .def("setReactionDefault", &Printer::setReactionDefault) - // rst: .. method:: disableAll() - // rst: - // rst: Disable all special printing features. + // rst: .. method:: disableAll() + // rst: + // rst: Disable all special printing features. .def("disableAll", &Printer::disableAll) - // rst: .. method:: enableAll() - // rst: - // rst: Enable all special printing features, except typewriter font. + // rst: .. method:: enableAll() + // rst: + // rst: Enable all special printing features, except typewriter font. .def("enableAll", &Printer::enableAll) - // rst: .. attribute:: edgesAsBonds - // rst: - // rst: Control whether edges with special labels are drawn as chemical bonds. - // rst: - // rst: :type: bool + // rst: .. attribute:: edgesAsBonds + // rst: + // rst: Control whether edges with special labels are drawn as chemical bonds. + // rst: + // rst: :type: bool .add_property("edgesAsBonds", &Printer::getEdgesAsBonds, &Printer::setEdgesAsBonds) - // rst: .. attribute:: collapseHydrogens - // rst: - // rst: Control whether vertices representing hydrogen atoms are collapsed into their neighbours labels. - // rst: - // rst: :type: bool + // rst: .. attribute:: collapseHydrogens + // rst: + // rst: Control whether vertices representing hydrogen atoms are collapsed into their neighbours labels. + // rst: + // rst: :type: bool .add_property("collapseHydrogens", &Printer::getCollapseHydrogens, &Printer::setCollapseHydrogens) - // rst: .. attribute:: raiseCharges - // rst: - // rst: Control whether a vertex label suffix encoding a charge is written as a superscript to the rest of the label. - // rst: - // rst: :type: bool + // rst: .. attribute:: raiseIsotopes + // rst: + // rst: Control whether a vertex label prefix encoding an isotope is written as a superscript to the rest of the label. + // rst: + // rst: :type: bool + .add_property("raiseIsotopes", &Printer::getRaiseIsotopes, &Printer::setRaiseIsotopes) + // rst: .. attribute:: raiseCharges + // rst: + // rst: Control whether a vertex label suffix encoding a charge is written as a superscript to the rest of the label. + // rst: + // rst: :type: bool .add_property("raiseCharges", &Printer::getRaiseCharges, &Printer::setRaiseCharges) - // rst: .. attribute:: simpleCarbons - // rst: - // rst: Control whether some vertices encoding carbon atoms are depicted without any label. - // rst: - // rst: :type: bool + // rst: .. attribute:: simpleCarbons + // rst: + // rst: Control whether some vertices encoding carbon atoms are depicted without any label. + // rst: + // rst: :type: bool .add_property("simpleCarbons", &Printer::getSimpleCarbons, &Printer::setSimpleCarbons) - // rst: .. attribute:: thick - // rst: - // rst: Control whether all edges are drawn thicker than normal and all labels are written in bold. - // rst: - // rst: :type: bool + // rst: .. attribute:: thick + // rst: + // rst: Control whether all edges are drawn thicker than normal and all labels are written in bold. + // rst: + // rst: :type: bool .add_property("thick", &Printer::getThick, &Printer::setThick) - // rst: .. attribute:: withColour - // rst: - // rst: Control whether colour is applied to certain elements of the graph which are molecule-like. - // rst: - // rst: :type: bool + // rst: .. attribute:: withColour + // rst: + // rst: Control whether colour is applied to certain elements of the graph which are molecule-like. + // rst: + // rst: :type: bool .add_property("withColour", &Printer::getWithColour, &Printer::setWithColour) - // rst: .. attribute:: withIndex - // rst: - // rst: Control whether the underlying indices of the vertices are printed. - // rst: - // rst: :type: bool + // rst: .. attribute:: withIndex + // rst: + // rst: Control whether the underlying indices of the vertices are printed. + // rst: + // rst: :type: bool .add_property("withIndex", &Printer::getWithIndex, &Printer::setWithIndex) - // rst: .. attribute:: withTexttt - // rst: - // rst: Control whether the vertex and edge labels are written with typewriter font. - // rst: - // rst: :type: bool + // rst: .. attribute:: withTexttt + // rst: + // rst: Control whether the vertex and edge labels are written with typewriter font. + // rst: + // rst: :type: bool .add_property("withTexttt", &Printer::getWithTexttt, &Printer::setWithTexttt) - // rst: .. attribute:: withRawStereo - // rst: - // rst: Control whether the vertices and edges are annotated with the raw stereo properties. - // rst: - // rst: :type: bool + // rst: .. attribute:: withRawStereo + // rst: + // rst: Control whether the vertices and edges are annotated with the raw stereo properties. + // rst: + // rst: :type: bool .add_property("withRawStereo", &Printer::getWithRawStereo, &Printer::setWithRawStereo) - // rst: .. attribute:: withPrettyStereo - // rst: - // rst: Control whether the vertices and edges are annotated with stylized stereo properties. - // rst: - // rst: :type: bool + // rst: .. attribute:: withPrettyStereo + // rst: + // rst: Control whether the vertices and edges are annotated with stylized stereo properties. + // rst: + // rst: :type: bool .add_property("withPrettyStereo", &Printer::getWithPrettyStereo, &Printer::setWithPrettyStereo) - // rst: .. attribute:: rotation - // rst: - // rst: Rotation of internally computed coordinates. - // rst: - // rst: :type: int + // rst: .. attribute:: rotation + // rst: + // rst: Rotation of internally computed coordinates. + // rst: + // rst: :type: int .add_property("rotation", &Printer::getRotation, &Printer::setRotation) - // rst: .. attribute:: mirror - // rst: - // rst: Mirror internally computed coordinates in the y-axis. - // rst: - // rst: :type: bool + // rst: .. attribute:: mirror + // rst: + // rst: Mirror internally computed coordinates in the y-axis. + // rst: + // rst: :type: bool .add_property("mirror", &Printer::getMirror, &Printer::setMirror) - ; + // rst: .. attribute:: withGraphvizCoords + // rst: + // rst: Do not use Open Babel for coordinate generation, but only the Graphviz fallback + // rst: during post-processing. + // rst: When setting this to ``True`` consider setting `simpleCarbons = False`` to avoid + // rst: misleading depictions due to colinear carbon chains. + // rst: + // rst: :type: bool + .add_property("withGraphvizCoords", &Printer::getWithGraphvizCoords, &Printer::setWithGraphvizCoords) + // =================================================================== + // rst: .. attribute:: graphvizPrefix + // rst: + // rst: The string that will be inserted into generated DOT files, + // rst: just after the graph declaration. + // rst: DOT files are only generated when ``withGraphvizCoords == True``. + // rst: + // rst: :type: str + .add_property("graphvizPrefix", + py::make_function(&Printer::getGraphvizPrefix, + py::return_value_policy()), + &Printer::setGraphvizPrefix); } } // namespace mod::graph::Py \ No newline at end of file diff --git a/libs/pymod/src/mod/py/graph/Union.cpp b/libs/pymod/src/mod/py/graph/Union.cpp index f22382f..a2410c1 100644 --- a/libs/pymod/src/mod/py/graph/Union.cpp +++ b/libs/pymod/src/mod/py/graph/Union.cpp @@ -40,6 +40,7 @@ void exportClass() { // rst: // rst: :param graphs: the list of graphs to adapt. // rst: :type graphs: list[Graph] + // rst: :raises LogicError: if a given graph is ``None``. .def("__init__", py::make_constructor(&make)) .def(py::self == py::self) .def(py::self != py::self) diff --git a/libs/pymod/src/mod/py/rule/Rule.cpp b/libs/pymod/src/mod/py/rule/Rule.cpp index 0188038..8997fa7 100644 --- a/libs/pymod/src/mod/py/rule/Rule.cpp +++ b/libs/pymod/src/mod/py/rule/Rule.cpp @@ -22,10 +22,11 @@ void Rule_doExport() { // rst: .. class:: Rule // rst: - // rst: Model of a transformation rule in the Double Pushout formalism, + // rst: This class models a graph transformation rule in the Double Pushout formalism, // rst: as the span :math:`L \leftarrow K \rightarrow R`. // rst: The three graphs are referred to as respectively // rst: the "left", "context", and "right" graphs of the rule. + // rst: See :ref:`graph-model` for more details. // rst: // rst: The class implements the :class:`protocols.Graph` protocol, // rst: which gives access to a graph view of the rule which has the left, context, and right graphs @@ -210,7 +211,20 @@ void Rule_doExport() { .def("fromGMLString", &Rule::fromGMLString) .staticmethod("fromGMLString") .def("fromGMLFile", &Rule::fromGMLFile) - .staticmethod("fromGMLFile"); + .staticmethod("fromGMLFile") + // rst: .. staticmethod:: Rule.fromDFS(s, invert=False, add=True) + // rst: + // rst: Load a rule from a :ref:`RuleDFS ` string. + // rst: + // rst: :param str s: the :ref:`RuleDFS ` string to parse. + // rst: :param bool invert: whether or not to invert the loaded rule. + // rst: :param str name: the name of the rule. If none is given the default name is used. + // rst: :param bool add: whether to append the rule to :data:`inputRules` or not. + // rst: :returns: the loaded rule. + // rst: :rtype: Rule + // rst: :raises: :class:`InputError` on bad input. + .def("fromDFS", &Rule::fromDFS) + .staticmethod("fromDFS"); // rst: .. function:: ruleGMLString(s, invert=False, add=True) // rst: diff --git a/libs/pymodutils/CMakeLists.txt b/libs/pymodutils/CMakeLists.txt new file mode 100644 index 0000000..c8466d3 --- /dev/null +++ b/libs/pymodutils/CMakeLists.txt @@ -0,0 +1,33 @@ +if(NOT BUILD_PY_MOD) + return() +endif() + +########################################################################### +# Targets and Artefacts +########################################################################### + +# pymodutils +# ------------------------------------------------------------------------- +add_library(pymodutils INTERFACE) +# TODO: when CMake 3.19 is required, add the sources +# https://cmake.org/cmake/help/latest/command/add_library.html#interface-libraries +# ${mod_pymodutils_INCLUDE_FILES} +# ${mod_pymodtils_SRC_FILES}) +add_library(mod::pymodutils ALIAS pymodutils) +target_include_directories(pymodutils INTERFACE + $ + $) +target_link_libraries(pymodutils INTERFACE + mod::libmod + Boost::${PYTHON_TARGET} + Python3::Python) + +install(TARGETS pymodutils + EXPORT PROJECT_exports + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/mod COMPONENT pymodutils_lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/mod COMPONENT pymodutils_lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT pymodutils_lib) +install(DIRECTORY src/mod + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT pymodutils_dev + FILES_MATCHING PATTERN "*.hpp") \ No newline at end of file diff --git a/libs/pymod/src/mod/py/Common.hpp b/libs/pymodutils/src/mod/py/Common.hpp similarity index 52% rename from libs/pymod/src/mod/py/Common.hpp rename to libs/pymodutils/src/mod/py/Common.hpp index 56e1426..98eb263 100644 --- a/libs/pymod/src/mod/py/Common.hpp +++ b/libs/pymodutils/src/mod/py/Common.hpp @@ -3,7 +3,9 @@ // TODO: https://github.com/boostorg/python/pull/296 #define BOOST_BIND_GLOBAL_PLACEHOLDERS + #include + #undef BOOST_BIND_GLOBAL_PLACEHOLDERS #include @@ -16,7 +18,7 @@ struct AttributeIsNotReadable { inline void noGet(AttributeIsNotReadable) {} -// From https://wiki.python.org/moin/boost.python/HowTo#Dynamic_template_to-python_converters. +// From https://wiki.python.org/moin/boost.python/HowTo#Dynamic_template_to-python_converters template struct PairToTupleConverter { static PyObject *convert(const std::pair &pair) { @@ -24,6 +26,27 @@ struct PairToTupleConverter { } }; +// From https://stackoverflow.com/questions/16497889/how-to-expose-stdpair-to-python-using-boostpython +template +struct TupleToPairConverter { + TupleToPairConverter() { + py::converter::registry::push_back(&convertible, &construct, py::type_id>()); + } + + static void *convertible(PyObject *obj) { + if(!PyTuple_CheckExact(obj)) return nullptr; + if(PyTuple_Size(obj) != 2) return nullptr; + return obj; + } + + static void construct(PyObject *obj, py::converter::rvalue_from_python_stage1_data *data) { + py::tuple tuple(py::borrowed(obj)); + void *storage = reinterpret_cast> *>(data)->storage.bytes; + storage = new(storage) std::pair(py::extract(tuple[0]), py::extract(tuple[1])); + data->convertible = storage; + } +}; + // https://stackoverflow.com/questions/36485840/wrap-boostoptional-using-boostpython template struct ToPythonOptionalValue { diff --git a/libs/pymod/src/mod/py/Function.hpp b/libs/pymodutils/src/mod/py/Function.hpp similarity index 95% rename from libs/pymod/src/mod/py/Function.hpp rename to libs/pymodutils/src/mod/py/Function.hpp index 24e2183..3d19b49 100644 --- a/libs/pymod/src/mod/py/Function.hpp +++ b/libs/pymodutils/src/mod/py/Function.hpp @@ -1,5 +1,5 @@ -#ifndef MOD_PY_FUNCTION_H -#define MOD_PY_FUNCTION_H +#ifndef MOD_PY_FUNCTION_HPP +#define MOD_PY_FUNCTION_HPP // The wrapping and haxing of reference counts could probably be done simpler. // The arg wrapping should also be looked into. @@ -7,7 +7,7 @@ #include -#include +#include #include @@ -102,4 +102,4 @@ void exportFunc(const char *name) { } // namespace mod::Py -#endif /* MOD_PY_FUNCTION_H */ +#endif // MOD_PY_FUNCTION_HPP \ No newline at end of file diff --git a/libs/pymod/src/mod/py/VertexMap.hpp b/libs/pymodutils/src/mod/py/VertexMap.hpp similarity index 100% rename from libs/pymod/src/mod/py/VertexMap.hpp rename to libs/pymodutils/src/mod/py/VertexMap.hpp diff --git a/scripts/checkJsonVisibility.sh b/scripts/checkJsonVisibility.sh index ea0584b..8b7ad84 100755 --- a/scripts/checkJsonVisibility.sh +++ b/scripts/checkJsonVisibility.sh @@ -6,10 +6,10 @@ if [ $? -ne 0 ]; then echo "grep failed:" grep -Rn '#include.*nlohman' fi -grep -Rn '#include.*nlohmann' | grep -v "^libmod/src/mod/lib/IO/JsonUtils.hpp" &> /dev/null +grep -Rn '#include.*nlohmann' | grep -v "^libmod/src/mod/lib/IO/Json.hpp" &> /dev/null if [ $? -eq 0 ]; then - echo "nlohmann json(_schema) may only be included via ibmod/src/mod/lib/IO/JsonUtils.hpp" + echo "nlohmann json(_schema) may only be included via libmod/src/mod/lib/IO/Json.hpp" echo "Found these other instances:" - grep -Rn '#include.*nlohmann' | grep -v "^libmod/src/mod/lib/IO/JsonUtils.hpp" + grep -Rn '#include.*nlohmann' | grep -v "^libmod/src/mod/lib/IO/Json.hpp" exit 1 fi diff --git a/scripts/flake8.sh b/scripts/flake8.sh index 8e2e007..a9a976e 100755 --- a/scripts/flake8.sh +++ b/scripts/flake8.sh @@ -8,7 +8,7 @@ if [ $res -ne 0 ]; then fi cd $root mkdir -p build/checkPython/mod -rm build/checkPython/mod/* +rm -f build/checkPython/mod/* for f in $(ls libs/pymod/lib/mod); do ln -s -T ../../../libs/pymod/lib/mod/$f build/checkPython/mod/$f done diff --git a/scripts/makePyExamples.py b/scripts/makePyExamples.py new file mode 100755 index 0000000..615a130 --- /dev/null +++ b/scripts/makePyExamples.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +import sys +import os +import json + +def processExamples(topSrcDir): + root = topSrcDir + "/examples/py/" + sections = [] + for sec in sorted(os.listdir(root)): + if not os.path.isdir(root + sec): + continue + with open(root + sec + "/meta.json") as meta: + section = json.load(meta) + assert "title" in section + section['id'] = sec + section['exs'] = [] + for ex in os.listdir(root + sec): + exFull = root + sec + "/" + ex + assert os.path.isfile(exFull), exFull + if not ex.endswith(".py"): + continue + with open(exFull, encoding='utf-8') as f: + desc = "" + endLine = 1 + for line in f.readlines(): + if not line.startswith("# rst"): + endLine += 1 + continue + line = line[5:] + if line.startswith("-name: "): + title = line[7:-1] + else: + assert line.startswith(": ") + desc += line[2:] + section['exs'].append({ + "id": ex, "title": title, "desc": desc, "endLine": endLine + }) + sections.append(section) + return {"sections": sections} + + +def outputRST(topSrcDir): + data = processExamples(topSrcDir) + root = topSrcDir + "/doc/source/" + os.makedirs(root + "examples", exist_ok=True) + with open(root + "examples/index.rst", "w") as f: + f.truncate() + f.write(".. Autogenerated in conf.py\n\n") + f.write(".. _examples:\n\n") + f.write("Examples\n########\n\n") + f.write(".. toctree::\n\n") + for sec in data["sections"]: + f.write("\t{}\n".format(sec["id"])) + + for sec in data['sections']: + with open(root + "examples/{}.rst".format(sec['id']), + "w", encoding='utf-8') as f: + f.truncate() + f.write(".. Autogenerated in conf.py\n\n") + for id_ in sec['oldIds']: + f.write(".. _examples-{}:\n".format(id_)) + f.write(".. _examples-{}:\n\n".format(sec['id'])) + f.write("{}\n".format(sec["title"])) + f.write(80*"#" + "\n\n") + for ex in sec['exs']: + f.write("\n\n{}\n".format(ex['title'])) + f.write(80*"^" + "\n\n") + f.write(ex['desc']) + f.write(""" +`Explore in the playground `__. + +.. literalinclude:: /_static/examples/py/{0}/{1} + :language: python + :linenos: + :lines: 1-{2} + :tab-width: 3 +""".format(sec['id'], ex['id'], ex['endLine'] - 1)) + + +def outputJSON(topSrcDir): + data = processExamples(topSrcDir) + for sec in data['sections']: + del sec['oldIds'] + for ex in sec['exs']: + del ex['desc'] + del ex['endLine'] + print(json.dumps(data, indent=1)) + + +if __name__ == '__main__': + topSrcDir = sys.argv[1] + outType = sys.argv[2] + if outType == 'rst': + outputRST(topSrcDir) + elif outType == 'json': + outputJSON(topSrcDir) + else: + print("Unknown output type '{}'.".format(outType)) + sys.exit(1) diff --git a/scripts/mypy.sh b/scripts/mypy.sh index 24adf3c..be6a7e7 100755 --- a/scripts/mypy.sh +++ b/scripts/mypy.sh @@ -8,7 +8,7 @@ if [ $res -ne 0 ]; then fi cd $root mkdir -p build/checkPython/mod -rm build/checkPython/mod/* +rm -f build/checkPython/mod/* for f in $(ls libs/pymod/lib/mod); do ln -s -T ../../../libs/pymod/lib/mod/$f build/checkPython/mod/$f done diff --git a/scripts/printDepGraph.py b/scripts/printDepGraph.py index 9365596..04d3d18 100644 --- a/scripts/printDepGraph.py +++ b/scripts/printDepGraph.py @@ -52,7 +52,6 @@ def clusterComponentFromFilename(f): ('interface', 'mod'), ('lib', 'lib'), ('lib', 'Chem'), - ('lib', 'IO'), ) clusterColour = { 'interface': 'green', diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7294449..943d63e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,6 +36,11 @@ if(BUILD_PY_MOD) string(REPLACE "/" "__" testName "${fileName}") make_py_test(${fileName} ${testName} "") endforeach() + + add_test(NAME pymod-module-direct + COMMAND ${CMAKE_INSTALL_FULL_BINDIR}/mod --nopost -e "assert not mod._redirected") + add_test(NAME pymod-module-redirected + COMMAND ${Python3_EXECUTABLE} -c "import mod\nassert mod._redirected") endif() add_subdirectory(cmake_add_subdirectory) diff --git a/test/cpp/dg/printer.cpp b/test/cpp/dg/printer.cpp index 28bfe3c..9d8d533 100644 --- a/test/cpp/dg/printer.cpp +++ b/test/cpp/dg/printer.cpp @@ -7,24 +7,35 @@ using namespace mod; + template -void test(F f, const Ts& ...ts) { +void test(F f, const std::string &msg, const Ts &...ts) { dg::Printer p; try { (p.*f)({}, ts...); assert(false); } catch(const LogicError &e) { - assert(e.what() == std::string("Can not push empty callback.")); + assert(e.what() == msg); } } +template +void testPush(F f, const Ts &...ts) { + test(f, "Can not push empty callback.", ts...); +} + +template +void testSet(F f, const Ts &...ts) { + test(f, "Can not set empty callback.", ts...); +} + int main() { - test(&dg::Printer::pushVertexVisible); - test(&dg::Printer::pushEdgeVisible); - test(&dg::Printer::pushVertexLabel); - test(&dg::Printer::pushEdgeLabel); - test(&dg::Printer::pushVertexColour, true); - test(&dg::Printer::pushEdgeColour); - test(&dg::Printer::setRotationOverwrite); - test(&dg::Printer::setMirrorOverwrite); + testPush(&dg::Printer::pushVertexVisible); + testPush(&dg::Printer::pushEdgeVisible); + testPush(&dg::Printer::pushVertexLabel); + testPush(&dg::Printer::pushEdgeLabel); + testPush(&dg::Printer::pushVertexColour, true); + testPush(&dg::Printer::pushEdgeColour); + testSet(&dg::Printer::setRotationOverwrite); + testSet(&dg::Printer::setMirrorOverwrite); } \ No newline at end of file diff --git a/test/cpp/graph/graph.cpp b/test/cpp/graph/graph.cpp new file mode 100644 index 0000000..312b760 --- /dev/null +++ b/test/cpp/graph/graph.cpp @@ -0,0 +1,19 @@ +#include +#include + +#undef NDEBUG + +#include + +using namespace mod; + +int main() { + auto dom = graph::Graph::fromSMILES("O"); + auto codom = dom; + try { + dom->enumerateMonomorphisms(dom, {}, LabelSettings(LabelType::String, LabelRelation::Isomorphism)); + assert(false); + } catch(const LogicError &e) { + assert(e.what() == std::string("callback is null.")); + } +} \ No newline at end of file diff --git a/test/py/chem.py b/test/py/chem.py index e50669d..45cc544 100644 --- a/test/py/chem.py +++ b/test/py/chem.py @@ -1,32 +1,61 @@ -post("disableSummary") -print("AtomId\n----------") -a = AtomId() -print(a) -a = AtomId(42) -print(a) -print(int(a)) - -print("Charge\n----------") -a = Charge() -print(a) -a = Charge(-42) -print(a) -print(int(a)) -assert a == -42 - -print("AtomData\n----------") -a = AtomData() -print(a.atomId) -print(a.isotope) -print(a.charge) -print(a.radical) +include("xxx_helpers.py") + +print("AtomId") +print("=" * 80) +assert AtomId() == AtomId(0) +fail(lambda: AtomId().symbol, "AtomId::Invalid has no symbol.") +assert int(AtomId()) == 0 +assert str(AtomId()) == str(int(AtomId())) + +assert AtomId(1) == AtomIds.H +assert AtomId(1) == AtomIds.Hydrogen +assert AtomId(1).symbol == "H" +assert int(AtomId(1)) == 1 +assert str(AtomId(1)) == str(int(AtomId(1))) + + +print("Isotope") +print("=" * 80) +assert Isotope() == Isotope(-1) +assert int(Isotope()) == -1 +assert str(Isotope()) == str(int(Isotope())) +assert Isotope() == -1 + +assert int(Isotope(42)) == 42 +assert str(Isotope(42)) == str(int(Isotope(42))) +assert Isotope(42) == 42 + + +print("Charge") +print("=" * 80) +assert Charge() == Charge(0) +assert int(Charge()) == 0 +assert str(Charge()) == str(int(Charge())) +assert Charge() == 0 + +assert int(Charge(2)) == 2 +assert str(Charge(2)) == str(int(Charge(2))) +assert Charge(2) == 2 + + +print("AtomData") +print("=" * 80) +assert AtomData().atomId == AtomId() +assert AtomData().isotope == Isotope() +assert AtomData().charge == Charge() +assert AtomData().radical == False + a = AtomData(AtomId(42), Isotope(60), Charge(-9), True) -print("AtomData:", a) +assert a.atomId == AtomId(42) +assert a.isotope == 60 +assert a.charge == -9 +assert a.radical == True + b = AtomData(AtomId(42), Isotope(60), Charge(-9), True) assert a == b + g = graphDFS("[60Mo9-.]") v = next(iter(g.vertices)) -print("Vertex:", v.atomId, v.isotope, v.charge, v.radical) assert v.atomId == a.atomId assert v.isotope == a.isotope assert v.charge == a.charge @@ -35,15 +64,21 @@ assert AtomData() < AtomData(AtomId(1)) assert AtomData(AtomId(1)) > AtomData() -print("BondType\n----------") -for a in [BondType.Invalid, BondType.Single, BondType.Aromatic, BondType.Double, BondType.Triple]: - print(repr(a)) - if a != BondType.Invalid: - print(a) -print(BondType.values) - -print("AtomId\n----------") -print(AtomIds.Invalid) -print(AtomIds.Max) -for k, v in AtomIds.__dict__.items(): - print(k, ":", v) + +print("BondType") +print("=" * 80) +fail(lambda: str(BondType.Invalid), "Can not print BondType::Invalid.") +assert int(BondType.Single) == 1 +assert int(BondType.Aromatic) == 2 +assert int(BondType.Double) == 3 +assert int(BondType.Triple) == 4 + +assert str(BondType.Single) == "-" +assert str(BondType.Aromatic) == ":" +assert str(BondType.Double) == "=" +assert str(BondType.Triple) == "#" + + +print("AtomIds") +print("=" * 80) +assert AtomIds.Invalid == AtomId() diff --git a/test/py/config.py b/test/py/config.py index abef0d3..3bc660a 100644 --- a/test/py/config.py +++ b/test/py/config.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() # LabelType assert LabelType() == LabelType.String @@ -70,3 +70,10 @@ assert SmilesClassPolicy() == SmilesClassPolicy.NoneOnDuplicate assert str(SmilesClassPolicy.NoneOnDuplicate) == "noneOnDuplicate" assert str(SmilesClassPolicy.ThrowOnDuplicate) == "throwOnDuplicate" +assert str(SmilesClassPolicy.MapUnique) == "mapUnique" + +# Action +assert Action() == Action.Error +assert str(Action.Error) == "error" +assert str(Action.Warn) == "warn" +assert str(Action.Ignore) == "ignore" diff --git a/test/py/derivation.py b/test/py/derivation.py index b924d7a..6a62429 100644 --- a/test/py/derivation.py +++ b/test/py/derivation.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() g1 = smiles('O', name="g1") g2 = smiles('C', name="g2") r = ruleGMLString("""rule [ ruleID "r" context [ node [ id 0 label "O" ] ] ]""") diff --git a/test/py/dg/040_build_basic.py b/test/py/dg/040_build_basic.py new file mode 100644 index 0000000..50deed2 --- /dev/null +++ b/test/py/dg/040_build_basic.py @@ -0,0 +1,6 @@ +include("xx0_helpers.py") + +dg = DG() +b = dg.build() +assert b.dg == dg +assert b.isActive diff --git a/test/py/dg/020_build_addDerivation.py b/test/py/dg/050_build_addDerivation.py similarity index 100% rename from test/py/dg/020_build_addDerivation.py rename to test/py/dg/050_build_addDerivation.py diff --git a/test/py/dg/051_build_addHyperEdge.py b/test/py/dg/051_build_addHyperEdge.py new file mode 100644 index 0000000..c4b073d --- /dev/null +++ b/test/py/dg/051_build_addHyperEdge.py @@ -0,0 +1,27 @@ +include("xx0_helpers.py") + +g1 = smiles('O', name="g1") +g2 = smiles('C', name="g2") +r1 = ruleGMLString('rule [ ruleID "r1" context [ node [ id 0 label "O" ] ] ]') +r2 = ruleGMLString('rule [ ruleID "r2" context [ node [ id 0 label "C" ] ] ]') + + +dg = DG() +with dg.build() as b: + d = Derivations() + d.left = [g1] + d.rules = [r1, r2] + d.right = [g2] + e = b.addDerivation(d) +assert dg.numEdges == 1 + + +dg2 = DG() +with dg2.build() as b: + fail(lambda: b.addHyperEdge(DGHyperEdge()), "The hyperedge is null.") + + e2 = b.addHyperEdge(e) + +assert set(v.graph for v in e.sources) == set(v.graph for v in e2.sources) +assert set(v.graph for v in e.targets) == set(v.graph for v in e2.targets) +assert set(e.rules) == set(e2.rules) diff --git a/test/py/dg/030_build_apply.py b/test/py/dg/060_build_apply.py similarity index 95% rename from test/py/dg/030_build_apply.py rename to test/py/dg/060_build_apply.py index 4cabe45..bacaf98 100644 --- a/test/py/dg/030_build_apply.py +++ b/test/py/dg/060_build_apply.py @@ -199,3 +199,12 @@ assert e.numTargets == 2 ts = [v.graph for v in e.targets] assert ts == [g2, g2] + + +print("Empty result") +gC = smiles('[C]', "gC") +rRemove = ruleGMLString('rule [ left [ node [ id 0 label "C" ] ] ]') +dg = DG() +with dg.build() as builder: + res = builder.apply([gC], rRemove, verbosity=4) + assert len(res) == 0 diff --git a/test/py/dg/035_build_apply_nonProper.py b/test/py/dg/065_build_apply_nonProper.py similarity index 90% rename from test/py/dg/035_build_apply_nonProper.py rename to test/py/dg/065_build_apply_nonProper.py index b8a4818..ac0451f 100644 --- a/test/py/dg/035_build_apply_nonProper.py +++ b/test/py/dg/065_build_apply_nonProper.py @@ -15,6 +15,12 @@ ]""") DG().build().apply([], r, onlyProper=False) +DG().build().apply([a], ruleGMLString("""rule [ + right [ + node [ id 0 label "O" ] + ] +]"""), onlyProper=False) + fail(lambda: DG().build().apply([None], r, onlyProper=False), "One of the graphs is a null pointer.") fail(lambda: DG().build().apply([], None, onlyProper=False), "The rule is a null pointer.") fail(lambda: DG(graphDatabase=[a]).build().apply([aa], r, onlyProper=False), "Isomorphic graphs. Candidate graph 'gaa' is isomorphic to 'ga' in the graph database.") @@ -136,3 +142,12 @@ assert e.numTargets == 2 ts = [v.graph for v in e.targets] assert ts == [g2, g2] + + +print("Empty result") +gC = smiles('[C]', "gC") +rRemove = ruleGMLString('rule [ left [ node [ id 0 label "C" ] ] ]') +dg = DG() +with dg.build() as builder: + res = builder.apply([gC], rRemove, onlyProper=False, verbosity=4) + assert len(res) == 0 diff --git a/test/py/dg/121_build_execute_rule.py b/test/py/dg/121_build_execute_rule.py index 0dc02f6..5b2695f 100644 --- a/test/py/dg/121_build_execute_rule.py +++ b/test/py/dg/121_build_execute_rule.py @@ -39,3 +39,6 @@ exeStrat(addSubset(gO) >> addUniverse(gC) >> rConnectOC, [gOC], [gO, gC, gOC], graphDatabase=inputGraphs, verbosity=10) +print("Empty result") +rRemove = ruleGMLString('rule [ left [ node [ id 0 label "C" ] ] ]') +exeStrat(addSubset(gC) >> rRemove, [], [gC], verbosity=10) diff --git a/test/py/dg/401_printer_using.py b/test/py/dg/401_printer_using.py index f9bf6d0..170672d 100644 --- a/test/py/dg/401_printer_using.py +++ b/test/py/dg/401_printer_using.py @@ -1,5 +1,5 @@ include("../xxx_helpers.py") -post("enableSummary") +post.enableInvokeMake() dg = DG() fail(lambda: dg.print(), "Can not create print data. The DG is not locked yet.") @@ -13,6 +13,10 @@ right [ node [ id 0 label "S" ] ] ]''') b.addDerivation(d) + d = Derivation() + d.left = [graphDFS("[image1]", name="image1")] + d.right = [graphDFS("[image2]", name="image2")] + b.addDerivation(d) s = "A + hide -> B\n" for i in range(1, 4): s += "{i} A{i} -> {i} B\n".format(i=i) @@ -22,19 +26,19 @@ b.addAbstract(s) dg.print() -postSection("withIndex, withShortcutEdges, labelsAsLatexMath") +post.summarySection("withIndex, withShortcutEdges, labelsAsLatexMath") p = DGPrinter() p.graphPrinter.withIndex = True p.withShortcutEdges = False p.labelsAsLatexMath = False dg.print(p) -postSection("withGraphImages") +post.summarySection("withGraphImages") p = DGPrinter() p.withGraphImages = False dg.print(p) -postSection("vertexVisible") +post.summarySection("vertexVisible") p = DGPrinter() p.pushVertexVisible(False) dg.print(p) @@ -42,14 +46,12 @@ p.pushVertexVisible(lambda v: v.graph.name != "hide") dg.print(p) p.popVertexVisible() -# deprecated -config.common.ignoreDeprecation = True -p.pushVertexVisible(lambda g, dg: g.name != "hide") -config.common.ignoreDeprecation = False +checkDeprecated(lambda: + p.pushVertexVisible(lambda g, dg: g.name != "hide")) dg.print(p) p.popVertexVisible() -postSection("edgeVisible") +post.summarySection("edgeVisible") p = DGPrinter() p.pushEdgeVisible(False) dg.print(p) @@ -58,19 +60,19 @@ dg.print(p) p.popEdgeVisible() -postSection("withShortcutEdgesAfterVisibility") +post.summarySection("withShortcutEdgesAfterVisibility") p = DGPrinter() p.pushVertexVisible(lambda v: v.graph.name != "hide") p.withShortcutEdgesAfterVisibility = True dg.print(p) -postSection("vertexLabelSep") +post.summarySection("vertexLabelSep") p = DGPrinter() p.pushVertexLabel("vLabelConstant") p.vertexLabelSep = " sep " dg.print(p) -postSection("vertexLabel") +post.summarySection("vertexLabel") p = DGPrinter() p.pushVertexLabel("vLabelConstant") dg.print(p) @@ -78,20 +80,18 @@ p.pushVertexLabel(lambda v: "vLabelCallback") dg.print(p) p.popVertexLabel() -# deprecated -config.common.ignoreDeprecation = True -p.pushVertexLabel(lambda g, dg: "vLabelCallbackDep") -config.common.ignoreDeprecation = False +checkDeprecated(lambda: + p.pushVertexLabel(lambda g, dg: "vLabelCallbackDep")) dg.print(p) p.popVertexLabel() -postSection("edgeLabelSep") +post.summarySection("edgeLabelSep") p = DGPrinter() p.pushEdgeLabel("eLabelConstant") p.edgeLabelSep = " sep " dg.print(p) -postSection("edgeLabel") +post.summarySection("edgeLabel") p = DGPrinter() p.pushEdgeLabel("eLabelConstant") dg.print(p) @@ -100,18 +100,18 @@ dg.print(p) p.popEdgeLabel() -postSection("withGraphName, withRuleName, withRuleId") +post.summarySection("withGraphName, withRuleName, withRuleId") p = DGPrinter() p.withGraphName = False p.withRuleName = True p.withRuleId = False dg.print(p) -postSection("withInlineGraphs") +post.summarySection("withInlineGraphs") p = DGPrinter() p.withInlineGraphs = True f = dg.print(p) -post("summaryInput {}.tex".format(f[0][:-4])) +post.summaryInput("{}.tex".format(f[0][:-4])) e = next(e for e in dg.edges if len(e.rules) > 0) src = next(iter(e.sources)) tar = next(iter(e.targets)) @@ -123,9 +123,9 @@ \draw[blue] (v-{dgSrc}-0-v-{gSrc}) to[bend left=45] (v-{dgTar}-0-v-{gTar}); \end{{tikzpicture}} """.format(dgSrc=src.id, dgTar=tar.id, gSrc=vSrc.id, gTar=vTar.id)) -post("summaryInput out/extra.tex") +post.summaryInput("out/extra.tex") -postSection("vertexColour") +post.summarySection("vertexColour") p = DGPrinter() p.pushVertexColour("blue") dg.print(p) @@ -133,10 +133,8 @@ p.pushVertexColour(lambda v: "green") dg.print(p) p.popVertexColour() -# deprecated -config.common.ignoreDeprecation = True -p.pushVertexColour(lambda g, dg: "red") -config.common.ignoreDeprecation = False +checkDeprecated(lambda: + p.pushVertexColour(lambda g, dg: "red")) dg.print(p) p.popVertexColour() @@ -144,7 +142,7 @@ p.pushVertexColour("blue", extendToEdges=False) dg.print(p) -postSection("edgeColour") +post.summarySection("edgeColour") p = DGPrinter() p.pushEdgeColour("blue") dg.print(p) @@ -153,14 +151,14 @@ dg.print(p) p.popEdgeColour() -postSection("rotationOverwrite") +post.summarySection("rotationOverwrite") p = DGPrinter() p.setRotationOverwrite(45) dg.print(p) p.setRotationOverwrite(lambda g: -45) dg.print(p) -postSection("mirrorOverwrite") +post.summarySection("mirrorOverwrite") p = DGPrinter() p.setMirrorOverwrite(True) dg.print(p) @@ -168,12 +166,27 @@ p.setMirrorOverwrite(lambda g: True) dg.print(p) -postSection("graphvizPrefix") +post.summarySection("imageOverride") +p = DGPrinter() +def customImage(v, dupNum): + if v.graph.name != "image1": + return ("", "") + with open("out/custom.tex", "w") as f: + f.write("""\\begin{tikzpicture} +\\node {custom}; +\\end{tikzpicture}""") + return ("out/custom", "compileTikz \"out/custom\" \"out/custom\"") +p.setImageOverwrite(customImage) +dg.print(p) +p.setImageOverwrite(None) +dg.print(p) + +post.summarySection("graphvizPrefix") p = DGPrinter() p.graphvizPrefix = 'layout = "dot";' dg.print(p) -postSection("tikzpictureOption") +post.summarySection("tikzpictureOption") p = DGPrinter() p.tikzpictureOption += ', draw=blue' dg.print(p) diff --git a/test/py/dg/402_printer_latex_escape.py b/test/py/dg/402_printer_latex_escape.py new file mode 100644 index 0000000..a6b278b --- /dev/null +++ b/test/py/dg/402_printer_latex_escape.py @@ -0,0 +1,25 @@ +include("../xxx_helpers.py") +post.enableInvokeMake() + +dg = DG() +with dg.build() as b: + d = Derivation() + d.left = [smiles("CO", name="g#_x")] + d.right = [smiles("CS")] + d.rule = ruleGMLString('''rule [ + ruleID "r#_x" + left [ node [ id 0 label "O" ] ] + right [ node [ id 0 label "S" ] ] + ]''') + b.addDerivation(d) + +p = DGPrinter() +p.withRuleName = True + +post.summarySection("labelsAsLatexMath=false") +p.labelsAsLatexMath = True +dg.print(p) + +post.summarySection("labelsAsLatexMath=true") +p.labelsAsLatexMath = True +dg.print(p) diff --git a/test/py/dg/411_printData_using.py b/test/py/dg/411_printData_using.py index 615fc1e..bcd2704 100644 --- a/test/py/dg/411_printData_using.py +++ b/test/py/dg/411_printData_using.py @@ -1,5 +1,5 @@ include("../xxx_helpers.py") -post("enableSummary") +post.enableInvokeMake() dg = DG() with dg.build() as b: @@ -14,7 +14,7 @@ for v in dg.vertices: globals()[v.graph.name] = v -postSection("makeDuplicate") +post.summarySection("makeDuplicate") d = DGPrintData(dg) d.makeDuplicate(e1, 1) dg.print(data=d) @@ -22,18 +22,18 @@ p.withShortcutEdges = False dg.print(data=d, printer=p) -postSection("removeDuplicate") +post.summarySection("removeDuplicate") d = DGPrintData(dg) d.removeDuplicate(e1, 0) dg.print(data=d) -postSection("reconnectSource") +post.summarySection("reconnectSource") d = DGPrintData(dg) d.makeDuplicate(e2, 1) d.reconnectSource(e2, 1, C, 1) dg.print(data=d) -postSection("reconnectTarget") +post.summarySection("reconnectTarget") d = DGPrintData(dg) d.makeDuplicate(e2, 1) d.reconnectTarget(e2, 1, E, 1) diff --git a/test/py/dg/449_printNonHyper.py b/test/py/dg/449_printNonHyper.py index 8d2c957..125f21a 100644 --- a/test/py/dg/449_printNonHyper.py +++ b/test/py/dg/449_printNonHyper.py @@ -1,5 +1,5 @@ include("../xxx_helpers.py") -post("enableSummary") +post.enableInvokeMake() dg = DG() diff --git a/test/py/dg/450_print_dpo.py b/test/py/dg/450_print_dpo_fail.py similarity index 70% rename from test/py/dg/450_print_dpo.py rename to test/py/dg/450_print_dpo_fail.py index 5defba6..b6da175 100644 --- a/test/py/dg/450_print_dpo.py +++ b/test/py/dg/450_print_dpo_fail.py @@ -1,5 +1,4 @@ include("../xxx_helpers.py") -post("enableSummary") e = DGHyperEdge() fail(lambda: e.print(), "Can not print null edge.") @@ -34,10 +33,13 @@ fail(lambda: eFail.print(), "No derivation exists for rule {}.".format(r.name)) -e.print() -p = GraphPrinter() -p.withIndex = True -e.print(printer=p) -e.print(nomatchColour="yellow") -e.print(matchColour="red") -e.print(nomatchColour="yellow", matchColour="red") +def printOk(withGraphvizCoords: bool): + post.enableInvokeMake() + p = GraphPrinter() + p.withGraphvizCoords = withGraphvizCoords + e.print(p) + e.print(p, nomatchColour="yellow") + e.print(p, matchColour="red") + e.print(p, nomatchColour="yellow", matchColour="red") + p.withIndex = True + e.print(p) diff --git a/test/py/dg/451_print_dpo_no_openbabel.py b/test/py/dg/451_print_dpo_no_openbabel.py deleted file mode 100644 index 111d9e6..0000000 --- a/test/py/dg/451_print_dpo_no_openbabel.py +++ /dev/null @@ -1,45 +0,0 @@ -include("../xxx_helpers.py") -post("enableSummary") - -config.io.useOpenBabelCoords = False - -e = DGHyperEdge() -fail(lambda: e.print(), "Can not print null edge.") - -dg = DG() -dg.build().addAbstract("A -> B") -e = next(iter(dg.edges)) -fail(lambda: e.print(), "The edge has no rules.") - -r = ruleGMLString("""rule [ - left [ node [ id 1 label "B" ] ] - context [ - node [ id 0 label "A" ] - edge [ source 0 target 1 label "-" ] - ] - right [ node [ id 1 label "Q" ] ] -]""") -g = graphDFS("[A][B][C]") -gFail = smiles("O") - -dg = DG() -with dg.build() as b: - es = b.apply([g], r) - assert len(es) == 1 - e = es[0] - - d = Derivation() - d.left = [gFail] - d.right = [gFail] - d.rule = r - eFail = b.addDerivation(d) - -fail(lambda: eFail.print(), "No derivation exists for rule {}.".format(r.name)) - -e.print() -p = GraphPrinter() -p.withIndex = True -e.print(printer=p) -e.print(nomatchColour="yellow") -e.print(matchColour="red") -e.print(nomatchColour="yellow", matchColour="red") diff --git a/test/py/dg/451_print_dpo_open_babel.py b/test/py/dg/451_print_dpo_open_babel.py new file mode 100644 index 0000000..0af4e83 --- /dev/null +++ b/test/py/dg/451_print_dpo_open_babel.py @@ -0,0 +1,2 @@ +include("450_print_dpo_fail.py") +printOk(withGraphvizCoords=False) diff --git a/test/py/dg/452_print_dpo_graphviz.py b/test/py/dg/452_print_dpo_graphviz.py new file mode 100644 index 0000000..2f3a4ce --- /dev/null +++ b/test/py/dg/452_print_dpo_graphviz.py @@ -0,0 +1,2 @@ +include("450_print_dpo_fail.py") +printOk(withGraphvizCoords=True) diff --git a/test/py/formoseCommon/test.py b/test/py/formoseCommon/test.py new file mode 100644 index 0000000..0af265a --- /dev/null +++ b/test/py/formoseCommon/test.py @@ -0,0 +1,21 @@ +include("grammar.py") + +r1 = ketoEnol_F +r2 = aldolAdd_F + +c1_1K = r1.getVertexFromExternalId(1) +c1_2K = r1.getVertexFromExternalId(2) +c2_1K = r2.getVertexFromExternalId(1) +c2_2K = r2.getVertexFromExternalId(2) +o2_3K = r2.getVertexFromExternalId(3) + +m = RCMatch(r1, r2) +print("hmm:", c1_1K.right.id, c2_2K.left.id) +m.push(c1_1K.right, c2_2K.left) +print("hmm:", c1_2K.right.id, c2_1K.left.id) +m.push(c1_2K.right, c2_1K.left) +res = m.compose() +res.print() + +for a in inputRules: + a.print() diff --git a/test/py/function.py b/test/py/function.py index 723d3a0..4dcb3b5 100644 --- a/test/py/function.py +++ b/test/py/function.py @@ -4,16 +4,16 @@ dg.calc() printer = DGPrinter() -postSection("Const False") +post.summarySection("Const False") printer.pushVertexVisible(False) dg.print(printer) printer.popVertexVisible() -postSection("Const True") +post.summarySection("Const True") printer.pushVertexVisible(True) dg.print(printer) printer.popVertexVisible() -postSection("Func") +post.summarySection("Func") def f(g, dg): return all(g != a for a in inputGraphs) printer.pushVertexVisible(f) dg.print(printer) @@ -23,7 +23,7 @@ def f(g, dg): return all(g != a for a in inputGraphs) dg.print(printer) printer.popVertexVisible() -postSection("Lambda") +post.summarySection("Lambda") printer.pushVertexVisible(lambda g, dg: all(g != a for a in inputGraphs)) dg.print(printer) printer.popVertexVisible() diff --git a/test/py/graph/010_basic_loading.py b/test/py/graph/010_basic_loading.py index 3f3d6b8..5b65484 100644 --- a/test/py/graph/010_basic_loading.py +++ b/test/py/graph/010_basic_loading.py @@ -5,8 +5,6 @@ with open(fGML, "w") as f: f.write(dataGML) fGML = CWDPath(fGML) -dataDFS = "C" -dataSMILES = dataDFS def check(f, arg): @@ -25,24 +23,77 @@ def makeList(a): f(arg, add=False) assert inputGraphs == res + res2 + +############################################################################### + check(Graph.fromGMLString, dataGML) assert Graph.fromGMLString == graphGMLString -check(Graph.fromGMLStringMulti, dataGML) - check(Graph.fromGMLFile, fGML) assert Graph.fromGMLFile == graphGML +fail(lambda: Graph.fromGMLFile("doesNotExist.gml"), + "Could not open GML file ", err=InputError, isSubstring=True) +check(Graph.fromGMLStringMulti, dataGML) check(Graph.fromGMLFileMulti, fGML) +fail(lambda: Graph.fromGMLFileMulti("doesNotExist.gml"), + "Could not open GML file ", err=InputError, isSubstring=True) + +############################################################################### + +dataDFS = "C" check(Graph.fromDFS, dataDFS) assert Graph.fromDFS == graphDFS +############################################################################### + +dataSMILES = dataDFS + check(Graph.fromSMILES, dataSMILES) assert Graph.fromSMILES == smiles check(Graph.fromSMILESMulti, dataSMILES) +############################################################################### -fail(lambda: Graph.fromGMLFile("doesNotExist.gml"), - "Could not open graph GML file ", err=InputError, isSubstring=True) +dataMOL = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END""" +fMOL = 'myGraph.mol' +with open(fMOL, "w") as f: + f.write(dataMOL) +fMOL = CWDPath(fMOL) + +check(Graph.fromMOLString, dataMOL) +check(Graph.fromMOLFile, fMOL) +fail(lambda: Graph.fromMOLFile("doesNotExist.mol"), + "Could not open MOL file ", err=InputError, isSubstring=True) + +Graph.fromMOLStringMulti(dataMOL) +Graph.fromMOLFileMulti(fMOL) +fail(lambda: Graph.fromMOLFileMulti("doesNotExist.mol"), + "Could not open MOL file ", err=InputError, isSubstring=True) + +############################################################################### + +dataSD = dataMOL + "\n$$$$" +fSD = 'myGraphs.sd' +with open(fSD, "w") as f: + f.write(dataSD) +fSD = CWDPath(fSD) + +check(Graph.fromSDString, dataSD) +check(Graph.fromSDFile, fSD) +fail(lambda: Graph.fromSDFile("doesNotExist.sd"), + "Could not open SD file ", err=InputError, isSubstring=True) + +Graph.fromSDStringMulti(dataSD) +Graph.fromSDFileMulti(fSD) +fail(lambda: Graph.fromSDFileMulti("doesNotExist.sd"), + "Could not open SD file ", err=InputError, isSubstring=True) diff --git a/test/py/graph/020_graphDFS.py b/test/py/graph/020_graphDFS.py deleted file mode 100644 index 91fd527..0000000 --- a/test/py/graph/020_graphDFS.py +++ /dev/null @@ -1,53 +0,0 @@ -def check(s, withIds=False): - if withIds: - toString = lambda a: a.graphDFSWithIds - else: - toString = lambda a: a.graphDFS - a = graphDFS(s) - a.print() - s1 = toString(a) - print(s1) - a1 = graphDFS(s1) - s2 = toString(a1) - a2 = graphDFS(s2) - iso1 = a.isomorphism(a1) - iso2 = a.isomorphism(a2) - if iso1 == 0 or iso2 == 0: - print("s: ", s) - print("s1:", s1) - print("s2:", s2) - postSection("Error, iso1=%d, iso2=%d" % (iso1, iso2)) - a.print() - a1.print() - a2.print() - assert False - return a - -postSection("Implicit vertices") -print("Implicit vertices") -for s in {"B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"}: - a = check(s) - print(a.id, a.graphDFS) - a.printGML() - -postSection("Explicit implicit vertices") -print("Explicit implicit vertices") -for s in {"B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"}: - a = check("[" + s + "]") - print(a.id, a.graphDFS) - a.printGML() - -postSection("Ids and Ring Closures") -print("Ids and Ring Closures") -check("O1CCC-1N") -check("O1CCC1N") - -postSection("More Stuff") -print("More Stuff") -check("C1CCC1") -check("[C\]]") -check("C{a\}b}C") - -postSection("All With Ids") -print("All With Ids") -check("NCC(O)C(O)=O", withIds=True) diff --git a/test/py/graph/020_graphDFS/common.py b/test/py/graph/020_graphDFS/common.py new file mode 100644 index 0000000..a019908 --- /dev/null +++ b/test/py/graph/020_graphDFS/common.py @@ -0,0 +1,39 @@ +include("../../xxx_helpers.py") + +def check(s): + def iso(g1, g2): + if g1.isomorphism(g2) == 0: + print(" Error") + g1.print() + g2.print() + post.enableInvokeMake() + assert False + + + print("check({})".format(s)) + a = Graph.fromDFS(s) + + s1 = a.graphDFS + print(" min. ids:", s1) + a1 = Graph.fromDFS(s1) + s2 = a1.graphDFS + s2id = a1.graphDFSWithIds + print(" min. ids:", s2) + print(" all ids: ", s2id) + iso(a, a1) + + + s1 = a.graphDFSWithIds + print(" all ids: ", s1) + a1 = Graph.fromDFS(s1) + s2 = a1.graphDFS + s2id = a1.graphDFSWithIds + print(" min. ids:", s2) + print(" all ids: ", s2id) + iso(a, a1) + + return a + +def checkMulti(s): + print("checkMulti({})".format(s)) + gs = Graph.fromDFSMulti(s) diff --git a/test/py/graph/020_graphDFS/dot.py b/test/py/graph/020_graphDFS/dot.py new file mode 100644 index 0000000..d6c7515 --- /dev/null +++ b/test/py/graph/020_graphDFS/dot.py @@ -0,0 +1,34 @@ +include("common.py") + +a = Graph.fromDFS("[C]{.}[C]") +assert a.numVertices == 2 +assert a.numEdges == 1 +assert next(iter(a.edges)).stringLabel == '.' + +fail(lambda: Graph.fromDFS("[C]1.[O]2"), "not connected", + err=InputError, isSubstring=True) +gs = Graph.fromDFSMulti("[C]1.[O]2") +assert len(gs) == 2 +v1_0 = gs[0].getVertexFromExternalId(1) +assert v1_0.stringLabel == "C" +v2_0 = gs[0].getVertexFromExternalId(2) +assert not v2_0 +v1_1 = gs[1].getVertexFromExternalId(1) +assert not v1_1 +v2_1 = gs[1].getVertexFromExternalId(2) +assert v2_1.stringLabel == "O" + +Graph.fromDFS("C1C.CC1") +gs = Graph.fromDFSMulti("C1C.CC1") +assert len(gs) == 1 + +Graph.fromDFS("C1CCCC.1") +gs = Graph.fromDFSMulti("C1CCCC.1") +assert len(gs) == 1 + +gs = Graph.fromDFSMulti("[A]1.[B].1[X]") +assert len(gs) == 2 +assert gs[0].numVertices == 1 +assert next(iter(gs[0].vertices)).stringLabel == "A" +assert gs[1].numVertices == 2 +assert tuple(sorted(v.stringLabel for v in gs[1].vertices)) == ("B", "X") diff --git a/test/py/graph/020_graphDFS/other.py b/test/py/graph/020_graphDFS/other.py new file mode 100644 index 0000000..73e1edc --- /dev/null +++ b/test/py/graph/020_graphDFS/other.py @@ -0,0 +1,7 @@ +include("common.py") + +a = smiles("[CH3][CH2][CH2][CH2][CH2][C@H]([OH])/[CH]=[CH]/[C@H]1[C@H]2[CH2][C@H]([O][O]2)[C@@H]1[CH2]/[CH]=[CH]\\[CH2][CH2][CH2][C](=[O])[O-]") +b = graphDFS(a.graphDFS) +assert a.isomorphism(b) != 0 +c = graphDFS(a.graphDFSWithIds) +assert a.isomorphism(c) != 0 diff --git a/test/py/graph/020_graphDFS/tests.py b/test/py/graph/020_graphDFS/tests.py new file mode 100644 index 0000000..882b177 --- /dev/null +++ b/test/py/graph/020_graphDFS/tests.py @@ -0,0 +1,28 @@ +include("common.py") + +def sec(s): + post.summarySection(s) + print("="*80) + print(s) + +sec("Implicit vertices") +for s in {"B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"}: + a = check(s) + +sec("Explicit implicit vertices") +for s in {"B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"}: + a = check("[" + s + "]") + +sec("Ids and Ring Closures") +check("O1CCC-1N") +check("O1CCC1N") +check("[A]1[B][C]-1([S])") +check("[A]1[B][C]1") + +sec("Branches") +check("NCC(O)C(O)=O") + +sec("More Stuff") +check("C1CCC1") +check(r"[C\]]") +check(r"C{a\}b}C") diff --git a/test/py/graph/020_graphDFS/topo.py b/test/py/graph/020_graphDFS/topo.py new file mode 100644 index 0000000..ecef6b2 --- /dev/null +++ b/test/py/graph/020_graphDFS/topo.py @@ -0,0 +1,80 @@ +include("common.py") + +print("Missing ring closure") +fail(lambda: check("[A]-1"), "Ring closure ID 1 not found.", + err=InputError) + +print("Loop edges") +fail(lambda: check("[A]1-1"), "Loop edge in DFS on vertex with ID 1.", + err=InputError) +check("[A]1.1") + +fail(lambda: check("[A][B]1-1"), "Loop edge in DFS on vertex with ID 1.", + err=InputError) +check("[A][B]1.1") + +fail(lambda: check("[A]1(-1)"), "Loop edge in DFS on vertex with ID 1.", + err=InputError) +check("[A]1(.1)") + +fail(lambda: check("[A]([B]1-1)"), "Loop edge in DFS on vertex with ID 1.", + err=InputError) +check("[A]([B]1.1)") + + +print("Parallel Edges") +def bad(s): + fail(lambda: check(s), "Parallel edge in DFS. Back-edge is to vertex with ID 1.", err=InputError) + +print("="*80) +print("without extra ring closures") +for first in ( + "[A]1", # chain + "[Q][A]1", # tail + "[Q]([A]1", # branch + ): + for second in ( + "{}[B]", # tail + "({}[B]", # branch + ): + suffix = '' + suffix += ')' if '(' in first else '' + suffix += ')' if '(' in second else '' + print("-"*80) + print("Bad:", first + second) + for third in ("-1", "(1)", "1"): # tail, branch, ring-closure + bad(first + second.format('') + third + suffix) + check(first + second.format('.') + third + suffix) + for third in (".1", "(.1)"): + check(first + second.format('') + third + suffix) + checkMulti(first + second.format('.') + third + suffix) + +print("="*80) +print("with extra ring closures") +for first in ( + "[X]2[Y][Z][A]1", # tail + "[X]2[Y][Z]([A]1", # branch + ): + for second in ( + "{}[B]", # tail + "({}[B]", # branch + ): + suffix = '' + suffix += ')' if '(' in first else '' + suffix += ')' if '(' in second else '' + print("-"*80) + print("Bad:", first + second) + for third in ("-1", "(1)", "1"): # tail, branch, ring-closure + bad(first + second.format('-2') + third + suffix) + check(first + second.format('-2.') + third + suffix) + for third in ("-2-1", "-2(1)"): # tail, branch + bad(first + second.format('') + third + suffix) + check(first + second.format('.') + third + suffix) + bad(first + second.format('-2') + third + suffix) + check(first + second.format('-2.') + third + suffix) + for third in (".1", "(.1)"): + check(first + second.format('-2') + third + suffix) + checkMulti(first + second.format('-2.') + third + suffix) + for third in ("-2.1", "-2(.1)"): + check(first + second.format('') + third + suffix) + checkMulti(first + second.format('.') + third + suffix) diff --git a/test/py/graph/020_graphDFS/whitespace.py b/test/py/graph/020_graphDFS/whitespace.py new file mode 100644 index 0000000..c5fcdc8 --- /dev/null +++ b/test/py/graph/020_graphDFS/whitespace.py @@ -0,0 +1,8 @@ +include("common.py") + +a1 = Graph.fromDFSMulti("[A] 1 ( [B] ) [C] [D] {E} [F] . [G]") +a2 = Graph.fromDFSMulti("[A]1([B])[C][D]{E}[F].[G]") +assert a1[0].isomorphism(a2[0]) != 0 +assert a1[1].isomorphism(a2[1]) != 0 +assert len(a1) == 2 +assert len(a2) == 2 diff --git a/test/py/graph/030_smiles/abstract.py b/test/py/graph/030_smiles/abstract.py index daf7cb6..faa38ab 100644 --- a/test/py/graph/030_smiles/abstract.py +++ b/test/py/graph/030_smiles/abstract.py @@ -34,10 +34,13 @@ def check(p, name): config.graph.appendSmilesClass = False check("{}", "abc") +check("{}", "[]") check("{}", "[def]") +check("{}", "abc[def]") check("{}", "[def]efg") check("{}", "abc[def]efg") -check("{}", "[]") +check("{}", "abc[def]ghi[jkl]mno") +check("{}", "[abc][def][ghi]") check("{}", "42") check("{}", "42Heblah") diff --git a/test/py/graph/030_smiles/components.py b/test/py/graph/030_smiles/components.py index 535bb0b..01775dd 100644 --- a/test/py/graph/030_smiles/components.py +++ b/test/py/graph/030_smiles/components.py @@ -18,5 +18,5 @@ assert len(gs) == 1 fail(lambda: Graph.fromSMILES("C.1CCCC.1"), - "Error in graph loading from smiles string", + "Error in loading from inline SMILES string", err=InputError, isSubstring=True) diff --git a/test/py/graph/030_smiles/mass/genSymbolJumpTable.py b/test/py/graph/030_smiles/mass/genSymbolJumpTable.py index f7505d2..e5035ad 100644 --- a/test/py/graph/030_smiles/mass/genSymbolJumpTable.py +++ b/test/py/graph/030_smiles/mass/genSymbolJumpTable.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() data = list() data.append((1, "H", "Hydrogen")) data.append((2, "He", "Helium")) diff --git a/test/py/graph/030_smiles/mass/problematic.py b/test/py/graph/030_smiles/mass/problematic.py index 82d26c9..01c2893 100644 --- a/test/py/graph/030_smiles/mass/problematic.py +++ b/test/py/graph/030_smiles/mass/problematic.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() smiles("C(C1C(C=O)=C(C)C=1(CO))") smiles("CC1=CC(C)=C1(O)") smiles(R"CN(C)C1=NC(=C2C=CC=C12)N(C)C") diff --git a/test/py/graph/030_smiles/mass/rhea.py b/test/py/graph/030_smiles/mass/rhea.py index 91389a3..00a5e9c 100644 --- a/test/py/graph/030_smiles/mass/rhea.py +++ b/test/py/graph/030_smiles/mass/rhea.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() smiles(R"[Ag+]") smiles(R"[As](=O)([O-])(C)C") smiles(R"[Br-]") diff --git a/test/py/graph/030_smiles/mass/smiles.py b/test/py/graph/030_smiles/mass/smiles.py index 5cab780..9ac6987 100644 --- a/test/py/graph/030_smiles/mass/smiles.py +++ b/test/py/graph/030_smiles/mass/smiles.py @@ -6,7 +6,7 @@ #config.canon.printStats = True include("loadGraphs.py") -post("disableSummary") +post.disableInvokeMake() if not "n" in globals(): n = 100 @@ -39,7 +39,7 @@ molLike.withIndex = True a.print(graphLike, molLike) b.print(graphLike, molLike) - post("enableSummary") + post.enableInvokeMake() sys.exit(1) for i in range(1, n): aPerm = a.makePermutation() @@ -52,7 +52,7 @@ aPerm.name = "aPerm" a.print() aPerm.print() - post("enableSummary") + post.enableInvokeMake() sys.exit(1) ls = LabelSettings(LabelType.String, LabelRelation.Isomorphism) if a.isomorphism(aPerm, labelSettings=ls) != 1: diff --git a/test/py/graph/030_smiles/mass/smilesMetacycReactionSmiles.py b/test/py/graph/030_smiles/mass/smilesMetacycReactionSmiles.py index 8bf1f81..c991872 100644 --- a/test/py/graph/030_smiles/mass/smilesMetacycReactionSmiles.py +++ b/test/py/graph/030_smiles/mass/smilesMetacycReactionSmiles.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() smiles('[a proteinH:4]', allowAbstract=True) smiles('[a proteinH:9]', allowAbstract=True) smiles('[C:100][C:123]([C:101])([C:118]([O:134])[C:121](=[O:135])[N:126][C:103][C:102][C:112](=[O:131])[N:125][C:104][C:105][S:144][C:114](=[O:132])[C:106][C:113](=[O:5])[O-:5])[C:108][O:137][P:143](=[O:8])([O:140][P:142](=[O:7])([O:136][C:107][C:111]1([C:117]([O:139][P:141]([O-:6])(=[O:6])[O-:6])[C:116]([O:133])[C:122]([O:138]1)[N:130]3([C:120]2(=[C:115]([C:119]([N:124])=[N:127][C:109]=[N:128]2)[N:129]=[C:110]3))))[O-:7])[O-:8]', allowAbstract=True) diff --git a/test/py/graph/030_smiles/mass/smiles_cansmi_roundtrip.py b/test/py/graph/030_smiles/mass/smiles_cansmi_roundtrip.py index 234dd2f..f585956 100644 --- a/test/py/graph/030_smiles/mass/smiles_cansmi_roundtrip.py +++ b/test/py/graph/030_smiles/mass/smiles_cansmi_roundtrip.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() smiles(R"[B@]123[C@@]45[B@]67[B@@]89[C@]1(B2468)B3579") smiles(R"[BH3-][N@+]12CN3CN(CN(C3)C2)C1") smiles(R"[BH3-][N+]12CN3CN(CN(C3)C2)C1") diff --git a/test/py/graph/030_smiles/mass/smiles_nci.py b/test/py/graph/030_smiles/mass/smiles_nci.py index ca4c4bc..eb84d9b 100644 --- a/test/py/graph/030_smiles/mass/smiles_nci.py +++ b/test/py/graph/030_smiles/mass/smiles_nci.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() smiles("B1(O)OB(OB2OB(O)O2)O1") smiles("B(O[C@H](C)CC(C)C)(O[C@H](C)CC(C)C)O[C@H](C)CC(C)C") smiles("Brc1cc(c(c(Br)c1)NC(=S)N)Br") diff --git a/test/py/graph/050_mdl/allSmiles.py b/test/py/graph/050_mdl/allSmiles.py new file mode 100644 index 0000000..cbe6735 --- /dev/null +++ b/test/py/graph/050_mdl/allSmiles.py @@ -0,0 +1,104 @@ +post.disableInvokeMake() + +from openbabel import pybel +from openbabel import openbabel as ob + +include("../030_smiles/mass/loadGraphs.py") + +def loadSmiles(s): + mol = pybel.readstring("smi", s) + mol.addh() + return mol + +def loadMol(s): + mol = pybel.readstring("mol", s) + mol.addh() + return mol + +def bonds(mol): + for b in ob.OBMolBondIter(mol.OBMol): + yield b + +def gmlFromOb(mol): + gml = "graph [\n" + for a in mol.atoms: + aid = AtomId(a.atomicnum) + iso = Isotope(a.isotope) if a.isotope != 0 else Isotope() + c = Charge(a.formalcharge) + spinMult = a.spin + if spinMult == 0: + rad = False + elif spinMult == 2: + rad = True + else: + rad = False + print("Warning: non-zero spin-multiplicity,", spinMult) + ad = AtomData(aid, iso, c, rad) + gml += "\tnode [ id %d label \"%s\" ]\n" % (a.idx, str(ad)) + for b in bonds(mol): + src = b.GetBeginAtom().GetIdx() + tar = b.GetEndAtom().GetIdx() + b = b.GetBondOrder() + if b == 1: + bt = BondType.Single + elif b == 2: + bt = BondType.Double + elif b == 3: + bt = BondType.Triple + else: + print(bt) + assert False + gml += "\tedge [ source %d target %d label \"%s\" ]\n" % (src, tar, str(bt)) + gml += "]\n" + return gml + + +gs = inputGraphs[:] +count = 0 +for g in gs: + count += 1 + try: + s = g.smiles + except LogicError: + # an abstract SMILES string, so skip + continue + # Open Babel may mangle the molecule as it gets loaded, + # so the base for comparison is the loaded OBMol. + obInput = loadSmiles(g.smiles) + # Writing the MOL and reading it can yield a different molecule. + # We just want to test MOL parsing, + # so let's do that round-trip in OB as well. + # The OB V3000 read/write does not handle the valence field. + obInput = loadMol(obInput.write("mol")) + obInput3 = loadMol(obInput.write("mol", opt={"3": None})) + molInput = obInput.write("mol") + mol3Input = obInput3.write("mol", opt={"3": None}) + gFromInput = graphGMLString(gmlFromOb(obInput)) + gFromInput3 = graphGMLString(gmlFromOb(obInput3)) + + # actual check + try: + gFromMol = Graph.fromMOLString(molInput) + except: + print(molInput) + raise + if gFromInput.isomorphism(gFromMol) == 0: + print("gFromInput:", gFromInput.smiles) + print("gFromMol: ", gFromMol.smiles) + gFromInput.print() + gFromMol.print() + assert False + + # actual check + try: + gFromMol3 = Graph.fromMOLString(mol3Input) + except: + print(mol3Input) + raise + if gFromInput3.isomorphism(gFromMol3) == 0: + print("gFromInput3:", gFromInput.smiles) + print("gFromMol3: ", gFromMol3.smiles) + post.enableInvokeMake() + gFromInput.print() + gFromMol3.print() + assert False diff --git a/test/py/graph/050_mdl/mol2000.py b/test/py/graph/050_mdl/mol2000.py new file mode 100644 index 0000000..8baf97d --- /dev/null +++ b/test/py/graph/050_mdl/mol2000.py @@ -0,0 +1,384 @@ +post.disableInvokeMake() + +def fail(s, options=MDLOptions()): + try: + Graph.fromMOLString(s, options=options) + assert False + except InputError as e: + print(e) + +def sep(name): + print("\n%s\n" % name + "="*80) + + +sep("Header") +fail("") +fail("$MDL\n") +fail("$$$$") +fail("$RXN") +fail("$RDFILE") + +fail("\n") + +fail("\n\n") + +fail("\n\n\n") + +sep("Counts") +fail("\n\n\n\n") +fail("\n\n\n 1 0 0 0 0 0 0 0 0 0999 a2000") + +fail("\n\n\n 0 0 0 0 0 0 0 0 0999 V2000") +fail("\n\n\n 1 0 0 0 0 0 0 0 0999 V2000") +fail("\n\n\n1 1 0 0 0 0 0 0 0 0 0999 V2000") +fail("\n\n\n 11 0 0 0 0 0 0 0 0 0999 V2000") + +sep("Atoms") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 0 0 0 0 0 0 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 LP 0 0 0 0 0 0 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 L 0 0 0 0 0 0 0 0 0 0 0 0 +""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 1 0 0 0 0 0 0 0 0 0 0 0 +""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 - 0 0 0 0 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 4 0 0 0 0 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 8 0 0 0 0 0 0 0 0 0 0 +""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0abc 0 0 0 0 0 0 +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 16 0 0 0 0 0 0 +""") + + +sep("Charge 4") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 4 0 0 0 0 0 0 0 0 0 0 +""") +o = MDLOptions() +o.onV2000Charge4 = Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 4 0 0 0 0 0 0 0 0 0 0 +M END""", options=o) +o.onV2000Charge4 = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 4 0 0 0 0 0 0 0 0 0 0 +M END""", options=o) + +sep("Abstract ISO") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 1 0 0 0 0 0 0 0 0 0 0 0 +M END""") + +o = MDLOptions() +o.onImplicitValenceOnAbstract = Action.Warn +o.onV2000AbstractISO = Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 1 0 0 0 0 0 0 0 0 0 0 0 +M END""", options=o) +o.onV2000AbstractISO = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 1 0 0 0 0 0 0 0 0 0 0 0 +M END""", options=o) + + +sep("Bonds") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +""") + +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2 1 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0 2 1 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3 2 1 0 0 0 0 +""") + +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 1 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 0 1 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 3 1 0 0 0 0 +""") + +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 0 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 9 0 0 0 0 +""") + +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 5 0 0 0 0 +""") +o = MDLOptions() +o.onUnsupportedQueryBondType = Action.Warn +Graph.fromMOLString("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 5 0 0 0 0 +M END +""", options=o) +o.onUnsupportedQueryBondType = Action.Ignore +Graph.fromMOLString("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 5 0 0 0 0 +M END +""", options=o).isomorphism(graphDFS("C([H])([H])([H]){_Q_1_2_5}C([H])([H])([H])")) == 1 + + +sep("Properties") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +blah +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHGxx +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 112345678x +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 1 xxxx +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 1 0xxxx +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 1 3xxxx +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 1 1 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M CHG 1 1 - +""") + +sep("End") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +""") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +M END +a +""") + +sep("Missing M End") +fail("""\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +""") + + +sep("Unhandled property") +def makeInput(p): + return """\n\n\n 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 +{} +M END""".format(p) + +o = MDLOptions() +a = Graph.fromMOLString(makeInput("blah"), options=o) +assert len(a.loadingWarnings) == 1 + +o.fullyIgnoreV2000UnhandledKnownProperty = True +a = Graph.fromMOLString(makeInput("blah"), options=o) +assert len(a.loadingWarnings) == 1 +a = Graph.fromMOLString(makeInput("M REG 123456489"), options=o) +assert len(a.loadingWarnings) == 0 + +o.onV2000UnhandledProperty = Action.Error +a = Graph.fromMOLString(makeInput("M REG 123456489"), options=o) +assert len(a.loadingWarnings) == 0 + + +sep("Implicit valence of abstract atom") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 +M END +""") +o = MDLOptions() +o.onImplicitValenceOnAbstract = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 +M END +""", options=o) +o.onImplicitValenceOnAbstract = Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 +M END +""", options=o) + + +for rad in 1, 3, 4, 5, 6: + sep("RAD {}".format(rad)) + data = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +M RAD 1 1 {} +M END""".format(rad) + fail(data) + o = MDLOptions() + setattr(o, "onRAD{}".format(rad), Action.Ignore) + Graph.fromMOLString(data, options=o) + setattr(o, "onRAD{}".format(rad), Action.Warn) + Graph.fromMOLString(data, options=o) + + +sep("Atom alias") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A +M END +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 1 +M END +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A x +M END +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 0 +M END +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 2 +M END +""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 1 +""") +assert Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 1 +$strangeLabel +M END +""").isomorphism(graphDFS("[$strangeLabel]")) == 1 +o = MDLOptions() +o.applyV2000AtomAliases = False +assert Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +A 1 +$strangeLabel +M END +""", options=o).isomorphism(smiles("C")) == 1 + + +sep("Parallel Edges") +fail("""\n\n\n 2 2 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 1 1 0 0 0 0 +M END""") + + +sep("Empty") +fail("""\n\n\n 0 0 0 0 0 0 0 0 0 0999 V2000 +M END""") + + +sep("Pattern Atoms") +for s, opt in (('ISO', 'onPatternIsotope'), ('CHG', 'onPatternCharge'), ('RAD', 'onPatternRadical')): + data = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0 +M {} 1 1 2 +M END""".format(s) + fail(data) + o = MDLOptions() + o.onImplicitValenceOnAbstract = Action.Ignore + setattr(o, opt, Action.Warn) + Graph.fromMOLString(data, options=o) + setattr(o, opt, Action.Ignore) diff --git a/test/py/graph/050_mdl/mol3000.py b/test/py/graph/050_mdl/mol3000.py new file mode 100644 index 0000000..f444841 --- /dev/null +++ b/test/py/graph/050_mdl/mol3000.py @@ -0,0 +1,640 @@ +post.disableInvokeMake() + +def fail(s, options=MDLOptions()): + try: + Graph.fromMOLString(s, options=options) + assert False + except InputError as e: + print(e) + +def sep(name): + print("\n%s\n" % name + "="*80) + + +sep("CTAB") +fail("\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 """) +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAx""") + +sep("CTAB counts") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 blah""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 2 3 4""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1a 2 3 4 5""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 2a 3 4 5""") + +sep("Atom block") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 2 3 4 5""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 2 3 4 5 +M V30 BEGIn ATOM""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 2 3 4 5 +M V30 BEGIN ATOm""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 0 0 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 x 0 0 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 0 0 0 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 "blah" 0 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 blah 0 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 x""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 1 C 0 0 0 0""") + +sep("Atom optional arguments") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 blah""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 blah""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 blah=""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG=""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG=x""") + + +sep("Bond block") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BONd""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BONd""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 0 0 0""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 x 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 0 0 0 0""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 0 x 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 0 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 5 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 6 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 7 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 9 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 10 0 0""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 1 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 x 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 0 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 3 0""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 x""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 0""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 3""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 1""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 1 1 2 3""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 END BONd""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 5 1 2 +M V30 END BOND +M V30 END CTAB""") +o = MDLOptions() +o.onUnsupportedQueryBondType = Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 5 1 2 +M V30 END BOND +M V30 END CTAB +M END""", options=o) +o.onUnsupportedQueryBondType = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 1 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 5 1 2 +M V30 END BOND +M V30 END CTAB +M END""", options=o).isomorphism(graphDFS("C([H])([H])([H]){_Q_1_2_5}C([H])([H])([H])")) == 1 + +sep("CTAB end") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 END BOND""") + + +sep("End") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 END BOND +M V30 END CTAB""") # TODO: may be allowed later +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 END BOND +M V30 END CTAB +M ENd""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 3 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 3 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 END BOND +M V30 END CTAB +M END +x""") + +sep("Implicit valence of abstract atom") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 * 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END""") +o = MDLOptions() +o.onImplicitValenceOnAbstract = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 * 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END""", options=o) +o.onImplicitValenceOnAbstract = Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 * 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END""", options=o) + + +for rad in 1, 3, 4, 5, 6: + sep("RAD {}".format(rad)) + data = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 RAD={} +M V30 END ATOM +M V30 END CTAB +M END""".format(rad) + fail(data) + o = MDLOptions() + setattr(o, "onRAD{}".format(rad), Action.Ignore) + Graph.fromMOLString(data, options=o) + setattr(o, "onRAD{}".format(rad), Action.Warn) + Graph.fromMOLString(data, options=o) + + +sep("Parallel Edges") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 2 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 C 0 0 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 1 +M V30 END BOND +M V30 END CTAB +M END""") + + +sep("Pattern Atoms") +for s, opt in (('MASS', 'onPatternIsotope'), ('CHG', 'onPatternCharge'), ('RAD', 'onPatternRadical')): + data = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 * 0 0 0 0 {}=2 +M V30 END ATOM +M V30 END CTAB +M END""".format(s) + fail(data) + o = MDLOptions() + o.onImplicitValenceOnAbstract = Action.Ignore + setattr(o, opt, Action.Warn) + Graph.fromMOLString(data, options=o) + setattr(o, opt, Action.Ignore) + + +sep("Posval vs. keyword val") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG=1 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG=1 0 RAD=2 +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG= +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 CHG= +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") + +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 RGROUPS=(1 1 +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") + +o = MDLOptions() +assert o.onV3000UnhandledAtomProperty == Action.Warn +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 RGROUPS=(1 1) RGROUPS=(2 1 1) +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""") +o.onV3000UnhandledAtomProperty = Action.Error +fail("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 RGROUPS=(1 1) RGROUPS=(2 1 1) +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""", options=o) +o.onV3000UnhandledAtomProperty = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 RGROUPS=(1 1) RGROUPS=(2 1 1) +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""", options=o) + + +sep("String args") +o = MDLOptions() +o.onV3000UnhandledAtomProperty = Action.Ignore +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 Something="hello ( ) ) = world" +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""", options=o) +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 SomethingElse="hello "" ( ) ) = world" +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""", options=o) + + +sep("Multiline") +Graph.fromMOLString("""\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1- +M V30 - +M V30 C 0 - +M V30 0 0 0 CHG=2 - +M V30 RAD=2 RGROUPS=(- +M V30 1 1) +M V30 END ATOM +M V30 BEGIN BOND +M V30 END BOND +M V30 END CTAB +M END""", options=o) + + +sep("Empty") +fail("""\n\n\n 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 0 0 0 0 0 +M V30 BEGIN ATOM +M V30 END ATOM +M V30 END CTAB +M END""") diff --git a/test/py/graph/050_mdl/molMult.py b/test/py/graph/050_mdl/molMult.py new file mode 100644 index 0000000..a8f77ef --- /dev/null +++ b/test/py/graph/050_mdl/molMult.py @@ -0,0 +1,15 @@ +include("../../xxx_helpers.py") + +dataDis = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 O 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END""" +fail(lambda: Graph.fromMOLString(dataDis), "not connected", + err=InputError, isSubstring=True) +gs = Graph.fromMOLStringMulti(dataDis) +assert len(gs) == 2 diff --git a/test/py/graph/050_mdl/sd.py b/test/py/graph/050_mdl/sd.py new file mode 100644 index 0000000..afff885 --- /dev/null +++ b/test/py/graph/050_mdl/sd.py @@ -0,0 +1,34 @@ +post.disableInvokeMake() + +mol = """ + OpenBabel03141812452D + + 3 2 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 +M END +""" + +def fail(s): + try: + res = Graph.fromSDString(s) + assert False, res + except InputError as e: + print(e) + +fail(mol) +Graph.fromSDString(mol + "$$$$") +Graph.fromSDString(mol + "$$$$\n") +fail(mol + "\n") +fail(mol + "a") +fail(mol + ">") +fail(mol + ">\na") +fail(mol + ">\n>") +fail(mol + ">\n>\n\n") +fail(mol + ">\n>\n\na") +Graph.fromSDString(mol + ">\n>\n\n$$$$") +Graph.fromSDString(mol + ">\n>\n\n$$$$\n") +fail(mol + ">\n>\n\n$$$$\na") diff --git a/test/py/graph/050_mdl/sdMulti.py b/test/py/graph/050_mdl/sdMulti.py new file mode 100644 index 0000000..6bd508f --- /dev/null +++ b/test/py/graph/050_mdl/sdMulti.py @@ -0,0 +1,37 @@ +include("../../xxx_helpers.py") + +mol = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 1 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 N 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END +""" +molDis = """\n\n\n 1 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 2 0 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0 0 0 0 +M V30 2 O 0 0 0 0 +M V30 END ATOM +M V30 END CTAB +M END +""" + +dc2 = molDis + "$$$$\n" +dc1c2 = mol + "$$$$\n" + molDis + "$$$$\n" + +fail(lambda: Graph.fromSDString(dc2), "not connected", + err=InputError, isSubstring=True) +fail(lambda: Graph.fromSDString(dc1c2), + "not connected", err=InputError, isSubstring=True) + +gc2 = Graph.fromSDStringMulti(dc2) +assert len(gc2) == 1 +assert len(gc2[0]) == 2 +gc1c2 = Graph.fromSDStringMulti(dc1c2) +assert len(gc1c2) == 2 +assert len(gc1c2[0]) == 1 +assert len(gc1c2[1]) == 2 diff --git a/test/py/graph/201_morphisms.py b/test/py/graph/201_morphisms.py new file mode 100644 index 0000000..118e54f --- /dev/null +++ b/test/py/graph/201_morphisms.py @@ -0,0 +1,67 @@ +include("../xxx_helpers.py") + +g = smiles("O") +fail(lambda: g.isomorphism(None), "codomain is null.") +fail(lambda: g.monomorphism(None), "codomain is null.") + +fail(lambda: g.enumerateMonomorphisms(None, callback=False), + "codomain is null.") + + +co2 = smiles("O=C=O", "CO2") +co = smiles("[C]=O", name="CO") +co2_2 = smiles("O=C=O", "CO2 2") + +assert(co2.isomorphism(co2_2) > 0) +assert(co2.isomorphism(co) == 0) +assert(co.monomorphism(co2) > 0) +assert(co2.monomorphism(co2_2) > 0) +assert(co2.monomorphism(co) == 0) + +def check(f, codom, maps, numMaps=2**30): + exp = list(sorted(maps)) + res = [] + def c(m): + if len(res) >= numMaps: + return False + m_ = [] + for v in m.domain.vertices: + assert m[v] + assert m.inverse(m[v]) == v + m_.append((v.id, m[v].id)) + res.append(m_) + if len(res) >= numMaps: + return False + return True + f(codom, callback=c) + res = list(sorted(res)) + if len(res) != len(exp): + print("res:", res) + print("exp:", exp) + + +check(co2.enumerateMonomorphisms, co2_2, [ + [(0, 0), (1, 1), (2, 2)], + [(0, 0), (1, 2), (1, 2)], +]) +check(co2.enumerateMonomorphisms, co2_2, [], numMaps=0) +check(co2.enumerateMonomorphisms, co, []) +check(co.enumerateMonomorphisms, co2, [ + [(0, 0), (1, 1)], + [(0, 0), (1, 2)], +]) +check(co.enumerateMonomorphisms, co2, [], numMaps=0) +check(co2.enumerateMonomorphisms, co, []) + +# check that the graphs and maps are still there +res = [] +def c(m): + res.append(m) +smiles('[C]', 'C').enumerateMonomorphisms(smiles('[C]', 'C 2'), + callback=c) +assert res[0].domain.name == 'C' +assert res[0].codomain.name == 'C 2' +assert res[0][next(iter(res[0].domain.vertices))] == \ + next(iter(res[0].codomain.vertices)) +assert res[0].inverse(next(iter(res[0].codomain.vertices))) == \ + next(iter(res[0].domain.vertices)) diff --git a/test/py/graph/130_aut.py b/test/py/graph/250_aut.py similarity index 96% rename from test/py/graph/130_aut.py rename to test/py/graph/250_aut.py index 174f045..1ca3621 100644 --- a/test/py/graph/130_aut.py +++ b/test/py/graph/250_aut.py @@ -41,4 +41,4 @@ pairs.add((s.id, t.id)) f.write("\\path[modRCMatchEdge] (v-%d) to[bend left=15] (v-%d);\n" % (v.id, vImg.id)) f.write("\\end{tikzpicture}\n") -post("summaryInput \"%s\"" % fName) +post.summaryInput(fName) diff --git a/test/py/graph/301_image.py b/test/py/graph/301_image.py new file mode 100644 index 0000000..33ab1b4 --- /dev/null +++ b/test/py/graph/301_image.py @@ -0,0 +1,11 @@ +a = smiles("C") +a.print() +def customImage(): + with open("out/custom.tex", "w") as f: + f.write("""\\begin{tikzpicture} +\\node {custom}; +\\end{tikzpicture}""") + return "out/custom" +a.image = customImage +a.imageCommand = "compileTikz \"out/custom\" \"out/custom\"" +a.print() diff --git a/test/py/graph/800_printer.py b/test/py/graph/800_printer.py new file mode 100644 index 0000000..f17f124 --- /dev/null +++ b/test/py/graph/800_printer.py @@ -0,0 +1,88 @@ +include("../xxx_helpers.py") + +p = GraphPrinter() +assert p.edgesAsBonds +assert not p.collapseHydrogens +assert p.raiseIsotopes +assert p.raiseCharges +assert not p.simpleCarbons +assert not p.thick +assert not p.withColour +assert not p.withIndex +assert not p.withTexttt +assert not p.withRawStereo +assert not p.withPrettyStereo +assert p.rotation == 0 +assert not p.mirror +assert not p.withGraphvizCoords +assert p.graphvizPrefix == "" + +p.disableAll() +assert not p.edgesAsBonds +assert not p.collapseHydrogens +assert not p.raiseIsotopes +assert not p.raiseCharges +assert not p.simpleCarbons +assert not p.thick +assert not p.withColour +assert not p.withIndex +assert not p.withTexttt +assert not p.withRawStereo +assert not p.withPrettyStereo +assert p.rotation == 0 +assert not p.mirror +assert not p.withGraphvizCoords +assert p.graphvizPrefix == "" + +p.enableAll() +assert p.edgesAsBonds +assert p.collapseHydrogens +assert p.raiseIsotopes +assert p.raiseCharges +assert p.simpleCarbons +assert p.thick +assert p.withColour +assert p.withIndex +assert not p.withTexttt +assert not p.withRawStereo +assert not p.withPrettyStereo +assert p.rotation == 0 +assert not p.mirror +assert not p.withGraphvizCoords +assert p.graphvizPrefix == "" + +p.disableAll() +p.setMolDefault() +assert p.edgesAsBonds +assert p.collapseHydrogens +assert p.raiseIsotopes +assert p.raiseCharges +assert p.simpleCarbons +assert not p.thick +assert p.withColour +assert not p.withIndex +assert not p.withTexttt +assert not p.withRawStereo +assert not p.withPrettyStereo +assert p.rotation == 0 +assert not p.mirror +assert not p.withGraphvizCoords +assert p.graphvizPrefix == "" + +p.disableAll() +p.setReactionDefault() +assert p.edgesAsBonds +assert p.collapseHydrogens +assert p.raiseIsotopes +assert p.raiseCharges +assert not p.simpleCarbons +assert not p.thick +assert p.withColour +assert not p.withIndex +assert not p.withTexttt +assert not p.withRawStereo +assert not p.withPrettyStereo +assert p.rotation == 0 +assert not p.mirror +assert not p.withGraphvizCoords +assert p.graphvizPrefix == "" diff --git a/test/py/graph/801_printer_graphviz.py b/test/py/graph/801_printer_graphviz.py new file mode 100644 index 0000000..07f7575 --- /dev/null +++ b/test/py/graph/801_printer_graphviz.py @@ -0,0 +1,24 @@ +include("../xxx_helpers.py") +post.enableInvokeMake() + +a = smiles("C1CCCCC1CCC") + +post.summaryChapter("First") +p = GraphPrinter() +f11 = a.print(p) +p.withGraphvizCoords = True +f12 = a.print(p) +p.graphvizPrefix = 'layout = "dot"'; +f13 = a.print(p) + +post.summaryChapter("Second") +p = GraphPrinter() +f21 = a.print(p) +p.withGraphvizCoords = True +f22 = a.print(p) +p.graphvizPrefix = 'layout = "dot"'; +f23 = a.print(p) + +assert f11 == f21 +assert f12 == f22 +assert f13 == f23 diff --git a/test/py/graph/900_union.py b/test/py/graph/900_union.py index 90c4230..8c8cc7e 100644 --- a/test/py/graph/900_union.py +++ b/test/py/graph/900_union.py @@ -1,6 +1,9 @@ include("../xxx_helpers.py") include("../xxx_graphInterface.py") +fail(lambda: UnionGraph([None]), "A graph is null.") + + O = smiles("O") def check(ug, gs): diff --git a/test/py/graph/graph.py b/test/py/graph/graph.py index 6628656..4061ff2 100644 --- a/test/py/graph/graph.py +++ b/test/py/graph/graph.py @@ -34,16 +34,6 @@ a = smiles("C") print("Energy:", a.energy) -host = smiles("O=C=O", "CO2") -pattern = smiles("[C]=O", name="Pattern") -host2 = smiles("O=C=O", "CO2 2") - -assert(host.isomorphism(host2) > 0) -assert(host.isomorphism(pattern) == 0) -assert(pattern.monomorphism(host) > 0) -assert(host.monomorphism(host2) > 0) -assert(host2.monomorphism(pattern) == 0) - a = smiles("O=C(O)C(CC(=O)O)C(O)C(=O)O", name="Isocitrate") aPerm = a.makePermutation() @@ -51,16 +41,3 @@ a.print() a = smiles("N[*]N") a.print() - - -a = smiles("C") -a.print() -def customImage(): - with open("out/custom.tex", "w") as f: - f.write("""\\begin{tikzpicture} -\\node {custom}; -\\end{tikzpicture}""") - return "out/custom" -a.image = customImage -a.imageCommand = "compileTikz \"out/custom\" \"out/custom\"" -a.print() diff --git a/test/py/matchConstraints/vertexAdjacency/main.py b/test/py/matchConstraints/vertexAdjacency/main.py index e367d3d..0b22126 100644 --- a/test/py/matchConstraints/vertexAdjacency/main.py +++ b/test/py/matchConstraints/vertexAdjacency/main.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() lString = LabelSettings(LabelType.String, LabelRelation.Unification) lTerm = LabelSettings(LabelType.Term, LabelRelation.Unification) diff --git a/test/py/matchConstraints/vertexAdjacency/termConversion.py b/test/py/matchConstraints/vertexAdjacency/termConversion.py index d7750ca..b723df2 100644 --- a/test/py/matchConstraints/vertexAdjacency/termConversion.py +++ b/test/py/matchConstraints/vertexAdjacency/termConversion.py @@ -22,25 +22,25 @@ ] ]""" a = ruleGMLString(rStr) -postChapter("TermState") +post.summaryChapter("TermState") a.printTermState() #b = a.makeInverse() -postChapter("Compose first") +post.summaryChapter("Compose first") rc = rcEvaluator([], ls) res = rc.eval(a *rcSuper* rId) for b in res: b.print() b.printTermState() -postChapter("Compose second") +post.summaryChapter("Compose second") rc = rcEvaluator([], ls) res = rc.eval(rIdInv *rcSub* a) for b in res: b.print() b.printTermState() -postChapter("DGRuleComp") +post.summaryChapter("DGRuleComp") graphDFS("C[Q]") dg = dgRuleComp([], addSubset(inputGraphs) >> a, ls) dg.calc() diff --git a/test/py/molDepiction.py b/test/py/molDepiction.py index 6e45a20..d7bca6a 100644 --- a/test/py/molDepiction.py +++ b/test/py/molDepiction.py @@ -1,4 +1,4 @@ -postSection("Hydrogen moving and charges") +post.summarySection("Hydrogen moving and charges") for b in ["", "=", "#"]: for c in ["", "+", ".", "+."]: for l in [6, 8, 10, 12]: @@ -16,7 +16,7 @@ for a in inputGraphs: a.print(p) inputGraphs[:] = [] -postSection("Double/Triple/Lablled bond") +post.summarySection("Double/Triple/Lablled bond") smiles("O=C=C") smiles("CCCC#N") smiles("CCC(O)=O") @@ -24,13 +24,13 @@ graphDFS("C{a}C") for a in inputGraphs: a.print() inputGraphs[:] = [] -postSection("middle C -> simple C + hidden H") +post.summarySection("middle C -> simple C + hidden H") smiles("CCC") smiles("CC(O)C") smiles("N=CC") for a in inputGraphs: a.print() inputGraphs[:] = [] -postSection("collapse H") +post.summarySection("collapse H") smiles("[H][H]") graphDFS("[Q][H][R]") graphDFS("[H]=[R]") @@ -38,12 +38,12 @@ smiles("CNC") for a in inputGraphs: a.print() inputGraphs[:] = [] -postSection("Aromatic") +post.summarySection("Aromatic") smiles("c1ccccc1") smiles("c1[nH]c2c(ncnc2n1)N") for a in inputGraphs: a.print() inputGraphs[:] = [] -postSection("Charges") +post.summarySection("Charges") smiles("[OH-]") graphDFS("[O2-]") smiles("[H+]") diff --git a/test/py/papers/21_tcs/calc.py b/test/py/papers/21_tcs/calc.py index 4d9a531..d9563f1 100644 --- a/test/py/papers/21_tcs/calc.py +++ b/test/py/papers/21_tcs/calc.py @@ -120,19 +120,19 @@ def handleResult(r1: Rule, r2: Rule, res: Tuple[CompRes, CompRes], pIndex.withIndex = True def printRules(rs, txt): - postChapter(txt) + post.summaryChapter(txt) if printGood: - postSection("Good") + post.summarySection("Good") for r in rs[0]: r.print(p) if printBad: - postSection("Bad") + post.summarySection("Bad") for r in rs[1]: r.print(p) # Insert visualizations of the input into the summary. if printInput: - postChapter("Input") + post.summaryChapter("Input") r1.print(p) r2.print(p) # Insert visualisations of the results into the summary. @@ -173,21 +173,21 @@ def compute(r1: Rule, r2: Rule, rc: RCEvaluator): # invariant. # Use our machinery from above: - postChapter("UseBoostCommonSubgraph = True") + post.summaryChapter("UseBoostCommonSubgraph = True") print("UseBoostCommonSubgraph = True") print("=" * 80) config.rc.useBoostCommonSubgraph = True res = compose(r1, r2, rc) handleResult(r1, r2, res) - postChapter("UseBoostCommonSubgraph = False") + post.summaryChapter("UseBoostCommonSubgraph = False") print("UseBoostCommonSubgraph = False") print("=" * 80) config.rc.useBoostCommonSubgraph = False res = compose(r1, r2, rc) handleResult(r1, r2, res) - post("disableSummary") # it takes quite a while due to all the matches + post.disableInvokeMake() # it takes quite a while due to all the matches # First load our two rules: # - an identity rule with the left-hand side of the Aldol Addition rule, and @@ -290,14 +290,14 @@ def compute(r1: Rule, r2: Rule, rc: RCEvaluator): rc = rcEvaluator(inputRules) -postChapter("No no-edge 'constraints'") +post.summaryChapter("No no-edge 'constraints'") print("No no-edge 'constraints'") print("#" * 80) compute(aldolAdd_F_id, aldolAdd_F, rc) -postChapter("No-edge 'constraints'") +post.summaryChapter("No-edge 'constraints'") print("No-edge 'constraints'") print("#" * 80) compute(aldolAdd_F_id_noEdge, aldolAdd_F_noEdge, rc) -#post("enableSummary") +#post.enableInvokeMake() diff --git a/test/py/post.py b/test/py/post.py new file mode 100644 index 0000000..0402b87 --- /dev/null +++ b/test/py/post.py @@ -0,0 +1,39 @@ +include("xxx_helpers.py") + +assert type(makeUniqueFilePrefix()) == str + +post.command("echo hello") +checkDeprecated(lambda: post("echo hello")) + +post.flushCommands() +checkDeprecated(lambda: postFlush()) + +post.disableCommands() +checkDeprecated(lambda: postDisable()) + +post.enableCommands() +checkDeprecated(lambda: postEnable()) + +post.reopenCommandFile() +checkDeprecated(lambda: postReset()) + + +post.enableCommands() + +post.summaryChapter("Some chapter") +checkDeprecated(lambda: postChapter("Some other chapter")) + +post.summarySection("Some section") +checkDeprecated(lambda: postSection("Some other section")) + +post.summaryRaw(r"$\frac{a}{b}$") +post.summaryRaw(r"$\frac{c}{d}$", "secondRaw.whatever") + +with open("out/input.txt", "w") as f: + f.write(r"$\frac{e}{f}$") +post.summaryInput("out/input.txt") + +post.disableCompileSummary() +post.enableCompileSummary() +post.disableInvokeMake() +post.enableInvokeMake() diff --git a/test/py/rc/000_rules.py b/test/py/rc/000_rules.py deleted file mode 100644 index 7668eb5..0000000 --- a/test/py/rc/000_rules.py +++ /dev/null @@ -1,363 +0,0 @@ -include("../xxx_helpers.py") - -_ = ruleGMLString("""rule [ ruleID "->" -]"""); -_A = ruleGMLString("""rule [ ruleID "-> A" - right [ - node [ id 0 label "A" ] - ] -]"""); -_AeA = ruleGMLString("""rule [ ruleID "-> A A" - right [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] -]"""); -_AAA = ruleGMLString("""rule [ ruleID "-> AAA" - right [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -_ABA = ruleGMLString("""rule [ ruleID "-> ABA" - right [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] -]"""); -_AAB = ruleGMLString("""rule [ ruleID "-> AAB" - right [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -_B = ruleGMLString("""rule [ ruleID "-> B" - right [ - node [ id 0 label "B" ] - ] -]"""); -A_ = ruleGMLString("""rule [ ruleID "A ->" - left [ - node [ id 0 label "A" ] - ] -]"""); -A__A = ruleGMLString("""rule [ ruleID "A -> other A" - left [ - node [ id 0 label "A" ] - ] - right [ - node [ id 1 label "A" ] - ] -]"""); -A_A = ruleGMLString("""rule [ ruleID "A -> A" - context [ - node [ id 0 label "A" ] - ] -]"""); -A_AeA = ruleGMLString("""rule [ ruleID "A -> A A" - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 1 label "A" ] - ] -]"""); -A_AAA = ruleGMLString("""rule [ ruleID "A -> AAA" - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -A_ABA = ruleGMLString("""rule [ ruleID "A -> ABA" - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] -]"""); -A_AAB = ruleGMLString("""rule [ ruleID "A -> AAB" - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 1 label "B" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -A_B = ruleGMLString("""rule [ ruleID "A -> B" - left [ - node [ id 0 label "A" ] - ] - right [ - node [ id 0 label "B" ] - ] -]"""); -A_BAA = ruleGMLString("""rule [ ruleID "A -> BAA" - left [ - node [ id 0 label "A" ] - ] - right [ - node [ id 0 label "B" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -A_C = ruleGMLString("""rule [ ruleID "A -> C" - left [ - node [ id 0 label "A" ] - ] - right [ - node [ id 0 label "C" ] - ] -]"""); -AeA_ = ruleGMLString("""rule [ ruleID "A A ->" - left [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] -]"""); -AeA_A = ruleGMLString("""rule [ ruleID "A A -> A" - left [ - node [ id 0 label "A" ] - ] - context [ - node [ id 1 label "A" ] - ] -]"""); -AeA_AeA = ruleGMLString("""rule [ ruleID "A A -> A A" - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] -]"""); -AeA_AAA = ruleGMLString("""rule [ ruleID "A A -> AAA" - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "A" ] - ] -]"""); -AeA_ABA = ruleGMLString("""rule [ ruleID "A A -> ABA" - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "B" ] - ] -]"""); -AeB_AAB = ruleGMLString("""rule [ ruleID "AeB -> AAB" - context [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - ] - right [ - edge [ source 0 target 1 label "A" ] - ] -]"""); -AAA_ = ruleGMLString("""rule [ ruleID "AAA ->" - left [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -AAA_A = ruleGMLString("""rule [ ruleID "AAA -> A" - left [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - ] -]"""); -AAA_AeA = ruleGMLString("""rule [ ruleID "AAA -> A A" - left [ - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] -]"""); -AAA_AAA = ruleGMLString("""rule [ ruleID "AAA -> AAA" - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -AAA_ABA = ruleGMLString("""rule [ ruleID "AAA -> ABA" - left [ - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "B" ] - ] -]"""); -AAA_ACA = ruleGMLString("""rule [ ruleID "AAA -> ACA" - left [ - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "C" ] - ] -]"""); -AAB_ = ruleGMLString("""rule [ ruleID "AAB ->" - left [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -AAB_AeB = ruleGMLString("""rule [ ruleID "AAB -> A B" - left [ - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - ] -]"""); -AAB_AAB = ruleGMLString("""rule [ ruleID "AAB -> AAB" - context [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); -AAB_ABB = ruleGMLString("""rule [ ruleID "AAB -> ABB" - left [ - edge [ source 0 target 1 label "A" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - ] - right [ - edge [ source 0 target 1 label "B" ] - ] -]"""); -ABA_ = ruleGMLString("""rule [ ruleID "ABA ->" - left [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] -]"""); -ABA__AAA = ruleGMLString("""rule [ ruleID "ABA -> AAA other" - left [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 2 label "A" ] - edge [ source 0 target 2 label "A" ] - ] -]"""); -ABA_A = ruleGMLString("""rule [ ruleID "ABA -> A" - left [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - ] -]"""); -ABA_AeA = ruleGMLString("""rule [ ruleID "ABA -> A A" - left [ - edge [ source 0 target 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] -]"""); -ABA_AAA = ruleGMLString("""rule [ ruleID "ABA -> AAA" - left [ - edge [ source 0 target 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "A" ] - ] -]"""); -ABA_ABA = ruleGMLString("""rule [ ruleID "ABA -> ABA" - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - edge [ source 0 target 1 label "B" ] - ] -]"""); -ABA_ACA = ruleGMLString("""rule [ ruleID "ABA -> ACA" - left [ - edge [ source 0 target 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - node [ id 1 label "A" ] - ] - right [ - edge [ source 0 target 1 label "C" ] - ] -]"""); - -B_ = ruleGMLString("""rule [ ruleID "B ->" - left [ - node [ id 0 label "B" ] - ] -]"""); -B_A = ruleGMLString("""rule [ ruleID "B -> A" - left [ - node [ id 0 label "B" ] - ] - right [ - node [ id 0 label "A" ] - ] -]"""); -B_B = ruleGMLString("""rule [ ruleID "B -> B" - context [ - node [ id 0 label "B" ] - ] -]"""); -B_C = ruleGMLString("""rule [ ruleID "B -> C" - left [ - node [ id 0 label "B" ] - ] - right [ - node [ id 0 label "C" ] - ] -]"""); -B_BAA = ruleGMLString("""rule [ ruleID "B -> BAA" - context [ - node [ id 0 label "B" ] - ] - right [ - node [ id 1 label "A" ] - edge [ source 0 target 1 label "A" ] - ] -]"""); diff --git a/test/py/rc/010_base_test.py b/test/py/rc/010_base_test.py new file mode 100644 index 0000000..39a6498 --- /dev/null +++ b/test/py/rc/010_base_test.py @@ -0,0 +1,62 @@ +include("xxx_base_checks.py") +initialVerbose = False + +def c(s1, s2, mId, sExpected, ls): + print("- first: ", s1) + print(" second: ", s2) + print(" matchId: ", mId) + print(" expected:", sExpected) + r1 = Rule.fromDFS(s1) + r2 = Rule.fromDFS(s2) + rExpected = None if sExpected is None else Rule.fromDFS(sExpected) + m = RCMatch(r1, r2, labelSettings=ls) + for i1, i2 in mId.items(): + m.push(r1.getVertexFromExternalId(i1).right, + r2.getVertexFromExternalId(i2).left) + + rs = [r1, r2] + if rExpected is not None: + rs.append(rExpected) + + rRes = m.compose(verbose=initialVerbose) + if rExpected is None: + if rRes is not None: + post.summarySection("Check Error, Result") + print("ERROR expected None, but got rule") + print("Composing again with verbose=True") + rRes.name = "rRes" + rRes.print() + m.compose(verbose=True) + post.enableInvokeMake() + assert False + else: + if rRes is None: + post.summarySection("Check Error, Result") + print("ERROR expected rule, but got None") + print("Composing again with verbose=True") + rExpected.name = "rExpected" + rExpected.print() + m.compose(verbose=True) + post.enableInvokeMake() + assert False + if rRes.isomorphism(rExpected, labelSettings=ls) != 1: + post.summarySection("Check Error, Result") + print("ERROR non-isomorphic result") + print("Composing again with verbose=True") + rRes.name = "rRes" + rExpected.name = "rExpected" + rRes.print() + rExpected.print() + m.compose(verbose=True) + post.enableInvokeMake() + assert False + +def testSettings(ls): + print("#" * 80) + print(ls) + print("#" * 80) + doChecks(lambda *args: c(*args, ls=ls)) + +testSettings(LabelSettings(LabelType.String, LabelRelation.Specialisation)) +testSettings(LabelSettings(LabelType.Term, LabelRelation.Specialisation)) +#testSettings(LabelSettings(LabelType.String, LabelRelation.Isomorphism, LabelRelation.Isomorphism)) diff --git a/test/py/rc/500_compositionMatch.py b/test/py/rc/500_compositionMatch.py index 66133ce..1dcb392 100644 --- a/test/py/rc/500_compositionMatch.py +++ b/test/py/rc/500_compositionMatch.py @@ -3,41 +3,14 @@ def vertex(id_, r): return r.getVertexFromExternalId(id_) -_ = ruleGMLString("""rule [ ruleID "->" - context [ node [ id 0 label "A" ] ] -]"""); -A_B = ruleGMLString("""rule [ ruleID "A -> B" - left [ - node [ id 0 label "A" ] - node [ id 1 label "X" ] - edge [ source 0 target 1 label "-" ] - ] - context [ - node [ id 2 label "P" ] - edge [ source 0 target 2 label "-" ] - ] - right [ - node [ id 0 label "B" ] - node [ id 1 label "Y" ] - edge [ source 0 target 1 label "=" ] - ] -]"""); -B_C = ruleGMLString("""rule [ ruleID "B -> C" - left [ - node [ id 0 label "B" ] - node [ id 1 label "Y" ] - edge [ source 0 target 1 label "=" ] - ] - context [ - node [ id 2 label "Q" ] - edge [ source 0 target 2 label "-" ] - ] - right [ - node [ id 0 label "C" ] - node [ id 1 label "Z" ] - edge [ source 0 target 1 label "#" ] - ] -]"""); +_ = Rule.fromDFS("[A]0>>[A]0") +_.name = "->" + +A_B = Rule.fromDFS("[P]2[A]0[X]1>>[P]2[B]0{=}[Y]1") +A_B.name = "A -> B" + +B_C = Rule.fromDFS("[Q]2[B]0{=}[Y]1>>[Q]2[C]0{#}[Z]1") +B_C.name = "B -> C" fail(lambda: RCMatch(None, _), "rFirst is null.") fail(lambda: RCMatch(_, None), "rSecond is null.") diff --git a/test/py/rc/checks.py b/test/py/rc/checks.py deleted file mode 100644 index 1853697..0000000 --- a/test/py/rc/checks.py +++ /dev/null @@ -1,358 +0,0 @@ -include("../xxx_helpers.py") - -def doChecks(): - print("############################################") - # Rules - # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - # Let ABC = {A, B, C} - # Let ABCe = {A, B, C, } - # We only enumerate connected rules - # ==== ==== ================================== ============ - # L R Label variations Id Pattern - # ==== ==== ================================== ============ - # (empty) _ - # O ABC l_ - # O ABC _l - # O O ABC l_l - # $ $ ABC x ABC l_l - # ---- ---- ---------------------------------- ------------ - # O-O ABC x ABC c ABC lel_ - # O-O' O ABC x ABC x ABC lel_l - # O-$ $ ABC x ABC x ABC x ABC lel_l - # O-O ABC x ABC x ABC _lel - # O O-O' ABC x ABC x ABC l_lel - # $ O-$ ABC x ABC x ABC x ABC l_lel - # ---- ---- ---------------------------------- ------------ - # O-O' O-O' ABC x ABC x ABCe x ABC lel_lel - # O-$ O-$ ABC x ABC x ABC x ABCe x ABC lel_lel - # $-$' $-$' ABC x ABC x ABC x ABC x ABCe x ABC lel_lel - # ==== ==== ================================== ============ - - # ======= ======= ======= ========================= - # First Second New - # === === === === === === ========================= - # L R L R L R Note - # === === === === === === ========================= - print("(empty) with everything"); checkNum = 1 - # A A - # A A - # A A A A - # A B A B - check(_ *rcParallel* A_, {iso(0, A_)}) - check(_ *rcParallel* _A, {iso(0, _A)}) - check(_ *rcParallel* A_A, {iso(0, A_A)}) - check(_ *rcParallel* A_B, {iso(0, A_B)}) - # AAA AAA - # AAA A AAA A - # AAA AAA - # A AAA A AAA - check(_ *rcParallel* AAA_, {iso(0, AAA_)}) - check(_ *rcParallel* AAA_A, {iso(0, AAA_A)}) - check(_ *rcParallel* _AAA, {iso(0, _AAA)}) - check(_ *rcParallel* A_AAA, {iso(0, A_AAA)}) - # AAA A A AAA A A - # A A AAA A A AAA - # AAA AAA AAA AAA - # AAA ABA AAA ABA - check(_ *rcParallel* AAA_AeA, {iso(0, AAA_AeA)}) - check(_ *rcParallel* AeA_AAA, {iso(0, AeA_AAA)}) - check(_ *rcParallel* AAA_AAA, {iso(0, AAA_AAA)}) - check(_ *rcParallel* AAA_ABA, {iso(0, AAA_ABA)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("everything with (empty)"); checkNum = 1 - # A A - # A A - # A A A A - # A B A B - # AAA A A AAA A A - # A A AAA A A AAA - # AAA AAA AAA AAA - # AAA ABA AAA ABA - check(A_ *rcParallel* _, {iso(0, A_)}) - check(_A *rcParallel* _, {iso(0, _A)}) - check(A_A *rcParallel* _, {iso(0, A_A)}) - check(A_B *rcParallel* _, {iso(0, A_B)}) - check(AAA_AeA *rcParallel* _, {iso(0, AAA_AeA)}) - check(AeA_AAA *rcParallel* _, {iso(0, AeA_AAA)}) - check(AAA_AAA *rcParallel* _, {iso(0, AAA_AAA)}) - check(AAA_ABA *rcParallel* _, {iso(0, AAA_ABA)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O -> with everything"); checkNum = 1 - # There would be no non-empty match - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("-> O with everything"); checkNum = 1 - # A A Delete node - # A A A A - # A A B B - # A AAB Invalid, eSecond dangling in L - # A A AAB AAB - # A AAB A B Invalid, eSecond dangling in L - # A A B AAB B AAB (B_BAA) - # A AAB AAB Invalid, eSecond dangling in L - # A AAB ABB Invalid, eSecond dangling in L - check(_A *rcCommon* A_, {iso(0, _)}) - check(_A *rcCommon* A_A, {iso(0, _A)}) - check(_A *rcCommon* A_B, {iso(0, _B)}) - check(_A *rcCommon* AAB_, {}, 0) - check(_A *rcCommon* A_AAB, {iso(0, _AAB)}) - check(_A *rcCommon* AAB_AeB, {}, 0) - check(_A *rcCommon* AeB_AAB, {iso(0, B_BAA)}) - check(_A *rcCommon* AAB_AAB, {}, 0) - check(_A *rcCommon* AAB_ABB, {}, 0) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O -> O with everything"); checkNum = 1 - # A A A A - # A A A A A A - # A A A B A B - # A A AAB AAB - # A A A AAB A AAB - # A A AAB A B AAB A B - # A A A B AAB A B AAB - # A A AAB AAB AAB AAB - # A A AAB ABB AAB ABB - check(A_A *rcCommon* A_, {iso(0, A_)}) - check(A_A *rcCommon* A_A, {iso(0, A_A)}) - check(A_A *rcCommon* A_B, {iso(0, A_B)}) - check(A_A *rcCommon* AAB_, {iso(0, AAB_)}) - check(A_A *rcCommon* A_AAB, {iso(0, A_AAB)}) - check(A_A *rcCommon* AAB_AeB, {iso(0, AAB_AeB)}) - check(A_A *rcCommon* AeB_AAB, {iso(0, AeB_AAB)}) - check(A_A *rcCommon* AAB_AAB, {iso(0, AAB_AAB)}) - check(A_A *rcCommon* AAB_ABB, {iso(0, AAB_ABB)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("$ -> $ with everything"); checkNum = 1 - # A B B A - # A B B B A B - # A B B A A A - # A B B C A C - check(A_B *rcCommon* B_, {iso(0, A_)}) - check(A_B *rcCommon* B_B, {iso(0, A_B)}) - check(A_B *rcCommon* B_A, {iso(0, A_A)}) - check(A_B *rcCommon* B_C, {iso(0, A_C)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O-O -> with everything"); checkNum = 1 - # There would be no non-empty match - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O-O' -> O with everything"); checkNum = 1 - # This would be similar to just O -> O - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O-$ -> $ with everything"); checkNum = 1 - # This would be similar to just $ -> $ - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("-> O-O with everything"); checkNum = 1 - # A A AAA Invalid, eSecond dangling in L - # A A A A AAA AAA - check(_AeA *rcSub* AAA_, {}, 0) - check(_AeA *rcSub* AeA_AAA, {iso(0, _AAA)}) - # AAA A Invalid, eFirst dangling in R - # AAA A A AAA - # AAA A B BAA (AAB) - # AAA A A Invalid, eFirst dangling in R - # AAA AAA (All deleted) - # AAA ABA (Only one vertex would be matched) Invalid, both edges dangling - check(_AAA *rcCommon* A_, {}, 0) - check(_AAA *rcCommon* A_A, {iso(0, _AAA)}) - check(_AAA *rcCommon* A_B, {iso(0, _AAB)}) - check(_AAA *rcSuper* AeA_, {}, 0) - check(_AAA *rcCommon* AAA_, {iso(0, _)}) - check(_AAA *rcCommon* ABA_, {}, 0) - # AAA A A A Invalid, eFirst dangling in R - # AAA AAA A A - # AAA ABA A (Only one vertex would be matched) Invalid, eSecond dangling in L - check(_AAA *rcSuper* AeA_A, {}, 0) - check(_AAA *rcCommon* AAA_A, {iso(0, _A)}) - check(_AAA *rcCommon* ABA_A, {}, 0) - # AAA A A AAA Invalid, duplicate edge in R - # AAA AAA A A A A - # AAA AAA AAA AAA - # AAA AAA ABA ABA - check(_AAA *rcSuper* AeA_AAA, {}, 0) - check(_AAA *rcSuper* AAA_AeA, {iso(0, _AeA)}) - check(_AAA *rcSuper* AAA_AAA, {iso(0, _AAA)}) - check(_AAA *rcSuper* AAA_ABA, {iso(0, _ABA)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O -> O-O with everything"); checkNum = 1 - # A A A AAA Invalid, eSecond dangling in L - # A A A AAA A Invalid, eSecond dangling in L - # A A A A A AAA A AAA - check(A_AeA *rcSub* AAA_, {}, 0) - check(A_AeA *rcSub* AAA_A, {}, 0) - check(A_AeA *rcSub* AeA_AAA, {iso(0, A_AAA)}) - # A AAA A Invalid, eFirst dangling in R - # A AAA A A A AAA - # A AAA A B A BAA - # A AAB - # A AAA A A Invalid, eFirst dangling in R - # A AAA AAA A - # A AAA ABA (Only one vertex would be matched) Invalid, both edges dangling - check(A_AAA *rcCommon* A_, {}, 0) - check(A_AAA *rcCommon* A_A, {iso(0, A_AAA)}) - check(A_AAA *rcCommon* A_B, {iso(0, A_AAB), iso(1, A_BAA)}, 2) - check(A_AAA *rcSuper* AeA_, {}, 0) - check(A_AAA *rcCommon* AAA_, {iso(0, A_)}) - check(A_AAA *rcCommon* ABA_, {}, 0) - # A AAA A A A Invalid, eFirst dangling in R - # A AAA AAA A A A - # A A - # A AAA ABA A ABA AAA' (Only one vertex would be matched, AB->A<- and AA->A<- are not the same) - check(A_AAA *rcSuper* AeA_A, {}, 0) - check(A_AAA *rcCommonMax* AAA_A, {iso(0, A__A), iso(1, A_A)}, 2) - check(A_AAA *rcCommonMax* ABA_A, {iso(0, ABA__AAA)}) - # A AAA A A AAA Invalid, duplicate edge in R - # A AAA AAA A A A A A - # A AAA AAA AAA A AAA - # A AAA AAA ABA A ABA - check(A_AAA *rcSuper* AeA_AAA, {}, 0) - check(A_AAA *rcSuper* AAA_AeA, {iso(0, A_AeA)}) - check(A_AAA *rcSuper* AAA_AAA, {iso(0, A_AAA)}) - check(A_AAA *rcSuper* AAA_ABA, {iso(0, A_ABA)}) - # --------------------------------------------------- - # L R L R L R Note - # === === === === === === ========================= - print("O-O -> O-O with everything"); checkNum = 1 - # A A A A A A AAA A A AAA - # A A A A AAA A A AAA A A - # A A A A AAA A AAA A - # A A A A AAA AAA AAA AAA - # A A A A AAA ABA AAA ABA - print("Group 1"); checkNum = 1 - check(AeA_AeA *rcSuper* AeA_AAA, {iso(0, AeA_AAA)}) - check(AeA_AeA *rcSub* AAA_AeA, {iso(0, AAA_AeA)}) - check(AeA_AeA *rcSub* AAA_A, {iso(0, AAA_A)}) - check(AeA_AeA *rcSub* AAA_AAA, {iso(0, AAA_AAA)}) - check(AeA_AeA *rcSub* AAA_ABA, {iso(0, AAA_ABA)}) - # A A AAA A Invalid, eFirst dangling in R - # A A AAA A A A A AAA - # A A AAA A A Invalid, eFirst dangling in R - # A A AAA AAA A A - # A A AAA A A A Invalid, eFirst dangling in R - # A A AAA AAA A A A A - print("Group 2"); checkNum = 1 - check(AeA_AAA *rcSuper* A_, {}, 0) - check(AeA_AAA *rcSuper* A_A, {iso(0, AeA_AAA)}) - check(AeA_AAA *rcSuper* AeA_, {}, 0) - check(AeA_AAA *rcSuper* AAA_, {iso(0, AeA_)}) - check(AeA_AAA *rcSuper* AeA_A, {}, 0) - check(AeA_AAA *rcSuper* AAA_A, {iso(0, AeA_A)}) - # A A AAA A A A A A A AAA - # A A AAA A A AAA Invalid, duplicate edge in R - # A A AAA AAA A A A A A A - # A A AAA AAA AAA A A AAA - # A A AAA AAA ABA A A ABA - print("Group 3"); checkNum = 1 - check(AeA_AAA *rcSuper* AeA_AeA, {iso(0, AeA_AAA)}) - check(AeA_AAA *rcSuper* AeA_AAA, {}, 0) - check(AeA_AAA *rcSuper* AAA_AeA, {iso(0, AeA_AeA)}) - check(AeA_AAA *rcSuper* AAA_AAA, {iso(0, AeA_AAA)}) - check(AeA_AAA *rcSuper* AAA_ABA, {iso(0, AeA_ABA)}) - # AAA A A A AAA A - # AAA A A A A A AAA A - # AAA A A AAA A Invalid, duplicate edge in L - print("Group 4"); checkNum = 1 - check(AAA_AeA *rcSuper* A_, {iso(0, AAA_A)}) - check(AAA_AeA *rcSuper* AeA_A, {iso(0, AAA_A)}) - check(AAA_AeA *rcSuper* AAA_A, {}, 0) - # AAA A A A A A A AAA A A - # AAA A A A A AAA AAA AAA - # AAA A A A A ABA AAA ABA - # AAA A A AAA A A Invalid, duplicate edge in L - # AAA A A AAA AAA Invalid, duplicate edge in L - # AAA A A AAA ABA Invalid, duplicate edge in L - print("Group 5"); checkNum = 1 - check(AAA_AeA *rcSuper* AeA_AeA, {iso(0, AAA_AeA)}) - check(AAA_AeA *rcSuper* AeA_AAA, {iso(0, AAA_AAA)}) - check(AAA_AeA *rcSuper* AeA_ABA, {iso(0, AAA_ABA)}) - check(AAA_AeA *rcSub* AAA_AeA, {}, 0) - check(AAA_AeA *rcSub* AAA_AAA, {}, 0) - check(AAA_AeA *rcSub* AAA_ABA, {}, 0) - # AAA AAA A Invalid, eFirst dangling in R - # AAA AAA A A AAA AAA - # AAA AAA A A A Invalid, eFirst dangling in R - # AAA AAA AAA A AAA A - print("Group 6"); checkNum = 1 - check(AAA_AAA *rcSuper* A_, {}, 0) - check(AAA_AAA *rcSuper* A_A, {iso(0, AAA_AAA)}) - check(AAA_AAA *rcSuper* AeA_A, {}, 0) - check(AAA_AAA *rcSuper* AAA_A, {iso(0, AAA_A)}) - # AAA AAA A A AAA Invalid, duplicate edge in R - # AAA AAA AAA A A AAA A A - # AAA AAA AAA AAA AAA AAA - # AAA AAA AAA ABA AAA ABA - print("Group 7"); checkNum = 1 - check(AAA_AAA *rcSuper* AeA_AAA, {}, 0) - check(AAA_AAA *rcSuper* AAA_AeA, {iso(0, AAA_AeA)}) - check(AAA_AAA *rcSuper* AAA_AAA, {iso(0, AAA_AAA)}) - check(AAA_AAA *rcSuper* AAA_ABA, {iso(0, AAA_ABA)}) - # AAA ABA A A AAA Invalid, duplicate edge in R - # AAA ABA ABA A A AAA A A - # AAA ABA ABA ABA AAA ABA - # AAA ABA ABA AAA AAA AAA - # AAA ABA ABA ACA AAA ACA - print("Group 8"); checkNum = 1 - check(AAA_ABA *rcSuper* AeA_AAA, {}, 0) - check(AAA_ABA *rcSuper* ABA_AeA, {iso(0, AAA_AeA)}) - check(AAA_ABA *rcSuper* ABA_ABA, {iso(0, AAA_ABA)}) - check(AAA_ABA *rcSuper* ABA_AAA, {iso(0, AAA_AAA)}) - check(AAA_ABA *rcSuper* ABA_ACA, {iso(0, AAA_ACA)}) - - - if False: - # ===== ===== ===== ============== =========== - # First Sec New Note - # == == == == == == ================================================= - # L R L R L R - # == == == == == == ================================================== - - # ===== ===== ===== ================== - # First Sec New Note - # == == == == == == ================================================= - # L R L R L R - # == == == == == == ================================================== - # B B Del - # B B B B - # B B A A - # B B C C - check(_B *rcSuper* B_, {iso(0, _)}) - check(_B *rcSuper* B_B, {iso(0, _B)}) - check(_B *rcSuper* B_A, {iso(0, _A)}) - check(_B *rcSuper* B_C, {iso(0, _C)}) - # -- -- -- -- -- -- ------------------------------------------------ - # B B B B - # B B B B B B - # B B B A B A - # B B B C B C - check(B_B *rcSuper* B_, {iso(0, B_)}) - check(B_B *rcSuper* B_B, {iso(0, B_B)}) - check(B_B *rcSuper* B_A, {iso(0, B_A)}) - check(B_B *rcSuper* B_C, {iso(0, B_C)}) - # -- -- -- -- -- -- ------------------------------------------------ - # A B B A - # A B B B A B - # A B B A A A - # A B B C A C - check(A_B *rcSuper* B_, {iso(0, A_)}) - check(A_B *rcSuper* B_B, {iso(0, A_B)}) - check(A_B *rcSuper* B_A, {iso(0, A_A)}) - check(A_B *rcSuper* B_C, {iso(0, A_C)}) - # ===== ===== ===== ================== - diff --git a/test/py/rc/test.py b/test/py/rc/test.py deleted file mode 100644 index a386ea2..0000000 --- a/test/py/rc/test.py +++ /dev/null @@ -1,57 +0,0 @@ -include("../xxx_helpers.py") - -include("000_rules.py") -#for a in inputRules: -# a.print() -# a.printTermState() - -rc = 'dummy' -iso = 'dummy' - -checkNum = 0 -def check(exp, checks, resSize=1): - global checkNum - print("Check", checkNum, exp) - checkNum += 1 - res = list(set(rc.eval(exp))) - def redo(): - rc.eval(exp, verbosity=20) - if len(rc.products) != 0: - postSection("Product Error, Result") - print("ERROR") - print("=============================================================") - for a in res: a.print() - redo() - assert False - if len(res) != resSize: - postSection("Len Error, Result") - print("ERROR") - print("=============================================================") - for a in res: a.print() - redo() - assert False - for a in checks: - if not a(res): - postSection("Check Error, Result") - print("ERROR") - print("=============================================================") - for a in res: a.print() - redo() - assert False -rcSub = rcSub(allowPartial=False) -rcSuper = rcSuper(allowPartial=False) -rcCommonMax = rcCommon(maximum=True, connected=False) -rcCommon = rcCommon(maximum=False, connected=False) - -def testSettings(settings): - global rc - global iso - global checkNum - checkNum = 0 - rc = rcEvaluator(inputRules, labelSettings=settings) - iso = lambda index, rule: lambda res: res[index].isomorphism(rule, labelSettings=settings) > 0 - include("checks.py", checkDup=False, skipDup=False) - doChecks() -testSettings(LabelSettings(LabelType.String, LabelRelation.Isomorphism)) -testSettings(LabelSettings(LabelType.Term, LabelRelation.Isomorphism)) -#testSettings(LabelSettings(LabelType.String, LabelRelation.Isomorphism, LabelRelation.Isomorphism)) diff --git a/test/py/rc/xxx_base_checks.py b/test/py/rc/xxx_base_checks.py new file mode 100644 index 0000000..627caf6 --- /dev/null +++ b/test/py/rc/xxx_base_checks.py @@ -0,0 +1,595 @@ +include("../xxx_helpers.py") + +# Rules +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Let ABC = {A, B, C} +# Let ABCe = {A, B, C, } +# ==== ==== ================================== +# L R Label variations +# ==== ==== ================================== +# (empty) +# ---- ---- ---------------------------------- +# O ABC +# O ABC +# O O ABC +# $ $ ABC x ABC +# ---- ---- ---------------------------------- +# O-O ABC x ABC x ABC +# O-O' O ABC x ABC x ABC +# O-$ $ ABC x ABC x ABC x ABC +# O-O ABC x ABC x ABC +# O O-O' ABC x ABC x ABC +# $ O-$ ABC x ABC x ABC x ABC +# ---- ---- ---------------------------------- +# O-O' O-O' ABC x ABC x ABCe x ABC +# O-$ O-$ ABC x ABC x ABC x ABCe x ABC +# $-$' $-$' ABC x ABC x ABC x ABC x ABCe x ABC +# ==== ==== ================================== +# Different basic rules: +# L R +# === === +# (empty) +# A +# A +# A A +# A B +# AEX +# AEX A +# AEX A X +# AEX +# A AEX +# A X AEX +# AEX AEX +# AEX AFX + + +def _check_0_vertexOverlap(c): + print() + print("0-vertex overlap") + print("#" * 80) + # Try all parallel compositions between all types of rules. + # L R + # === === + # (empty) + # A + # A + # A A + # A B + rs1 = (">>", "[A]1>>", ">>[A]1", "[A]1>>[A]1", "[A]1>>[B]1", + # AEX + # AEX A + # AEX A X + "[A]1{E}[X]2>>", + "[A]1{E}[X]2>>[A]1", + "[A]1{E}[X]2>>[A]1.[X]2", + # AEX + # A AEX + # A X AEX + " >>[A]1{E}[X]2", + "[A]1 >>[A]1{E}[X]2", + "[A]1.[X]2>>[A]1{E}[X]2", + # AEX AEX + # AEX AFX + "[A]1{E}[X]2>>[A]1{E}[X]2", + "[A]1{E}[X]2>>[A]1{F}[X]2") + # and another copy, with different IDs so we can construct a combined res + rs2 = (">>", "[A]3>>", ">>[A]3", "[A]3>>[A]3", "[A]3>>[B]3", + "[A]3{E}[X]4>>", + "[A]3{E}[X]4>>[A]3", + "[A]3{E}[X]4>>[A]3.[X]4", + " >>[A]3{E}[X]4", + "[A]3 >>[A]3{E}[X]4", + "[A]3. [X]4>>[A]3{E}[X]4", + "[A]3{E}[X]4>>[A]3{E}[X]4", + "[A]3{E}[X]4>>[A]3{F}[X]4") + + for r1 in rs1: + for r2 in rs2: + r2L, r2R = r2.split(">>") + res = r1 + if res.lstrip()[0] == ">": + res = r2L + res + elif r2L.strip() != "": + res = r2L + "." + res + if res.rstrip()[-1] == ">": + res += r2R + elif r2R.strip() != "": + res += "." + r2R + c(r1, r2, {}, res) + + +def _check_1_vertexOverlap(c): + print() + print("1-vertex overlap") + print("#" * 80) + o = {1: 1} + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + # (empty) no overlaps + # A no overlaps + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print(" >>A with everything") + print("-" * 80) + r1 = ">>[A]1" + # A (empty) no overlaps + # A A (empty) + c(r1, "[A]1>>", o, ">>") + # A A no overlaps + # A A A A + # A A B B + c(r1, "[A]1>>[A]1", o, ">>[A]") + c(r1, "[A]1>>[B]1", o, ">>[B]") + # A AEX dangling condition + # A AEX A dangling condition + # A AEX A X dangling condition + c(r1, "[A]1{E}[X]2>>", o, None) + c(r1, "[A]1{E}[X]2>>[A]1", o, None) + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, None) + # A AEX no overlaps + # A A AEX AEX + # A A X AEX X AEX + c(r1, "[A]1 >>[A]1{E}[X]2", o, " >>[A]{E}[X]") + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, "[X]2>>[A]{E}[X]2") + # A AEX AEX dangling condition + # A AEX AFX dangling condition + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, None) + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, None) + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("A >>A with everything") + print("-" * 80) + r1 = "[A]1>>[A]1" + # A A (empty) no overlaps + # A A A A + c(r1, "[A]1>>", o, "[A]1>>") + # A A A no overlaps + # A A A A A A + # A A A B A B + c(r1, "[A]1>>[A]1", o, "[A]1>>[A]1") + c(r1, "[A]1>>[B]1", o, "[A]1>>[B]1") + # A A AEX AEX + # A A AEX A AEX A + # A A AEX A X AEX A X + c(r1, "[A]1{E}[X]2>>", o, "[A]1{E}[X]2>>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[A]1{E}[X]2>>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[A]1{E}[X]2>>[A]1.[X]2") + # A A AEX no overlaps + # A A A AEX A AEX + # A A A X AEX A X AEX + c(r1, "[A]1 >>[A]1{E}[X]2", o, "[A]1 >>[A]1{E}[X]") + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, "[A]1.[X]2>>[A]1{E}[X]2") + # A A AEX AEX AEX AEX + # A A AEX AFX AEX AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[A]1{E}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[A]1{E}[X]2>>[A]1{F}[X]2") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("Q >>A with everything") + print("-" * 80) + r1 = "[Q]1>>[A]1" + # Q A (empty) no overlaps + # Q A A Q + c(r1, "[A]1>>", o, "[Q]1>>") + # Q A A no overlaps + # Q A A A Q A + # Q A A B Q B + c(r1, "[A]1>>[A]1", o, "[Q]1>>[A]1") + c(r1, "[A]1>>[B]1", o, "[Q]1>>[B]1") + # Q A AEX QEX + # Q A AEX A QEX A + # Q A AEX A X QEX A X + c(r1, "[A]1{E}[X]2>>", o, "[Q]1{E}[X] >>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[Q]1{E}[X] >>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[Q]1{E}[X]2>>[A]1.[X]2") + # Q A AEX no overlaps + # Q A A AEX Q AEX + # Q A A X AEX Q X AEX + c(r1, "[A]1 >>[A]1{E}[X]2", o, "[Q]1 >>[A]1{E}[X]") + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, "[Q]1.[X]2>>[A]1{E}[X]2") + # Q A AEX AEX QEX AEX + # Q A AEX AFX QEX AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[Q]1{E}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[Q]1{E}[X]2>>[A]1{F}[X]2") + + # AEX no overlaps + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AEX>>A with everything") + print("-" * 80) + r1 = "[A]1{E}[X]>>[A]1" + # AEX A (empty) no overlaps + # AEX A A AEX + c(r1, "[A]1>>", o, "[A]{E}[X]>>") + # AEX A A no overlaps + # AEX A A A AEX A + # AEX A A B AEX B + c(r1, "[A]1>>[A]1", o, "[A]1{E}[X]>>[A]1") + c(r1, "[A]1>>[B]1", o, "[A]1{E}[X]>>[B]1") + # AEX A AEY A(EY)EX + # AEX A AEY A A(EY)EX A + # AEX A AEY A Y A(EY)EX A Y + c(r1, "[A]1{E}[Y]2>>", o, "[A]1({E}[Y] ){E}[X]>>") + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1({E}[Y] ){E}[X]>>[A]1") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1({E}[Y]2){E}[X]>>[A]1.[Y]2") + # AEX A AEY no overlaps + # AEX A A AEY AEX AEY + # AEX A A Y AEY A( Y)EX AEY + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 {E}[X]>>[A]1{E}[Y]") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1(.[Y]2){E}[X]>>[A]1{E}[Y]2") + # AEX A AEY AEY A(EY)EX AEY + # AEX A AEY AFY A(EY)EX AFY + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1({E}[Y]2){E}[X]>>[A]1{E}[Y]2") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1({E}[Y]2){E}[X]>>[A]1{F}[Y]2") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AEX>>A X with everything") + print("-" * 80) + r1 = "[A]1{E}[X]2>>[A]1.[X]2" + # AEX A X (empty) no overlaps + # AEX A X A AEX X + c(r1, "[A]1>>", o, "[A]{E}[X]2>>[X]2") + # AEX A X A no overlaps + # AEX A X A A AEX A X + # AEX A X A B AEX B X + c(r1, "[A]1>>[A]1", o, "[A]1{E}[X]2>>[A]1.[X]2") + c(r1, "[A]1>>[B]1", o, "[A]1{E}[X]2>>[B]1.[X]2") + # AEX A X AEY A(EY)EX X + # AEX A X AEY A A(EY)EX A X + # AEX A X AEY A Y A(EY)EX A Y X + c(r1, "[A]1{E}[Y]2>>", o, "[A]1({E}[Y] ){E}[X]3>> [X]3") + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1({E}[Y] ){E}[X]3>>[A]1 .[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1.[Y]2.[X]3") + # AEX A X AEY no overlaps + # AEX A X A AEY AEX AEY X + # AEX A X A Y AEY A( Y)EX AEY X + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 {E}[X]3>>[A]1{E}[Y] .[X]3") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1(.[Y]2){E}[X]3>>[A]1{E}[Y]2.[X]3") + # AEX A X AEY AEY A(EY)EX AEY X + # AEX A X AEY AFY A(EY)EX AFY X + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1{E}[Y]2.[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1{F}[Y]2.[X]3") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print(" >>AEX with everything") + print("-" * 80) + r1 = ">>[A]1{E}[X]2" + # AEX (empty) no overlaps + # AEX A dangling condition + c(r1, "[A]1>>", o, None) + # AEX A no overlaps + # AEX A A AEX + # AEX A B BEX + c(r1, "[A]1>>[A]1", o, ">>[A]{E}[X]") + c(r1, "[A]1>>[B]1", o, ">>[B]{E}[X]") + # AEX AEY dangling condition + # AEX AEY A dangling condition + # AEX AEY A Y dangling conditoin + c(r1, "[A]1{E}[Y]2>>", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, None) + # AEX AEY no overlaps + # AEX A AEY A(EY)EX + # AEX A Y AEY Y A(EY)EX + c(r1, "[A]1 >>[A]1{E}[Y]2", o, " >>[A]({E}[Y] ){E}[X]") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[Y]2>>[A]({E}[Y]2){E}[X]") + # AEX AEY AEY dangling condition + # AEX AEY AFY dangling condition + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, None) + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("A >>AEX with everything") + print("-" * 80) + r1 = "[A]1>>[A]1{E}[X]2" + # A AEX (empty) no overlaps + # A AEX A dangling condition + c(r1, "[A]1>>", o, None) + # A AEX A no overlaps + # A AEX A A A AEX + # A AEX A B A BEX + c(r1, "[A]1>>[A]1", o, "[A]1>>[A]1{E}[X]") + c(r1, "[A]1>>[B]1", o, "[A]1>>[B]1{E}[X]") + # A AEX AEY dangling condition + # A AEX AEY A AEY AEX + # A AEX AEY A Y AEY A( Y)EX + c(r1, "[A]1{E}[Y]2>>", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1{E}[Y] >>[A]1 {E}[X]") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1{E}[Y]2>>[A]1(.[Y]2){E}[X]") + # A AEX AEY no overlaps + # A AEX A AEY A A(EY)EX + # A AEX A Y AEY A Y A(EY)EX + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 >>[A]1({E}[Y] ){E}[X]") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1.[Y]2>>[A]1({E}[Y]2){E}[X]") + # A AEX AEY AEY AEY A(EY)EX + # A AEX AEY AFY AEY A(FY)EX + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1{E}[Y]2>>[A]1({E}[Y]2){E}[X]") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1{E}[Y]2>>[A]1({F}[Y]2){E}[X]") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("A X>>AEX with everything") + print("-" * 80) + r1 = "[A]1.[X]2>>[A]1{E}[X]2" + # A X AEX (empty) no overlaps + # A X AEX A dangling condition + c(r1, "[A]1>>", o, None) + # A X AEX A no overlaps + # A X AEX A A A X AEX + # A X AEX A B A X BEX + c(r1, "[A]1>>[A]1", o, "[A]1.[X]2>>[A]1{E}[X]2") + c(r1, "[A]1>>[B]1", o, "[A]1.[X]2>>[B]1{E}[X]2") + # A X AEX AEY dangling condition + # A X AEX AEY A AEY X AEX + # A X AEX AEY A X AEY X A( Y)EX + c(r1, "[A]1{E}[Y]2>>", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1{E}[Y] .[X]3>>[A]1 {E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1{E}[Y]2.[X]3>>[A]1(.[Y]2){E}[X]3") + # A X AEX AEY no overlaps + # A X AEX A AEY A X A(EY)EX + # A X AEX A Y AEY A Y X A(EY)EX + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 .[X]3>>[A]1({E}[Y] ){E}[X]3") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1.[Y]2.[X]3>>[A]1({E}[Y]2){E}[X]3") + # A X AEX AEY AEY AEY X A(EY)EX + # A X AEX AEY AFY AEY X A(FY)EX + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1{E}[Y]2.[X]3>>[A]1({E}[Y]2){E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1{E}[Y]2.[X]3>>[A]1({F}[Y]2){E}[X]3") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AEX>>AEX with everything") + print("-" * 80) + r1 = "[A]1{E}[X]2>>[A]1{E}[X]2" + # AEX AEX (empty) no overlaps + # AEX AEX A dangling condition + c(r1, "[A]1>>", o, None) + # AEX AEX A no overlaps + # AEX AEX A A AEX AEX + # AEX AEX A B AEX BEX + c(r1, "[A]1>>[A]1", o, "[A]1{E}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1>>[B]1", o, "[A]1{E}[X]2>>[B]1{E}[X]2") + # AEX AEX AEY dangling condition + # AEX AEX AEY A A(EY)EX AEX + # AEX AEX AEY A Y A(EY)EX A( Y)EX + c(r1, "[A]1{E}[Y]2>>", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1({E}[Y] ){E}[X]3>>[A]1 {E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1(.[Y]2){E}[X]3") + # AEX AEX AEY no overlaps + # AEX AEX A AEY AEX A(EY)EX + # AEX AEX A Y AEY A( Y)EX A(EY)EX + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 {E}[X]3>>[A]1({E}[Y] ){E}[X]3") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1(.[Y]2){E}[X]3>>[A]1({E}[Y]2){E}[X]3") + # AEX AEX AEY AEY A(EY)EX A(EY)EX + # AEX AEX AEY AFY A(EY)EX A(FY)EX + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1({E}[Y]2){E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1({E}[Y]2){E}[X]3>>[A]1({F}[Y]2){E}[X]3") + + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AQX>>AEX with everything") + print("-" * 80) + r1 = "[A]1{Q}[X]2>>[A]1{E}[X]2" + # AQX AEX (empty) no overlaps + # AQX AEX A dangling condition + c(r1, "[A]1>>", o, None) + # AQX AEX A no overlaps + # AQX AEX A A AQX AEX + # AQX AEX A B AQX BEX + c(r1, "[A]1>>[A]1", o, "[A]1{Q}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1>>[B]1", o, "[A]1{Q}[X]2>>[B]1{E}[X]2") + # AQX AEX AEY dangling condition + # AQX AEX AEY A A(EY)QX AEX + # AQX AEX AEY A Y A(EY)QX A( Y)EX + c(r1, "[A]1{E}[Y]2>>", o, None) + c(r1, "[A]1{E}[Y]2>>[A]1", o, "[A]1({E}[Y] ){Q}[X]3>>[A]1 {E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1.[Y]2", o, "[A]1({E}[Y]2){Q}[X]3>>[A]1(.[Y]2){E}[X]3") + # AQX AEX AEY no overlaps + # AQX AEX A AEY AQX A(EY)EX + # AQX AEX A Y AEY A( Y)QX A(EY)EX + c(r1, "[A]1 >>[A]1{E}[Y]2", o, "[A]1 {Q}[X]3>>[A]1({E}[Y] ){E}[X]3") + c(r1, "[A]1.[Y]2>>[A]1{E}[Y]2", o, "[A]1(.[Y]2){Q}[X]3>>[A]1({E}[Y]2){E}[X]3") + # AQX AEX AEY AEY A(EY)QX A(EY)EX + # AQX AEX AEY AFY A(EY)QX A(FY)EX + c(r1, "[A]1{E}[Y]2>>[A]1{E}[Y]2", o, "[A]1({E}[Y]2){Q}[X]3>>[A]1({E}[Y]2){E}[X]3") + c(r1, "[A]1{E}[Y]2>>[A]1{F}[Y]2", o, "[A]1({E}[Y]2){Q}[X]3>>[A]1({F}[Y]2){E}[X]3") + + +def _check_2_vertexOverlap(c): + print() + print("2-vertex overlap") + print("#" * 80) + o = {1: 1, 2: 2} + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + # (empty) no overlaps + # A no overlaps + # A no overlaps + # A A no overlaps + # A B no overlaps + # AEX no overlaps + # AEX A no overlaps + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AEX>>A X with everything") + print("-" * 80) + r1 = "[A]1{E}[X]2>>[A]1.[X]2" + # AEX A X (empty) no overlaps + # AEX A X A no overlaps + # AEX A X A no overlaps + # AEX A X A A no overlaps + # AEX A X A B no overlaps + # AEX A X AEX parallel edge in L + # AEX A X AEX A parallel edge in L + # AEX A X AEX A X parallel edge in L + c(r1, "[A]1{E}[X]2>>", o, None) + c(r1, "[A]1{E}[X]2>>[A]1", o, None) + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, None) + # AEX A X AEX no overlaps + # AEX A X A AEX no overlaps + # AEX A X A X AEX AEX AEX + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, "[A]1{E}[X]2>>[A]1{E}[X]2") + # AEX A X AEX AEX parallel edge in L + # AEX A X AEX AFX parallel edge in L + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, None) + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, None) + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + # AEX + print(" >>AEX with everything") + print("-" * 80) + r1 = ">>[A]1{E}[X]2" + # AEX (empty) no overlaps + # AEX A no overlaps + # AEX A no overlaps + # AEX A A no overlaps + # AEX A B no overlaps + # AEX AEX (empty) + # AEX AEX A A + # AEX AEX A X A X + c(r1, "[A]1{E}[X]2>>", o, ">>") + c(r1, "[A]1{E}[X]2>>[A]1", o, ">>[A]") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, ">>[A].[X]") + # AEX AEX no overlaps + # AEX A AEX no overlaps + # AEX A X AEX parallel edge in R + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, None) + # AEX AEX AEX AEX + # AEX AEX AFX AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, ">>[A]{E}[X]") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, ">>[A]{F}[X]") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("A >>AEX with everything") + print("-" * 80) + r1 = "[A]1>>[A]1{E}[X]2" + # A AEX (empty) no overlaps + # A AEX A no overlaps + # A AEX A no overlaps + # A AEX A A no overlaps + # A AEX A B no overlaps + # A AEX AEX A + # A AEX AEX A A A + # A AEX AEX A X A A X + c(r1, "[A]1{E}[X]2>>", o, "[A]1>>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[A]1>>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[A]1>>[A]1.[X]") + # A AEX AEX no overlaps + # A AEX A AEX no overlaps + # A AEX A X AEX parallel edge in R + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, None) + # A AEX AEX AEX A AEX + # A AEX AEX AFX A AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[A]1>>[A]1{E}[X]") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[A]1>>[A]1{F}[X]") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("A X>>AEX with everything") + print("-" * 80) + r1 = "[A]1.[X]2>>[A]1{E}[X]2" + # A X AEX (empty) no overlaps + # A X AEX A no overlaps + # A X AEX A no overlaps + # A X AEX A A no overlaps + # A X AEX A B no overlaps + # A X AEX AEX A X + # A X AEX AEX A A X A + # A X AEX AEX A X A X A X + c(r1, "[A]1{E}[X]2>>", o, "[A]1.[X]2>>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[A]1.[X]2>>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[A]1.[X]2>>[A]1.[X]2") + # A X AEX AEX no overlaps + # A X AEX A AEX no overlaps + # A X AEX A X AEX parallel edge in R + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, None) + # A X AEX AEX AEX A X AEX + # A X AEX AEX AFX A X AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[A]1.[X]2>>[A]1{E}[X]2") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[A]1.[X]2>>[A]1{F}[X]2") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AEX>>AEX with everything") + print("-" * 80) + r1 = "[A]1{E}[X]2>>[A]1{E}[X]2" + # AEX AEX (empty) no overlaps + # AEX AEX A no overlaps + # AEX AEX A no overlaps + # AEX AEX A A no overlaps + # AEX AEX A B no overlaps + # AEX AEX AEX AEX + # AEX AEX AEX A AEX A + # AEX AEX AEX A X AEX A X + c(r1, "[A]1{E}[X]2>>", o, "[A]1{E}[X]2>>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[A]1{E}[X]2>>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[A]1{E}[X]2>>[A]1.[X]2") + # AEX AEX AEX no overlaps + # AEX AEX A AEX no overlaps + # AEX AEX A X AEX parallel edge in R + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, None) + # AEX AEX AEX AEX AEX AEX + # AEX AEX AEX AFX AEX AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[A]1{E}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[A]1{E}[X]2>>[A]1{F}[X]2") + + # --------------------------------------------------- + # L R L R L R Note + # === === === === === === ========================= + print("AQX>>AEX with everything") + print("-" * 80) + r1 = "[A]1{Q}[X]2>>[A]1{E}[X]2" + # AQX AEX (empty) no overlaps + # AQX AEX A no overlaps + # AQX AEX A no overlaps + # AQX AEX A A no overlaps + # AQX AEX A B no overlaps + # AQX AEX AEX AQX + # AQX AEX AEX A AQX A + # AQX AEX AEX A X AQX A X + c(r1, "[A]1{E}[X]2>>", o, "[A]1{Q}[X]2>>") + c(r1, "[A]1{E}[X]2>>[A]1", o, "[A]1{Q}[X]2>>[A]1") + c(r1, "[A]1{E}[X]2>>[A]1.[X]2", o, "[A]1{Q}[X]2>>[A]1.[X]2") + # AQX AEX AEX no overlaps + # AQX AEX A AEX no overlaps + # AQX AEX A X AEX parallel edge in R + c(r1, "[A]1.[X]2>>[A]1{E}[X]2", o, None) + # AQX AEX AEX AEX AQX AEX + # AQX AEX AEX AFX AQX AFX + c(r1, "[A]1{E}[X]2>>[A]1{E}[X]2", o, "[A]1{Q}[X]2>>[A]1{E}[X]2") + c(r1, "[A]1{E}[X]2>>[A]1{F}[X]2", o, "[A]1{Q}[X]2>>[A]1{F}[X]2") + + +def doChecks(c): + _check_0_vertexOverlap(c) + _check_1_vertexOverlap(c) + _check_2_vertexOverlap(c) diff --git a/test/py/rcEval/common.py b/test/py/rcEval/common.py index 46d7eaa..e2e4ef0 100644 --- a/test/py/rcEval/common.py +++ b/test/py/rcEval/common.py @@ -13,8 +13,13 @@ def handleExp(exp, printRules=False): print("Database:\t", rc.ruleDatabase) print("Products:\t", rc.products) if printRules: - for a in rc.ruleDatabase: a.print() - postSection("Products") - for a in rc.products: a.print() + for a in rc.ruleDatabase: + print(" Printing", a) + a.print() + post.summarySection("Products") + for a in rc.products: + print(" Printing", a) + a.print() + print("Printing", rc) rc.print() inputRules.extend(a for a in rc.products) diff --git a/test/py/rcEval/composeCommon.py b/test/py/rcEval/composeCommon.py index d61f2be..74f4451 100644 --- a/test/py/rcEval/composeCommon.py +++ b/test/py/rcEval/composeCommon.py @@ -9,7 +9,7 @@ print("Implicit 2 -----------------------------------------------------------") handleExp(ketoEnol_F * rcCommon * aldolAdd_F) -post("disableSummary") +post.disableInvokeMake() rc = rcEvaluator(inputRules) resWithout = rc.eval(ketoEnol_F *rcCommon* ketoEnol_B) diff --git a/test/py/rcEval/convertGraph.py b/test/py/rcEval/convertGraph.py index 645e140..e20e4f7 100644 --- a/test/py/rcEval/convertGraph.py +++ b/test/py/rcEval/convertGraph.py @@ -1,7 +1,7 @@ include("common.py") def convert(name, cls, f): - postSection(name) + post.summarySection(name) print(name + ", explicit -------------------------------------------------") handleExp(cls(formaldehyde)) print(name + ", implicit -------------------------------------------------") diff --git a/test/py/rule/004_basic_loading.py b/test/py/rule/001_gml_interface.py similarity index 96% rename from test/py/rule/004_basic_loading.py rename to test/py/rule/001_gml_interface.py index 8995ad9..81e6d94 100644 --- a/test/py/rule/004_basic_loading.py +++ b/test/py/rule/001_gml_interface.py @@ -1,4 +1,4 @@ -include("../xxx_helpers.py") +include("xxx_helpers.py") data = """rule [ left [ node [ id 0 label "L" ] ] diff --git a/test/py/rule/001_loadFail.py b/test/py/rule/002_gml_loadFail.py similarity index 52% rename from test/py/rule/001_loadFail.py rename to test/py/rule/002_gml_loadFail.py index 2430d4a..fcd249a 100644 --- a/test/py/rule/001_loadFail.py +++ b/test/py/rule/002_gml_loadFail.py @@ -1,66 +1,44 @@ include("xxx_helpers.py") # for Boost < 1.77 it's column 12, for 1.77 it's 13 -ruleFail('blah', "Parsing failed at 1:") +gmlFail('blah', "Parsing failed at 1:") -ruleFail('labelType "blah"', "Unknown labelType 'blah'.") +gmlFail('labelType "blah"', "Unknown labelType 'blah'.") # Vertex properties # ============================================================================ -ruleFail(''' +gmlFail(''' left [ node [ id 0 label "A" ] ] context [ node [ id 0 label "A" ] ] ''', "Vertex 0 has a label both in 'context' and 'left'.") -ruleFail(''' +gmlFail(''' right [ node [ id 0 label "A" ] ] context [ node [ id 0 label "A" ] ] ''', "Vertex 0 has a label both in 'context' and 'right'.") -ruleFail(''' - left [ node [ id 0 stereo "any" ] ] - context [ node [ id 0 label "A" stereo "any" ] ] -''', "Vertex 0 has stereo both in 'context' and 'left'.") -ruleFail(''' - right [ node [ id 0 stereo "any" ] ] - context [ node [ id 0 label "A" stereo "any" ] ] -''', "Vertex 0 has stereo both in 'context' and 'right'.") - -ruleFail('left [ node [ id 0 ] ]', "Vertex 0 is in L, but has no label.") -ruleFail('right [ node [ id 0 ] ]', "Vertex 0 is in R, but has no label.") +gmlFail('left [ node [ id 0 ] ]', "Vertex 0 is in L, but has no label.") +gmlFail('right [ node [ id 0 ] ]', "Vertex 0 is in R, but has no label.") # Edge properties # ============================================================================ -ruleFail(''' +gmlFail(''' left [ edge [ source 0 target 1 label "-" ] ] context [ edge [ source 0 target 1 label "-" ] node [ id 0 label "A" ] node [ id 1 label "B" ] ]''', "Edge (0, 1) has a label both in 'context' and 'left'.") -ruleFail(''' +gmlFail(''' right [ edge [ source 0 target 1 label "-" ] ] context [ edge [ source 0 target 1 label "-" ] node [ id 0 label "A" ] node [ id 1 label "B" ] ]''', "Edge (0, 1) has a label both in 'context' and 'right'.") -ruleFail(''' - left [ edge [ source 0 target 1 stereo "" ] ] - context [ edge [ source 0 target 1 label "-" stereo "" ] - node [ id 0 label "A" ] - node [ id 1 label "B" ] - ]''', "Edge (0, 1) has stereo both in 'context' and 'left'.") -ruleFail(''' - right [ edge [ source 0 target 1 stereo "" ] ] - context [ edge [ source 0 target 1 label "-" stereo "" ] - node [ id 0 label "A" ] - node [ id 1 label "B" ] - ]''', "Edge (0, 1) has stereo both in 'context' and 'right'.") - -ruleFail(''' +gmlFail(''' left [ edge [ source 0 target 1 ] ] context [ node [ id 0 label "A" ] node [ id 1 label "B" ] ]''', "Edge (0, 1) is in L, but has no label.") -ruleFail(''' +gmlFail(''' right [ edge [ source 0 target 1 ] ] context [ node [ id 0 label "A" ] node [ id 1 label "B" ] ]''', "Edge (0, 1) is in R, but has no label.") diff --git a/test/py/rule/002_loadTopoFail.py b/test/py/rule/002_loadTopoFail.py deleted file mode 100644 index 79261f7..0000000 --- a/test/py/rule/002_loadTopoFail.py +++ /dev/null @@ -1,29 +0,0 @@ -include("xxx_helpers.py") - -for side in "left", "context", "right": - ruleFail(''' %s [ - node [ id 0 label "A" ] - edge [ source 0 target 1 label "-" ] - ]''' % side, "Edge endpoint '1' does not exist for edge (0, 1).") - ruleFail('''%s [ - node [ id 0 label "A" ] - edge [ source 1 target 0 label "-" ] - ]''' % side, "Edge endpoint '1' does not exist for edge (0, 1).") - -for side in "left", "context", "right": - ruleFail(""" %s [ - node [ id 0 label "C" ] - edge [ source 0 target 0 label "-" ] - ]""" % side, "Loop edge (on 0, in") - - ruleFail(""" %s [ - node [ id 0 label "C" ] - node [ id 1 label "C" ] - edge [ source 0 target 1 label "-" ] - edge [ source 1 target 0 label "-" ] - ] """ % side, "Duplicate edge (1, 0) in ") - - ruleFail(""" %s [ - node [ id 0 label "C" ] - node [ id 0 label "C" ] - ] """ % side, "Duplicate vertex 0 in ") diff --git a/test/py/rule/003_gml_loadTopoFail.py b/test/py/rule/003_gml_loadTopoFail.py new file mode 100644 index 0000000..3e0452c --- /dev/null +++ b/test/py/rule/003_gml_loadTopoFail.py @@ -0,0 +1,53 @@ +include("xxx_helpers.py") + +for side in "left", "context", "right": + gmlFail(''' %s [ + node [ id 0 label "A" ] + edge [ source 0 target 1 label "-" ] + ]''' % side, "Edge endpoint '1' does not exist for edge (0, 1).") + gmlFail('''%s [ + node [ id 0 label "A" ] + edge [ source 1 target 0 label "-" ] + ]''' % side, "Edge endpoint '1' does not exist for edge (0, 1).") + +for side in "left", "context", "right": + gmlFail(""" %s [ + node [ id 0 label "C" ] + edge [ source 0 target 0 label "-" ] + ]""" % side, "Loop edge (on 0, in") + + gmlFail(""" %s [ + node [ id 0 label "C" ] + node [ id 1 label "C" ] + edge [ source 0 target 1 label "-" ] + edge [ source 1 target 0 label "-" ] + ] """ % side, "Duplicate edge (1, 0) in ") + + gmlFail(""" %s [ + node [ id 0 label "C" ] + node [ id 0 label "C" ] + ] """ % side, "Duplicate vertex 0 in ") + +msg = "Edge (0, 1) dangling: edge is present in {} but endpoint {} only present in {}." + +gmlFail(""" +left [ edge [ source 0 target 1 label "-" ] ] +context [ node [ id 0 label "A" ] ] +right [ node [ id 1 label "B" ] ] +""", msg.format("L", 1, "R")) +gmlFail(""" +left [ edge [ source 0 target 1 label "-" ] ] +context [ node [ id 1 label "A" ] ] +right [ node [ id 0 label "B" ] ] +""", msg.format("L", 0, "R")) + +gmlFail(""" +left [ node [ id 1 label "B" ] ] +context [ node [ id 0 label "A" ] ] +right [ edge [ source 0 target 1 label "-" ] ] +""", msg.format("R", 1, "L")) +gmlFail(""" +left [ node [ id 0 label "B" ] ] +context [ node [ id 1 label "A" ] ] +right [ edge [ source 0 target 1 label "-" ] ] +""", msg.format("R", 0, "L")) diff --git a/test/py/rule/003_loadConstraintsFail.py b/test/py/rule/004_gml_loadConstraintsFail.py similarity index 72% rename from test/py/rule/003_loadConstraintsFail.py rename to test/py/rule/004_gml_loadConstraintsFail.py index a121b48..fb5c80a 100644 --- a/test/py/rule/003_loadConstraintsFail.py +++ b/test/py/rule/004_gml_loadConstraintsFail.py @@ -2,8 +2,8 @@ # ConstrainAdj # ============================================================================ -ruleFail('''constrainAdj [ id 0 op "=" count 0 ]''', +gmlFail('''constrainAdj [ id 0 op "=" count 0 ]''', "Vertex 0 in adjacency constraint does not exist.") -ruleFail('''context [ node [ id 0 label "A" ] ] +gmlFail('''context [ node [ id 0 label "A" ] ] constrainAdj [ id 0 op "a" count 0 ]''', "Unknown operator 'a' in adjacency constraint.") diff --git a/test/py/rule/010_gml_basic_structure.py b/test/py/rule/010_gml_basic_structure.py new file mode 100644 index 0000000..8bb7f15 --- /dev/null +++ b/test/py/rule/010_gml_basic_structure.py @@ -0,0 +1,83 @@ +include("xxx_helpers.py") + +data = """rule [ + left [ + node [ id 11 label "vL1" ] + node [ id 12 label "vL2" ] + edge [ source 11 target 12 label "eL" ] + + node [ id 41 label "vCL1" ] + node [ id 42 label "vCL2" ] + edge [ source 41 target 42 label "eCL" ] + ] + context [ + node [ id 31 label "vK1" ] + node [ id 32 label "vK2" ] + edge [ source 31 target 32 label "eK" ] + ] + right [ + node [ id 21 label "vR1" ] + node [ id 22 label "vR2" ] + edge [ source 21 target 22 label "eR" ] + + node [ id 41 label "vCR1" ] + node [ id 42 label "vCR2" ] + edge [ source 41 target 42 label "eCR" ] + ] +]""" + +r = Rule.fromGMLString(data) +for i in (1, 2, 3, 4): + globals()["v{}1".format(i)] = r.getVertexFromExternalId(i * 10 + 1) + globals()["v{}2".format(i)] = r.getVertexFromExternalId(i * 10 + 2) + +assert r.numVertices == 8 +assert r.numEdges == 4 +assert r.left.numVertices == 6 +assert r.left.numEdges == 3 +assert r.context.numVertices == 4 +assert r.context.numEdges == 2 +assert r.right.numVertices == 6 +assert r.right.numEdges == 3 + +assert v11.left +assert not v11.right +assert not v11.context +assert v11.left.stringLabel == "vL1" +assert v12.left +assert not v12.right +assert not v12.context +assert v12.left.stringLabel == "vL2" + +assert not v21.left +assert v21.right +assert not v21.context +assert v21.right.stringLabel == "vR1" +assert not v22.left +assert v22.right +assert not v22.context +assert v22.right.stringLabel == "vR2" + +assert v31.left +assert v31.right +assert v31.context +assert v31.left.stringLabel == "vK1" +assert v31.right.stringLabel == "vK1" +assert v32.left +assert v32.right +assert v32.context +assert v32.left.stringLabel == "vK2" +assert v32.right.stringLabel == "vK2" + +assert v41.left +assert v41.right +assert v41.context +assert v41.left.stringLabel == "vCL1" +assert v41.right.stringLabel == "vCR1" +assert v42.left +assert v42.right +assert v42.context +assert v42.left.stringLabel == "vCL2" +assert v42.right.stringLabel == "vCR2" + +commonChecks(r) diff --git a/test/py/rule/030_gml_stereo_loadFail.py b/test/py/rule/030_gml_stereo_loadFail.py new file mode 100644 index 0000000..1e7eca3 --- /dev/null +++ b/test/py/rule/030_gml_stereo_loadFail.py @@ -0,0 +1,96 @@ +include("xxx_helpers.py") + +# Vertex Properties +# ============================================================================ +gmlFail(''' + left [ node [ id 0 stereo "any" ] ] + context [ node [ id 0 label "A" stereo "any" ] ] +''', "Vertex 0 has stereo both in 'context' and 'left'.") +gmlFail(''' + right [ node [ id 0 stereo "any" ] ] + context [ node [ id 0 label "A" stereo "any" ] ] +''', "Vertex 0 has stereo both in 'context' and 'right'.") + +# check that context data is copied to left and right +gmlFail('context [ node [ id 0 label "A" stereo "blah" ] ]', + "Error in stereo data for vertex 0 in L. Invalid geometry 'blah'.") + + +# Edge properties +# ============================================================================ +gmlFail(''' + left [ edge [ source 0 target 1 stereo "" ] ] + context [ edge [ source 0 target 1 label "-" stereo "" ] + node [ id 0 label "A" ] + node [ id 1 label "B" ] + ]''', "Edge (0, 1) has stereo both in 'context' and 'left'.") +gmlFail(''' + right [ edge [ source 0 target 1 stereo "" ] ] + context [ edge [ source 0 target 1 label "-" stereo "" ] + node [ id 0 label "A" ] + node [ id 1 label "B" ] + ]''', "Edge (0, 1) has stereo both in 'context' and 'right'.") + +# check that context data is copied to left and right +gmlFail('''context [ + node [ id 0 label "C" ] + node [ id 1 label "C" ] + edge [ source 0 target 1 label "-" stereo "blah" ] +]''', "Error in stereo data for edge (0, 1) in L. Parsing error in stereo data 'blah'.") + + +# Embedding +# ============================================================================ + +# parsing error +gmlFail('left [ node [ id 0 label "C" stereo "" ] ]', + "Error in stereo data for vertex 0 in L. Parsing failed:") +gmlFail('left [ node [ id 0 label "C" stereo " " ] ]', + "Error in stereo data for vertex 0 in L. Parsing failed:") +gmlFail('left [ node [ id 0 label "C" stereo "tetrahedral[" ] ]', + "Error in stereo data for vertex 0 in L. Parsing failed:") +gmlFail('left [ node [ id 0 label "C" stereo "tetrahedral[1" ] ]', + "Error in stereo data for vertex 0 in L. Parsing failed:") +gmlFail('left [ node [ id 0 label "C" stereo "tetrahedral[.]" ] ]', + "Error in stereo data for vertex 0 in L. Parsing failed:") +# higher level parsing error +gmlFail('left [ node [ id 0 label "C" stereo "tetrahedral[a]" ] ]', + "Error in graph GML. Virtual neighbour in stereo embedding for vertex 0 in L has unknown type 'a'.") + +# not all edges, do offset to force the vertexPrinter to fail if wrong mapping +gmlFail('''right [ + node [ id 0 label "offset" ] + ] + left [ + node [ id 10 label "C" stereo "tetrahedral[]" ] + node [ id 11 label "H" ] edge [ source 10 target 11 label "-" ] + node [ id 12 label "H" ] edge [ source 10 target 12 label "-" ] + node [ id 13 label "H" ] edge [ source 10 target 13 label "-" ] + node [ id 14 label "H" ] edge [ source 10 target 14 label "-" ] +]''', "Too few edges in stereo embedding for vertex 10 in L. Got 0 edges, but the degree is 4.") +gmlFail('''left [ + node [ id 0 label "offset" ] + ] + right [ + node [ id 10 label "C" stereo "tetrahedral[]" ] + node [ id 11 label "H" ] edge [ source 10 target 11 label "-" ] + node [ id 12 label "H" ] edge [ source 10 target 12 label "-" ] + node [ id 13 label "H" ] edge [ source 10 target 13 label "-" ] + node [ id 14 label "H" ] edge [ source 10 target 14 label "-" ] +]''', "Too few edges in stereo embedding for vertex 10 in R. Got 0 edges, but the degree is 4.") + +# duplicate edge +gmlFail('''left [ + node [ id 10 label "C" stereo "tetrahedral[11, 12, 13, 13]" ] + node [ id 11 label "H" ] edge [ source 10 target 11 label "-" ] + node [ id 12 label "H" ] edge [ source 10 target 12 label "-" ] + node [ id 13 label "H" ] edge [ source 10 target 13 label "-" ] + node [ id 14 label "H" ] edge [ source 10 target 14 label "-" ] +]''', "Duplicate edge in stereo embedding for vertex 10 in L.") + +# duplicate radical +gmlFail('''left [ + node [ id 10 label "C" stereo "tetrahedral[11, 12, r, r]" ] + node [ id 11 label "H" ] edge [ source 10 target 11 label "-" ] + node [ id 12 label "H" ] edge [ source 10 target 12 label "-" ] +]''', "Multiple radicals in stereo embedding for vertex 10 in L.") diff --git a/test/py/rule/110_extId.py b/test/py/rule/049_gml_externalIds.py similarity index 90% rename from test/py/rule/110_extId.py rename to test/py/rule/049_gml_externalIds.py index 0460e20..335fcf7 100644 --- a/test/py/rule/110_extId.py +++ b/test/py/rule/049_gml_externalIds.py @@ -16,7 +16,7 @@ def check(a): # GML -a = ruleGMLString("""rule [ context [ +a = Rule.fromGMLString("""rule [ context [ node [ id 42 label "C" ] node [ id 1337 label "O" ] node [ id 0 label "U" ] @@ -24,3 +24,5 @@ def check(a): edge [ source 1337 target 0 label "-" ] ] ]""") check(a) + +commonChecks(a) diff --git a/test/py/rule/100_ruleInterface.py b/test/py/rule/100_ruleInterface.py deleted file mode 100644 index 007a0ff..0000000 --- a/test/py/rule/100_ruleInterface.py +++ /dev/null @@ -1,146 +0,0 @@ -include("../xxx_graphInterface.py") - -a = ruleGMLString("""rule [ - left [ - node [ id 0 label "C+." ] - edge [ source 100 target 0 label "-" ] - ] - context [ - node [ id 2 label "H-." ] - edge [ source 100 target 2 label ":" ] - node [ id 100 label "U" ] - ] - right [ - node [ id 1 label "O-." ] - edge [ source 100 target 1 label "=" ] - ] -]""") - - -checkGraph(a, string="'{}'".format(a.name), - vertexString="RuleVertex", edgeString="RuleEdge") -checkLabelledGraph(a.left, string="RuleLeftGraph('{}')".format(a.name), - vertexString="RuleLeftGraphVertex", edgeString="RuleLeftGraphEdge", - graphNameInElements=str(a), vIdFull=False) -checkGraph(a.context, string="RuleContextGraph('{}')".format(a.name), - vertexString="RuleContextGraphVertex", edgeString="RuleContextGraphEdge", - graphNameInElements=str(a), vIdFull=False) -checkLabelledGraph(a.right, string="RuleRightGraph('{}')".format(a.name), - vertexString="RuleRightGraphVertex", edgeString="RuleRightGraphEdge", - graphNameInElements=str(a), vIdFull=False) - - - - -print("Core\n" + "="*80) -print("numVertices:", a.numVertices) -for v in a.vertices: - print("Vertex:", v.rule, v.id, v.degree) - if v.left.isNull(): - print("\tLeft: N/A") - else: - print("\tLeft:", v.left, v.left.stringLabel) - assert v.left.core == v - if v.context.isNull(): - print("\tContext: N/A") - else: - print("\tContext:", v.context) - assert v.context.core == v - if v.right.isNull(): - print("\tRight: N/A") - else: - print("\tRight:", v.right, v.right.stringLabel) - assert v.right.core == v - for e in v.incidentEdges: - print("\tEdge:", e.target.id) -print("numEdges:", a.numEdges) -for e in a.edges: - print("Edge:", e.rule, e.source.id, e.target.id) - if e.left.isNull(): - print("Left: N/A") - else: - print("Left:", e.left) - assert e.left.core == e - if e.context.isNull(): - print("Context: N/A") - else: - print("Context:", e.context) - assert e.context.core == e - if e.right.isNull(): - print("Right: N/A") - else: - print("Right:", e.right) - assert e.right.core == e - - -def checkGraph(a, withLabels, name): - print(name + "\n" + "="*80) - print("rule:", a.rule.name) - print("numVertices:", a.numVertices) - for v in a.vertices: - if withLabels: - print("Vertex:", v.rule, v.id, v.degree, v.stringLabel, v.atomId, v.isotope, v.charge, v.radical) - else: - print("Vertex:", v.rule, v.id, v.degree) - assert v.id == v.core.id - for e in v.incidentEdges: - print("\tEdge:", e.target.id) - print("numEdges:", a.numEdges) - for e in a.edges: - if withLabels: - print("Edge:", e.rule, e.source.id, e.target.id, e.stringLabel, e.bondType) - else: - print("Edge:", e.rule, e.source.id, e.target.id) - print("Core:", e.core) - -checkGraph(a.left, True, "Left") -checkGraph(a.context, False, "Context") -checkGraph(a.right, True, "Right") - - - -a = ruleGMLString("""rule [ - context [ - node [ id 0 label "A" ] - node [ id 1 label "B" ] - edge [ source 0 target 1 label "-" ] - ] -]""") -v0 = a.vertices[0] -v1 = a.vertices[1] -assert v0 == v0 -assert v0 != v1 -e1 = list(v0.incidentEdges)[0] -e2 = list(v1.incidentEdges)[0] -assert e1 == e2 -assert not e1 != e2 - - - -a = ruleGMLString("""rule [ - left [ - node [ id 1 label "B" ] - ] - context [ - node [ id 0 label "A" ] - ] - right [ - node [ id 2 label "C" ] - ] -]""") -v0 = a.getVertexFromExternalId(0) -v1 = a.getVertexFromExternalId(1) -v2 = a.getVertexFromExternalId(2) -assert v0.id == 0, v0.id -assert v1.id == 1, v1.id -assert v2.id == 2, v2.id - -assert v0.left.id == 0, v0.left.id -assert v0.context.id == 0, v0.context.id -assert v0.right.id == 0, v0.right.id -assert v1.left.id == 1, v1.left.id -assert v2.right.id == 2, v2.right.id - -print(v0.left.stringLabel, v0.right.stringLabel) -print(v1.left.stringLabel) -print(v2.right.stringLabel) diff --git a/test/py/rule/101_dfs_basic_loading.py b/test/py/rule/101_dfs_basic_loading.py new file mode 100644 index 0000000..e4a32e5 --- /dev/null +++ b/test/py/rule/101_dfs_basic_loading.py @@ -0,0 +1,11 @@ +include("xxx_helpers.py") + +data = "[L]1>>[R]1" + +inputRules[:] = [] +r1 = Rule.fromDFS(data) +assert inputRules == [r1] +r2 = Rule.fromDFS(data) +assert inputRules == [r1, r2] +Rule.fromDFS(data, add=False) +assert inputRules == [r1, r2] diff --git a/test/py/rule/102_dfs_loadFail.py b/test/py/rule/102_dfs_loadFail.py new file mode 100644 index 0000000..4f0a3f5 --- /dev/null +++ b/test/py/rule/102_dfs_loadFail.py @@ -0,0 +1,4 @@ +include("xxx_helpers.py") + +dfsFail('', "Parsing failed at 1:1:") +dfsFail('[C]', "Parsing failed at 1:4:") diff --git a/test/py/rule/103_dfs_topo.py b/test/py/rule/103_dfs_topo.py new file mode 100644 index 0000000..0e762ac --- /dev/null +++ b/test/py/rule/103_dfs_topo.py @@ -0,0 +1,26 @@ +include("xxx_helpers.py") + +# The tests here assume that the Graph DFS tests cover all details. +# Here we just test that the issues are caught in Rule DFS as well. + +# Missing ring closure +dfsFail("[A]-1>>", "Ring closure ID 1 not found, in the left side.") +dfsFail(">>[A]-1", "Ring closure ID 1 not found, in the right side.") +dfsFail("[A]-1>>[A]-1", "Ring closure ID 1 not found, in the left side.") + +# Loop edges +dfsFail("[A]1-1>>", + "Loop edge in DFS on vertex in the left side with ID 1.") +dfsFail(">>[A]1-1", + "Loop edge in DFS on vertex in the right side with ID 1.") +dfsFail("[A]1-1>>[A]1-1", + "Loop edge in DFS on vertex in the left side with ID 1.") + +# Parallel +dfsFail("[A]1[B]-1>>", + "Parallel edge in DFS in the left side. Back-edge is to vertex with ID 1.") +dfsFail(">>[A]1[B]-1", + "Parallel edge in DFS in the right side. Back-edge is to vertex with ID 1.") +dfsFail("[A]1[B]-1>>[A]1[B]-1", + "Parallel edge in DFS in the left side. Back-edge is to vertex with ID 1.") + diff --git a/test/py/rule/110_dfs_basic_structure.py b/test/py/rule/110_dfs_basic_structure.py new file mode 100644 index 0000000..31aa246 --- /dev/null +++ b/test/py/rule/110_dfs_basic_structure.py @@ -0,0 +1,121 @@ +include("xxx_helpers.py") + +dfsCheck(">>", '') + +dfsCheck("[A]>>", 'left [ node [ id 1 label "A" ] ]') +dfsCheck(">>[A]", 'right [ node [ id 1 label "A" ] ]') + +dfsCheck("[A]1>>", 'left [ node [ id 1 label "A" ] ]') +dfsCheck(">>[A]1", 'right [ node [ id 1 label "A" ] ]') + +dfsCheck("[A].[B]>>", '''left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] +]''') +dfsCheck(">>[A].[B]", '''right [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] +]''') + +dfsCheck("[A][B]>>", '''left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + edge [ source 1 target 2 label "-" ] +]''') +dfsCheck(">>[A][B]", '''right [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + edge [ source 1 target 2 label "-" ] +]''') + +dfsCheck("[A]1>>[A]1", 'context [ node [ id 1 label "A" ] ]') +dfsCheck("[A]1>>[C]1", ''' + left [ node [ id 1 label "A" ] ] + right [ node [ id 1 label "C" ] ] +''') + +dfsCheck("[A]1.[B]>>[C]1", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + ] + right [ node [ id 1 label "C" ] ] +''') +dfsCheck("[A]1>>[C]1.[D]", ''' + left [ node [ id 1 label "A" ] ] + right [ + node [ id 1 label "C" ] + node [ id 2 label "D" ] + ] +''') + +dfsCheck("[A]1[B]>>[C]1", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + edge [ source 1 target 2 label "-" ] + ] + right [ node [ id 1 label "C" ] ] +''') +dfsCheck("[A]1>>[C]1[D]", ''' + left [ node [ id 1 label "A" ] ] + right [ + node [ id 1 label "C" ] + node [ id 2 label "D" ] + edge [ source 1 target 2 label "-" ] + ] +''') + +dfsCheck("[A]1[B]>>[C]1[D]", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + edge [ source 1 target 2 label "-" ] + ] + right [ + node [ id 1 label "C" ] + node [ id 3 label "D" ] + edge [ source 1 target 3 label "-" ] + ] +''') + +dfsCheck("[A]1[B]2>>[C]1[D]2", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + ] + context [ + edge [ source 1 target 2 label "-" ] + ] + right [ + node [ id 1 label "C" ] + node [ id 2 label "D" ] + ] +''') + +dfsCheck("[A]1{-}[B]2>>[C]1{=}[D]2", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "B" ] + edge [ source 1 target 2 label "-" ] + ] + right [ + node [ id 1 label "C" ] + node [ id 2 label "D" ] + edge [ source 1 target 2 label "=" ] + ] +''') + +# other checks +dfsCheck("[A]1[C]>>[B][C]1", ''' + left [ + node [ id 1 label "A" ] + node [ id 2 label "C" ] + edge [ source 1 target 2 label "-" ] + ] + right [ + node [ id 3 label "B" ] + node [ id 1 label "C" ] + edge [ source 3 target 1 label "-" ] + ] +''') diff --git a/test/py/rule/111_dfs_load_implicit.py b/test/py/rule/111_dfs_load_implicit.py new file mode 100644 index 0000000..b384fa4 --- /dev/null +++ b/test/py/rule/111_dfs_load_implicit.py @@ -0,0 +1,6 @@ +include("xxx_helpers.py") + +msg = "Vertices with implicit hydrogen atoms currently not supported." +dfsFail("C>>", msg) +dfsFail(">>C", msg) +dfsFail("C1>>C1", msg) diff --git a/test/py/rule/112_dfs_whitespace.py b/test/py/rule/112_dfs_whitespace.py new file mode 100644 index 0000000..29cf49c --- /dev/null +++ b/test/py/rule/112_dfs_whitespace.py @@ -0,0 +1,37 @@ +include("xxx_helpers.py") + +r1 = Rule.fromDFS("[A] 1 ( [B] ) [C] [D] {E} [F] . [G] >> [H]") +r2 = Rule.fromDFS("[A]1([B])[C][D]{E}[F].[G]>>[H]") + +def cmpGraphs(g1, g2): + assert g1.numVertices == g2.numVertices + assert g1.numEdges == g2.numEdges + assert len(list(g1.vertices)) == len(list(g2.vertices)) + assert len(list(g1.edges)) == len(list(g2.edges)) + for v1, v2 in zip(g1.vertices, g2.vertices): + assert v1.id == v2.id + assert v1.degree == v2.degree + assert len(list(v1.incidentEdges)) == len(list(v2.incidentEdges)) + for e1, e2 in zip(v1.incidentEdges, v2.incidentEdges): + assert e1.source.id == e2.source.id + assert e1.target.id == e2.target.id + for e1, e2 in zip(g1.edges, g2.edges): + assert e1.source.id == e2.source.id + assert e1.target.id == e2.target.id + +def cmpGraphsLab(g1, g2): + cmpGraphs(g1, g2) + for v1, v2 in zip(g1.vertices, g2.vertices): + assert v1.stringLabel == v2.stringLabel + for e1, e2 in zip(v1.incidentEdges, v2.incidentEdges): + assert e1.stringLabel == e2.stringLabel + for e1, e2 in zip(g1.edges, g2.edges): + assert e1.stringLabel == e2.stringLabel + +cmpGraphs(r1, r2) +cmpGraphsLab(r1.left, r2.left) +cmpGraphs(r1.context, r2.context) +cmpGraphsLab(r1.right, r2.right) + +commonChecks(r1) +commonChecks(r2) diff --git a/test/py/rule/149_dfs_externalIds.py b/test/py/rule/149_dfs_externalIds.py new file mode 100644 index 0000000..ed48055 --- /dev/null +++ b/test/py/rule/149_dfs_externalIds.py @@ -0,0 +1,24 @@ +include("xxx_helpers.py") + +r = Rule.fromDFS("[A]1[B]2>>[A]1[C]3") + +v1 = r.getVertexFromExternalId(1) +assert v1 +assert v1.left +assert v1.left.stringLabel == "A" +assert v1.right +assert v1.right.stringLabel == "A" + +v2 = r.getVertexFromExternalId(2) +assert v2 +assert v2.left +assert v2.left.stringLabel == "B" +assert not v2.right + +v3 = r.getVertexFromExternalId(3) +assert v3 +assert not v3.left +assert v3.right +assert v3.right.stringLabel == "C" + +commonChecks(r) diff --git a/test/py/rule/010_basic.py b/test/py/rule/500_basic.py similarity index 62% rename from test/py/rule/010_basic.py rename to test/py/rule/500_basic.py index c6ef5be..4b8e154 100644 --- a/test/py/rule/010_basic.py +++ b/test/py/rule/500_basic.py @@ -15,12 +15,3 @@ r.print(p, printCombined=True) r.print(p, p) r.print(p, p, printCombined=True) - - -data = """rule [ -left [ node [ id 0 label "L" ] ] -right [ node [ id 0 label "R" ] ] -]""" -r = Rule.fromGMLString(data) -ri = Rule.fromGMLString(data, invert=True) -assert r.makeInverse().isomorphism(ri) != 0 diff --git a/test/py/rule/501_numComponents.py b/test/py/rule/501_numComponents.py new file mode 100644 index 0000000..3c3a329 --- /dev/null +++ b/test/py/rule/501_numComponents.py @@ -0,0 +1,22 @@ +include("xxx_helpers.py") + +def c(dfs, numL, numR): + r = Rule.fromDFS(dfs) + assert r.numLeftComponents == numL + assert r.numRightComponents == numR + commonChecks(r) + +# L +c("[X]>>", 1, 0) +c("[X][Y]>>", 1, 0) +c("[X].[Y]>>", 2, 0) + +# K +c("[X]1>>[X]1", 1, 1) +c("[X]1[Y]2>>[X]1[Y]2", 1, 1) +c("[X]1.[Y]2>>[X]1.[Y]2", 2, 2) + +# R +c(">>[X]", 0, 1) +c(">>[X][Y]", 0, 1) +c(">>[X].[Y]", 0, 2) diff --git a/test/py/rule/510_ruleInterface.py b/test/py/rule/510_ruleInterface.py new file mode 100644 index 0000000..5563d94 --- /dev/null +++ b/test/py/rule/510_ruleInterface.py @@ -0,0 +1,234 @@ +include("../xxx_graphInterface.py") +include("xxx_helpers.py") +import math + +def _getOrNan(f): + try: + return f() + except LogicError: + return float('nan') + +def checkCore(a): + print("Core") + print("-" * 80) + print("numVertices:", a.numVertices) + for v in a.vertices: + print("Vertex:", v.rule, v.id, v.degree) + xn = _getOrNan(lambda: v.get2DX(False)) + yn = _getOrNan(lambda: v.get2DY(False)) + xh = _getOrNan(lambda: v.get2DX(True)) + yh = _getOrNan(lambda: v.get2DY(True)) + x = _getOrNan(lambda: v.get2DX()) + y = _getOrNan(lambda: v.get2DY()) + assert math.isnan(xn) == math.isnan(yn) + assert math.isnan(xh) == math.isnan(yh) + assert math.isnan(x) == math.isnan(y) + + print(" coords without hydrogens: x={}, y={}".format(xn, yn)) + print(" coords with hydrogens: x={}, y={}".format(xh, yh)) + assert math.isnan(x) == math.isnan(xh) + assert math.isnan(y) == math.isnan(yh) + assert math.isnan(x) or x == xh, (x, xh) + assert math.isnan(y) or y == yh, (y, yh) + + if v.left.isNull(): + print("\tLeft: N/A") + else: + print("\tLeft:", v.left, v.left.stringLabel) + assert v.left.core == v + if v.context.isNull(): + print("\tContext: N/A") + else: + print("\tContext:", v.context) + assert v.context.core == v + if v.right.isNull(): + print("\tRight: N/A") + else: + print("\tRight:", v.right, v.right.stringLabel) + assert v.right.core == v + for e in v.incidentEdges: + print("\tEdge:", e.target.id) + print("numEdges:", a.numEdges) + for e in a.edges: + print("Edge:", e.rule, e.source.id, e.target.id) + if e.left.isNull(): + print("Left: N/A") + else: + print("Left:", e.left) + assert e.left.core == e + if e.context.isNull(): + print("Context: N/A") + else: + print("Context:", e.context) + assert e.context.core == e + if e.right.isNull(): + print("Right: N/A") + else: + print("Right:", e.right) + assert e.right.core == e + + +def checkSide(a, withLabels, side): + print(side) + print("-" * 80) + print("rule:", a.rule.name) + print("numVertices:", a.numVertices) + for v in a.vertices: + if withLabels: + print("Vertex:", v.rule, v.id, v.degree, v.stringLabel, v.atomId, v.isotope, v.charge, v.radical) + else: + print("Vertex:", v.rule, v.id, v.degree) + assert v.id == v.core.id + for e in v.incidentEdges: + print("\tEdge:", e.target.id) + print("numEdges:", a.numEdges) + for e in a.edges: + if withLabels: + try: + b = str(e.bondType) + except LogicError: + b = "I" + print("Edge:", e.rule, e.source.id, e.target.id, e.stringLabel, b) + else: + print("Edge:", e.rule, e.source.id, e.target.id) + print("Core:", e.core) + + +def checkRule(name, a): + print("#" * 80) + print(name) + print("#" * 80) + a.name = name + + checkGraph(a, string="'{}'".format(a.name), + vertexString="RuleVertex", edgeString="RuleEdge") + checkLabelledGraph(a.left, string="RuleLeftGraph('{}')".format(a.name), + vertexString="RuleLeftGraphVertex", edgeString="RuleLeftGraphEdge", + graphNameInElements=str(a), vIdFull=False) + checkGraph(a.context, string="RuleContextGraph('{}')".format(a.name), + vertexString="RuleContextGraphVertex", edgeString="RuleContextGraphEdge", + graphNameInElements=str(a), vIdFull=False) + checkLabelledGraph(a.right, string="RuleRightGraph('{}')".format(a.name), + vertexString="RuleRightGraphVertex", edgeString="RuleRightGraphEdge", + graphNameInElements=str(a), vIdFull=False) + + checkCore(a) + checkSide(a.left, True, "L") + checkSide(a.context, False, "K") + checkSide(a.right, True, "R") + + commonChecks(a) + + +def checkGML(name, gml): + return checkRule(name, Rule.fromGMLString("rule [ {} ]".format(gml))) + +def checkDFS(name, dfs): + return checkRule(name, Rule.fromDFS(dfs)) + + +checkGML("L only", """left [ node [ id 0 label "C" ] ]""") + +checkGML("orig example", """ + left [ + node [ id 0 label "C+." ] + edge [ source 100 target 0 label "-" ] + ] + context [ + node [ id 2 label "H-." ] + edge [ source 100 target 2 label ":" ] + node [ id 100 label "U" ] + ] + right [ + node [ id 1 label "O-." ] + edge [ source 100 target 1 label "=" ] + ]""") + +# K +checkDFS("K, vertex change", "[x]1{e}[y]2>>[a]1{e}[b]2") +checkDFS("K, vertex and edge change", "[x]1{q}[y]2>>[a]1{r}[b]2") +checkDFS("K, chemical vertex change", "[C]1-[S]2>>[N]1-[O]2") +checkDFS("K, chemical vertex and edge change", "[C]1=[S]2>>[N]1#[O]2") + +# L +checkDFS("L", "[x]{q}[y]>>") +checkDFS("L, chemical", "[C]=[H]>>") + +# R +checkDFS("R", ">>[x]{q}[y]") +checkDFS("R, chemical", ">>[C]=[H]") + +# Combinations K +# offset a side to provoke index out of bounds problems +checkDFS("Combinations K, offset L, vertex change", + "[ox]{oq}[oy].[x]1{e}[y]2>>[a]1{e}[b]2") +checkDFS("Combinations K, offset L, vertex and edge change", + "[ox]{oq}[oy].[x]1{q}[y]2>>[a]1{r}[b]2") +checkDFS("Combinations K, offset L, chemical vertex chagne", + "[ox]{oq}[oy].[C]1-[S]2>>[N]1-[O]2") +checkDFS("Combinations K, offset L, chemical vertex and edge change", + "[ox]{oq}[oy].[C]1=[S]2>>[N]1#[O]2") +checkDFS("Combinations K, offset R, vertex change", + "[x]1{e}[y]2>>[ox]{oq}[oy].[a]1{e}[b]2") +checkDFS("Combinations K, offset R, vertex and edge change", + "[x]1{q}[y]2>>[ox]{oq}[oy].[a]1{r}[b]2") +checkDFS("Combinations K, offset R, chemical vertex change", + "[C]1-[S]2>>[ox]{oq}[oy].[N]1-[O]2") +checkDFS("Combinations K, offset R, chemical vertex and edge change", + "[C]1=[S]2>>[ox]{oq}[oy].[N]1#[O]2") + +# lots of hydrogens, to check coord difference +checkDFS("Hydrogens, coords", + "{0}>>{0}".format("[C]1([H]2)([H]3)([H]4)[C]5([H]6)([H]7)[C]8([H]9)([H]10)([H]11)")) + + +# Some structural checks +a = Rule.fromGMLString("""rule [ + context [ + node [ id 0 label "A" ] + node [ id 1 label "B" ] + edge [ source 0 target 1 label "-" ] + ] +]""") +v0 = a.vertices[0] +v1 = a.vertices[1] +assert v0 == v0 +assert v0 != v1 +e1 = list(v0.incidentEdges)[0] +e2 = list(v1.incidentEdges)[0] +assert e1 == e2 +assert not e1 != e2 + +commonChecks(a) + + +# Some ID checks +a = Rule.fromGMLString("""rule [ + left [ + node [ id 1 label "B" ] + ] + context [ + node [ id 0 label "A" ] + ] + right [ + node [ id 2 label "C" ] + ] +]""") +v0 = a.getVertexFromExternalId(0) +v1 = a.getVertexFromExternalId(1) +v2 = a.getVertexFromExternalId(2) +assert v0.id == 0, v0.id +assert v1.id == 1, v1.id +assert v2.id == 2, v2.id + +assert v0.left.id == 0, v0.left.id +assert v0.context.id == 0, v0.context.id +assert v0.right.id == 0, v0.right.id +assert v1.left.id == 1, v1.left.id +assert v2.right.id == 2, v2.right.id + +print(v0.left.stringLabel, v0.right.stringLabel) +print(v1.left.stringLabel) +print(v2.right.stringLabel) + +commonChecks(a) diff --git a/test/py/rule/520_inverse.py b/test/py/rule/520_inverse.py new file mode 100644 index 0000000..f18e4f4 --- /dev/null +++ b/test/py/rule/520_inverse.py @@ -0,0 +1,88 @@ +include("xxx_helpers.py") + +def check(s1, s2): + print("#" * 80) + data1 = "rule [ left [ {} ] right [ {} ] ]".format(s1, s2) + data2 = "rule [ left [ {} ] right [ {} ] ]".format(s2, s1) + print("data1:") + print(data1) + print("data2:") + print(data2) + print("=" * 80) + print("r1 load") + print("-" * 80) + r1 = Rule.fromGMLString(data1) + print("r1 invert") + print("-" * 80) + r1inv = r1.makeInverse() + + print("=" * 80) + print("r2 load") + print("-" * 80) + r2 = Rule.fromGMLString(data2) + print("r2 invert") + print("-" * 80) + r2inv = r2.makeInverse() + + print("=" * 80) + print("r1inv iso r2") + print("-" * 80) + assert r1inv.isomorphism(r2) != 0 + print("r2inv iso r1") + print("-" * 80) + assert r2inv.isomorphism(r1) != 0 + + commonChecks(r1) + commonChecks(r2) + +# vertices +check('node [ id 0 label "A" ]', '') +check('', 'node [ id 0 label "A" ]') +check('node [ id 0 label "A" ]', 'node [ id 0 label "B" ]') +check('node [ id 0 label "C" ]', 'node [ id 0 label "C" ]') +# edges +check(''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "A" ] +''', ''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] +''') +check(''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] +''', ''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "A" ] +''') +check(''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "A" ] +''', ''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "B" ] +''') +check(''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "C" ] +''', ''' + node [ id 0 label "X" ] + node [ id 1 label "Y" ] + edge [ source 0 target 1 label "C" ] +''') + +# TODO: stereo tests + +# constraints +data = """rule [ + left [ node [ id 0 label "X" ] ] + constrainAdj [ id 0 count 0 op "=" ] +]""" +r = Rule.fromGMLString(data) +fail(lambda: Rule.fromGMLString(data, invert=True), err=InputError, + pattern="has matching constraints and can not be reversed. Use Rule::ignoreConstraintsDuringInversion == true to strip constraints.") diff --git a/test/py/rule/800_morphism.py b/test/py/rule/800_morphism.py new file mode 100644 index 0000000..8ad1142 --- /dev/null +++ b/test/py/rule/800_morphism.py @@ -0,0 +1,42 @@ +include("xxx_helpers.py") + +data = """rule [ + left [ + node [ id 11 label "vL1" ] + node [ id 12 label "vL2" ] + edge [ source 11 target 12 label "eL" ] + + node [ id 41 label "vCL1" ] + node [ id 42 label "vCL2" ] + edge [ source 41 target 42 label "eCL" ] + ] + context [ + node [ id 31 label "vK1" ] + node [ id 32 label "vK2" ] + edge [ source 31 target 32 label "eK" ] + ] + right [ + node [ id 21 label "vR1" ] + node [ id 22 label "vR2" ] + edge [ source 21 target 22 label "eR" ] + + node [ id 41 label "vCR1" ] + node [ id 42 label "vCR2" ] + edge [ source 41 target 42 label "eCR" ] + ] +]""" +print("Loading r1") +print("=" * 80) +r1 = Rule.fromGMLString(data) +print("Loading r2") +print("=" * 80) +r2 = Rule.fromGMLString(data) +print("Isomorphism") +print("=" * 80) +assert r1.isomorphism(r2) != 0 +print("Monoomorphism") +print("=" * 80) +assert r1.monomorphism(r2) != 0 + +commonChecks(r1) +commonChecks(r2) diff --git a/test/py/rule/200_isomorphismLeftRight.py b/test/py/rule/850_isomorphismLeftRight.py similarity index 74% rename from test/py/rule/200_isomorphismLeftRight.py rename to test/py/rule/850_isomorphismLeftRight.py index d01df0f..e735240 100644 --- a/test/py/rule/200_isomorphismLeftRight.py +++ b/test/py/rule/850_isomorphismLeftRight.py @@ -1,13 +1,13 @@ include("xxx_helpers.py") -rId = ruleGMLString("""rule [ +rId = Rule.fromGMLString("""rule [ ruleID "id" context [ node [ id 0 label "A" ] node [ id 1 label "B" ] ] ]""") -rSwap = ruleGMLString("""rule [ +rSwap = Rule.fromGMLString("""rule [ ruleID "id" left [ node [ id 0 label "A" ] @@ -20,3 +20,6 @@ ]""") assert rId.isomorphism(rSwap) == 0 assert rId.isomorphicLeftRight(rSwap) + +commonChecks(rId) +commonChecks(rSwap) diff --git a/test/py/rule/500_print.py b/test/py/rule/900_print.py similarity index 79% rename from test/py/rule/500_print.py rename to test/py/rule/900_print.py index 440eb5b..eaf22dc 100644 --- a/test/py/rule/500_print.py +++ b/test/py/rule/900_print.py @@ -1,4 +1,4 @@ -a = ruleGMLString('rule [ context [ node [ id 0 label "C" ] ] ]') +a = Rule.fromGMLString('rule [ context [ node [ id 0 label "C" ] ] ]') fs = a.print() assert len(fs) == 2 assert fs[0] != fs[1] diff --git a/test/py/rule/901_print_graphviz.py b/test/py/rule/901_print_graphviz.py new file mode 100644 index 0000000..cebc81d --- /dev/null +++ b/test/py/rule/901_print_graphviz.py @@ -0,0 +1,20 @@ +include("../xxx_helpers.py") +post.enableInvokeMake() +a = Rule.fromDFS("[O][C][C][C]1[C]2[C]3[C]4[C]5[C][S][P]>>[N][C][C][C]1[C]2[C]3[C]4[C]5[C][S][P]") +a = Rule.fromGMLString('rule [ context [ node [ id 0 label "C" ] ] ]') + +post.summaryChapter("First") +p = GraphPrinter() +f11 = a.print(p) +p.withGraphvizCoords = True +f12 = a.print(p) +p.graphvizPrefix = 'layout = "dot"'; +f13 = a.print(p) + +post.summaryChapter("Second") +p = GraphPrinter() +f21 = a.print(p) +p.withGraphvizCoords = True +f22 = a.print(p) +p.graphvizPrefix = 'layout = "dot"'; +f23 = a.print(p) diff --git a/test/py/rule/910_depict.py b/test/py/rule/910_depict.py new file mode 100644 index 0000000..0a64bdd --- /dev/null +++ b/test/py/rule/910_depict.py @@ -0,0 +1,42 @@ +def p(dfs): + print(dfs) + post.summarySection(dfs) + r = Rule.fromDFS(dfs) + p = GraphPrinter() + r.print(p, printCombined=True) + p.withGraphvizCoords = True + r.print(p) + r.printTermState() + +print("=" * 80) +print("K") +print("-" * 80) +p("[x]1{e}[y]2>>[a]1{e}[b]2") +p("[x]1{q}[y]2>>[a]1{r}[b]2") +p("[C]1-[S]2>>[N]1-[O]2") +p("[C]1=[S]2>>[N]1#[O]2") + +print("=" * 80) +print("L") +print("-" * 80) +p("[x]{q}[y]>>") +p("[C]=[H]>>") + +print("=" * 80) +print("R") +print("-" * 80) +p(">>[x]{q}[y]") +p(">>[C]=[H]") + +print("=" * 80) +print("Combinations K") +print("-" * 80) +# offset a side to provoke index out of bounds problems +p("[ox]{oq}[oy].[x]1{e}[y]2>>[a]1{e}[b]2") +p("[ox]{oq}[oy].[x]1{q}[y]2>>[a]1{r}[b]2") +p("[ox]{oq}[oy].[C]1-[S]2>>[N]1-[O]2") +p("[ox]{oq}[oy].[C]1=[S]2>>[N]1#[O]2") +p("[x]1{e}[y]2>>[ox]{oq}[oy].[a]1{e}[b]2") +p("[x]1{q}[y]2>>[ox]{oq}[oy].[a]1{r}[b]2") +p("[C]1-[S]2>>[ox]{oq}[oy].[N]1-[O]2") +p("[C]1=[S]2>>[ox]{oq}[oy].[N]1#[O]2") diff --git a/test/py/rule/911_depict_hydrogen_collapse.py b/test/py/rule/911_depict_hydrogen_collapse.py new file mode 100644 index 0000000..ee8fbd8 --- /dev/null +++ b/test/py/rule/911_depict_hydrogen_collapse.py @@ -0,0 +1,16 @@ +def p(dfs): + print(dfs) + post.summarySection(dfs) + r = Rule.fromDFS(dfs) + pr = GraphPrinter() + r.print(pr) + pr.setReactionDefault() + r.print(pr) + +p("[C]1[H]2>>[N]1[O]2") +p("[N]1[O]2>>[C]1[H]2") + +p("[C]1[H]2>>[C]1[H]2") + +p("[ox]{oq}[oy].[C]1[H]2>>[C]1[H]2") +p("[C]1[H]2>>[ox]{oq}[oy].[C]1[H]2") diff --git a/test/py/rule/920_depict_stereo.py b/test/py/rule/920_depict_stereo.py new file mode 100644 index 0000000..830df8f --- /dev/null +++ b/test/py/rule/920_depict_stereo.py @@ -0,0 +1,28 @@ +include("xxx_helpers.py") +include("xxx_gml_stereo.py") + +def f(conf, group, title, gml): + r = Rule.fromGMLString(gml) + r.name = title + p = GraphPrinter() + p.setReactionDefault() + r.print(p) + + p = GraphPrinter() + p.setReactionDefault() + p.withPrettyStereo = True + r.print(p) + + p = GraphPrinter() + p.setReactionDefault() + p.withRawStereo = True + r.print(p) + + p = GraphPrinter() + p.setReactionDefault() + p.withIndex = True + r.print(p) + + commonChecks(r) + +foreachStereoExample(f) diff --git a/test/py/rule/rule.gml b/test/py/rule/rule.gml deleted file mode 100644 index 5963eac..0000000 --- a/test/py/rule/rule.gml +++ /dev/null @@ -1,27 +0,0 @@ -rule [ - ruleID "Test Rule" - left [ - node [ id 1 label "L" ] - node [ id 4 label "LC_A" ] - edge [ source 1 target 4 label "-" ] - ] - context [ - node [ id 3 label "C" ] - edge [ source 3 target 4 label "-" ] - ] - right [ - node [ id 2 label "R" ] - node [ id 4 label "LC_B" ] - edge [ source 2 target 4 label "-" ] - ] - constrainAdj [ - id 1 count 0 op "=" - nodeLabels [ label "A" label "B" ] - edgeLabels [ label "Q" label "R" ] - ] - constrainAdj [ - id 4 count 2 op "<" - nodeLabels [ label "C" label "D" ] - edgeLabels [ label "S" label "T" ] - ] -] diff --git a/test/py/rule/rule.py b/test/py/rule/rule.py deleted file mode 100644 index 92bfaef..0000000 --- a/test/py/rule/rule.py +++ /dev/null @@ -1,53 +0,0 @@ -include("xxx_helpers.py") - -testRule = ruleGMLString(""" - rule [ ruleID "inline rule" context [ node [ id 0 label "A" ] ] ] -""") -testRuleInverse = testRule.makeInverse() -ruleGML("rule.gml") -a = ruleGML("rule.gml") -print("a:\t", a) -print("inputRules:\t", inputRules) -with open("myRule.gml", "w") as f: - f.write(a.getGMLString()) -a = ruleGML(CWDPath("myRule.gml")) - -try: - b = a.makeInverse() -except LogicError: - pass -else: - assert False - -a.printGML() -print(a.getGMLString()) - -print("name:\t", a.name) -a.name = "New Name" -print("name:\t", a.name) - -print("numLeftComponents:\t", a.numLeftComponents) -print("numRightComponents:\t", a.numRightComponents) - -# Check eq operator -inputRules[:] = [] -a = ruleGML("rule.gml") -rs = inputRules # TODO: make rs something comming from C++ to get difference Python object with same ptr -b = rs[0] -print("a:", a) -print("b:", b) -assert a == b -assert a.isomorphism(b) > 0 -assert a.isomorphism(testRule) == 0 - -a = ruleGMLString("""rule [ context [ node [ id 0 label "C" ] ] ]""") - - -r = ruleGMLString("""rule [ - context [ - node [ id 0 label "*" ] - node [ id 1 label "*" ] - edge [ source 0 target 1 label "*" ] - ] -]""") -r.printTermState() diff --git a/test/py/rule/xxx_gml_stereo.py b/test/py/rule/xxx_gml_stereo.py new file mode 100644 index 0000000..855a575 --- /dev/null +++ b/test/py/rule/xxx_gml_stereo.py @@ -0,0 +1,68 @@ +_gmlStereoExamples = {} + +def foreachStereoExample(f): + for conf, confExamples in _gmlStereoExamples.items(): + print("#" * 80) + print(conf) + print("#" * 80) + post.summaryChapter(conf) + for group, examples in confExamples.items(): + print("=" * 80) + print(group) + print("=" * 80) + post.summarySection(group) + for title, gml in examples: + print(title) + print("-" * 80) + f(conf, group, title, gml) + +# ========================================================================= + +offset = """ + node [ id 0 label "ox" ] + node [ id 1 label "oy" ] + edge [ source 0 target 1 label "oq" stereo "*" ] +""" + +# ========================================================================= +# Tetrahedral +# ========================================================================= + +tetrahedralPattern = """ + {} + node [ id 101 label "r" ] + node [ id 102 label "s" ] + node [ id 103 label "t" ] + edge [ source 100 target 101 label "-" stereo "*" ] + edge [ source 100 target 102 label "-" stereo "*" ] + edge [ source 100 target 103 label "-" stereo "*" ] +""" +tetrahedral = tetrahedralPattern.format( + 'node [ id 100 label "q" stereo "tetrahedral[101, 102, 103, e]!" ]') +tetrahedralAnti = tetrahedralPattern.format( + 'node [ id 100 label "q" stereo "tetrahedral[101, 102, e, 103]!" ]') + +_gmlStereoExamples["Tetrahedral" ] = { + "K": [ + ("context", "rule [ context [ {} ] ]".format(tetrahedral)), + ("left + right", "rule [ left [ {} ] right [ {} ] ]".format( + tetrahedral, tetrahedralAnti)), + ], + "L": [ + ("left", "rule [ left [ {} ] ]".format(tetrahedral)), + ], + "R": [ + ("right", "rule [ right [ {} ] ]".format(tetrahedral)), + ], + "Combinations K": [ + # offset a side to provoke index out of bounds problems + ("context, offset left", """rule [ + left [ {} ] + context [ {} ] + ]""".format(offset, tetrahedral)), + ("context, offset right", """rule [ + right [ {} ] + context [ {} ] + ]""".format(offset, tetrahedral)), + ], +} diff --git a/test/py/rule/xxx_helpers.py b/test/py/rule/xxx_helpers.py index 73edd11..d1b65ba 100644 --- a/test/py/rule/xxx_helpers.py +++ b/test/py/rule/xxx_helpers.py @@ -1,5 +1,43 @@ include("../xxx_helpers.py") -def ruleFail(s, pattern): - fail(lambda: ruleGMLString("rule [ %s ]" % s), +def commonChecks(r): + # round-trip as GML + s = r.getGMLString() + rCopy = Rule.fromGMLString(s) + assert r.isomorphism(rCopy) != 0 + sCopy = rCopy.getGMLString() + if s != sCopy: + print("=" * 80) + print("Originally generated GML:") + print(s) + print("=" * 80) + print("Generated GML from reloaded GML:") + print(sCopy) + assert False + + +def gmlFail(s, pattern): + fail(lambda: Rule.fromGMLString("rule [ %s ]" % s), + pattern=pattern, err=InputError, isSubstring=True) + + +def dfsFail(s, pattern): + fail(lambda: Rule.fromDFS(s), pattern=pattern, err=InputError, isSubstring=True) + + +def dfsCheck(dfsInput, gmlInput): + print("DFS:", dfsInput) + dfs = Rule.fromDFS(dfsInput) + gml = Rule.fromGMLString("rule [ %s ]" % gmlInput) + if dfs.isomorphism(gml) != 1: + print("DFS Input:", dfs) + print("GML Input: rule [\n%s\n]" % gmlInput) + dfs.name = "DFS" + gml.name = "GML" + dfs.print() + gml.print() + post.enableInvokeMake() + assert False, "Run mod_post to see rules." + + commonChecks(dfs) diff --git a/test/py/stereo/00_geometryAndEmbedding.py b/test/py/stereo/00_geometryAndEmbedding.py index 40b5105..998ec87 100644 --- a/test/py/stereo/00_geometryAndEmbedding.py +++ b/test/py/stereo/00_geometryAndEmbedding.py @@ -14,12 +14,12 @@ def gmlNode(i, edge="-"): data['any'] = 'node [ id 0 label "Q" stereo "any[1, 2, 3, 4]" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4) data['tetrahedral'] = 'node [ id 0 label "C" stereo "tetrahedral[1, 2, 3, 4]" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4) -postChapter("Graph") +post.summaryChapter("Graph") for n, d in data.items(): - postSection(n) + post.summarySection(n) gGML(d) for side in ["context", "left", "right"]: - postChapter("Rule " + side) + post.summaryChapter("Rule " + side) for n, d in data.items(): - postSection(n + " " + side) + post.summarySection(n + " " + side) rGML(d, side) diff --git a/test/py/stereo/01_geometry.py b/test/py/stereo/01_geometry.py index 5f6c150..734967e 100644 --- a/test/py/stereo/01_geometry.py +++ b/test/py/stereo/01_geometry.py @@ -25,14 +25,14 @@ def gmlNode(i, edge="-"): data.append(('tetrahedral', 'node [ id 0 label "P" stereo "tetrahedral" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3, "=") + gmlNode(4))) # for testing initialisation data.append(('tetrahedral', 'node [ id 0 label "P" stereo "tetrahedral" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4, "="))) # for testing initialisation -postChapter("Graph") +post.summaryChapter("Graph") for n, d in data: - postSection(n) + post.summarySection(n) gGML(d) for side in ["context", "left", "right"]: - postChapter("Rule " + side) + post.summaryChapter("Rule " + side) for n, d in data: # TODO: this should work too if n == "trigonalPlanar2": continue - postSection(n + " " + side) + post.summarySection(n + " " + side) rGML(d, side) diff --git a/test/py/stereo/02_embedding.py b/test/py/stereo/02_embedding.py index f684f08..5fa9bd3 100644 --- a/test/py/stereo/02_embedding.py +++ b/test/py/stereo/02_embedding.py @@ -30,12 +30,12 @@ def gmlNode(i, edge="-"): data.append(('tetrahedral', 'node [ id 0 label "S" stereo "[1, 2, 3, 4]" ]' + gmlNode(1) + gmlNode(2, "=") + gmlNode(3) + gmlNode(4, "="))) # for testing initialisation data.append(('tetrahedral', 'node [ id 0 label "S" stereo "[1, 2, 3, 4]" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3, "=") + gmlNode(4, "="))) # for testing initialisation -postChapter("Graph") +post.summaryChapter("Graph") for n, d in data: - postSection(n) + post.summarySection(n) gGML(d) for side in ["context", "left", "right"]: - postChapter("Rule " + side) + post.summaryChapter("Rule " + side) for n, d in data: - postSection(n + " " + side) + post.summarySection(n + " " + side) rGML(d, side) diff --git a/test/py/stereo/03_completeDeduce.py b/test/py/stereo/03_completeDeduce.py index e4b01c6..b8f9f27 100644 --- a/test/py/stereo/03_completeDeduce.py +++ b/test/py/stereo/03_completeDeduce.py @@ -23,9 +23,9 @@ data.append(('tetrahedral', dfs("[Z]P([Z])([Z])=[Z]"))) # for testing initialisation for side in ["context", "left", "right"]: - postChapter("Rule " + side) + post.summaryChapter("Rule " + side) for n, a in data: - postSection(n + " " + side) + post.summarySection(n + " " + side) gml = a.getGMLString() gml = gml[7:-2] rGML(gml, side) diff --git a/test/py/stereo/04_graphFix.py b/test/py/stereo/04_graphFix.py index 3950f1e..acd7908 100644 --- a/test/py/stereo/04_graphFix.py +++ b/test/py/stereo/04_graphFix.py @@ -3,29 +3,29 @@ def gmlNode(i, edge="-"): return 'node [ id %d label "Z" ] edge [ source 0 target %d label "%s" ]' % (i, i, edge) -postSection('any') +post.summarySection('any') gGML('node [ id 0 label "Q" stereo "[]!" ]') gGML('node [ id 0 label "H+" stereo "[]!" ]') gGML('node [ id 0 label "H" stereo "[1]!" ]' + gmlNode(1)) gGML('node [ id 0 label "Q" stereo "[1, 2]!" ]' + gmlNode(1) + gmlNode(2)) -postSection('linear') +post.summarySection('linear') gGML('node [ id 0 label "C" stereo "[1, 2]!" ]' + gmlNode(1, "#") + gmlNode(2)) gGML('node [ id 0 label "C" stereo "[1, 2]!" ]' + gmlNode(1, "=") + gmlNode(2, "=")) -postSection('any') +post.summarySection('any') gGML('node [ id 0 label "Q" stereo "[1, 2, 3]!" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3)) -postSection('trigonalPlanar') +post.summarySection('trigonalPlanar') gGML('node [ id 0 label "C" stereo "[1, 2, 3]!" ]' + gmlNode(1, "=") + gmlNode(2) + gmlNode(3)) gGML('node [ id 0 label "C" stereo "[1, 2, 3]!" ]' + gmlNode(1, ":") + gmlNode(2, ":") + gmlNode(3, ":")) gGML('node [ id 0 label "N" stereo "[1, 2, 3]!" ]' + gmlNode(1, ":") + gmlNode(2, ":") + gmlNode(3)) gGML('node [ id 0 label "N" stereo "[1, 2, e]!" ]' + gmlNode(1, ":") + gmlNode(2, ":")) -postSection('any') +post.summarySection('any') gGML('node [ id 0 label "Q" stereo "[1, 2, 3, 4]!" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4)) -postSection('tetrahedral') +post.summarySection('tetrahedral') gGML('node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4)) gGML('node [ id 0 label "N" stereo "[1, 2, 3, e]!" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3)) gGML('node [ id 0 label "N+" stereo "[1, 2, 3, 4]!" ]' + gmlNode(1) + gmlNode(2) + gmlNode(3) + gmlNode(4)) diff --git a/test/py/stereo/20_morphismGraph.py b/test/py/stereo/20_morphismGraph.py index ff09729..05dda7a 100644 --- a/test/py/stereo/20_morphismGraph.py +++ b/test/py/stereo/20_morphismGraph.py @@ -35,10 +35,10 @@ def makeGraphDict(): for n2, g2 in sorted(graphs2.items()): def check(res): if not res: - postChapter("Error: %s, %s" % (n1, n2)) - postSection("Stereo, %s" % n1) + post.summaryChapter("Error: %s, %s" % (n1, n2)) + post.summarySection("Stereo, %s" % n1) printStereo(g1) - postSection("Stereo, %s" % n2) + post.summarySection("Stereo, %s" % n2) printStereo(g2) assert False if n1 == n2: @@ -74,7 +74,7 @@ def check(res): if n1 == n2: def check(res): if not res: - postChapter("Error") + post.summaryChapter("Error") printStereo(g1) printStereo(g2) assert False @@ -86,7 +86,7 @@ def check(res): def check(res, n1, n2): isOk = (n1, n2) in ok if isOk != res: - postChapter("Error: %d" % res) + post.summaryChapter("Error: %d" % res) print(n1, "vs.", n2) print("isOk:", isOk) print("res:", res) @@ -112,7 +112,7 @@ def check(res, n1, n2): tb2 = graphs2["t_b"] = gGML('node [ id 0 label "Q" stereo "tetrahedral[1, 2, 4, 3]!" ]' + gmlNodeIdx(1) + gmlNodeIdx(2) + gmlNodeIdx(3) + gmlNodeIdx(4)) print("\tIsomorphism") -postChapter("Tetrahedral Isomorphism") +post.summaryChapter("Tetrahedral Isomorphism") assert tf1.isomorphism(tf2, labelSettings=isoLabelSettings) > 0 assert tf1.isomorphism(ta2, labelSettings=isoLabelSettings) == 0 assert tf1.isomorphism(tb2, labelSettings=isoLabelSettings) == 0 @@ -124,7 +124,7 @@ def check(res, n1, n2): assert tb1.isomorphism(tb2, labelSettings=isoLabelSettings) > 0 print("\tSpecialisation") -postChapter("Tetrahedral Specialisation") +post.summaryChapter("Tetrahedral Specialisation") assert tf1.isomorphism(tf2, labelSettings=specLabelSettings) > 0 assert tf1.isomorphism(ta2, labelSettings=specLabelSettings) > 0 assert tf1.isomorphism(tb2, labelSettings=specLabelSettings) > 0 diff --git a/test/py/stereo/graphDepiction.py b/test/py/stereo/graphDepiction.py index 3cc851d..f55143f 100644 --- a/test/py/stereo/graphDepiction.py +++ b/test/py/stereo/graphDepiction.py @@ -10,7 +10,7 @@ def doPrint(a): a.print(pg, pm) -postSection("All") +post.summarySection("All") a = graphGMLString("""graph [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] node [ id 1 label "O" ] @@ -36,7 +36,7 @@ def doPrint(a): ]""") doPrint(a) -postSection("Lone Pair") +post.summarySection("Lone Pair") a = graphGMLString("""graph [ node [ id 0 label "N" stereo "[1, 2, 3, e]!" ] node [ id 1 label "O" ] @@ -69,7 +69,7 @@ def doPrint(a): ]""") doPrint(a) -postSection("H") +post.summarySection("H") a = graphGMLString("""graph [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] node [ id 1 label "O" ] @@ -109,7 +109,7 @@ def doPrint(a): doPrint(a) -postSection("All H") +post.summarySection("All H") a = graphGMLString("""graph [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] node [ id 1 label "H" ] diff --git a/test/py/stereo/ruleDepiction.py b/test/py/stereo/ruleDepiction.py index c22d608..744116b 100644 --- a/test/py/stereo/ruleDepiction.py +++ b/test/py/stereo/ruleDepiction.py @@ -10,7 +10,7 @@ def doPrint(a): a.print(pg, pm) -postSection("Context") +post.summarySection("Context") a = ruleGMLString("""rule [ context [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] @@ -25,7 +25,7 @@ def doPrint(a): ] ]""") doPrint(a) -postSection("Left and right") +post.summarySection("Left and right") a = ruleGMLString("""rule [ left [ node [ id 0 stereo "[1, 2, 3, 4]!" ] @@ -80,7 +80,7 @@ def doPrint(a): ]""") doPrint(a) -postSection("Lone Pair") +post.summarySection("Lone Pair") a = graphGMLString("""graph [ node [ id 0 label "N" stereo "[1, 2, 3, e]!" ] node [ id 1 label "O" ] @@ -113,7 +113,7 @@ def doPrint(a): ]""") doPrint(a) -postSection("H") +post.summarySection("H") a = graphGMLString("""graph [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] node [ id 1 label "O" ] @@ -153,7 +153,7 @@ def doPrint(a): doPrint(a) -postSection("All H") +post.summarySection("All H") a = graphGMLString("""graph [ node [ id 0 label "C" stereo "[1, 2, 3, 4]!" ] node [ id 1 label "H" ] diff --git a/test/py/stereo/smiles.py b/test/py/stereo/smiles.py index a6233db..f2e621f 100644 --- a/test/py/stereo/smiles.py +++ b/test/py/stereo/smiles.py @@ -1,24 +1,24 @@ -postSection("prev, branch, branch, tail") +post.summarySection("prev, branch, branch, tail") smiles("O[C@](N)(P)S").print() smiles("O[C@@](N)(P)S").print() -postSection("prev, branch, branch, branch") +post.summarySection("prev, branch, branch, branch") smiles("O[C@](N)(P)(S)").print() smiles("O[C@@](N)(P)(S)").print() -postSection("branch, branch, branch, tail") +post.summarySection("branch, branch, branch, tail") smiles("[C@](O)(N)(P)S").print() smiles("[C@@](O)(N)(P)S").print() -postSection("prev, H, branch, tail") +post.summarySection("prev, H, branch, tail") smiles("O[C@H](P)S").print() smiles("O[C@@H](P)S").print() -postSection("prev, H, branch, branch") +post.summarySection("prev, H, branch, branch") smiles("O[C@H](P)(S)").print() smiles("O[C@@H](P)(S)").print() -postSection("branch, H, branch, tail") +post.summarySection("branch, H, branch, tail") smiles("[C@H](N)(P)S").print() smiles("[C@@H](N)(P)S").print() -postSection("ring closure") +post.summarySection("ring closure") inputGraphs[:] = [] smiles("C1O[C@](N)(P)S1", name="1").print() smiles("O1[C@](N)(P)SC1", name="2").print() @@ -34,6 +34,6 @@ print("{}: {}".format(b.name, b.smiles)) assert False -postSection("from OpenSMILES") +post.summarySection("from OpenSMILES") smiles("FC1C[C@](Br)(Cl)CCC1").print() smiles("[C@]1(Br)(Cl)CCCC(F)C1").print() diff --git a/test/py/stereo/smilesAll.py b/test/py/stereo/smilesAll.py index dbd806c..c739e97 100644 --- a/test/py/stereo/smilesAll.py +++ b/test/py/stereo/smilesAll.py @@ -1,7 +1,7 @@ include("../graph/030_smiles/mass/loadGraphs.py") sys.exit(0) for a in inputGraphs: - postSection(a.name) + post.summarySection(a.name) for v in a.vertices: try: v.printStereo() diff --git a/test/py/stereo/testTetraRule.py b/test/py/stereo/testTetraRule.py index 7cd6e5c..0a31685 100644 --- a/test/py/stereo/testTetraRule.py +++ b/test/py/stereo/testTetraRule.py @@ -18,7 +18,7 @@ ]""") g = smiles("[O][C@]([C@H3])([C@H2][C@H3])([C@H2][C@H3])") -postChapter("DG") +post.summaryChapter("DG") ls = LabelSettings(LabelType.String, LabelRelation.Isomorphism, LabelRelation.Specialisation) dg = DG(graphDatabase=inputGraphs, labelSettings=ls) dg.build().execute(addSubset(g) >> r, verbosity=100) @@ -26,13 +26,13 @@ for a in dg.graphDatabase: a.print() -postChapter("RC") +post.summaryChapter("RC") rc = rcEvaluator([r], labelSettings=ls) res = rc.eval(rcBind(g) *rcSuper* r) print("Res:", len(res)) for a in res: a.print() -postChapter("Iso") +post.summaryChapter("Iso") g1 = smiles("[O][C@]([C@H3])([C@H2][C@H3])([C@H2][C@H]([C@H3])([C@H3]))") g2 = smiles("[O][C@@]([C@H3])([C@H2][C@H3])([C@H2][C@H]([C@H3])([C@H3]))") p = GraphPrinter() diff --git a/test/py/term/counter.py b/test/py/term/counter.py index 0c9a9b7..245a326 100644 --- a/test/py/term/counter.py +++ b/test/py/term/counter.py @@ -1,4 +1,3 @@ -config.io.useOpenBabelCoords = False a = graphDFS("[f(0)][sd(0)]") incFirst = ruleGMLString("""rule [ ruleID "Inc first" @@ -24,11 +23,13 @@ ]""") incSecond.printTermState() -dg = dgRuleComp(inputGraphs, +dg = DG(graphDatabase=inputGraphs, + labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification)) +dg.build().execute( addSubset(a) >> repeat[1]([incSecond, incFirst]) - >> incSecond, - labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification) + >> incSecond ) -dg.calc() -dg.print() +p = DGPrinter() +p.graphPrinter.withGraphvizCoords = True +dg.print(p) diff --git a/test/py/term/makeVars.py b/test/py/term/makeVars.py index a9879e5..ad89c3d 100644 --- a/test/py/term/makeVars.py +++ b/test/py/term/makeVars.py @@ -29,18 +29,18 @@ dg = dgRuleComp(inputGraphs, addSubset(inputGraphs) >> inputRules, labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification)) dg.calc() dg.print() -postSection("Input Graphs") +post.summarySection("Input Graphs") for a in inputGraphs: a.print() a.printTermState() -postSection("Input Rules") +post.summarySection("Input Rules") for a in inputRules: a.print() a.printTermState() -postSection("Vertex Graphs") +post.summarySection("Vertex Graphs") for a in set((v.graph for v in dg.vertices)) - set(inputGraphs): a.print() a.printTermState() -postSection("Derivations") +post.summarySection("Derivations") for e in dg.edges: e.print() diff --git a/test/py/term/parsing.py b/test/py/term/parsing.py index 60a0bf0..84720fd 100644 --- a/test/py/term/parsing.py +++ b/test/py/term/parsing.py @@ -1,33 +1,37 @@ +include("../xxx_helpers.py") + ls = LabelSettings(LabelType.Term, LabelRelation.Unification) -def fail(f): - try: - f() - assert False - except TermParsingError as e: - print(e) -g = graphDFS("[C/][C]") -r = ruleGMLString("""rule [ - context [ - node [ id 0 label "C/" ] - ] -]""") +g = Graph.fromDFS("[C/][C]") +r = Rule.fromGMLString("""rule [ context [ node [ id 0 label "C/" ] ] ]""") for a in inputGraphs: - fail(lambda: a.printTermState()) + fail(lambda: a.printTermState(), "Parsing failed", err=TermParsingError, + isSubstring=True) for a in inputRules: - fail(lambda: a.printTermState()) + fail(lambda: a.printTermState(), "Parsing failed", err=TermParsingError, + isSubstring=True) -fail(lambda: dgRuleComp(inputGraphs, addSubset(lambda: []), ls)) -fail(lambda: dgRuleComp([], addSubset(inputGraphs), ls).calc()) -dg = dgRuleComp([], addSubset(lambda: inputGraphs), ls) -fail(lambda: dg.calc()) -dg = dgRuleComp([], inputRules, ls) -fail(lambda: dg.calc()) -fail(lambda: g.isomorphism(g, 1, ls)) -fail(lambda: g.monomorphism(g, 1, ls)) -fail(lambda: r.isomorphism(r, 1, ls)) -fail(lambda: r.monomorphism(r, 1, ls)) -fail(lambda: rcEvaluator(inputRules, ls)) +fail(lambda: DG(graphDatabase=inputGraphs, labelSettings=ls).build().execute(addSubset(lambda: [])), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: DG(graphDatabase=[], labelSettings=ls).build().execute(addSubset(inputGraphs)), + "Parsing failed", err=TermParsingError, isSubstring=True) +dg = DG(graphDatabase=[], labelSettings=ls) +fail(lambda: dg.build().execute(addSubset(lambda: inputGraphs)), + "Parsing failed", err=TermParsingError, isSubstring=True) +dg = DG(graphDatabase=[], labelSettings=ls) +fail(lambda: dg.build().execute(inputRules), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: g.isomorphism(g, 1, ls), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: g.monomorphism(g, 1, ls), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: r.isomorphism(r, 1, ls), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: r.monomorphism(r, 1, ls), + "Parsing failed", err=TermParsingError, isSubstring=True) +fail(lambda: rcEvaluator(inputRules, ls), + "Parsing failed", err=TermParsingError, isSubstring=True) rc = rcEvaluator([], ls) -fail(lambda: rc.eval(inputRules *rcSuper* inputRules)) +fail(lambda: rc.eval(inputRules *rcSuper* inputRules), + "Parsing failed", err=TermParsingError, isSubstring=True) diff --git a/test/py/term/relations.py b/test/py/term/relations.py index d936130..dedc129 100644 --- a/test/py/term/relations.py +++ b/test/py/term/relations.py @@ -1,7 +1,6 @@ -postChapter("DG") +post.summaryChapter("DG") print("DG") print("="*80) -config.io.useOpenBabelCoords = False a = graphDFS("[f(a)][f(b)]") b = ruleGMLString("""rule [ ruleID "b" @@ -13,12 +12,12 @@ ]""") b.printTermState() -dg = dgRuleComp(inputGraphs, - addSubset(a) >> b, - labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification) -) -dg.calc() -dg.print() +dg = DG(graphDatabase=inputGraphs, + labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification)) +dg.build().execute(addSubset(a) >> b) +p = DGPrinter() +p.graphPrinter.withGraphvizCoords = True +dg.print(p) def doRelations(xy1, xy2, xx): all = [xy1, xy2, xx] @@ -44,7 +43,7 @@ def doRelations(xy1, xy2, xx): assert xy1.isomorphism(xx, labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification)) > 0 assert xx.isomorphism(xy1, labelSettings=LabelSettings(LabelType.Term, LabelRelation.Unification)) > 0 -postChapter("Graph") +post.summaryChapter("Graph") print("Graph") print("="*80) xy1 = graphDFS("[f(_X, _Y)][a]") @@ -52,7 +51,7 @@ def doRelations(xy1, xy2, xx): xx = graphDFS("[a][f(_X, _X)]") doRelations(xy1, xy2, xx) -postChapter("Rule") +post.summaryChapter("Rule") print("Rule") print("="*80) xy1 = rcEvaluator([]).eval(rcId(xy1))[0] diff --git a/test/py/uniqueFilePrefix.py b/test/py/uniqueFilePrefix.py deleted file mode 100644 index 1b4af4a..0000000 --- a/test/py/uniqueFilePrefix.py +++ /dev/null @@ -1,5 +0,0 @@ -post("disableSummary") - -for _ in range(10): - filePrefix = makeUniqueFilePrefix() - print(filePrefix) diff --git a/test/py/xxx_graphInterface.py b/test/py/xxx_graphInterface.py index d81de16..79234bc 100644 --- a/test/py/xxx_graphInterface.py +++ b/test/py/xxx_graphInterface.py @@ -1,5 +1,5 @@ include("xxx_helpers.py") -postDisable() +post.disableCommands() def checkGraph(g, *, string: str, vertexString: str, edgeString: str, graphNameInElements: str = None, diff --git a/test/py/xxx_helpers.py b/test/py/xxx_helpers.py index 16a8946..a8b4f8e 100644 --- a/test/py/xxx_helpers.py +++ b/test/py/xxx_helpers.py @@ -1,4 +1,4 @@ -post("disableSummary") +post.disableInvokeMake() def fail(f, pattern, err=LogicError, isSubstring=False): try: @@ -16,6 +16,20 @@ def fail(f, pattern, err=LogicError, isSubstring=False): raise +def checkDeprecated(f): + old = config.common.ignoreDeprecation + config.common.ignoreDeprecation = True + res = f() + config.common.ignoreDeprecation = False + try: + f() + assert False + except DeprecationWarning: + pass + config.common.ignoreDeprecation = old + return res + + def _compareFiles(f1, f2): import difflib with open(f1, 'rb') as file1, open(f2, 'rb') as file2: