libcifpp-7.0.9/0000755000175000017500000000000014746170722013171 5ustar maartenmaartenlibcifpp-7.0.9/.github/0000755000175000017500000000000014746170722014531 5ustar maartenmaartenlibcifpp-7.0.9/.github/workflows/0000755000175000017500000000000014746170722016566 5ustar maartenmaartenlibcifpp-7.0.9/.github/workflows/build-documentation.yml0000644000175000017500000000362014746170722023260 0ustar maartenmaarten# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml name: publish docs on: push: branches: [ "trunk" ] permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: false jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Install dependencies Ubuntu run: sudo apt-get update && sudo apt-get install cmake doxygen - uses: actions/setup-python@v4 with: python-version: '3.9' cache: 'pip' # caching pip dependencies - run: pip install -r docs/requirements.txt - name: Configure CMake run: cmake -S . -B ${{ steps.strings.outputs.build-output-dir }} -DBUILD_DOCUMENTATION=ON -DBUILD_TESTING=OFF - name: Run Sphinx run: | cmake --build ${{ steps.strings.outputs.build-output-dir }} --target Sphinx-libcifpp ls -l ${{ steps.strings.outputs.build-output-dir }} ls -l ${{ steps.strings.outputs.build-output-dir }}/docs/sphinx - name: Upload artifact uses: actions/upload-pages-artifact@v2 with: path: ${{ steps.strings.outputs.build-output-dir }}/docs/sphinx deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: docs steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 libcifpp-7.0.9/.github/workflows/cmake-multi-platform.yml0000644000175000017500000000307714746170722023352 0ustar maartenmaartenname: multi platform test on: push: branches: [ "trunk", "develop" ] pull_request: branches: [ "trunk" ] jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] include: - os: windows-latest cpp_compiler: cl - os: ubuntu-latest cpp_compiler: g++ - os: macos-latest cpp_compiler: clang++ steps: - uses: actions/checkout@v3 - name: Set reusable strings id: strings shell: bash run: echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Install dependencies Ubuntu if: matrix.os == 'ubuntu-latest' run: sudo apt-get update && sudo apt-get install mrc - name: Install dependencies Window if: matrix.os == 'windows-latest' run: ./tools/depends.cmd shell: cmd - name: Configure CMake run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_BUILD_TYPE=Release -S ${{ github.workspace }} - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release - name: Test working-directory: ${{ steps.strings.outputs.build-output-dir }} run: ctest --build-config Release --output-on-failure - name: Install if: matrix.os != 'windows-latest' run: sudo cmake --install ${{ steps.strings.outputs.build-output-dir }} --config Release libcifpp-7.0.9/.gitignore0000644000175000017500000000034314746170722015161 0ustar maartenmaartenbuild/ .vscode/ .vs/ tools/update-libcifpp-data rsrc/components.cif* CMakeSettings.json msvc/ src/revision.hpp test/test-create_sugar_?.cif Testing/ include/cif++/exports.hpp docs/api docs/conf.py build_ci/ data/components.cif libcifpp-7.0.9/.readthedocs.yaml0000644000175000017500000000066314746170722016425 0ustar maartenmaartenversion: 2 build: os: ubuntu-22.04 tools: python: "3.11" apt_packages: - doxygen - cmake jobs: pre_build: - cmake -S . -B build -DBUILD_DOCUMENTATION=ON - cmake --build build --target Doxygen # Build from the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Explicitly set the version of Python and its requirements python: install: - requirements: docs/requirements.txt libcifpp-7.0.9/CMakeLists.txt0000644000175000017500000004327514746170722015744 0ustar maartenmaarten# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2021 NKI/AVL, Netherlands Cancer Institute # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmake_minimum_required(VERSION 3.23) # set the project name project( libcifpp VERSION 7.0.9 LANGUAGES CXX) list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(FindAtomic) include(CheckFunctionExists) include(CheckIncludeFiles) include(CheckLibraryExists) include(CMakePackageConfigHelpers) include(CheckCXXSourceCompiles) include(GenerateExportHeader) include(CTest) include(FetchContent) # FindBoost, take care of it now. if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.30) cmake_policy(SET CMP0167 NEW) endif() # When building with ninja-multiconfig, build both debug and release by default if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config") set(CMAKE_CROSS_CONFIGS "Debug;Release") set(CMAKE_DEFAULT_CONFIGS "Debug;Release") endif() if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers" ) elseif(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() # Build documentation? option(BUILD_DOCUMENTATION "Build the documentation" OFF) # Optionally build a version to be installed inside CCP4 option(BUILD_FOR_CCP4 "Build a version to be installed in CCP4") # Building shared libraries? if(NOT(BUILD_FOR_CCP4 AND WIN32)) option(BUILD_SHARED_LIBS "Build a shared library instead of a static one" OFF) endif() if(PROJECT_IS_TOP_LEVEL AND NOT BUILD_FOR_CCP4) # Lots of code depend on the availability of the components.cif file option(CIFPP_DOWNLOAD_CCD "Download the CCD file components.cif during installation" ON) # An optional cron script can be installed to keep the data files up-to-date if(UNIX AND NOT APPLE) option(CIFPP_INSTALL_UPDATE_SCRIPT "Install the script to update CCD and dictionary files" ON) endif() else() unset(CIFPP_DOWNLOAD_CCD) unset(CIFPP_INSTALL_UPDATE_SCRIPT) endif() # When CCP4 is sourced in the environment, we can recreate the symmetry # operations table if(EXISTS "$ENV{CCP4}/lib/data/syminfo.lib") option(CIFPP_RECREATE_SYMOP_DATA "Recreate SymOp data table in case it is out of date" ON) endif() # CCP4 build if(BUILD_FOR_CCP4) if("$ENV{CCP4}" STREQUAL "" OR NOT EXISTS $ENV{CCP4}) message(FATAL_ERROR "A CCP4 built was requested but CCP4 was not sourced") else() list(PREPEND CMAKE_MODULE_PATH "$ENV{CCP4}") list(PREPEND CMAKE_PREFIX_PATH "$ENV{CCP4}") set(CMAKE_INSTALL_PREFIX "$ENV{CCP4}") if(WIN32) set(BUILD_SHARED_LIBS ON) endif() endif() endif() # Now include the GNUInstallDirs module include(GNUInstallDirs) if(WIN32) if(${CMAKE_SYSTEM_VERSION} GREATER_EQUAL 10) # Windows 10 add_definitions(-D _WIN32_WINNT=0x0A00) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.3) # Windows 8.1 add_definitions(-D _WIN32_WINNT=0x0603) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.2) # Windows 8 add_definitions(-D _WIN32_WINNT=0x0602) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.1) # Windows 7 add_definitions(-D _WIN32_WINNT=0x0601) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.0) # Windows Vista add_definitions(-D _WIN32_WINNT=0x0600) else() # Windows XP (5.1) add_definitions(-D _WIN32_WINNT=0x0501) endif() # Man, this is 2024 we're living in... add_definitions(-DNOMINMAX) # We do not want to write an export file for all our symbols... set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() if(MSVC) # make msvc standards compliant... add_compile_options(/permissive- /bigobj) add_link_options(/NODEFAULTLIB:library) # This is dubious... if(BUILD_SHARED_LIBS) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") else() set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() endif() # Libraries # Start by finding out if std:regex is usable. Note that the current # implementation in GCC is not acceptable, it crashes on long lines. The # implementation in libc++ (clang) and MSVC seem to be OK. check_cxx_source_compiles( " #include #ifndef __GLIBCXX__ #error #endif int main(int argc, char *argv[]) { return 0; }" GXX_LIBSTDCPP) if(GXX_LIBSTDCPP) message( STATUS "Testing for known regex bug, since you're using GNU libstdc++") try_run(STD_REGEX_RUNNING STD_REGEX_COMPILING ${CMAKE_CURRENT_BINARY_DIR}/test ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-rx.cpp) if(STD_REGEX_RUNNING STREQUAL FAILED_TO_RUN) message( STATUS "You are probably trying to compile using the g++ standard library which contains a crashing std::regex implementation. Will use boost::regex instead" ) find_package(Boost 1.80 QUIET COMPONENTS regex) if(NOT Boost_FOUND) set(BOOST_REGEX_STANDALONE ON) FetchContent_Declare( boost-rx GIT_REPOSITORY https://github.com/boostorg/regex GIT_TAG boost-1.83.0) FetchContent_MakeAvailable(boost-rx) endif() set(BOOST_REGEX ON) endif() endif() if(MSVC) # Avoid linking the shared library of zlib Search ZLIB_ROOT first if it is # set. if(ZLIB_ROOT) set(_ZLIB_SEARCH_ROOT PATHS ${ZLIB_ROOT} NO_DEFAULT_PATH) list(APPEND _ZLIB_SEARCHES _ZLIB_SEARCH_ROOT) endif() # Normal search. set(_ZLIB_x86 "(x86)") set(_ZLIB_SEARCH_NORMAL PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\Zlib;InstallPath]" "$ENV{ProgramFiles}/zlib" "$ENV{ProgramFiles${_ZLIB_x86}}/zlib") unset(_ZLIB_x86) list(APPEND _ZLIB_SEARCHES _ZLIB_SEARCH_NORMAL) if(BUILD_FOR_CCP4) list(PREPEND _ZLIB_SEARCHES "$ENV{CCP4}/lib") endif() foreach(search ${_ZLIB_SEARCHES}) find_library( ZLIB_LIBRARY NAMES zlibstatic NAMES_PER_DIR ${${search}} PATH_SUFFIXES lib) endforeach() endif() find_package(ZLIB QUIET) find_package(Threads) if(NOT ZLIB_FOUND) message(FATAL_ERROR "The zlib development files were not found you this system, please install them and try again (hint: on debian/ubuntu use apt-get install zlib1g-dev)") endif() # Using Eigen3 is a bit of a thing. We don't want to build it completely since # we only need a couple of header files. Nothing special. But often, eigen3 is # already installed and then we prefer that. find_package(Eigen3 3.4 QUIET) if(Eigen3_FOUND AND TARGET Eigen3::Eigen) get_target_property(EIGEN_INCLUDE_DIR Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES) else() # Create a private copy of eigen3 and populate it only, no need to build FetchContent_Declare( my-eigen3 GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git GIT_TAG 3.4.0) FetchContent_GetProperties(my-eigen3) if(NOT my-eigen3_POPULATED) FetchContent_Populate(my-eigen3) endif() set(EIGEN_INCLUDE_DIR ${my-eigen3_SOURCE_DIR}) endif() # Create a revision file, containing the current git version info include(VersionString) write_version_header(${CMAKE_CURRENT_SOURCE_DIR}/src/ LIB_NAME "LibCIFPP") # SymOp data table if(CIFPP_RECREATE_SYMOP_DATA) # The tool to create the table add_executable(symop-map-generator "${CMAKE_CURRENT_SOURCE_DIR}/src/symop-map-generator.cpp") target_compile_features(symop-map-generator PUBLIC cxx_std_20) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp COMMAND $ $ENV{CLIBD}/syminfo.lib $ENV{CLIBD}/symop.lib ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp) add_custom_target( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp DEPENDS symop-map-generator "$ENV{CLIBD}/syminfo.lib" "$ENV{CLIBD}/symop.lib") endif() # Sources set(project_sources ${CMAKE_CURRENT_SOURCE_DIR}/src/category.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/condition.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/datablock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/dictionary_parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/file.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/item.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/row.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/validate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/text.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utilities.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/atom_type.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/compound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/point.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/symmetry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/model.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/cif2pdb.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb_record.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/reconstruct.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/pdb/validate-pdbx.cpp ) set(project_headers include/cif++.hpp include/cif++/atom_type.hpp include/cif++/category.hpp include/cif++/compound.hpp include/cif++/condition.hpp include/cif++/datablock.hpp include/cif++/dictionary_parser.hpp include/cif++/exports.hpp include/cif++/file.hpp include/cif++/format.hpp include/cif++/forward_decl.hpp include/cif++/gzio.hpp include/cif++/item.hpp include/cif++/iterator.hpp include/cif++/matrix.hpp include/cif++/model.hpp include/cif++/parser.hpp include/cif++/pdb/cif2pdb.hpp include/cif++/pdb.hpp include/cif++/pdb/io.hpp include/cif++/pdb/pdb2cif.hpp include/cif++/pdb/tls.hpp include/cif++/point.hpp include/cif++/row.hpp include/cif++/symmetry.hpp include/cif++/text.hpp include/cif++/utilities.hpp include/cif++/validate.hpp ) add_library(cifpp) add_library(cifpp::cifpp ALIAS cifpp) target_sources(cifpp PRIVATE ${project_sources} ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp PUBLIC FILE_SET cifpp_headers TYPE HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include FILES ${project_headers} ) # The code now really requires C++20 target_compile_features(cifpp PUBLIC cxx_std_20) set(CMAKE_DEBUG_POSTFIX d) set_target_properties(cifpp PROPERTIES DEBUG_POSTFIX "d") generate_export_header(cifpp EXPORT_FILE_NAME ${CMAKE_CURRENT_SOURCE_DIR}/include/cif++/exports.hpp) if(BOOST_REGEX) target_compile_definitions(cifpp PRIVATE USE_BOOST_REGEX=1 BOOST_REGEX_STANDALONE=1) get_target_property(BOOST_REGEX_INCLUDE_DIR Boost::regex INTERFACE_INCLUDE_DIRECTORIES) endif() if(MSVC) target_compile_definitions(cifpp PUBLIC NOMINMAX=1) endif() set_target_properties(cifpp PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories( cifpp PUBLIC "$" "$" PRIVATE "${BOOST_REGEX_INCLUDE_DIR}" "${EIGEN_INCLUDE_DIR}") target_link_libraries(cifpp PUBLIC Threads::Threads ZLIB::ZLIB $<$:std::atomic>) if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") target_link_options(cifpp PRIVATE -undefined dynamic_lookup) endif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") if(CIFPP_DOWNLOAD_CCD) # download the components.cif file from CCD set(COMPONENTS_CIF ${CMAKE_CURRENT_SOURCE_DIR}/rsrc/components.cif) if(EXISTS ${COMPONENTS_CIF}) file(SIZE ${COMPONENTS_CIF} CCD_FILE_SIZE) if(CCD_FILE_SIZE EQUAL 0) message(STATUS "Removing empty ${COMPONENTS_CIF} file") file(REMOVE "${COMPONENTS_CIF}") endif() endif() if(NOT EXISTS ${COMPONENTS_CIF}) # Since the file(DOWNLOAD) command in cmake does not use compression, we try # to download the gzipped version and decompress it ourselves. find_program(GUNZIP gunzip) if(WIN32 OR GUNZIP STREQUAL "GUNZIP-NOTFOUND") file( DOWNLOAD https://files.wwpdb.org/pub/pdb/data/monomers/components.cif ${COMPONENTS_CIF} SHOW_PROGRESS STATUS CCD_FETCH_STATUS) else() if(NOT EXISTS "${COMPONENTS_CIF}.gz") file( DOWNLOAD https://files.wwpdb.org/pub/pdb/data/monomers/components.cif.gz ${COMPONENTS_CIF}.gz SHOW_PROGRESS STATUS CCD_FETCH_STATUS) endif() add_custom_command( OUTPUT ${COMPONENTS_CIF} COMMAND "${GUNZIP}" ${COMPONENTS_CIF}.gz WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/rsrc/) add_custom_target(COMPONENTS ALL DEPENDS ${COMPONENTS_CIF}) endif() # Do not continue if downloading went wrong list(POP_FRONT CCD_FETCH_STATUS CCD_FETCH_STATUS_CODE) if(CCD_FETCH_STATUS_CODE) message( FATAL_ERROR "Error trying to download CCD file: ${CCD_FETCH_STATUS}") endif() endif() endif() # Installation directories if(BUILD_FOR_CCP4) set(CIFPP_DATA_DIR "$ENV{CCP4}/share/libcifpp" CACHE PATH "Directory where dictionary and other static data is stored") else() set(CIFPP_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/libcifpp" CACHE PATH "Directory where dictionary and other static data is stored") endif() if(CIFPP_DATA_DIR) target_compile_definitions(cifpp PUBLIC DATA_DIR="${CIFPP_DATA_DIR}") set_target_properties(cifpp PROPERTIES CIFPP_DATA_DIR ${CIFPP_DATA_DIR}) endif() if(NOT PROJECT_IS_TOP_LEVEL) set(CIFPP_SHARE_DIR ${CIFPP_DATA_DIR} PARENT_SCOPE) endif() if(UNIX AND NOT BUILD_FOR_CCP4) if("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") set(CIFPP_CACHE_DIR "/var/cache/libcifpp" CACHE PATH "The directory where downloaded data files are stored") else() set(CIFPP_CACHE_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/cache/libcifpp" CACHE PATH "The directory where downloaded data files are stored") endif() target_compile_definitions(cifpp PUBLIC CACHE_DIR="${CIFPP_CACHE_DIR}") set(CIFPP_ETC_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}" CACHE PATH "The directory where the update configuration file is stored") else() unset(CIFPP_CACHE_DIR) endif() # Install rules install(TARGETS cifpp EXPORT cifpp FILE_SET cifpp_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if(MSVC AND BUILD_SHARED_LIBS) install( FILES $ DESTINATION ${CMAKE_INSTALL_LIBDIR} OPTIONAL) endif() # Clean up old config files (with old names) file(GLOB OLD_CONFIG_FILES ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cifpp/cifppConfig*.cmake ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cifpp/cifppTargets*.cmake) if(OLD_CONFIG_FILES) message( STATUS "Installation will remove old config files: ${OLD_CONFIG_FILES}") install(CODE "file(REMOVE ${OLD_CONFIG_FILES})") endif() install(EXPORT cifpp NAMESPACE cifpp:: FILE "cifpp-targets.cmake" DESTINATION lib/cmake/cifpp) install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/rsrc/mmcif_ddl.dic ${CMAKE_CURRENT_SOURCE_DIR}/rsrc/mmcif_pdbx.dic ${CMAKE_CURRENT_SOURCE_DIR}/rsrc/mmcif_ma.dic DESTINATION ${CMAKE_INSTALL_DATADIR}/libcifpp) if(CIFPP_DATA_DIR AND CIFPP_DOWNLOAD_CCD) install(FILES ${COMPONENTS_CIF} DESTINATION ${CMAKE_INSTALL_DATADIR}/libcifpp) endif() set(CONFIG_TEMPLATE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cifpp-config.cmake.in) configure_package_config_file( ${CONFIG_TEMPLATE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/cifpp/cifpp-config.cmake INSTALL_DESTINATION lib/cmake/cifpp PATH_VARS CIFPP_DATA_DIR) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/cifpp/cifpp-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/cifpp/cifpp-config-version.cmake" DESTINATION lib/cmake/cifpp) set_target_properties( cifpp PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" INTERFACE_cifpp_MAJOR_VERSION ${PROJECT_VERSION_MAJOR}) set_property( TARGET cifpp APPEND PROPERTY COMPATIBLE_INTERFACE_STRING cifpp_MAJOR_VERSION) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/cifpp/cifpp-config-version.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) add_subdirectory(test) endif() # Optionally install the update scripts for CCD and dictionary files if(CIFPP_INSTALL_UPDATE_SCRIPT) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tools/update-libcifpp-data.in update-libcifpp-data @ONLY) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "GNU" OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") install( FILES ${CMAKE_CURRENT_BINARY_DIR}/update-libcifpp-data DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cron.weekly PERMISSIONS OWNER_EXECUTE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) else() message(FATAL_ERROR "Don't know where to install the update script") endif() # a config file, to make it complete # install(DIRECTORY DESTINATION "${CMAKE_INSTALL_LOCALSTATEDIR}/libcifpp") if(NOT EXISTS "${CMAKE_INSTALL_SYSCONFDIR}/libcifpp.conf") file( WRITE ${CMAKE_CURRENT_BINARY_DIR}/libcifpp.conf [[# Uncomment the next line to enable automatic updates # update=true ]]) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libcifpp.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}) install( CODE "message(\"A configuration file has been written to ${CIFPP_ETC_DIR}/libcifpp.conf, please edit this file to enable automatic updates\")" ) install(DIRECTORY DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/libcifpp/cache-update.d) endif() target_compile_definitions(cifpp PUBLIC CACHE_DIR="${CIFPP_CACHE_DIR}") endif() if(BUILD_DOCUMENTATION) add_subdirectory(docs) endif() libcifpp-7.0.9/LICENSE0000644000175000017500000000247714746170722014210 0ustar maartenmaartenBSD-2-Clause License Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libcifpp-7.0.9/README.md0000644000175000017500000001670414746170722014460 0ustar maartenmaarten[![github CI](https://github.com/pdb-redo/libcifpp/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/pdb-redo/libcifpp/actions) [![GitHub License](https://img.shields.io/github/license/pdb-redo/libcifpp)](https://github.com/pdb-redo/libcifpp/LICENSE) # libcifpp As the name implies, this library was originally written to work with mmCIF files using C++ as programming language. The design of this library leanes heavily on the structure of CIF files. These files can be thought of as a text dump of a relational databank with, often but not always, a very strict schema describing the data. These schema's are called dictionaries. Using information from the content of a mmCIF file and an optional schema, libcifpp allows you to access the data in the file as a collection of datablock each containing a collection of categories with rows of data. The categories can be searched for data using queries written in regular C++ syntax. When a dictionary was specified, inserted data is checked for validity. Likewise removal of data may result in cascaded removal of linked data in other categories using parent/child relationship information. Since there were still many programs using the legacy PDB format at the time development started, a layer was added that converts data to and from PDB format into mmCIF format. This means you can manipulate PDB files as if they were normal mmCIF files. Apart from this basic functionality, libcifpp also offers code to help with symmetry calculations, 3d manipulations and obtaining information from the CCD [Chemical Component Dictionary](https://www.wwpdb.org/data/ccd). ## Documentation The documentation can be found at [github.io](https://pdb-redo.github.io/libcifpp/) ## Synopsis ```c++ // A simple program counting residues with an OXT atom #include #include #include namespace fs = std::filesystem; int main(int argc, char *argv[]) { if (argc != 2) exit(1); // Read file, can be PDB or mmCIF and can even be compressed with gzip. cif::file file = cif::pdb::read(argv[1]); if (file.empty()) { std::cerr << "Empty file\n"; exit(1); } // Take the first datablock in the file auto &db = file.front(); // Use the atom_site category auto &atom_site = db["atom_site"]; // Count the atoms with atom-id "OXT" auto n = atom_site.count(cif::key("label_atom_id") == "OXT"); std::cout << "File contains " << atom_site.size() << " atoms of which " << n << (n == 1 ? " is" : " are") << " OXT\n" << "residues with an OXT are:\n"; // Loop over all atoms with atom-id "OXT" and print out some info. // That info is extracted using structured binding in C++ for (const auto &[asym, comp, seqnr] : atom_site.find( cif::key("label_atom_id") == "OXT", "label_asym_id", "label_comp_id", "label_seq_id")) { std::cout << asym << ' ' << comp << ' ' << seqnr << '\n'; } return 0; } ``` ## Installation You might be able to use libcifpp from a package manager used by your OS distribution. But most likely this package will be out-of-date. Therefore it is recommended to build *libcifpp* from code. It is not hard to do. But it is recommended to read the following instructions carefully. ### Requirements The code for this library was written in C++17. You therefore need a recent compiler to build it. For the development gcc >= 9.4 and clang >= 9.0 have been used as well as MSVC version 2019. The other requirement you really need to have installed on your computer is a version of [CMake](https://cmake.org). For now the minimum version is 3.16 but that may soon change into a higher version. You should also install the gui version of CMake to set build options easily, on Debian I prefer to use the curses version installed with `cmake-curses-gui`. It is very useful to have [mrc](https://github.com/mhekkel/mrc) available. However, this is only an option if you use Windows or an operating system using the ELF executable format (i.e. Linux or FreeBSD). MRC is a resource compiler that allows including data files into the executable making them easier to install. Other libraries you might want to install beforehand are: - [libeigen](https://eigen.tuxfamily.org/index.php?title=Main_Page), a library to do amongst others matrix calculations. This usually can be installed using your package manager, in Debian/Ubuntu it is called `libeigen3-dev` - [zlib](https://github.com/madler/zlib), the development version of this library. On Debian/Ubuntu this is the package `zlib1g-dev`. - [boost](https://www.boost.org), in Debian/Ubuntu this is `libboost-dev`. The Boost libraries are only needed in case you are using GCC due to a long standing bug in GNU's implementation of std::regex. It simply crashes on the regular expressions used in the mmcif_pdbx dictionary and so we use the boost regex implementation instead. ### Building First you need to download the code: ```console git clone https://github.com/PDB-REDO/libcifpp.git cd libcifpp ``` You should start by considering where to install libcifpp. If you have sufficient permissions on your computer you perhaps should use the default but libcifpp can be configured to be installed anywhere including e.g. *$HOME/.local*. Next step is to configure, for this use the CMake gui application. If you installed the curses version of cmake you can type `ccmake`. On Windows you can use `cmake-gui.exe`. To install in the default location: ```console ccmake -S . -B build ``` To install elsewhere, e.g. *$HOME/.local*: ```console ccmake -S . -B build -DCMAKE_INSTALL_PREFIX=$HOME/.local ``` In the cmake window, start the configure command (use button or press 'c'). After the first configure step you will see a list of settable options. Alter these to match your preferences. Most options are self explaining and contain a description. Some may need a bit more explanation: - CIFPP_DATA_DIR, this directory will be used to store initial versions of the mmcif_pdbx dictionary as well as the optional CCD file. - CIFPP_DOWNLOAD_CCD The CCD file is huge and perhaps you think you don't need it. In that case you can leave this OFF. But that will limit the use cases. - CIFPP_INSTALL_UPDATE_SCRIPT The files in CIFPP_DATA_DIR are quickly becoming out of date. On FreeBSD and Linux you can install a script that updates these files on a weekly basis. - CIFPP_CRON_DIR The directory where the update script is to be installed. - CIFPP_ETC_DIR The update script will only work if the file called *libcifpp.conf* in this *etc* directory will contain an uncommented line with ```console update=true ``` - CIFPP_CACHE_DIR When you installed and enabled the update script, new files are written to this directory. - CIFPP_RECREATE_SYMOP_DATA If you had CCP4 sourced into your environment, this option allows you to recreate the symop data file. - BUILD_FOR_CCP4 Build a special version of libcifpp to be installed in the CCP4 environment. After setting these options you can run the configure step again and then use generate to create the makefiles. Building and installing is then as simple as: ```console cmake --build build cmake --install build ``` If this fails due to lack of permissions, you can try: ```console sudo cmake --install build ``` Tests are created by default, and to test the code you can run: ```console ctest --test-dir build ``` libcifpp-7.0.9/changelog0000644000175000017500000001516314746170722015051 0ustar maartenmaartenVersion 7.0.9 - Using cif::file::load_dictionary it is now possible to load a dictionary along with its extensions in one go. E.g. file.load_dictionary("mmcif_pdbx;dssp-extension") - Fix in compound factory to avoid errors with lower case compound id's - Fix sac_parser's index to be case insensitive Version 7.0.8 - Fix PDB Remark 3 parser - Added three way comparison for point Version 7.0.7 - Set CIFPP_DATA_DIR on target cifpp for use in projects that include libcifpp directly Version 7.0.6 - Fix linking to std::atomic Version 7.0.5 - Fix case where category index was not updated for updated value Version 7.0.4 - Do not install headers and library in case we're not the top project Version 7.0.3 - Fix installation, write exports.hpp again Version 7.0.2 - Fix in testing error_code results. Version 7.0.1 - Various reconstruction fixes - category order in output fixed - better implementation of constructors for file, datablock and category - small optimisation in iterator Version 7.0.0 - Renaming many methods and parameters to be more consistent with the mmCIF dictionaries. (Most notably, item used to be called column or tag sometimes). - validation_error is now a std::system_error error value. The exception is gone. - Added repairSequenceInfo to repair invalid files Version 6.1.0 - Add formula weight to entity in pdb2cif - Change order of categories inside a datablock to match order in file - Change default order to write out categories in a file based on parent/child relationship - Added validate_pdbx and recover_pdbx - Fixed a serious bug in category_index when moving categories Version 6.0.0 - Drop the use of CCP4's monomer library for compound information Version 5.2.5 - Correctly import the Eigen3 library Version 5.2.4 - Changes required to build on Windows Version 5.2.3 - New constructors for cif::item, one taking std::optional values and another taking only a name resulting in a value '.' (i.e. inapplicable). - added cif::cell::get_volume Version 5.2.2 - Remove dependency on Eigen3 for users of libcifpp - Fix typos in documentation - Do not build latex files in documentation - Fixed conversion from string to integer, would fail on +2 e.g. - sqrt is not constexpr, thus kGoldenRatio should be const, not constexpr Version 5.2.1 - New versionstring module - small fixes for generating documentation - correctly setting SONAME Version 5.2.0 - With lots of documentation - Refactored coloured text output - Removed the subdirectory cif++/pdb, there now is a single header file pdb.hpp for I/O of legacy PDB files. Version 5.1.3 - Dropped pkgconfig support Version 5.1.2 - New version string code - Added check for Eigen3 in cifppConfig.cmake Version 5.1.1 - Added missing include in symmetry.hpp - Added empty() to matrix - Fix for parsing legacy PDB files with a last line that does not end with a new line character. Version 5.1 - New parser, optimised for speed - Fix in unique ID generator Version 5.0.10 - Fix in progress_bar, was using too much CPU - Optimised mmCIF parser Version 5.0.9 - Fix in dihedral angle calculations - Added create_water to model - Writing twin domain info in PDB files and more PDB fixes - remove_atom improved (remove struct_conn records) - Added a specialisation for category::find1 - fix memory leak in category Version 5.0.8 - implemented find_first, find_min, find_max and count in category - find1 now throws an exception if condition does not not exactly match one row - Change in writing out PDB files, now looking up the original auth_seq_num via the pdbx_xxx_scheme categories based on the atom_site.auth_seq_num -> pdbx_xxx_scheme.pdb_seq_num relationship. - fix memory leak in category Version 5.0.7.1 - Use the implementation from zeep for std::experimental::is_detected Version 5.0.7 - Reintroduce exports file. For DLL's Version 5.0.6 - Fix file::contains, using iequals - Fix is_cis Version 5.0.5 - Fix code to work on 32 bit machines Version 5.0.4 - Revert removal of CIFPP_SHARE_DIR export Version 5.0.3 - Fix installation of libcifpp into the correct locations Version 5.0.2 - Fix export of CISPEP records in PDB format - Better support for exporting package_source Version 5.0.1 - Fix loading dictionaries - Support for cifv1.0 files Version 5.0.0 - Total rewrite of cif part - Removed DSSP code, moved into dssp project itself Version 4.2.1 - Improved REMARK 3 parser (for TLS in large molecules) Version 4.2.0 - Yet another rewrite of resource loading Version 4.1.1 - Fall back to zero charge for scattering factors if the atom was not found in the table. - Improve code to locate resources, failing less. Version 4.1.0 - Some interface changes for mmcif::Atom Version 4.0.1 - Added a bunch of const methods to Datablock and Category. - Changed PDB writing interface to accept Datablock instead of File. Version 4.0.0 - getResidue in mmcif::Structure now requires both a sequence ID and an auth sequence ID. As a result the code was cleaned up considerably. Version 3.0.5 - mmcif::Structure redesign. It is now a wrapper around a cif::Datablock. Version 3.0.4 - Fix in mmCIF parser, now correctly handles the unquoted string ?? Version 3.0.3 - Better configuration checks, for atomic e.g. - Fixed a problem introduced in refactoring mmcif::Atom - Version string creation Version 3.0.2 - refactored mmcif::Atom for performance reasons Version 3.0.1 - Fixed processing of proline restraints file from CCP4, proline is a peptide, really. - Added code to facilitate DSSP Version 3.0.0 - Replaced many strings in the API with string_view for performance reasons. - Upgraded mmcif::Structure - various other small fixes Version 2.0.5 - Backporting updated CMakeLists.txt file Version 2.0.4 - Reverted a too strict test when reading cif files. Version 2.0.3 - Fixed reading mmCIF files where model numbers are used and model number 1 is missing. Version 2.0.2 - Added configuration flag to disable downloading CCD data during build Note that there are now two flags for CCD data: DOWNLOAD_CCD to enable downloading during build INSTALL_UPDATE_SCRIPT to install an update mechanism for this file - Updated unit tests to work even if no CCD data is available Version 2.0.1 - Fixed the generator for the symmetry operator table Version 2.0.0 - New API interface for accessing query results - Removed bzip2 support - improved makefiles Version 1.1.1 - Now with full support for MS Windows Version 1.1.0 - Changed from GNU configure to CMake. - Loading compound information from CCD file Version 1.0.1 - Changed the way resources are looked up, local dir first, then /var/cache and finally compiled in resources (with mrc). Version 1.0.0 - First public release libcifpp-7.0.9/cmake/0000755000175000017500000000000014746170722014251 5ustar maartenmaartenlibcifpp-7.0.9/cmake/FindAtomic.cmake0000644000175000017500000000277414746170722017302 0ustar maartenmaarten# Simple check to see if we need a library for std::atomic if(TARGET std::atomic) return() endif() cmake_minimum_required(VERSION 3.10) include(CMakePushCheckState) include(CheckIncludeFileCXX) include(CheckCXXSourceRuns) cmake_push_check_state() check_include_file_cxx("atomic" _CXX_ATOMIC_HAVE_HEADER) mark_as_advanced(_CXX_ATOMIC_HAVE_HEADER) set(code [[ #include int main(int argc, char** argv) { std::atomic s; ++s; return 0; } ]]) check_cxx_source_runs("${code}" _CXX_ATOMIC_BUILTIN) if(_CXX_ATOMIC_BUILTIN) set(_found 1) else() list(APPEND CMAKE_REQUIRED_LIBRARIES atomic) list(APPEND FOLLY_LINK_LIBRARIES atomic) check_cxx_source_runs("${code}" _CXX_ATOMIC_LIB_NEEDED) if (NOT _CXX_ATOMIC_LIB_NEEDED) message(FATAL_ERROR "unable to link C++ std::atomic code: you may need \ to install GNU libatomic") else() set(_found 1) endif() endif() if(_found) add_library(std::atomic INTERFACE IMPORTED) set_property(TARGET std::atomic APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_14) if(_CXX_ATOMIC_BUILTIN) # Nothing to add... elseif(_CXX_ATOMIC_LIB_NEEDED) set_target_properties(std::atomic PROPERTIES IMPORTED_LIBNAME atomic) set(STDCPPATOMIC_LIBRARY atomic) endif() endif() cmake_pop_check_state() set(Atomic_FOUND ${_found} CACHE BOOL "TRUE if we can run a program using std::atomic" FORCE) mark_as_advanced(Atomic_FOUND) if(Atomic_FIND_REQUIRED AND NOT Atomic_FOUND) message(FATAL_ERROR "Cannot run simple program using std::atomic") endif() libcifpp-7.0.9/cmake/FindSphinx.cmake0000644000175000017500000000066314746170722017332 0ustar maartenmaarten#Look for an executable called sphinx-build find_program(SPHINX_EXECUTABLE NAMES sphinx-build DOC "Path to sphinx-build executable") include(FindPackageHandleStandardArgs) #Handle standard arguments to find_package like REQUIRED and QUIET find_package_handle_standard_args(Sphinx "Failed to find sphinx-build executable" SPHINX_EXECUTABLE)libcifpp-7.0.9/cmake/VersionString.cmake0000644000175000017500000002314514746170722020074 0ustar maartenmaarten# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2021-2023 Maarten L. Hekkelman # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # This cmake extension writes out a revision.hpp file in a specified directory. # The file will contain a C++ inline function that can be used to write out # version information. cmake_minimum_required(VERSION 3.15) # We want the revision.hpp file to be updated whenever the status of the # git repository changes. Use the same technique as in GetGitRevisionDescription.cmake # from https://github.com/rpavlik/cmake-modules #[=======================================================================[.rst: .. command:: write_version_header Write a file named revision.hpp containing version info:: write_version_header( [FILE_NAME ] [LIB_NAME ] ) This command will generate the code to write a file name revision.hpp in the directory ````. ``FILE_NAME`` Specify the name of the file to create, default is ``revision.hpp``. ``LIB_NAME`` Specify the library name which will be used as a prefix part for the variables contained in the revision file. #]=======================================================================] # Record the location of this module now, not at the time the CMakeLists.txt # is being processed get_filename_component(_current_cmake_module_dir ${CMAKE_CURRENT_LIST_FILE} PATH) # First locate a .git file or directory. function(_get_git_dir _start_dir _variable) set(cur_dir "${_start_dir}") set(git_dir "${_start_dir}/.git") while(NOT EXISTS "${git_dir}") # .git dir not found, search parent directories set(prev_dir "${cur_dir}") get_filename_component(cur_dir "${cur_dir}" DIRECTORY) if(cur_dir STREQUAL prev_dir OR cur_dir STREQUAL ${_start_dir}) # we are not in git since we either hit root or # the ${_start_dir} which should be the top set(${_variable} "" PARENT_SCOPE) return() endif() set(git_dir "${cur_dir}/.git") endwhile() set(${_variable} "${git_dir}" PARENT_SCOPE) endfunction() # Locate the git refspec hash and load the hash # This code locates the file containing the git refspec/hash # and loads it. Doing it this way assures that each time the git # repository changes the revision.hpp file gets out of date. function(_get_git_hash _data_dir _variable) # Be pessimistic set(_variable "" PARENT_SCOPE) # Load git package if needed if(NOT GIT_FOUND) find_package(Git QUIET) endif() # And fail if not found if(NOT GIT_FOUND) return() endif() # Locate the nearest .git file or directory _get_git_dir(${CMAKE_CURRENT_SOURCE_DIR} GIT_DIR) # And fail if not found if("${GIT_DIR}" STREQUAL "") return() endif() # Check if the current source dir is a git submodule or a worktree. # In both cases .git is a file instead of a directory. # if(IS_DIRECTORY ${GIT_DIR}) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # The following git command will return a non empty string that # points to the super project working tree if the current # source dir is inside a git submodule. # Otherwise the command will return an empty string. # execute_process( COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT "${out}" STREQUAL "") # If out is not empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree file(READ ${GIT_DIR} worktree_ref) # The .git directory contains a path to the worktree information directory # inside the parent git repo of the worktree. # string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref}) string(STRIP ${git_worktree_dir} git_worktree_dir) _get_git_dir("${git_worktree_dir}" GIT_DIR) set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") endif() endif() # Fail if the 'head' file was not found if(NOT EXISTS "${HEAD_SOURCE_FILE}") return() endif() # Make a copy of the head file set(HEAD_FILE "${_data_dir}/HEAD") configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) # Now we create a cmake file that will read the contents of this # head file in the appropriate way file(WRITE "${_data_dir}/grab-ref.cmake.in" [[ set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@VERSION_STRING_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@VERSION_STRING_DATA@/packed-refs" COPYONLY) file(READ "@VERSION_STRING_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@VERSION_STRING_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@VERSION_STRING_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() ]]) configure_file("${VERSION_STRING_DATA}/grab-ref.cmake.in" "${VERSION_STRING_DATA}/grab-ref.cmake" @ONLY) # Include the aforementioned file, this will define # the HEAD_HASH variable we're looking for include("${VERSION_STRING_DATA}/grab-ref.cmake") set(${_variable} "${HEAD_HASH}" PARENT_SCOPE) endfunction() # Create a revision file, containing the current git version info, if any function(write_version_header dir) set(flags ) set(options LIB_NAME FILE_NAME) set(sources ) cmake_parse_arguments(VERSION_STRING_OPTION "${flags}" "${options}" "${sources}" ${ARGN}) # parameter check if(NOT IS_DIRECTORY ${dir}) message(FATAL_ERROR "First parameter to write_version_header should be a directory where the final revision.hpp file will be placed") endif() if(VERSION_STRING_OPTION_FILE_NAME) set(file_name "${VERSION_STRING_OPTION_FILE_NAME}") else() set(file_name "revision.hpp") endif() # Where to store intermediate files set(VERSION_STRING_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/VersionString") if(NOT EXISTS "${VERSION_STRING_DATA}") file(MAKE_DIRECTORY "${VERSION_STRING_DATA}") endif() # Load the git hash using the wizzard-like code above. _get_git_hash("${VERSION_STRING_DATA}" GIT_HASH) # If git was found, fetch the git description string if(GIT_HASH) execute_process( COMMAND "${GIT_EXECUTABLE}" describe --dirty --match=build WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(res EQUAL 0) set(REVISION_STRING "${out}") else() message(STATUS "Git hash not found, does this project has a 'build' tag?") endif() else() message(STATUS "Git hash not found") endif() # Check the revision string, if it matches we fill in the required info if(REVISION_STRING MATCHES "build-([0-9]+)-g([0-9a-f]+)(-dirty)?") set(BUILD_NUMBER ${CMAKE_MATCH_1}) if(CMAKE_MATCH_3) set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}*") else() set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}") endif() string(TIMESTAMP REVISION_DATE_TIME "%Y-%m-%dT%H:%M:%SZ" UTC) else() set(REVISION_GIT_TAGREF "") set(BUILD_NUMBER 0) set(REVISION_DATE_TIME "") endif() if(VERSION_STRING_OPTION_LIB_NAME) set(VAR_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}") set(IDENT_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}_") set(BOOL_IS_MAIN "false") else() set(VAR_PREFIX "") set(IDENT_PREFIX "") set(BOOL_IS_MAIN "true") endif() configure_file("${_current_cmake_module_dir}/revision.hpp.in" "${dir}/${file_name}" @ONLY) endfunction() libcifpp-7.0.9/cmake/cifpp-config.cmake.in0000644000175000017500000000040214746170722020220 0ustar maartenmaarten@PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/cifpp-targets.cmake") set_and_check(CIFPP_SHARE_DIR "@PACKAGE_CIFPP_DATA_DIR@") include(CMakeFindDependencyMacro) find_dependency(Threads) find_dependency(ZLIB REQUIRED) check_required_components(cifpp) libcifpp-7.0.9/cmake/revision.hpp.in0000644000175000017500000000563314746170722017234 0ustar maartenmaarten// This file was generated by VersionString.cmake #pragma once #include constexpr const char k@VAR_PREFIX@ProjectName[] = "@PROJECT_NAME@"; constexpr const char k@VAR_PREFIX@VersionNumber[] = "@PROJECT_VERSION@"; constexpr int k@VAR_PREFIX@BuildNumber = @BUILD_NUMBER@; constexpr const char k@VAR_PREFIX@RevisionGitTag[] = "@REVISION_GIT_TAGREF@"; constexpr const char k@VAR_PREFIX@RevisionDate[] = "@REVISION_DATE_TIME@"; #ifndef VERSION_INFO_DEFINED #define VERSION_INFO_DEFINED 1 namespace version_info_v1_1 { class version_info_base { public: static void write_version_string(std::ostream &os, bool verbose) { auto s_main = registered_main(); if (s_main != nullptr) s_main->write(os, verbose); if (verbose) { for (auto lib = registered_libraries(); lib != nullptr; lib = lib->m_next) { os << "-\n"; lib->write(os, verbose); } } } protected: version_info_base(const char *name, const char *version, int build_number, const char *git_tag, const char *revision_date, bool is_main) : m_name(name) , m_version(version) , m_build_number(build_number) , m_git_tag(git_tag) , m_revision_date(revision_date) { if (is_main) registered_main() = this; else { auto &s_head = registered_libraries(); m_next = s_head; s_head = this; } } void write(std::ostream &os, bool verbose) { os << m_name << " version " << m_version << '\n'; if (verbose) { if (m_build_number != 0) { os << "build: " << m_build_number << ' ' << m_revision_date << '\n'; if (m_git_tag[0] != 0) os << "git tag: " << m_git_tag << '\n'; } } } using version_info_ptr = version_info_base *; static version_info_ptr ®istered_main() { static version_info_ptr s_main = nullptr; return s_main; } static version_info_ptr ®istered_libraries() { static version_info_ptr s_head = nullptr; return s_head; } const char *m_name; const char *m_version; int m_build_number; const char *m_git_tag; const char *m_revision_date; version_info_base *m_next = nullptr; }; template class version_info : public version_info_base { public: using implementation_type = T; version_info(const char *name, const char *version, int build_number, const char *git_tag, const char *revision_date, bool is_main) : version_info_base(name, version, build_number, git_tag, revision_date, is_main) { } }; } // namespace version_info_v1_1 inline void write_version_string(std::ostream &os, bool verbose) { version_info_v1_1::version_info_base::write_version_string(os, verbose); } #endif const class version_info_@IDENT_PREFIX@impl : public version_info_v1_1::version_info { public: version_info_@IDENT_PREFIX@impl() : version_info(k@VAR_PREFIX@ProjectName, k@VAR_PREFIX@VersionNumber, k@VAR_PREFIX@BuildNumber, k@VAR_PREFIX@RevisionGitTag, k@VAR_PREFIX@RevisionDate, @BOOL_IS_MAIN@) { } } s_version_info_@IDENT_PREFIX@instance; libcifpp-7.0.9/cmake/test-rx.cpp0000644000175000017500000000047114746170722016365 0ustar maartenmaarten// See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164 #include #include int main() { std::string s(100'000, '*'); std::smatch m; std::regex r("^(.*?)$"); std::regex_search(s, m, r); std::cout << s.substr(0, 10) << '\n'; std::cout << m.str(1).substr(0, 10) << '\n'; return 0; } libcifpp-7.0.9/docs/0000755000175000017500000000000014746170722014121 5ustar maartenmaartenlibcifpp-7.0.9/docs/CMakeLists.txt0000644000175000017500000000351014746170722016660 0ustar maartenmaartenfind_package(Doxygen REQUIRED) find_package(Sphinx REQUIRED) # Find all the public headers # get_target_property(CIFPP_PUBLIC_HEADER_DIR libCIFPP INTERFACE_INCLUDE_DIRECTORIES) set(CIFPP_PUBLIC_HEADER_DIR ${PROJECT_SOURCE_DIR}/include) file(GLOB_RECURSE CIFPP_PUBLIC_HEADERS ${CIFPP_PUBLIC_HEADER_DIR}/*.hpp) set(DOXYGEN_INPUT_DIR ${CIFPP_PUBLIC_HEADER_DIR}) set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/xml) set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/index.xml) set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) # Replace variables inside @@ with the current values configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) add_custom_command( OUTPUT ${DOXYGEN_OUTPUT_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIR}) add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} BYPRODUCTS ${DOXYGEN_OUTPUT_DIR} DEPENDS ${DOXYGEN_OUTPUT_DIR} ${CIFPP_PUBLIC_HEADERS} ${DOXYFILE_OUT} COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} COMMENT "Generating docs") add_custom_target("Doxygen-${PROJECT_NAME}" ALL DEPENDS ${DOXYGEN_INDEX_FILE}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in ${CMAKE_CURRENT_SOURCE_DIR}/conf.py @ONLY) set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) add_custom_target("Sphinx-${PROJECT_NAME}" ALL COMMAND ${SPHINX_EXECUTABLE} -b html -Dbreathe_projects.${PROJECT_NAME}=${DOXYGEN_OUTPUT_DIR} ${SPHINX_SOURCE} ${SPHINX_BUILD} DEPENDS ${DOXYGEN_INDEX_FILE} BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/api WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating documentation with Sphinx") install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sphinx/ DESTINATION ${CMAKE_INSTALL_DOCDIR} PATTERN .doctrees EXCLUDE PATTERN .buildinfo EXCLUDE) libcifpp-7.0.9/docs/Doxyfile.in0000644000175000017500000000060214746170722016232 0ustar maartenmaartenEXCLUDE_SYMBOLS = cif::detail::*, std* FILE_PATTERNS = *.hpp STRIP_FROM_PATH = @DOXYGEN_INPUT_DIR@ RECURSIVE = YES GENERATE_XML = YES GENERATE_LATEX = NO PREDEFINED += and=&& or=|| not=! CIFPP_EXPORT= HAVE_LIBCLIPPER=1 GENERATE_HTML = NO GENERATE_TODOLIST = NO INPUT = @DOXYGEN_INPUT_DIR@ libcifpp-7.0.9/docs/_static/0000755000175000017500000000000014746170722015547 5ustar maartenmaartenlibcifpp-7.0.9/docs/_static/.gitignore0000644000175000017500000000010714746170722017535 0ustar maartenmaarten# Ignore everything in this directory * # Except this file !.gitignore libcifpp-7.0.9/docs/basics.rst0000644000175000017500000003326714746170722016132 0ustar maartenmaartenBasic usage =========== This library, *libcifpp*, is a generic *CIF* library with some specific additions to work with *mmCIF* files. The main focus of this library is to make sure that files read or written are valid. That is, they are syntactically valid *and* their content is valid with respect to a CIF dictionary, if such a dictionary is available and specified. Reading a file is as simple as: .. code-block:: cpp #include cif::file f("/path/to/file.cif"); The file may also be compressed using *gzip* which is detected automatically. Writing out the file again is also simple, to write out the terminal you can do: .. code-block:: cpp std::cout << f; // or f.save(std::cout); // or write a compressed file using gzip compression: f.save("/tmp/f.cif.gz"); CIF files contain one or more datablocks. To print out the names of all datablocks in our file: .. code-block:: cpp for (auto &db : f) std::cout << db.name() << '\n'; Most often *libcifpp* is used to read in structure files in mmCIF format. These files only contain one datablock and so you can safely use code like this: .. code-block:: cpp // get a reference to the first datablock in f auto &db = f.front(); But if you know the name of the datablock, this also works: .. code-block:: cpp // get a reference to the datablock name '1CBS' auto &db = f["1CBS"]; Now, each datablock contains categories. To print out all their names: .. code-block:: cpp for (auto &cat : db) std::cout << cat.name() << '\n'; But you probably know what category you need to use, so lets fetch it by name: .. _atom_site-label: .. code-block:: cpp // get a reference to the atom_site category in db auto &atom_site = db["atom_site"]; // and make sure there's some data in it: assert(not atom_site.empty()); .. note:: Note that we omit the leading underscore in the name of the category here. Categories contain rows of data and each row has fields or items. Referencing a row in a category results in a :cpp:class:`cif::row_handle` object which you can use to request or manipulate item data. .. code-block:: cpp // Get the first row in atom_site auto rh = atom_site.front(); // Get the label_atom_id value from this row handle as a std::string std::string atom_id = rh["label_atom_id"].as(); // Get the x, y and z coordinates using structered binding const auto &[x, y, z] = rh.get("Cartn_x", "Cartn_y", "Cartn_z"); // Assign a new value to the x coordinate or our atom rh["Cartn_x"] = x + 1; Querying -------- Walking over the rows in a category is often not very useful. More often you are interested in specific rows in a category. The function :cpp:func:`cif::category::find` and friends are here to help. What these functions have in common is that they return data based on a query implemented by :cpp:class:`cif::condition`. These condition objects are built in code using regular C++ syntax. The most basic example of a query is: .. code-block:: cpp cif::condition c = cif::key("id") == 1; Here the condition is that all rows returned should have a value of 1 in there item named *id*. Likewise you can use other data types and even combine those. Oh, and I said we use regular C++ syntax for conditions, so you may as well use other operators to compare values: .. code-block:: cpp // condition for C-alpha atoms having an occupancy less than 1.0 cif::condition c = cif::key("occupancy") < 1.0f and cif::key("label_atom_id") == "CA"; Using the namespace *cif::literals* that code becomes a little less verbose: .. code-block:: cpp using namespace cif::literals; cif::condition c = "occupancy"_key < 1.0f and "label_atom_id"_key == "CA"; Conditions can also be combined: .. code-block:: cpp cif::condition c = "occupancy"_key < 1.0f and "label_atom_id"_key == "CA"; // extend the condition by requiring the compound ID to be unequal to PRO c = std::move(c) and "label_comp_id"_key != "PRO"; .. note:: Note the use of std::move here. Using queries constructed in this way is simple: .. code-block:: cpp cif::condition c = ... auto result = atom_site.find(std::move(c)); // or construct a condition inline: auto result = atom_site.find("label_atom_id"_key == "CA"); In the example above the result is a range of :cpp:class:`cif::row_handle` objects. Often, using individual field values is more useful: .. code-block:: cpp // Requesting a single item: for (auto id : atom_site.find("label_atom_id"_key == "CA", "id")) std::cout << "ID for CA: " << id << '\n'; // Requesting multiple items: for (const auto &[id, x, y, z] : atom_site.find("label_atom_id"_key == "CA", "id", "Cartn_x", "Cartn_y", "Cartn_z")) { std::cout << "Atom " << id << " is at [" << x << ", " << y << ", " z << "]\n"; } Returning a complete set if often not required, if you only want to have the first you can use :cpp:func:`cif::category::find_first` as shown here: .. code-block:: cpp // return the ID item for the first C-alpha atom std::string v1 = atom_site.find_first("label_atom_id"_key == "CA", "id"); // If you're not sure the row exists, use std::optional auto v2 = atom_site.find_first>("label_atom_id"_key == "CA", "id"); if (v2.has_value()) ... There are cases when you really need exactly one result. The :cpp:func:`cif::category::find1` can be used in that case, it will throw an exception if the query does not result in exactly one row. NULL and ANY ------------ Sometimes items may be empty. The trouble is a bit that empty comes in two flavors: unknown and null. Null in *CIF* parlance means the item should not contain a value since it makes no sense in this case, the value stored in the file is a single dot character: ``'.'``. E.g. *atom_site* records may have a NULL value for label_seq_id for atoms that are part of a *non-polymer*. The other empty value is indicated by a question mark character: ``'?'``. This means the value is simply unknown. Both these are NULL in *libcifpp* conditions and can be searched for using :cpp:var:`cif::null`. So you can search for: .. code-block:: cpp cif::condition c = "label_seq_id"_key == cif::null; You might also want to look for a certain value and don't care in which item it is stored, in that case you can use :cpp:var:`cif::any`. .. code-block:: cpp cif::condition c = cif::any == "foo"; And in linked record you might have the items that have a value in both parent and child or both should be NULL. For that, you can request the value to return by find to be of type std::optional and then use that value to build the query. An example to explain this, let's find the location of the atom that is referenced as the first atom in a struct_conn record: .. code-block:: cpp // Take references to the two categories we need auto struct_conn = db["struct_conn"]; auto atom_site = db["atom_site"]; // Loop over all rows in struct_conn taking only the values we need // Note that the label_seq_id is returned as a std::optional // That means it may contain an integer or may be empty for (const auto &[asym1, seqid1, authseqid1, atomid1] : struct_conn.rows,std::string,std::string,std::string>( "ptnr1_label_asym_id", "ptnr1_label_seq_id", "ptnr1_auth_seq_id", "ptnr1_label_atom_id" )) { // Find the location of the first atom cif::point p1 = atom_site.find1( "label_asym_id"_key == asym1 and "label_seq_id"_key == seqid1 and "auth_seq_id"_key == authseqid1 and "label_atom_id"_key == atomid1, "cartn_x", "cartn_y", "cartn_z"); } Validation ---------- CIF files can have a dictionary attached. And based on such a dictionary a :cpp:class:`cif::validator` object can be constructed which in turn can be used to validate the content of the file. A simple case: .. code-block:: cpp #include cif::file f("1cbs.cif.gz"); f.load_dictionary("mmcif_pdbx"); if (not f.is_valid()) std::cout << "This file is not valid\n"; If you want to know why it is not valid, you should set the global variable :cpp:var:`cif::VERBOSE` to something higer than zero. Depending on the value more or less diagnostic output is sent to std::cerr. In the case above we load a dictionary based on its name. You can of course also load dictionaries based on a specific file, that's a bit more work: .. code-block:: cpp std::filesystem::ifstream dictFile("/tmp/my-dictionary.dic"); auto &validator = cif::parse_dictionary("my-dictionary", dictFile); cif::file f("1cbs.cif.gz"); // assign the validator f.set_validator(&validator); // alternatively, load it by name f.load_dictionary("my-dictionary"); if (not f.is_valid()) std::cout << "This file is not valid\n"; Creating your own dictionary is a lot of work, especially if you are only extending an existing dictionary with a couple of new categories or items. So, what you can do is extend a loaded validator like this (code taken from DSSP): .. code-block:: cpp // db is a cif::datablock reference containing an mmCIF file with DSSP annotations auto &validator = const_cast(*db.get_validator()); if (validator.get_validator_for_category("dssp_struct_summary") == nullptr) { auto dssp_extension = cif::load_resource("dssp-extension.dic"); if (dssp_extension) cif::extend_dictionary(validator, *dssp_extension); } .. note:: In the example above we're loading the data using :doc:`/resources`. See the documentation on that for more information. If a validator has been assigned to a file, assignments to items are checked for valid data. So the following code will throw an exception (see: :ref:`_atom_site-label`): .. code-block:: cpp auto rh = atom_site.front(); rh["Cartn_x"] = "foo"; Linking ------- Based on information recorded in dictionary files (see :ref:`Validation`) you can locate linked records in parent or child categories. To make this example not too complex, lets assume the following example file: .. code-block:: cif data_test loop_ _cat_1.id _cat_1.name _cat_1.desc 1 aap Aap 2 noot Noot 3 mies Mies loop_ _cat_2.id _cat_2.name _cat_2.num _cat_2.desc 1 aap 1 'Een dier' 2 aap 2 'Een andere aap' 3 noot 1 'walnoot bijvoorbeeld' And we have a dictionary containing the following link definition: .. code-block:: cif loop_ _pdbx_item_linked_group_list.parent_category_id _pdbx_item_linked_group_list.link_group_id _pdbx_item_linked_group_list.parent_name _pdbx_item_linked_group_list.child_name _pdbx_item_linked_group_list.child_category_id cat_1 1 '_cat_1.name' '_cat_2.name' cat_2 So, there are links between *cat_1* and *cat_2* based on the value in items named *name*. Using this information, we can now locate children and parents: .. code-block:: cpp // Assuming the file was loaded in f: auto &cat1 = f.front()["cat_1"]; auto &cat2 = f.front()["cat_2"]; auto &cat3 = f.front()["cat_3"]; // Loop over all ape's in cat2 for (auto r : cat1.get_children(cat1.find1("name"_key == "aap"), cat2)) std::cout << r.get("desc") << '\n'; Updating a value in an item in a parent category will update the corresponding value in all related children: .. code-block:: cpp auto r1 = cat1.find1("id"_key == 1); r1["name"] = "aapje"; auto rs1 = cat2.find("name"_key == "aapje"); assert(rs1.size() == 2); However, changing a value in a child record will not update the parent. This may result in an invalid file since you may then have a child that has no parent: .. code-block:: cpp auto r2 = cat2.find1("id"_key == 3); r2["name"] = "wim"; assert(f.is_valid() == false); So you have to fix this yourself by inserting a new item in cat1 with the new value. .. _splitting-rows: Another situation is when you change a value in a parent and updating children might introduce a situation where you need to split a child. To give an example, consider this: .. code-block:: cif data_test loop_ _cat_1.id _cat_1.name _cat_1.desc 1 aap Aap 2 noot Noot 3 mies Mies loop_ _cat_2.id _cat_2.name _cat_2.num _cat_2.desc 1 aap 1 'Een dier' 2 aap 2 'Een andere aap' 3 noot 1 'walnoot bijvoorbeeld' loop_ _cat_3.id _cat_3.name _cat_3.num 1 aap 1 2 aap 2 And we have a dictionary containing the following link definition (reversed compared to the previous example): .. code-block:: cif loop_ _pdbx_item_linked_group_list.parent_category_id _pdbx_item_linked_group_list.link_group_id _pdbx_item_linked_group_list.parent_name _pdbx_item_linked_group_list.child_name _pdbx_item_linked_group_list.child_category_id cat_2 1 '_cat_2.name' '_cat_1.name' cat_1 cat_3 1 '_cat_3.name' '_cat_2.name' cat_2 cat_3 1 '_cat_3.num' '_cat_2.num' cat_2 So *cat3* is a parent of *cat2* and *cat2* is a parent of *cat1*. Now, if you change the *name* value of the first row of *cat3* to 'aapje', the corresponding row in *cat2* is updated as well. But when you update *cat2* you have to update *cat1* too. And simply changing the name field in row 1 of *cat1* is wrong. The default behaviour in libcifpp is to split the record in *cat1* and have a new child with the new name whereas the other remains as is. The new *cat1* will thus be like: .. code-block:: cif loop_ _cat_1.id _cat_1.name _cat_1.desc 1 aapje Aap 2 noot Noot 3 mies Mies 5 aap Aap libcifpp-7.0.9/docs/bitsandpieces.rst0000644000175000017500000000452514746170722017476 0ustar maartenmaartenBits & Pieces ============= The *libcifpp* library offers some extra code that makes the life of developers a bit easier. gzio ---- To work with compressed data files a *std::streambuf* implemenation was added based on the code in `gxrio `_. This allows you to read and write compressed data streams transparently. When working with files you can use :cpp:class:`cif::gzio::ifstream` and :cpp:class:`cif::gzio::ofstream`. The selection of whether to use compression or not is based on the file extension. If it is ``.gz`` gzip compression is used: .. code-block:: cpp cif::gzio::ifstream file("my-file.txt.gz"); std::string line; while (std::getline(file, line)) std::cout << line << '\n'; Writing is equally easy: .. code-block:: cpp cif::gzio::ofstream file("/tmp/output.txt.gz"); file << "Hello, world!"; file.close(); You can also use the :cpp:class:`cif::gzio::istream` and feed it a *std::streambuf* object that may or may not contain compressed data. In that case the first bytes of the input are sniffed and if it is gzip compressed data, decompression will be done. A progress bar -------------- Applications based on *libcifpp* may have a longer run time. To give some feedback to the user running your application in a terminal you can use the :cpp:class:`cif::progress_bar`. This class will display an ASCII progress bar along with optional status messages, but only if output is to a real TTY (terminal). A progress bar is also shown only if the duration is more than two seconds. To avoid having flashing progress bars for short actions. The progress bar uses an internal progress counter that starts at zero and ends when the max value has been reached after which it will be removed from the screen. Updating this internal progress counter can be done by adding a number of steps calling :cpp:func:`cif::progress_bar::consumed` or by setting the exact value for the counter by calling :cpp:func:`cif::progress_bar::progress`. Colouring output ---------------- It is also nice to emphasise some output in the terminal by using colours. For this you can create output manipulators using :cpp:func:`cif::coloured`. To write a string in white, and bold letters on a red background you can do: .. code-block:: cpp using namespace cif::colour; std::cout << cif::coloured("Hello, world!", white, red, bold) << '\n'; libcifpp-7.0.9/docs/compound.rst0000644000175000017500000000601414746170722016500 0ustar maartenmaartenChemical Compounds ================== The data in *CIF* and *mmCIF* files often describes the structure of some chemical compounds. The structure is recorded in the categories *atom_site* and friends. Records in these categories refer to chemical compounds using a compound ID. This compound ID is the ID field of the *chem_comp* category. For all of the known compounds in the PDB there is an entry in the Chemical Compounds Dictionary or `CCD `_. If *libcifpp* was properly installed you have a copy of this file somewhere on your disk. And if you have installed the update scripts, a fresh version of this file will be retrieved weekly. As an alternative to CCD there are the monomer library files from `CCP4 `_. These contain somewhat different data but the overlap is good enough for usage in *libcifpp*. Information about compounds is captured in the :cpp:class:`cif::compound`. An instance of a compound object for a certain compound ID can be obtained by using the singleton :cpp:class:`cif::compound_factory`. If the compound you want to use is not available in the CCD or in CCP4, you can add that information yourself. For this you can use the method :cpp:func:`cif::compound_factory::push_dictionary`. So, given that we have CCD, CCP4 monomer library and used defined compound definitions, what will you get when you try to retrieve such a compound by ID? The answer is, the factory has a stack of compound generators. The first thrown on the stack is the one for a CCD file (*components.cif*) if it can be found. Then, if the *CLIBD_MON* environmental variable is defined, a generator for monomer library files is added to the stack. And then all generators for files you added using *push_dictionary* are added in order. The generators are searched in the reverse order in which they were added to see if it creates a compound object for the ID. If no compound was created at all, nullptr is returned. Updating CCD ------------ The CCD data is stored in a single file called *components.cif* and can be downloaded from `CCD `_. As can be read in the section on resources (:doc:`/resources`) files in libcifpp are loaded in a specific order. If the CCD datafile was downloaded during installation, a copy can be found in the directory */usr/share/libcifpp/* (if you installed in */usr*). This is a static file and will not be updated until the next installation of libcifpp. When configuring libcifpp, you can specify the *CIFPP_INSTALL_UPDATE_SCRIPT* option, as in: .. code-block:: console cmake -S . -B build -DCIFPP_INSTALL_UPDATE_SCRIPT=ON # ... more options? This will install a script named *update-libcifpp-data* in */etc/cron.weekly* or */etc/periodic/weekly*. This file uses a config file named */etc/libcifpp.conf* which you then need to edit. In this config file the following line needs to be uncommented: .. code-block:: console # update=true After that, the update script will weekly download the latest components.cif file to */var/cache/libcifpp*. libcifpp-7.0.9/docs/conf.py.in0000644000175000017500000000343014746170722016025 0ustar maartenmaartenproject = '@PROJECT_NAME@' copyright = '2023, Maarten L. Hekkelman' author = 'Maarten L. Hekkelman' release = '@PROJECT_VERSION@' # -- General configuration --------------------------------------------------- extensions = [ "breathe", "exhale", "myst_parser" ] breathe_projects = { "@PROJECT_NAME@": "../build/docs/xml" } myst_enable_extensions = [ "colon_fence" ] breathe_default_project = "@PROJECT_NAME@" # Setup the exhale extension exhale_args = { # These arguments are required "containmentFolder": "./api", "rootFileName": "library_root.rst", "doxygenStripFromPath": "../include/", # Heavily encouraged optional argument (see docs) "rootFileTitle": "API Reference", # Suggested optional arguments # "createTreeView": True, # TIP: if using the sphinx-bootstrap-theme, you need # "treeViewIsBootstrap": True, "exhaleExecutesDoxygen": False, "contentsDirectives" : False, "verboseBuild": False } # Tell sphinx what the primary language being documented is. primary_domain = 'cpp' # Tell sphinx what the pygments highlight language should be. highlight_language = 'cpp' templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_theme_options = { } cpp_index_common_prefix = [ 'cif::' ] libcifpp-7.0.9/docs/genindex.rst0000644000175000017500000000001414746170722016447 0ustar maartenmaartenIndex ===== libcifpp-7.0.9/docs/index.rst0000644000175000017500000000457114746170722015771 0ustar maartenmaartenIntroduction ============ Information on 3D structures of proteins originally came formatted in `PDB `_ files. Although the specification for this format had some real restrictions like a mandatory HEADER and CRYST line, many programs implemented this very poorly often writing out only ATOM records. And users became used to this. The legacy PDB format has some severe limitations rendering it useless for all but very small protein structures. A new format called `mmCIF `_ has been around for decades and now is the default format for the Protein Data Bank. The software developed in the `PDB-REDO `_ project aims at improving 3D models based on original experimental data. For this, the tools need to be able to work with both legacy PDB and mmCIF files. A decision was made to make mmCIF leading internally in all programs and convert legacy PDB directly into mmCIF before processing the data. A robust conversion had to be developed to make this possible since, as noted above, files can come with more or less information making it sometimes needed to do a sequence alignment to find out the exact residue numbers. And so libcif++ came to life, a library to work with mmCIF files. Work on this library started early 2017 and has developed quite a bit since then. To reduce dependency on other libraries, some functionality was added that is not strictly related to reading and writing mmCIF files but may be useful nonetheless. This is mostly code that is used in 3D calculations and symmetry operations. Design ------ The main part of the library is a set of classes that work with mmCIF files. They are: * :cpp:class:`cif::file` * :cpp:class:`cif::datablock` * :cpp:class:`cif::category` The :cpp:class:`cif::file` class encapsulates the contents of a mmCIF file. In such a file there are one or more :cpp:class:`cif::datablock` objects and each datablock contains one or more :cpp:class:`cif::category` objects. Synopsis -------- Using *libcifpp* is easy, if you are familiar with modern C++: .. literalinclude:: ../README.md :language: c++ :start-after: ```c++ :end-before: ``` .. toctree:: :maxdepth: 2 :caption: Contents self basics.rst compound.rst model.rst resources.rst symmetry.rst bitsandpieces.rst api/library_root.rst genindex.rst libcifpp-7.0.9/docs/model.rst0000644000175000017500000000416714746170722015763 0ustar maartenmaartenMolecular Model =============== Theoretically it is possible to get along with only the classes *cif::file*, *cif::datablock* and *cif::category*. But to keep your data complete and valid you then have to update lots of categories for all but the simplest manipulations. For this *libcifpp* comes with a higher level API modelling atoms, residues, monomers, polymers and complete structures in their respective classes. Note that these classes only work properly if you are using *mmCIF* files and have an mmcif_pdbx dictionary available, either compiled in using `mrc `_ or installed in the proper location. .. note:: This part of *libcifpp* is the least developed part. What is available should work but functionality should eventually be extended. Atom ---- The :cpp:class:`cif::mm::atom` is a lightweight proxy class giving access to the data stored in *atom_site* and *atom_site_anisotrop*. It only caches the most often used item data and every modification is directly written back into the *mmCIF* categories. Atoms can be copied by value with low cost. The atom class only contains a pointer to an implementation that is reference counted. Residue, Monomer and Polymer ---------------------------- The :cpp:class:`cif::mm::residue`, :cpp:class:`cif::mm::monomer` and :cpp:class:`cif::mm::polymer` implement what you'd expect. A monomer is a residue that is part of a polymer and thus has a sequence number and siblings. Sugars & Branches ----------------- There are also classes for modelling sugars and sugar branches. You can create sugar branches Structure --------- The :cpp:class:`cif::mm::structure` can be used to load one of the models from an *mmCIF* file. By default the first model is loaded. (Multiple models are often only available files containing structures defined using NMR). A structure holds a reference to a *cif::datablock* and retrieves its data from this datablock and writes any modification back into that datablock. One of the most useful parts of the structure class is the ability to create and modify residues. This updates related *chem_comp* and *entity* categories as well.libcifpp-7.0.9/docs/requirements.in0000644000175000017500000000010314746170722017166 0ustar maartenmaartensphinx<5 exhale==0.3.6 myst-parser breathe sphinx_rtd_theme==1.3.0 libcifpp-7.0.9/docs/requirements.txt0000644000175000017500000000346714746170722017417 0ustar maartenmaarten# # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --output-file=requirements.txt requirements.in # alabaster==0.7.13 # via sphinx babel==2.12.1 # via sphinx beautifulsoup4==4.12.2 # via exhale breathe==4.35.0 # via # -r requirements.in # exhale certifi==2023.7.22 # via requests charset-normalizer==3.2.0 # via requests docutils==0.17.1 # via # breathe # exhale # myst-parser # sphinx # sphinx-rtd-theme exhale==0.3.6 # via -r requirements.in idna==3.4 # via requests imagesize==1.4.1 # via sphinx jinja2==3.1.2 # via # myst-parser # sphinx lxml==4.9.3 # via exhale markdown-it-py==2.2.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.3 # via jinja2 mdit-py-plugins==0.3.5 # via myst-parser mdurl==0.1.2 # via markdown-it-py myst-parser==0.18.1 # via -r requirements.in packaging==23.1 # via sphinx pygments==2.16.1 # via sphinx pyyaml==6.0.1 # via myst-parser requests==2.31.0 # via sphinx six==1.16.0 # via exhale snowballstemmer==2.2.0 # via sphinx soupsieve==2.4.1 # via beautifulsoup4 sphinx==4.5.0 # via # -r requirements.in # breathe # exhale # myst-parser # sphinx-rtd-theme # sphinxcontrib-jquery sphinx-rtd-theme==1.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx typing-extensions==4.7.1 # via myst-parser urllib3==2.0.4 # via requests libcifpp-7.0.9/docs/resources.rst0000644000175000017500000000374414746170722016675 0ustar maartenmaartenResources ========= Programs using libcifpp often need access to common data files. E.g. CIF dictionary files, CCP4 monomer restraints files or the CCD data file. In libcifpp these files are called resources. These files are often also based on external sources that are updated on a regular basis. Resources can be compiled into the executable so that the resulting application can be made portable to other machines. For this you need to use `mrc `_ which only works on Un*x like systems using the ELF executable format or on MS Windows But resources may also be located as files on the filesytem at specific locations. And you can specify your own location for files (a directory) or even override named resources with your own data. Loading Resources ----------------- No matter where the resource is located, you should always use the single libcifpp API call :cpp:func:`cif::load_resource` to load them. This function returns a *std::istream* wrapped inside a *std::unique_ptr*. The order in which resources are searched for is: * Use the resource that was defined by calling :cpp:func:`cif::add_file_resource` for this name. * Search the paths specified by :cpp:func:`cif::add_data_directory`, last one added is searched first * Search the so-called *CACHE_DIR*. This location is defined at compile time and based on the installation directory of libcifpp. Usually it is */var/cache/libcifpp*. It is in this directory where the cron job for libcifpp will put the updated files weekly. * If the *CCP4* environment is available, the *$ENV{CCP4}/share/libcifpp* is searched. * If the environment variable *LIBCIFPP_DATA_DIR* is set it is searched * The *DATA_DIR* is searched, this is also a variable defined at compile time, also based on the installation directory of libcifpp. It usually is */usr/share/libcifpp* * As a last resort an attempt is made to load the data from resources compiled by `mrc `_. libcifpp-7.0.9/docs/symmetry.rst0000644000175000017500000001515514746170722016553 0ustar maartenmaartenSymmetry & Geometry =================== Although not really a core *CIF* functionality, when working with *mmCIF* files you often need to work with symmetry information. And symmetry works on points in a certain space and thus geometry calculations are also something you need often. Former versions of *libcifpp* used to use `clipper `_ to do many of these calculations, but that introduces a dependency and besides, the way clipper numbers symmetry operations is not completely compatible with the way this is done in the PDB. Points ------ The most basic type in use is :cpp:type:`cif::point`. It can be thought of as a point in space with three coordinates, but it is also often used as a vector in 3d space. To keep the interface simple there's no separate vector type. Many functions are available in :ref:`file_cif++_point.hpp` that work on points. There are functions to calculate the :cpp:func:`cif::distance` between two points and also function to calculate dot products, cross products and dihedral angles between sets of points. Quaternions ----------- All operations inside *libcifpp* that perform some kind of rotation use :cpp:type:`cif::quaternion`. The reason to use Quaternions is not only that they are cool, they are faster than multiplying with a matrix and the results also suffer less from numerical instability. Matrix ------ Although Quaternions are the preferred way of doing rotations, not every manipulation is a rotation and thus we need a matrix class as well. Matrices and their operations are encoded as matrix_expressions in *libcifpp* allowing the compiler to generate very fast code. See the :ref:`file_cif++_matrix.hpp` for what is on offer. Crystals -------- The *CIF* and *mmCIF* were initially developed to store crystallographic information on structures. Apart from coordinates and the chemical information the crystallographic information is important. This information can be split into two parts, a unit cell and a set of :ref:`symmetry-ops` making up a spacegroup. The spacegroup number and name are stored in the *symmetry* category. The corresponding symmetry operations can be obtained in *libcifpp* by using the :cpp:class:`cif::spacegroup`. The cell is stored in the category *cell* and likewise can be loaded using the :cpp:class:`cif::cell`. Together these two classes make up a crystal and so we have a :cpp:class:`cif::crystal` which contains both. You can easily create such a crystal object by passing the datablock containing the data to the constructor. As in: .. code:: cpp // Load the file cif::file f("1cbs.cif.gz"); auto &db = f.front(); cif::crystal c(db); .. _symmetry-ops: Symmetry operations ------------------- Each basic symmetry operation in the crystallographic world consists of a matrix multiplication followed by a translation. To apply such an operation on a carthesian coordinate you first have to convert the point into a fractional coordinate with respect to the unit cell of the crystal, then apply the matrix and translation operations and then convert the result back into carthesian coordinates. This is all done by the proper routines in *libcifpp*. Symmetry operations are encoded as a string in *mmCIF* PDBx files. The format is a string with the rotational number followed by an underscore and then the encoded translation in each direction where 5 means no translation. So, the identity operator is ``1_555`` meaning that we have rotational number 1 (which is always the identity rotation, point multiplied with the identity matrix) and a translation of zero in each direction. To give an idea how this works, here's a piece of code copied from one of the unit tests in *libcifpp*. It takes the *struct_conn* records in a certain PDB file and checks wether the distances in each row correspond to what we can calculate. .. code:: cpp using namespace cif::literals; // Load the file cif::file f(gTestDir / "2bi3.cif.gz"); // Simply assume we can use the first datablock auto &db = f.front(); // Load the crystal information cif::crystal c(db); // Take references to the two categories we need auto struct_conn = db["struct_conn"]; auto atom_site = db["atom_site"]; // Loop over all rows in struct_conn taking only the values we need for (const auto &[ asym1, seqid1, authseqid1, atomid1, symm1, asym2, seqid2, authseqid2, atomid2, symm2, dist] : struct_conn.find< std::string,std::optional,std::string,std::string,std::string, std::string,std::optional,std::string,std::string,std::string, float>( cif::key("ptnr1_symmetry") != "1_555" or cif::key("ptnr2_symmetry") != "1_555", "ptnr1_label_asym_id", "ptnr1_label_seq_id", "ptnr1_auth_seq_id", "ptnr1_label_atom_id", "ptnr1_symmetry", "ptnr2_label_asym_id", "ptnr2_label_seq_id", "ptnr2_auth_seq_id", "ptnr2_label_atom_id", "ptnr2_symmetry", "pdbx_dist_value" )) { // Find the location of the first atom cif::point p1 = atom_site.find1( "label_asym_id"_key == asym1 and "label_seq_id"_key == seqid1 and "auth_seq_id"_key == authseqid1 and "label_atom_id"_key == atomid1, "cartn_x", "cartn_y", "cartn_z"); // Find the location of the second atom cif::point p2 = atom_site.find1( "label_asym_id"_key == asym2 and "label_seq_id"_key == seqid2 and "auth_seq_id"_key == authseqid2 and "label_atom_id"_key == atomid2, "cartn_x", "cartn_y", "cartn_z"); // Calculate the position of the first atom using the symmetry operator defined in struct_conn auto sa1 = c.symmetry_copy(p1, cif::sym_op(symm1)); // Calculate the position of the second atom using the symmetry operator defined in struct_conn auto sa2 = c.symmetry_copy(p2, cif::sym_op(symm2)); // The distance between these symmetry atoms should be equal to the distance in the struct_conn record assert(cif::distance(sa1, sa2) == dist); // And to show how you can obtain the closest symmetry copy of an atom near another one: // here we request the symmetry copy of p2 that lies closest to p1 const auto &[d, p, so] = c.closest_symmetry_copy(p1, p2); // And that should of course be equal to the location in struct_conn for p2 assert(p.m_x == sa2.m_x); assert(p.m_y == sa2.m_y); assert(p.m_z == sa2.m_z); // Distance and symmetry operator string should also be the same assert(d == dist); assert(so.string() == symm2); }libcifpp-7.0.9/examples/0000755000175000017500000000000014746170722015007 5ustar maartenmaartenlibcifpp-7.0.9/examples/1cbs.cif.gz0000644000175000017500000011047314746170722016747 0ustar maartenmaarten‹Äʳ_1cbs.cifÔ\Ýs⸲ç¯PÕy`OÕà±ù:û@9@&„°@2›skËeŒ|ÇØÛ$Ãþõ§[þ’mÙÀ<Ýë™ÅRÿZ­V«Ý-ÉÞš¡i(£›Uã¤aP7ôO’½%„`a…æqk‡†å¹ož¿—¶¶®¹§$ºö{Ë~3ÛÍO¬“P?°=É;’Úˆ©Ï2Èl†‡}ýÊxKÀ[òü÷¯HÕ¦oÓà«X¶ýµØ8ˆëxÞÁh[èׯ ¨¡JéOè—¸Âò¶”4㛤ßÉÕøþKdž"˲ÒSU¥é›ÌX¡)ú_ÄMt-'³*(S¼Á/¼¢Ñc}j}l ÛµCÛtŒ-=xŠBBD ´–é{ø…ÜIߤ&Q ì†úï ið…¬¡„ʾy.e:+"m¾QËÙœ“ØÜHïB;t(_ð¿Þчf s³ñ釨æÃsŽûæ`¾SãÍöƒ°Tê˜ùÂ5}S{kè«õ#_eyG6ˆbêéj5¯¨­Æ|ÍÆó~‡ãƱƒÍ5žJ@.Ž›Gº­%?MIãàÛ{Eû}äŸ`ì„þÑ > ˆ÷F,ê8GzêÓÐv=pX¦zߨ.Œé;ÉRÛ È”˜î–L§Äv œC’O;ÜÓqZ¡oºA+ÏÉMœÜpåVÂ+5~o¬Êæ§Â_EÕø§Ógn‚¬ÖËçI—ìxQC Â¦n…&IÚôÞ}éËŠZ¡4n’Ž S¸'DzG½-aÐÆ8hr¯¥iڀȽn÷šŽ¶“¿s­š‡ȸ Á ¡C¯¹‘ƒÑõ ÞŠò`IÐÅ7èò<./Tòxm¹ S ˜+ÉìsLCê£:Sçy´çõ†ÓeÄO¦ ­#`:|“=Gº±áŸÒthË0ÔNªÙ6ùöøt£?°é «*ÕF›¯œQòè…!ýBŒ@ã+ï0Ÿ/ä&vrlwÇ­÷…<$•]¾²øíÎÇݘ®ümþøBf¬¶ÏW~·]0é/dóð•¿sêÈ<[껎·ì˜5«(\åÈû4A +é{WT®²(­Òæ¼XQ·ŠÆU–F¥ÃU–Øv3ŸQ¨ÇUÎé'8BTã4A¶}®²ÄvÀM˜b¥*s•%3Q¸ÊÝþmØ$ÓFU¹Êg÷&(VED>žEx{³j‡ºïáÎ0¹j­#uÁ)ç 6Lõ]×W ýe2}øcr7^ß¾NÖ«õúe¹F“¸}x¹›L÷Çúe|·\4F«ÙËÃ÷ p{x|MþxŽw“» ¾¯!µ_ÏÇw“Ùt¶~\ëãñËËh½|y}YN >»HtÃ2Ýÿ¢c²¶Ù‘.¤ÐôßiäXófsÃ;,;#†CåpÖZ®g"(Ý'\±b‡!¸‡îgLyââ F:$ÖìŸÝ‹ûItƒÕÕdÝàĺ›½F7]ƒ‘êz¹ˆn D"³×Utƒ9Ûtsƒ‡ w"èË»øFåÚÚyŽo4®œÛ©l¢pd=þD˜M’›w!F&<’È‹>‹oTž¬ÍõTÕx²'$Ï\ Hð8YÇ7¼ê€ëi›— ­p iƒúLoÚë¶Æ×tø›.Óã”Øîóí ¸ÑÖd£ñ:ÐTN‰Z›“MÓ8n˜ŒßÍbhü(h=NoZU¤¬d}Ÿ’Ì c$X¿&5*×h§Í5ÚÑxÞ,»|M¿ésëð£ñZFÖUø^]^]Þ»¼%B*ž Úíqvûü”p¬1ÅÏnŽ[— ÇÏ…žÆ)¾×á¤îñ–ØëqŠïñôxôeÎ(ú Á(éB_åúÓosºîkœ]÷;\£}^ýïú\p'å6àu0P8õx QÏfÖ€Ÿƒ'õ€·ÄASÕ€Ÿ Þ`˜ÀÝ(™Oª7EnsÞEÖøšN¦DEîò zÙø(rŸóoò ÓŽN1#§˜ùDE姘 N‘Ãt8 N1zœb&(8EŽÛ€ãN1óÊ๕kœb: ŠÊKN1u(¸¢˜IN‘»ég#§€SÌnÀ)¦Ã¨€SÌXƒSÌN‘Ãh™ÛPÀ)r5]ÓKºÍEοS÷\ XTô쇛šýœ ”é„Fà}HwÞ³˜È&®nU5qRu$:¬@mè;Cb p9ŠBTs þRvcy{Œ>¸ ÄäÚ1 ©F£`®{oïUʣ˚iÞ??ês2Zê7‹æ4Õ­já"èu½ÅÐv/ÚAp¬ÜO«GaÒgÅ»¨— ’Œð¢¶ò*MÓË«P¨‘…·gM6„y€º@0 y®µ±!Xþ‰9P:9]p®g€¦o›ÏKÌV;o?73µj§@3´,R¼Î6çùïfÉd.C ô*víz “},~Ç^„ÚyAˆ‚ÕLúª¶R¨Ø\&µ£¾míl“Xžc³áømZƒ…6Sj+:&ÉrmquE¤®”è¶x é=5¹ÌaXI÷h†áŽ~Æ´ªÔ“+IÔ· ëFàaÙÑ$m  % q¶öÛ›ïV%JFaî7¸Zc@²uȳQp‰GKT &^Ã7·vt;.¨sK¨OóƒÆ±ª¨„ŠÎÚx®gí|oe¸ìf8æ‘{ÃÉuƒCq€R²[PO±­¸†{–ç\ˆ q‡¦Jy?àgË7ObU™RòC-ÒOœý$èùÎóË3Š$G²@¤è¸gÞ‚‰çjLêmêPHT|;‹Y¾ý¾7)¶¥ oc%Æt[ø3äÈv¼Ï’²Et;+è’c\ÑB8¦^¿Dk –yr<“™|ð çØ84Ç} KmŽ-a ϤwjL‹R ‹tÁio|˜Nq¥¯HçÒx}€¨L…SÝ &´Æw˜Ö „öÞÌ^ *òóéöènM×:Õ·[5É•]rÒ¸¤îÔÊl—ž?Á˜ÂR‹CXêêã&+†·'r+qâaN{Ö'àLºâªÄÜVcT©ÂY ëzooÌ¢ sñņx ̤ÃÚñ÷A8Ì)ŽM¶Òl,^}t#%Tqn/EêçPÉäªÝl–Ű¥ñf²ÅÏ*s`— Ù»ÄØ*“¨$Å,OÏÿñkíÁŸV­o¶Ý;‡5¨ï g•²f˜Òúè°n*$Ö̯ªÎ 1Ó7÷xêMðãÓµ,Ì Îb8r¶¡jXf@…˜¥q‹¾- ñŽžÈŠŒ¡Àç“Õ³±$â«ÃÚ¬Å$ä@ý8»pL9ÌM¤$[ô»>¦'Ó¹øà\.GÒ/Ŭg+ôÇöö¥¢cSœ´–çûÔ‰²Ë£ˆÜzhŠ·ÅÍÜ‹0…MÉšâéû±ýÄnCYJd×j2Á`c×b‚½Fp&êÃß`>û0ç,ciŒü£mýv¦ûÃ/¦µÃ€,(£k17][¹¤2pȧ†‰ïêþ}>Èe)jvüûo0 0ˆ(é“è&!ˆªE!ºY ‡Õ4.€j %Ñbk­$‚¨‹«š )$Ÿ¶àÍý-8Úm¯…F/µ–CØË¡‰¯‚æ%¾ZØçW$|›åRhNäëZ͉|tkïèÖÇO¼ðªV{’v54’û—Zä¾jïö€ƒX‘ð5©+¡W\‚^.ðÞbÀ.˜¾5—Ç^ ~½ÕàW[ÍN/ÕDW¹óDñ‡DWã÷ÑòuµÖgìã4£õórBžnIéuån´¶ ¯+¯È”èó1~Lg:'£§ÇÅlò'ù>]ß}6k­—ú|ÕÊ¿ðŒä:Y½Î×÷<׎³ó[ù3{¹À¹Q~Z(Wòu"Òd\/Ôo `!÷{ÒÅýëjú4{º›Ž@-³éÈÌŽÇòò '†¢‘¾*§LE¢ˆ>÷ò[vÜ!µôô A]y 'j,%c|“; kæ´E˜ OËuS€ éÏœAÖ`³§xÌÄ Nûè`_½ ã`F‚Ç߬<0íô\Q‰ô¯ç¹ðïáòåéK÷:™Ã ªî†ýTñçˆýlºÁ/ÔDât¿›'á?ѨðM²ªRAz´oGûg€GËsÃŒ' êê±KuõxÄKÜ<Ò°Ó…Éñ²< ž<«!«‹ÕW‹€45" „ìœZµXuµÒSnBñjx§ÕÕ«æ]]Ë1ƒ @’a0ÞÅmÜOf ýßÇSg:¾j=d/ÿéï³Ò´ m?r“c ‚G¤ìÛJxÚEÇw‘‡YiZ† ”vn&¤fžÛ•Ž[PÅû¼fÞ“q'CòéZ´äÅ‚¥aùœƒ^¤(m«‹q.ŒEǾï’#)-‘,]–¢ßüàóµÐÐ;ûr¦^×{ooËLD-S7 è¿Ø§†ÄO×ÂåhÇ¡–«øUAy›hÂr ¿ (ïàgå]ü‚€ ¼‡”÷Ñ åœBy…Ö™Æ*µÕV•×øS1YiÚ‹ÉŠÓŸ§:ãdyÒ_+&«¯Úóþ·³wëÕä6²Dßý+60~i’wòI]u$=Ò å9˜ÑÁ@P[Õí:ÐmJ²Ûž_?¹Vð$S—öÌןvUîU‘d ÆeÅ&Þó¼¾ïy¯<>õ³cwÜ9×7øùðy>>ÕþA,V,HZXþ!I>˜þ|ü)TýOE–ý1„ñ¾ÖÉËŸr©±ÆúCÒ7|ÀšæI¾ðÁôçãO¹Yýý!y;Úo'ÿÝþ|ü)—'«¨?d©ö,¢þåÔL>þ” ·H.l’žãÏÇŸrI×7D}üxÊõ7ÇŸ?åb¯cW¸9Éß•2FºØ—éO¹ °&ÿCR´ÑÍüñçãO1ƒù>$Ãdøt L>þtÚRZÄP“¿àB6ï+O¨-ýg²?ûi‹ùÇï¾ù%ÿ²Ùöˆ_ˆß—Ç/{¾¯ÌŸ{|Ù9~îqY«¿øe— à—¡ÿâWÕ{ÐO?måDÙ_9QöWN”ýueÝDÙ_7Qö×M”ýue÷½ûÞ=>­«$×Îê§OÕÞ=ÿÔÌ«OÔŽ>ÿôiÛIáš ö ©3>$‰Æê§OÕ~>ÿÔÌ´Ož­¿Ûú´¾;ÈN>P?5SîÓvz‘lüô ¹E>$×Ëê§fî}Úvû,#2~ú¤þw¶ò»ã§f¶YÉnž£ìÆÌ˾?ÿ¤.÷•ãœýáÌò˜ØAËן߶:«Ï¾û·ÿãË÷oOÏ*ý].ú¡¶&~ò¡ªÚ?ýйúm9mÈZYerµ?äÿf–ôð×O°þûÝÖÓ¿™•ê_׃¹?ô`tª¿~–àl×.ò÷mõáï¿þñü×+óðW$üž}m=¶ÿ~uÿ侈ö¸dëòщ£~ü76_yØõ‡eÿ~ؾøoõ°ï‹¸ŒÿV‡åáÇÃøoõpì×Κa”Þÿ­Nã« l'+Ùê‡óxÁi«ÿ­.ýaÔ–ýáå×%þûº«è‡ñï?>|ŸêaV4"{Ûß|ùãûw®•9=üÝûwÿþw•Eh^õôÓŸü…žÚwýÂ_p¿êì$Ò/ýû«ÞÁþZ‘ܯÉýÚQš³,É;T«þ ¿äyû+Ÿwêyv–ÁÞ„]å§K?ªæMO“Yä ¶Bø·úouõ»ÿk É€~þ7æw¸~Ño¸_÷ö©qýe¿aû{\ö¼‡ýÕR¹_-•ûÕcå†TÆæ~â7fT:õ3¿`í/¸õ¦ö+øMú‡ïƒï_¿û™ß|òò›O_~óÙöMᣙöü§ jHó}ÅÝcz.Scï¹S ‹žæyëÓ öÞOë˜&€uPï4¨–´Ì[ß šJÄjµ÷ÖçÊ+DB¨Ñ’j=µ×¼õ)P¬ý{Ï»%ÅRhnÊ/S¯%Uz ó±õ­ WrS„m\¾—f{ý[¥‚Uzj늢ž. ùÕ¹¡D!i¸—Q“´\Té©­+ŠzºI Bˆ4Üïí YuEa‹ 謧àf蛃¤ 5’Ø=bS_´`ž4è%i¨ Ió+ÿÞ á^?·¤—7}LÓ"é%iÐÏ•^9ìKØù@s³ï–1ý\MTš–iu·òÜ–]ê¶î¡7ĽLm)]Rž¦Û³Pò´LP÷Ê$9;2A}ô”’P½LÁß—ézo÷‹à¨Â~zÕ¡R®hP%)¸ôû2]@Á§òÿ{´uÍì¹A—××ËÔ™i™î¯Ï~=ÿî3 ×Ц§ÁiIÕ2…ÿ ’~t½#›ãÜcÊ#Úö]*ŠJM’ªeŠ}ö%@`jÀ¯¿%οd±õùx5åÏ̵hÍÖÙw~šýÇh¨Ï‘ûéåú˜æ¨@û4L³¿–{3â^-»T*Eoܪ%Óìo  +Ç^Ñ á^û¶½~1úõ—ÙŸWT;M£€Þ»ó}âAÒí»Ô=¾´õÄØÆt^Q(Ž“4Þs}¯}p µ×¿¢]Æt^Q (È~'êy_¹yCqTIêçµÏeïiw°ô¦‰Ê T©ŸW”Åm u//;¿ý4-AKªV”·/»Ö%÷ÔÜûJ¾í˜{¢xô>½´¤úàCäm3Ð:èuŸ‹÷ÂJ%±Izï1V¿¾:øÐ;c3ÐÆëcLoIÏ(S·ú¬ž(m ù0é)HššJ¹{¯,td®Ê_é oíZV=õqÒÓ 4ܦÖ=ûˆ:ÿÊ7K:¿’<‰ªõÔ§IOPÜëЀô¶vï×w÷Îß—it ¨–4Ozº^‘[ŸÃëß–žï[ŸtÑÓ2éézQ·¡©¾»_Ÿ†¥¼þu-’*= פ§ è½0-AMɘ(×>pÄ- JOC]QÔÓô6MÇôrèsKZ¨- ¨ÒÓЬ>{’Ô$1Ïï½ú–´´[ô½Ö×Wzêõæ é}±´Pœ¬(b›(»€~¢ ´Ð¬¾ƒ¤·ºßG¯¼Ö1½mVßAÒûõ/½YvþlBÓ´€~®$­+êó£¤ÎÒè ·I…¿¹Ò+‰šÍ JÒzF}|S´Õ¥%í¸IÇúú·"Øôc%i™v) ËK(Ùú‚ÌþæÀpÖ׾㘶>Rë.¯i—Ú@Ù°®p{r÷ù7]yBT z—ŠfÚ¥6Pçy´÷ùpÿͰOWP-©v©ýõ±Ú“˜=÷õ×#š‰ØTïRÑM»Ôj¹”8¦@“i¯sXKª=h~Ú¥6Ў΋ÿ„qý4åÁ7ª]*Öõúêï{£Çó¾¤Ü }EÝl—1U´XWÔ›h1½-³{UÞ¯ßÍ4‹¤ohµú>?ƒFº;\áì»X÷S-Y멺œ¡÷SW~Éì‘~hœ}k©RÇ¿ì€m“æÎßú¦mÊ_&å× 7LºW$½ï:Øã8ÉT+š}}›¤é>œŒU7( ˺¡ä¸€*IÓìë[@ͽmò~A»¯ï6]Iµò§Ù×·½¾§÷ÞùKâíÇ-'I•ò§æë3;è=P~\RÐïí•¿&wGРÍgTj+Êg?ROCUxï%ýhÞOS[QI±ÁóàCÇh^›͉)9ªs?;¾j®¶õ%îüXp–~F‰Jµ–~«ž¦Ù×·€Â0¥K­oÐØÎýT ´ºèéìëÛ$ÍF\ò—LÔP~ɱž@µ¤å §]Ò€‰º÷<Ó<ìÓTëi¾z:@1QNn>ùUn¶<YKªô4›ƒžŽ‰º¼H긡d;Î(£A•žf{ÐÓ Šh ƒ\ å}ÿ‹COYh1ƒÎzšÝAO»¤ŒAÀ e)·7>³¼¾ÒÓì'=­Y7Œ(pí‡È1õ÷íäžýË ³G&ªv›\õ4‡IO7P05ßb¥[RÜÑBW©tiP­§9Nzº€&z{à/jƒq;1 ¨–4Mzº€FÙOýã¡§WÛP ¢q tÑÓ<ééšy×cï¾E[ð*÷ß•Iµž–ɘØ^¿`™ÂˆŠ¸ö—’ûìóÜŸ@•1Q®É˜Ø@­ó°ᾸÇÔuÒ­ ¨2&à âíäøúæ6´3×g¿/ÓÛöZ¥”1QêŠúü +n‘ôr ´Ô`ìT¥Þ£>6'Ðxo·‰¡.ú¼Œï þÒ’~<¯¨RWmþ}EÝzé‘€¤¡¿þý­ óÚ/³g¢¹:Åá~OT’`,¼ç·¤¥ùPrÓS{öI—Ù3±€âV†>±÷†B?¢kFï=.FƒêeZfÏÄj%í}³Wì+ÊçTK:{&¶×çeÜݦ i6\Ë2µgŸt™=›¤Œê:oIKÛOá°Z$Õ!N8.VŸt½:¼p¤õORj;ÿý$-©qÂq1|Òÿ]@]½Œ|Žž¤Ò\H}“v­ i âE†OZz9M‘-À›OjwÓRoÑt p»Ù'­@/¥7hF²°©BÓ¸€.’úÙ'­@-cðHÁî\æû¾ót p_aÌþöúöémýÜ7txçÚ&}„]$]f?ŽMz›( I‘ÙʼnÊÍêCˆ*iÐeöÓØ¤ÐÈØfݤ™ìÐv©,Ièk ÊÊOéBÚ@áå*\󲱃2 3©r!¡+AíšèBBÊEÔV¦­î§vUQsH±Œï*åèÕ6¼£¥<üR¢üþ<@uÄ<¨ ·Ö_4zýÅ›Ïeû~jÐEù݃ÔÜšé°ŸæÅAẞ†t‘ÔíÁƒ zønÒðyÅn ‰ÿt€.Ê¿Xƒ´ llï£Êʽ·KjÝ"©V~öàAeê…áŠ2p|¿T^@µò#ib  I1˜æÞ“dÙ) “ôëëì“öà€b_ºê#òýà‹ÛD-Ù&ïÙ&T•B¢2œ½ÁõË™•­/Ó'5¨ÞùmÚ³;ÆDÅ$¡xÏÌÉkäLÄkÕ’æ=»c(?Âq÷Ð^ÌòÍxôs=Qå`KÅ J­¿—Ò:‚x¨«¤rDÇ[Ê][ª†ì$Ç…ÀÕv~l±Y.ËÔ™ƒ-ëDÙÄešJ’ Yî’J®d|°¥œ=ØR±î§á¢¤¾YØiã£]–©s[*ÖÙOƈ£›7ô«Q÷ø–EÒ%YÐl©juÉáªw«Q³P \@õ2uá`KÅjõ9dufQ‚ë¶ŸZØ8Ôel)¶TlC["ý3-û8!Ì£ÆT¯($Ml¶T“ôz;¹—Âü® šëŠŠ¶”ËåOm“6ܽ¨T4Ó2¥ò§'å/åOõ4e˜#Ș"ÓgäöÄ @å÷×Aù’"’(Åá”eð`Õ’zsP~M‚™ØFIÃx} º(¿·åo¯Ÿ oÑ.Rù‡[.IÊ$©V~ïÊŸê2Ío|G’.›J9¸zè’)ëÊŸšÑ+©÷ÑÒãYÚ& ßÚ¥AµòûpPþʤÖûõg?7 YòV©V~ÊŸêÚ¿ŠlOòe[|ß™UÒEù}šâ¦o*h®*=g?‹vùaJJ‘@nt‹òû<ÅM5hÿN íVãoΙ «ò—)nº‚º‹A®ìyF™Ð=6- ZÒpMqS å>"™#¨j°]ÒËkÐEùƒ™â¦«¤ÅñŒŠ’„lC÷ö¤UR­üÁNqÓUÒbyœÐ0ͯL>rªL ZùƒÚzoOF&J†6„áêdNﺤ‰ûa m¯o,±PºÙc%£{UzÂAOKÕS'Îduâ6§p%-zâAO¨Ef×%™—þþ)³Gƒ.zÒAOK{ýÛÁf•xCÏɳÇjÐEÒ|ÐÓÒfß1U”KÉŠí«lþò¤§å §¥nÒQÜ‘œ¯C¯’^FKªõ4^=-u“F¢0<‡Q’<̘(¿¼¾ÖÓhzZêâ% ˜u“Ñ»Jªõ4Úƒž¶×Gòµ•€ ²ÅÍXQ~™ý¥œÁíy(’c„µïi¡¤Â`é)#^²:íuÎC1Ñïy(Ô[*?f±1÷ Ä[j¯@=aÏC© ·*7¾ !”ìFÙ mþ t‘4îy(”Óë^‡ºÙÆ4H>ÿ]ô4¦=¥‚f9•ˆ(ÁÂnŸ^¯®EÒEOóž‡ÒAC±rþ9xÍ0R툎z¢V=-{J-‘6"HDHÔÕøþª‰tíy(Še¥F¢ÀqààÐ ÚúFƒš=¥‚ÙBpDÃ9‰ _åqªôT_#ánÊ_y¼^$Ë ³ðNžê…¬% âo8¦¦q‚­µQ²K¹s… jó&'Âjj9¶'„ã\_¦´ú誧Ó=jEÐÄ0Ò‰ô¾´)“Ϩ–´\{…Le)S¸Í‘€[žm§©)bK ÐEO‹9'¾½È>†{ÊHPrZ¦‰Ÿ8NŠ='¾ÙRžž-ŒA¯®Rt ÐeL‹;'¾]$îGv†_*^íÜï[Ÿ8NŠ?' 4Þ#üÝžá"¸Ûë³4t€®cÇI—T’Ïè•´¬ïùR‹¤zí—x8NZèÈß‹áÓÀ­Ï\½H ŠïŽ“’ÇIŸ(ɲ8ªî3*O/ƒ\3¨Z¦%ªâC·úDRlœ S¶[( qÓ2ùúPI¶pëz¤Û»®üIÊmèRÆxM¾> JgWf²ü-#Òù›ÕÇ)Ô /ÔìqSÛÂ.qöaûÞÆ*’úÚ t­6ž|}Ûë[GwLÃÈÛÔXûFKª«¯É×·½þUèÖuÁ¼Hím5Ï-‘ TWל ]ß^i¬ÈźW+‚y€F=Q:nj¯p¨Šo¯w$2œn3÷ô6¦8µµ¤:nj¯xØúZ˜Ãžû%!m*¹i¢è“Æs§­ÏÖœ‰ ÷M7°ÍßWÔe¢XÄ:@W=͇­¯E$2NSÊ0åF^“”Áƒ t‘´¶¾æçÇ`Þ_éž–Ìü摈q)ÐEOÍuØúzì§)bù%ðlé×ȤÇt­Š7‡­¯I ¿1Žè;°F`èéòújë³5gBo}m¢à‰‚Ãì ³žÖÚÈT©”™<Bzi^lsÓ…YЕX.Óg_Îýû9ùE¥Ìä™X@oõ̺FIO¢bd zº¨”™<(’˜Þ‡lœ«”IÒt‘tòL, %AL˜• ¹y?Í tU©É3±"E U·.¾À‡ÞUÊÕ ïtQ©É3±–$¢e_P×& 9ÓEƒê­gÄg/Ÿ½>OT"(*¹<[wÕû~’ÐÑýLy&¬< hdJ%pï½Q;MQ&ºLÔ j7¾Ü¢æY¢„ ^aØR±ž¦ kíáÆ×@'ØuPla†³K–éCDÂZw¸ñuIqšFI>3¯’ ,`· Óï7¾ ´ûõqñƒû8¬·è‡ˆ„µápãËõ8A-‹Îí^s*sËCDÂÚx¸ñ5Püq^˜E̘(«ABt¸ñåvî£|óº±yåI­âÁ´¬Aõ]s&ô/·œ‰{)¹'q¢F±uãíyˆHØ9g¢¥4K.h q¸{Líx}±¥Ê¹HÀÎ9¨—s?XQ©îíÉÂ13@=s&P\Kä%Áƒ«‡9j!ÛºHj÷" è;‚ºfFÍállF¯Ü÷˹HÀÎ9Ûë£Ô›§£J•~ãË(áV’j=s&Ðû¢/œ¸œa¢r§iÔ ZO眉 Ô¹Ò1 §í…ÓËhP½Ÿº¸ Ø; ɈHô?g­Rz?ui/è’^¦% ü5@åÆWÎE¶æL¨" jɃÆl}Ìn¼fºÂ %Õz^×Ënÿ©ÂÖ‘1Ã7ž;ú¥â!ÂÛAé™°-ц‘+ÉĶºLT/!Í-]Œu­t‘ôáí ±8‰Ge¡—d•rð¹¿T³G0v‘t™¨|ZQMÒ„µo˜Ø†ÈÎTËáºLT>­¨VÎP$!g'*5ªŽÈ|=Iª'*û½€Ý¥¦R™ž p!ñ†>$¥ÏIU§¥Aá‹EªeìÕw)ÙúÒ©jžVÔŠT\Í¥*~¤ŒDáD˜@I§µ€"N†ù1ã£Ü–•±t¨iEm ḨgBο<$-t¨²°WP„´9CÉÔ×ïûQ©.`Çî™ÈmLC³'JeϸGÉ.•<åä™Èõõ£€"Y‚G•ícÊ4ñºRêž<MR¤‰ã4…M&:˜šÕ9.’ž<MR#´š Là]ÜÇt™ýròLè}h\ŽsE°­>ÊIìd’TÏ~9y&rýr‘VyðQ6óS˜4è2û'Ï„€zò5X‰C’¼Ž©‹B2jÏD9y&:(²ån¯³n{}—… jÕž‰ròL´1Eê nè’-ÛÉ™˜ôT{&ÊtFµžÂƇ›ndr а»®ÜÇ®œ;^¸k:£ÐLRÐàkSê,#•õ~€.ì·×tFm’biÏô`—߇$½†§—$+“¤ZOýµ_x}# ’‚‡¼>oØ¥§% ð¾?êMºæL¼>ƒ&ÆMãmD±…ÎèÇÇÞ ToÒ5gâÍÔ±s ïhcZšgÂU†ö ôuû…w€æ ¥L•êÙrÎË-zÒÓÏ5¨Ÿ-I÷=MÜË嬰˟è`È1ƒçäwÖ:a¶Ph–|~T:€æ)9º(¿³…²€Ò{àH¦ŽÞq ( ÉʺHšf eEDΓû÷Aêƒ+(«è¢ü>ÏÊúúEÓÜû9½!Mñ$é¢üe¶Pè-OÌÍÅë°öÖ …åa¨Vþš3ñúJy¡£åFèˆ5¼¨Vþ`f e}}©‹N÷ì…Vºùúän:ÆT[(A"¼ÕBQ QX2 [=òúRW©¤Uêm¡w0ÏcÍ‘*û›ÆnJfÉíñüR.øƒyÞ@ÁÊMž¥{ö# ðT^ß]ûG…ƒy^™ã^ ÒŠÑGf¥´t±k]$ó\@½8øï×GÓ05M½¤ƒÐ]”?¤ƒyÞ^Ÿ¨ÀR^…´‘ Qâû“¤ZùC>˜ç±ê)â0ýÑ=, }OMmòËë/Ê_æy½Š¸;nëÇç5G³O¯Aµòל mž·×¯Ž˜þyâURôÕ¯¿(4ó¼Išc·y¨k±“ÔøT)ÿœ3Ñj#}j*%ŽS\Î0Qv$µ²”ɧ‡æisÎÄŠƒ(tzhçîÈÕìIçÒP7çLl ðìcë»Wˆ{C–Üž t‘4쥡Ô Û¿Ð6ÜãhÝHmºôë/Êg_ßgÒC¦Ñ%Së¹ÐyF¥0EÍùúù¥þÎ:¦³¯Oƒ"ÑŽÇ ˆgï­Ï÷ÈYÍç ë˜Î¾¾4Áì±, ÂÓ€\#è"éìë[AQ· “ʱúF"ë£èÚnöõiЋÍGaó|ƒ¥M’ê ¥æL|´ƒ¢Ë’˜‘hÎã$·3Ч¶ÕJÍ™¨u|‹¤å""†^ d›AÎýñúúv’$UëøfÐÌ^?<:¥oo£AüT­ý9gâO×nŒ¥êi4 ±Ý–rB[„çäw=s&6Ð,9½œx7yÐjrË$颧5ÂkvP#}Îy´oÐâ§r¯%ÕWóš3Á«ùê$lœHÌïåÔ%Õ³¿\Íóä=o áj铆GÆ£§éS¦á๥.öö9Ó t0Ì’çÕ<^“Å*ÐE¥òä=ß@³—#úx—§¼Kü§è"éä=ß^ܬðJJƒÏ©iátQ©”05Q¡Wr’T«Tžê£vPCÇ t žZè§AõÖ—§ú¨íõAÈ´ljr#xÀXôª·¾<ÕG- ‘w±#_â•Lƒh.¤Të©X}oÎ’ O/Ž\´wí§i¥›@µVjÅáYRW¤:<³_ôD IµVs&>?NTeû™Ým{ßHLTРú¾_&«ïOÿSÖ~0íéY.—§ý´rtã9ùe™–ÉêÛ@QÅÂ\,ö8,½#[å— k{×Éê[@3[|X!@CC×ÃÆÒGb]$¬¾å¿ÚEËÜ4VˆQ5è²LË”)»I nrvÑòÒÜw¨TÐcº,ÓÆ3q”ÿ$½‰Êe_Óp&P½LKËt“´¸"}”,^ßÙADŸôª—iå™¶™ v9\ò™Tú˜ý4¬¾k™}ÝÞµæL¼9HZؘ€i¨>é8•12j>ªeêkÎÄ›ƒ¤Øž+n¹‘H#ù:® ZR;–é ¢(&#eJÚÞ(é·¨Z¦þ«ïã¨%/3Ò¦œc+R7œ]Ëëã÷gP¿d‚­³O«/JæÉ{ÞÈÔñÜ) ã¯Éê[@ 3ùßÇíosu°ÞÓÜ KËÜk²ú4(6R‰‰p7Pp. ‹¤iÈtI‘øÄVlÒ‡wŠHÐæ KËÜk²ú¶1Í’„•;_mLyßÕ’êÖÎWÙ2¡B:|={3šž4`Ö×WVŸ7“Õ·½~’„!šâõÓØ¤]Y@•ò›‰sEEX~©Èwì¯/Ð&PÝ/ÚØ= Z”ÇIÏ<ðð8'uB*½ŽðÜ) ãÍtF- (Ž(ÒÛ1 ¬™§:Qƒ.zj¦3jÅ~ŠŽR1Qw~¿.›´Í{>¿œ•Hnñäî€C ¯ßj#Ù“  áœÏïí”…´€FR+Àê‹ÒÝvº›Ò×7@•rSÒšØy(Ù ƒIS¡_›@µ¤Îìùü4 żŒ¬èíÇIUþpÎç÷nÊBÚ^¿€ ¨Ð+‰Pq—3ò¡L’j•rnÏçï’"´‹Eï£ÔÈ7_Ÿ“*ÎIR½Ÿºƒ¯/´ºô‹†ˆRŸÇ†"½ñÜqCq__-‰iÇY¼sqj›4è:û__he7µû´•|ŒÞ-<7S2>l(îàëë’¢…Úñ: ¡´×g€W®³ðõuPš’™ac¦õ××µiê$é2û__¤ĘÚÒìv7]&jÙPüÁ××A£”0'IA¿ZòuÚ@— şΨV äDO«™jGFwÓô RþtF5МŒT߈Eéýrð¥•ò§3*U•Âátø`a–} [êZ@IOgT-rƒ€Û¨eåõýº¨”?Q­@Ëzz‘6™žÚ$í*'IµJùÓÕê£è޼d¯¾F?¾Ö‡w’tQ©ÓÕªŽPëì$Ø3¥Šf-é¢RyO¾“ÖJ¯@¤ÇéúEB:^à¹Sòµ÷Óµ€ Cá-0I/v#ÖwÓ|N¾öa:£ÐÀ nª”—¨œí·“´€jIƒÙ“¯+¨'Ÿ& ÓÂHgp¦m(e]T*LgÔöúQø¥lmð9Z“Ô»i>'_ûÊ3¡’¯ûDÁ4cFº»û)÷œI¨V©Æ3aO’‚ù [@¹¤7‡_ô4Ÿ“¯ýÜ›£ñö„F…0|(RÊdÇÝ4˹ÿÀ„åçÞ (ÒÔ³IÅa;÷»Õ÷À„åçÞhöNj…kª;a|]$òúPOFa¬ýK¨»¦¬NÆ£èªRS^ßj™&Î}ÅHÆè  å ËϽ9PCN!P4Å"ÆúHÌZÒåÙzs@/f ±Ÿ»pW¹Á'–1Õ×Èš3ñfe¢õ”d͵`½›²ÚxR)}¬9Ÿ@³T\tÉ3.Ó¬>+1¾IR}Œ~g ·ý‚Ó4ÊÁ×ò¤±a-©bÂeòÆ„Zd³‚SVŠ:beŒL›AÕ2s&Úq¯ªüFV¸/QÜÐ=FªŽðÜј˜s&vP˵ÂÖ”9htY¦sÎÄŠE/)œ5|z‘¤%]–éœ3±€^¼HÜ;?®Úló}õÅxÔ]–éœ3±½>xµŠùBaÓ¨;1‹¤z™&³ñêm)'שі¨æóO’ê?ÙÝ˜è’Æ{ ‘fJ.)רäbúí$©Þù“;¨TwÉ·"ž«˜õ‰‚žâ¹£J%P©‘HböÀË?âT D6œº¨T •êa'–´‘Ûää“ÎZÒE¥R<¨TÈÈÝ4$öêç~¤»c€®*•*ÕCG‘±OB¥&†vÖšO’.*•*ÕÃqÑ5›‚îŽ:=5O*U*Õ$Í™®NäçÓ´l8f‘T«T¾öTÑØýü‰–4<‡¤\Y1âC%—ÏSUüŠÔ{Ü÷ ÷êÁ‡bëì?Trù Z-³ÌZ€Î¤U ¨’¦ç”~«@…ùî)‰ïwg×úé:óõ­¯Ÿ` ]r$é¥Ò.gIKú{ :u ]@s¹àóÊE1Ú~Z9e'Ð4¨ÛÛhtP8¤p7LÃéÝÖ® :ý6Ôœ ÕF£ƒâ: Ò•8QÓåŒi“J©ä–p…=W2†:û¨IBDâbÒ Ö«…l1œs%Ã5y&РÞu;&wr™æ<‚,¹›@•Í*Ï„ È(îu†‘t{sSÅ!¨ Ìª6iw¨8ŒYÆ]rïeî¹3YîQù\qÜ¡âP@=YßÔr—ÁXÉí ‹J¹CÅ¡€ÞGîí0ñŠ$_o÷¨|®8D”a«8PËH”röK…Ú›Cû¥*(ïí$ÿe%Wi¾>ì†N©¶új΄öKÕ×GWwiNAJÛü§Èøó ¨ÚP|Ø•_’l³ã…ÿÛböÓ€—#Ï•ßÇ]ù(šp1¿ Ás”0Œ–ŒFÐEù}Ú•_@=½<S+iâSÑYE'ÐEÒ¼+E‹t£Ë/ÇaŸ²Úx€®Ê_våPËVäþtNŽ¿¶y›$ÕÊ®]ù(Ùy7•\¬ÜLI[)u'P­üÁìÊ/ †t}RÎ%&u³'- Zùko¥üzÑ$¡ÍŸ‹p­4§lî Ó˜jånWþ&é%®‰ˆŒ!¸P¥üÁïN„šYÎëUÊÓ»{Ù±K‰žš'B»A@#{â—bŽ ¢ýýÂè¢ü!îN„šñ—6·‰ralÒ4Ð&ÐEÒ´;(ëF:ª¹fP•Ò@ ‹ò‡¼;4ÕÙϬ²vBŒÑ 4«%]”¿ìNöW&žzÉwÃ@c¾Ôª•¿æL('Bå®…ÁÎæ-eÕN„–3a7P¼ä%¹ç¡’UöZé…5ƒ*=vw"h!—,3ð ýü©ôb–äP½¢jÎÄ›£¤Yº†"Ê¨æ ªTKêw'B%h á#g=³KH#&P}Ч3ÊŠJQsæèèË´Ö劉Àaˆ§3ª‚f\²@×—é¹’¨ï, tY¦ñtFÙ:Q¸¸Ô1¡Ý…Q(§éCà0ÄÓeë˜'í׌‘¦ŒSŒ/*Ðe™ÆÓU_Ÿ«Ý!$—F÷°k‘T/Ót:£l½FµÎÌ.f6Ä«î§ÃNgT»}¼¤T¢Q¾îüÃNg”$‚²”góÅŠ¦žˆ8ë1Õ+*Ψ*iFPÕ‰ùÃRÞjõášnPµ¢fž‰êD¥båw’-n' …¼=xî¨ü3Ï„ež¡L{íœëG7CœtQþ™gâÊ¥:WX?_ÉÕ&ÐEÒtP~×”?0Å0S{a°—ü]”æ™Ø$MBÎä3ä̧)Cƒ®Ê_Êß@Ùž J¯?T-¹=¨Vþ|”¿–’Ä×G=Š‹ìüîAù³9(¿«ž^ö ÌÅ‚MáG˜ÃêÙ_”¿öæÐÊß$­< M¡¤›xZ@•ògÅ.&¡£Ôb|(5d½œGŒ^¦FO¹;#ÉqìRŒFN’j÷q™:²- —W Ó³g~)Ù¤Ã9/̽9šO:5ºB¡9“†iò¡POã9"ÝÛ"©ÓJSª«¤ÇLQ Z¥âÜ›c%=cÑìVfî-éIí‘HÑ-\y.Vs”©¡˜’ñ‘ˆsoŽMÒ\~#݉®¾ŸÒ-7Iú{ :e!- Y:|A<@ót;I ¨R©xMYHûDéÉU¤Tgk~©xŽHDÕ›ã3!WK‘i`Š‘NÝ@ãD¥3__T½9VPä‘ >ñ•óP)¯AW•:ðõuP/9h|ï·ñ×ÉÔCõ ¥3__T½96ÐX³®-¯Ža¤61e€.*¥zs¬ NÚÜðª'êçPíÓtæë‹æÀ×—d*•e$ ‡tç¾Õ iÐ__E÷Sاé*‹A§Í0Çôúʇͯ¯ƒÖýxåž4$]lUzjü9R"#mè–c7ÆÑå.T[ê!rºG)Pé=Î>¼—p‹O¶Ô¥@=5ê¥@1¦Lf¿Ì"mN[ºXÑ ‹¤iœuЫ³`)•‰`©¶zJ‘³8÷æhÅ,©ñõ±Ra8–ÞD«)žÞ‡ò°8÷æX@/!ö"Rw îcVÇ ÐeLçÞ(m©Â®5¹I*—³‡ò°8÷æØ@qq$ÉEÇÎ… h€.c:÷æØ@ᥴHq`ÏD oµ–T¯ý¹7Ç#¯<¸MÒÁÕ@CU©‡ò°8÷æÐ …7ÜVy`Ð菉>†Y¿¾6ÏcÍ™xs~}賂‡ ¹™=h§cµJ½Ñ q/ –^Iz¸– Y¦åa±öæPåaýõ“©aIdíaŽTÝåa±òL¨ò°¡§B‰X4VQ÷ !ýŸAÕÖg'«¯õä’”aP¦ÎÕÂà©É@ñÜ©#[t‡ŽlŠôð" ‘WžÆiÊ#z€.ËÔ:² hbv õÇJi¨§)è t‘ôБ­"©“»”˜=ÅŽ3ŠT¥tY¦îБ­½¾¡«ÓJ“;è`hfhIõ2u‡Žlm¢p5¥«3pCÉ=õÍ?4¨^¦îБ­’)¾øÚ=³Ä~š^ ¨^¦sÎÄj¾T®QÐÀ¤ÇÓô.y'Ë”%;‡¼¾8çLhÐH#Ÿn¹ ž$7²å˜×7@W•𬾔á_‚ wÇÔäo‘tU©Éê[_ß2ŸIPÏÍjêëè¢RsÎÄ Êh ˆtÌt椻-©V)äL|öòÙG;haxCº»sLGCºê— Ÿi•òv±\½ç¤!  ¯Sf²ùÔž³:£?°‹ (²X”e u®Î[²]fßØÅº¤Pb\#SG÷ðõ‘Oz]$=°‹5P‹c=¸é=¿â”ÛSè:ûv1udªZ)ìWžÚÞu’t™ý»˜€"£ùªz´ûQ‚±è2ûv±&)ûA 6ò’nŒm?¥ûOƒê ÅØÅš¤(ÛlíÔI€9)TŸûáÀ.Ö@Ó”x“:Q­KršôTŸûabni CÙÕx6ixÐ\–J‘±ö™ÒŒçNTP1LÌ-+( -ìyA ê2ÊÃÈ40@åsË zeIj-R ›ÂÈ?¥£{]$˜[4h`Ï8ñö°ü$Lù§Lk ‹ò‡‰¹Eƒ¢|¿Ô‹kzÔœgUKª•¿öæøèJ•‚ÛÜp¢F5‡‘‰IR­üaªúç7æÂÜÈ“N|éÿ%j^¦Da¯@×ÙŸê£VII’†[C!‹ŸÈ*ÙŽpÕ’Æ©>jÅf‹¤VO“8U0¯o€.³§ú( êIÛC‡TŠâî^I¿¼¾žý8ÕGiPÉãÂOÏ|ýPèªg¿æLÐݱI¾Ž™õ¦>J.& O ÚÝQs&èîР–¥¤f~ÅÞéŠýPeóÏ9Í×—Å7N2qzmVi8`€çòžÞ9gBƒfQ~x\–¼ž1ÐEO眉URöÖjÕáaÐÀµä– t‘ô{. ‘wñFƒƒ[^î)Í6kÐUO¹çZ˜Í)Mâ “«å²Hªõ´åL˜ ”½–…ZÈó’[šžæZ;jOoëÍaO —øâa©ªt¤áäT©T:Y}µF‚E~FìTJÚS›„ )?Ôò ‡ånõÕjÔ,ÓÝo¥æÌ^z%è¢RédõÕ P`0ç&OÚÎݳ]$=Y}YrôÍzZ%>,•\tQ©t²úb=Mqd䕯ß[çµF´“¤‹J¬¾(û)sÈá<1lñxM C$™@õÖ—NV_¬VnÃDÞÆS0VìÓ‡Zž˜NV_¬[ìSlžŽ9½®çI»zߨå‰ùdõå¯åE¢¤˜+- ç¯~ýÅê›{süé_Ä'S½ðb·AMÊmÝ”ÝÁО“ßY”îÍ¡A3«myi2¬7Mn¤à‰ ÓAåŸ{s¬ –³o9C¬6îûiÝ¥è"éÌ×·¼¾á&m¥H@x@ûŠºè¢üsoŽíõqîã&Yeã U‹¤ZùçÞ´°T†-bßúò ª•îͱJZlÝPl`>»FæZv3êsîÍ¡AIOS'*—ên[^IOáTmÒ•gâÍ.ia3c’6ûñW?N.£A?Qé qîͱHj„èŽSöâÌnØüY+¿N•gâ;òPölŽÙÏn±P&P§@k'ƒ¤–»3§·%‰*Ã>õ‹¤ŸkI+»ØARìE’l*Ým½ ?Xy0ƒjIåŒúø 齡"M@šÝy{FÎ –&Píé-‡LÙ\;]á:‚Q¸pF¡qâÈ™ %Ÿ“c9dÊ6P–#bª¥@ÈOd•r9ËçdÁX™² ”é·1c]´Œ²Ÿæs² x~¶dÁza“Î4ù¥ãÅpÌȘæs²`,‡LÙ\ûœ±0’ø<­>3% „åõÕ.•®C¦lŸ(… ½IƒÚ¯‘Y¢æèGô)Û$M¸ C ruMÞvg˜@_kÐC¦le&aa®`ãVm.$¦4Ocª6”t2eskó†¨îA$mé·lw¼€Zꎙ±/Ü!iâfÕ×>_¿œ3iîÍ¡AÙÏʶ……‚Ë0ôT3åì˜IsoŽ£1_8æPk‡¤dš@IÓÁ1Ó@é4Bn˜“Âà~ã+rœ”³c&ͽ9vIo»<±¡7½f×p³m’tQþrpÌ´°1R›29ª±L‹5gNO”vÌ$s`“Þ``>¸o…¹òÜOíäAÊ*…lÉØÅ*¨%•~‘Í -ÈÃà™`¶Ü]fߨÅ*¨ò'ô%(4¥a  ÁÁC![2v±Êþ6 =f·p;•Ýp“ Ëì›»X}P—d:{t;Âz9›$Õ³oìb]Rã‚YϾ°gï× e]fÿÀ.6^ßXÒ›Öï÷|©"ö骷>s`+½Ï™µ/‰2õ}?5Ž›ÆTo}æÀ.ÖAA¦W˜ƒÌŽÁiäŸ2ývU[Ÿ)s–äŸSAÑ’¾bVƦfKÁƦJ™3kS²S„w ,â…YjoÓP~|tQ~;Ex7Pð QP[Bå¸Hº(¿µs–E S.-ÞNls§ÚBg€.Êo§ïš"P08P¿ß©J™Ô’jå·S„wM4É–¸KÅ2¥6 ª•߆µ©ƒ2§éb§ôŒm¤pÿ%ýúêvBãkemꯩÉtIÓ’¶#Y›ô¤Rêv’lÚ]Hr1Š/vc–kdè—3±ùËCà0ÙÙ{¾‚BFæ/ñõõæÕ†y&;{Ï5(bG†_Æ Xo¢"™å!pˆlÈÍ…TA ƒ'Fšû¢~\x%¥¹<“›½çÔ±ã“J[ÔfÓEOÝì=_AÙͨ(ÄiçTëiÍ™x}Å2-ÌfcY¬ã›@õ&]s&ÞAá˜bŠô¨(¥'²L‡ Ú\HD}ÐÏ d¸Fγå&=ý\ƒN9ÿõÍŸ´—Ýà¤w tßÕÝ­)ž“ßY”ΙX@­TEx^¦á”m¹ç0¯4è¢üsÎÄzÏñ}€ãÂë<ð¥ûùÍ"éªüSÎÄ&)³yÐFŠuѽ’+U?ÿ]”ΙØ@Ù9¢–ÚÃ?3œ«¤Zùgž‰m¢²$Ÿ+cêF<ŠqÓ T+?ü6Ÿ½|öúZÛ££ê˜íGÂP’ÛÉýL+¿Ÿ¸Ð{†Ê†l$ö¼ÒH[jHª•ßû=t$eØIpÉ ,Œr޹sÍ/%cêÏ¡£ä§{ÔÊÞhÇë…¬r„Ž‚]ôÔO÷( Z˜ÏÇà“X´Ž^Î&ÐEÒ´‡Ž43¶Ç“Ú ûíð¡H ó]õtºG­’²“: eøú¨ëÁƒ %]ôtêq¸JŠˆÍ‘à¥ùËD_5¨ ¥Æ3aO*}žŒðúžÓë%Ì1ƒªsΙhYHõX«Ø³®+ÿwS±úÂ9],Í9+¨ÅMϽš¯Î% º¨Ôœ3¡A3ù^ Šjô…ÃûÒ ‹¤~Ok’²ÚØÈdê“_е<tQ©9gBƒÖ”fd]KïÔN²Òmþ!©V)x‰Öt±&)“¨Ò±VZsM5gIêt±4çLô+OlÁ™(ØØ<šQšfŸÆ‡«ùœ3¡A#éÄ„SÝJjÓp"Ð×7@×ÙŸ¬¾ÔFÉ™ðÒ“ÒL-Éä¾®æsÎÄ nRŽ©1Rt|õÙg9Ã]fΙРèe—øúN²år Úš†3IªgÎ™Ð Ž¦³4c¶R}Ó2»¬ÔšO zöãdõiPËNl¼ßÄ ÙrSU¼žýÅê‹þp5¯ Ö0>Éíi;¿G!®S}5áp5¯ ÎUVYðõm pûÊ„5ƒª­oîÍÑJCKeÁ#;(òP, Ë5ê÷å•Î%Ìiîͱ‚’jÜ 3=ªo&šb–3 ÐEùçÞ4­‚e°Ž„­=td*'ºHªØÅ4èe* ``Rk(£ðÂjIåŸ{shÐDö¡*eRkòƒb‡YH“¤Zù“™•-$•¶Fߊãè:¦Zù“b›A™Ýa¤¹¯cOJ;v©UR­ügœ@ÙŠ `YÈE}»ÝLcª•¿òL¨æÒÂq¬äB¬+Ø´Ÿê××%ÌŒao÷ýÊ,ˆ®Óœv);õ}LSõJæ‡û~šâQÔ1ƒ]¤­4ÐmÊ_êΟîûiŠG­ ²0ØKY¸é·,˜@Ióá¾__eÑÒ•°VÅ›þúI¿þªüSUÊ5O/ªE5è¢üyŠGiPþ‰Òƒ›µ¬¹¥à!ÅÑkP­üyŠGiPD2¦—ð*—Öâ‘Á ª•¿æL¼ÙAwI“£ûTMVšQ¶œÞ"”e¨¾òTž‰Ï èq¤ó$D ~ìü^ÏþrßÏ~O-•ðJ•§×³á(¼1@Ë9U4å鵂Ê&éIb±u£3B§=@åÏÓ=Jƒ:qGÂ0Mµ@¨Ö" t‘4í©¢jy4K"d*Z+¹³•¥y€.ÊŸ§{ÔúúlƒÅ(¡v—¼BIÒEù'æ R™J¬XX\Zò5˜–1ÕÊ_&v±”l!L/rÇ¿ú닳k€j§l™ØÅ4¨p!Èëg©ÜÑ, j“žy&¨á}ÎÑ¿/‡Vب—†tx3Ï„½õ&IyXÔÉ3Ñf?I1Ë]ôtæ™XA܃?NJ™r»E·]j]$ »ž è%c „+‹Ç¼m}÷BР‹žÎ<ë˜2AÍI&;±¶Ù;™$ÕzZÒ®§ê~”wMRtuõ‰Ê 袧y×ÓZ¢²±¿©—…ÕBñËD-zZv=m¯rjNTð²ó‡GI•ž‚Ë|3& “-ýûuFxeZÁe¥(Çs'c"_f7&(XT9¦µggG›Õzš/»]RxŠ ü÷V†nR-³ !¢AIÝnL¨§I"¼=I\)£ÞÔZúéêwc¢IÊú¶Î+­¢|—”0O’þ^ƒ†Ý˜h †cê$`Z˜‡) A „&Ð4h܉Zà ç›çì÷f?>o’¾Ö i7&Ú˜² K‹™ƒm[€;ÔŽè šwc¢2·ÇUûô™²¡fwLzú¹-ó5òVPÎ-%VÆæZlÇ실öH_Í5_#gPi˜(eáŽ<­œÁE!¨ ‹ò3_#5(…yî{I— Lž@Ií|Ô¯ÏôD%ÖEwŠrW«9è¢üÆí~)C†C+I=6’»Š¹'¨;ú¥²Qìb hÎõ"‘%0Ÿ:hÝùè:¦Š]l•º(nžRknÚqâj"ƺHw¿”€òKÈM× ‰²ë Ñ(ÐuL»˜~ý+Im$,cdN¶¤Wy&&Iõ†b»Ø z±~÷GËZš6”°¼¾ÞPŒb›Aaóƒ®0»Š³.É=Ÿ@õ†b¯Ý/ÕAa¢€ )–´}ê„K~Su5Ï•gBù¥*¨a %7|ck VâQ3¨:Mí¡“€¡õé+•¾“”.hXËC©n9Q(ZÆUÄ‘b'6o±,zª¯‘qêºIê.9ñ¤9E‰Â×2ûºŠoö²VqöÙ7XCY*¸¯QتŽfP§@ã^Å9V”œÍ8ðéDï’¦ôs-iÚ«8‡¤Ñ ó'ÛFf×%™3¨–4ïUœcö˜&B²bü(»©±“r¬â̱ÌFE!©‹BËJ.¤àûÚwµ,ë:›=é:˜=4f!;•Ù¿êëGX’ tÙ¥’9˜=Š‘Ô³:¯áCI¸ñhÐER{0{4’¶ص3K+ˆ(9S Ë.•ÜÁìPéGh¤ß@í(I‹¤z—Jþ`ö´×¿ä@º$±­$×_™¨ÅìIá`ö(rE¢PÊ“²3·„!´¥^$ÕfOšYš+¯¤-¼ÑH.ÝÞ+>5к¢îçNL­9Í,Íœ7’Ó¢ÄBW©¤AW•šYš7Ðê;ʤ.ñ¹i«I4G¦VW¦Öþú,_‘â]Ôð–^å‚]T*Ï,ÍuFRkn­ô§+êZ$Õ*•g–æu³5[Ž\H=KÞåT|yòL, ÂV‰+Ï=Ù(¼V_Z%Õ×Èš3ñæÊ*³"­H8|«Õç•Ò×È<õÚ% …½¼DåF¹ðIõ52‡©µ«®ÃE@w‡øú†¤Š©5ç¸3µvIÙƒÔŒŒ?›~;1’04ƒªeš™²FœÃ‰i,”!iUpÝ”lkßž#ù);@SBÛc]õõ%ÈÕA—e𙲴pÑóÊÌú(32º[‰=G$Ê!SV@q)”Ô&¡×²ý8iTö‘(‡LÙþúÚ·ÙcXÌRZì–kÐ’êeZ™²4KÛ鋉˜lò×òPj·› T/ÓrÈ”’fÖªxÓBG¸S ª—i9dÊvP{/xå„ ÷6¶íj>ÆT›’å)ÛAÍ}‚§OÚ•VÅÉ6² ¨Rþy(.ôËáŽ-Ê´É.U/g÷s§<”\¦3JƒÂ{€5dåA…LÏ?µ’†3@å/Óµ"Œ-†UñýjШAIË!¥½~!)€­ô=l Ь@µò—kî¿JJ~FÓHBHTRÕIÒßkйWüöú8êÓoòù›¤I²å&Ð4¨=ä¡tP\qÓ ~¿ŒzS‰H Pu9+µ7‡ÎCP+‰áŽ´bìl=ê£VIÕå¬Ì<Ã@ƒÂXé!ƒˆŽ%}ApS-€ú£y^fž‰Ô’V4“®ˆÊ?quJ,ºƒj=-3ÏÄŠ¥t«â€`oè¤U¦†ãè"i:™çutËåš8“I.ÞA“]õ4ŸÌó&齇¥Ú@00>Ó”¿Ñþhž—Æ3avPO ¢F@ãˆHH8nHªÌób®“y^%½ë¢]Kò0}E‰£{HªÌs0¼mõû†Œ±d:÷´e¢úåÌ4?ÿýÜ©~¿˜©>jy¦g†#Š-º¤Q:\ÐE¥ÌTµ€"—ëo‰k@·1jx­× ‹¤S}ÔXpx)ãÚï;¾)8Öï3ÕGm¯."žÉÇÒq4öÎËëk•2»Øjhž#¤á… õj=&P½õUž U¿_AåüttKI³ô‘Ø–õìëúýR{s¨úý.)<¦ž]˜ÍeF-O¹´Jé­OóLTw}ãV¦(²• $½Ú¹O×"Aã±ÙOÑ< 41ÎYhIЉ³«ºè©æ™X@½õ¬1„Ê•l]$U< ôÞì XFLötªãs tÑSÍ3±HŠ\xEŸ¯z­¹kzÚ%Õzªy&P÷1ï2z·›(}$&P­§šgBF†yP¹á´é)óôëk=µqoöÓ%ÙväBlþ7½j8nUzjÓ!¹…uZhdIÕP×~œHªëë§crK±êµ€ra^lÅæ¦4ñqáMÇä–bÕ=j äíAv'vþÑ/ºæ¤crKq×!¹¥’FUˆ¤±¢ÌðJVåOÇä…Î~æë£K½“6çUÒæ–ËÇ»iqöt7m ÞŠ9â„Tµs"ÔŽ(tSçNwÓ,gT›ŸÛj«Z½E_ è"©?ÝM³'ðYòr¤Kp'Z¸‚]Ç4œî¦õõ“ðö ËáíWžIõÚwñt7ͲK1ª‡‰—Õõ´9òñnZjÎÄë#(jCj¨}“[³ŸVÆ8¾Ö ùt7­ © ./IÈ. KºžQùx7-•gb¹›TÚúayfIBî¹=-¡}UŠ¿NwÓ"*¶ÖÕsLmḺLËñnZ¼9ÝMK5Ðb’NNÚFw§l-Æï ‹ò{{º›VP“‹d^²wªw]¥lY@IÝénZ_e$¦LXà?GOJŸž^¹›Vž‰×I‘À@6°¸%ímˆK‹ P­üáäëc†¼=x}/. #Ó-Æ'Qóû¹ã~N¾>I[«¼w ®‘¨yêÊ_3:誧'__%£”—DŒ4HÓ&骧'__{}ܾxe&Ó@ËôZ@='__õrÊÅ6’º´Hªõ4ž|}íõѧ•=kí^Z2f¨ÞOãÉ×gkìÄIìPØ+ƒ ª¬cªU*ž|}íõ鉺§½rw4wÇAR½ŸÆ“¯Ï¶ØIfࣀæL¶Ç¢S•Ôž}}ñäëk ÁHˆó Ò±½ùÒ ‹žÆ“¯O@##Û5?SBäÊ.6.’ž|}äõo­tlÊhFj“]^ÕÓ“¯¯I ï l)Ïú‹ž%qY$]ôôäëk’òâhÉ¢ŽÌ®ª±è!©ÞOÓÉ×g§€ ©‹´yë’Vó|Hª÷Ó4Ý£g—AS24Jcºò7sw¤+iºG- àa"?r%I÷܃±íˆvG¸’¦{Ôj¤„ ç8yAb\@I§{ÔŠ„~›1Ã)Žƒï*tQ©4Ý£ö1 ? Šhfo(}ësG¸’¦{ÔšH*BnC¦ÓÙL-  zëCÎÄJWAsë<yJ§Þr;é š®Ôœ‰7PärU‰ &,ùšTâŽ4pàˆ>l}´>Ãô³§¹’5½Á>„9ò) ©Ù¥Ùf*Z×Óf¡<„9ò) I@­ôsªðµeJVH ºHzÊBªnìWE*.˜…„|þð$颧ù”…Ô$'úŽ)µ#AÝö!Ì‘OYH  ‰0zÑ9תx”øOíC˜#Ÿ²Úë³iŽ“´Æ‹$c T|}ö!Ì‘ç,¤þ°‚‘”eq‘ùüÌ\#g÷sò;«JÍYHÔH„7’¾€5QƒLMþèªRs’½„E6H’d¤ªŒzjÐEÒ9 i“T’°˜+§>¼®& vÐE¥Êœ…´Ži¬ C9Jù‰ ‹¤Z¥Š9øù-é>XÆNi8©qØOmÝeLüüEÅ£fPп "Ip#iÀ[ØX*¹ìƒŸ¿¨x”–žm:¡j!ÛTÉÐERðówÐò"y}QÚŽ7gW©Qsûàç/*¥_ŸÅSdjµÍëÑcÑ—–t™(šAeÖÅ@³œœNýl+y…}ðó—tðó[)# wþÜ-+‚¶h¤}ðó×ÞÚÏ_%-µÕ[Z;öûP/Ä53¨ÚPÊ)e[1K¤) *}3uà K¶¾tÔSƒÄÍ]O+h”[4‰…'8Û2»:¨ÖÓÔô4É25’ˆaS‘ö<#! ºJjzZAiJ‚hIjz4ò’."ôÓÔô´¾¾Úw{pŸhù§ˆªhP­§7¨?èi­‚wQ²ã…O®°»Zs6@?Z@ÃAO¥<ŒZð÷‚~©X3fÆë+=5X×»žŠ¤`VhùRŠh! kÓ j5hÚ(u‘~‰b‚˜•âÝÈÊû–‰ Wû¹¥î š»ž. žYòJ¢+ wÐMOK×Ó ”ÅKr9ÃÆ<Ñ9 ‹¤·¥¹RêvP$×>gÜîF>µ¥ò‘R÷5]O7P;Ð'íd—ššý-颧÷‘ÑÜr hdµ5Òú½ÈéNÙ²ŒéGZ¥Œë¶Ô>¦’€‰[±ª6öÂØ6ƒj•2þ REîQAfŸõ6S€»4?ÿýuV)*Uäõ/¹šÛÀê›Ô8»R%¨ «JÝûÙ®R¥º;‚¸}”Ö±ýíI7•J•*õ•üKMäÍ'¶kdrtS©|P©:¦¬…Žš>¦~ÝTªTªŽ© t[ðõÙÁÑ]ú…·<¨”½*U‹YÈ£Êp·÷¥ßNü:Q‹JÝÛíÊÒlD ›¨°F‚ Söq½òÜ~bi¾AmW©´6Îð LÀ7iÐU¥¬ë*µF‰GE¡×²Í'kw† t•Ôo,Í4K«nªÝÏœ]Uʆ®R›¤  Cê„Zšxlaã!é¢R·=´²4 (*Ø"M”Ãã§Ô3ÙjÐÏ–ÓÔ¦ý¾ïšK^6¤€À|LÝÕYˆ÷s§û¾AWàí¾/ FBÚ`Qä˜ö~'¡¥‰wÐmöǵ€ZÉ´Zžº.ÓÛ.ØLIE¾”“Â[÷~Ñ¡•ÚÐUÒ¼›’ŠÎ[²¡Vqvö†P»ÚÐu™ú²›’MRz@\ÇRû8ÊmkDbHº,Ópí¦dÇ2,3ŽKîò¾þ²ó³›’}ö3»‡u\ߤ}\AµJ;Œ‰ÿÙŽ_%5E:¥%& ¸>Q5ià~N~gU©à†1¡ASÙyeÎdgm~)ß’°:èªRÁcBƒf6çÀ y(vØüð|, «¤a«¤è÷R™êÐB§Óvåo «J…8Œ‰uL³xÏ™€iF5Gl4CÒU¥Rßù7Pdµ`“Là )·rÛºìü!÷øþêéêeTŽYÌ‘ûË]3¡ôøþ®R6‹÷€ì½]6’ZWP­§‘+êÍQR’“J?w[$:U7éËiÐ7ZÒÈõæ i`vþÀ†¥Ô£[׺H:ΨMRò3f¹Hä‰\ͶӴƒ.gÔmØÝzúñÔ±„\HÒD¥ƒ’gCééÇ èÁ3AÿFQ²åPÊ å7›ÑŽ“xðL4Pt“&ë‹XÝhöã­]×~麪Tž‰íõÑÚ¹I³4t€JØxHº¨TЇ‹D­A¿6$ÎÜ'®Ë#K¾³ Ðå8Iép‘he7I%ïÙ÷Â\Ön'Y¿þz‘Hùp‘h2-ÑmŽ.Ì®0ÕàÁP©å"‘Êá"Q+dBäÁgoÔMSGÇ฼þ²Iß{ø~‘¨’ZIi†»c–w­§ëE"›ÃE¢Vr!W’Ú$ý¢GY¸©O z™æaõõÔ{®l'lêìÉÅe;{C¨‰.KD èó¶uíåÝ%eá×8£.º.Ó<¬¾æY›Øá2IÚp-Qƒ®’«oåŠ* õ¡ê¨Wp§¶õ¥c‰ˆÁQ±•ˆô׿U 5‚žcj»¤¥­¨t,1輕ˆ4Ы0W|Ÿ?7³'KD í·‘ê#É‹³änôºä Ë2½_d+éåDÓï5€b–vᕾÑcºhåÚKDšžFÇë3¢rsa0Ž–¼€jå¿Í¥ôüi ²×g¶~@‡›4V”ÕcŠß¿A?~ó§ÿô_Ê3Ê`Lÿå͇/¿Ì/â–CÖèšœÔn8f¬^üùû ”+Ê@±/‰±o™Þ—[ÕQ#ÿ@íÊ厠Øó`§9ºû³‰mRð0@ÝÊ3Êï d‘ PJðô#Æ×˽dËM ~åŠ GIÑ2 7]c¥rЛKƒ†”+*' d%TÎ?k†¤Þjи€rE¥¨•üpð2Æg{k=M'д€òŒÊGIAhÖ¨ô‹ÕsŸfÏš¨‘œ‰r”4Áˆ.O]È#±lhY@yºþŠÞÆà=w•·®»:Å);+ÿ¥AeE™h’N’«YÉuût]QFƒrE{œ(W[²Dî€SÛÌàP«A¹¢Œ;J¸KÑ+éGÇ ÆP§A¹¢Œ?Ž©£;i«”l³ŸP¯A¹¢îcóÊ$Ió*¦‰\SºØªR8vgP®¨ûùã˜f)7bBéJ<”4jP®¨ûÀØ@{±XésÊ„¡)¯oY¦0ºfP®¨{Yv)ôl‚ò{¦á”‰ývÝO±-L †+êÖæMRÔÃqúÙ³Æì_ëD Êe+Ê3›‡-©R¾…ŽZÃäyç×+Ê0u:£™é†”—S®¤Õ»Ô§úŒ‚Ÿà=QF8» =hÈôÜ—ÜN&P»€Ž{ÔÇŸ~|…›@M’šva6qhÞñRtñwº6@Ãh¢ÇPx_­t¶›f_nZæ 4>Ijys²’ƒøD˜ÒÅÔ>¦P¸aµ!$‡gÍs(Çh~’´ÐÙrµÛ4Á嬌sŸ¸@Ë“¤b˜B¥ +¹’Ü@íõŠD-㸤ⰵÏ*Hç'h|5Ï ˜E&‚ؾ­6’õMO ö 4’\ ;`)òúÍè ’}Œä´P÷Êo¹I£xt%}ç‰ AËèÓŠÂ ¿DÒ˜¤í`o uâ O+ S#HêËšÞNP/m3ÑHùôiE%ö!bmÈ=Q.Œ2Æ–3Öä O+ê^óløaY"Iê·v*’Ï2¤ЧÅ4œ$ÒdÚ5’ }ZQöqEa_’„¡c&õ†E¢‘èHuO+ Þž{e$š¾lwæGÝIЧåW”—¼4‹ŸÜt5çÝÔ™§åžVT$© Ò ‘Ëڢ΅ä¤;Ò<@ŸVKC£ø5%ÒÙr{l’æ¾ð¬=€>žQAʬîSNn|! —<9»°m=€>­(x{üà-ÈcLKÍD@—âÐÇ3ÊI¸à:Ñ9iÀH›7Äç@ŸV”©u›–IÈðQ6–¸ôiE¹Ç3* aa{"oG¾Œk}ZQîqE]dêcsh–2u.y×}ZQþqEéRoºyêxqI”}j@ŸV”g¹©\Q«ƒ¢µÎ u™Ú§åŸVT&é! ê‹^䨮<®6RpöiEùÇåäÂ[¤©±ÜÑ´H"†³O+Ê?žQ—”Û¢ÜJzûhM"Æ„{ZQþiE‘‡™£ª7uµ[¸sO+Ê?­(©‰”ê p·ãÎ%îRîiEù§…ûS®h%vÒØÄA@•rO+Ê?®(v¸’ž±‘®¸–…ä+kª@ŸVT$eO•ÀtûÜ$%*AŸVTx´ú¬0^\Q )n‘3o¤èʹ§Ϩ[3©0L`Yx˜Žè:QO+*­(+éñ‰Ö9y%{ßÈ"õ¦è)÷ú¸¢n»eƯÈåËx”ë%¦¤ZQñÑêù/RŽ û(ʼn¬ÒI8Î…§Ï(Ï´ =ÓïU”ºžV Hôy}ZQ™©ÌLŠ6•YpœQlGˆªÖÐÇåYq‰ãä"ùo·O-Ë9ú´¢âO¬¨¤üž!ŸF'˜pôiEÅÇö[ÏHg k“iž Ÿ¤êfgÐôg”/\¦ðyc±Œjö¯î€Ч+Tº3$iÑa͸8‘ôiE¥Çu¯pÇœ‰(_Ý}ìk„×…§•WTbîݨVZeº~œÔ×ZQéÑêCfh‹ØÁnšÒA8tñiE¥Ç"!+û\á&ÝWT^ŸVTú _òHáëKLeĬJh1>­¨ô¸¢ ϨÈ&é$ýo4¶ÔeŸVTz\QŽ÷G¼¶áÖçÚåÌá>FöèÓŠB–¼™:Bð4ÊÒ´ó˘>­¨üxÂñ&ùv‰ÇIÏ“F¾ŒéÓŠÊVŸ!û  öÓЋ­Q„AЧ•Wè$˜¬xÍZ”õ¦2QO+*?­(0Ÿ…,ÜrI(!F·ð,õ´¢ò£ÕgØD¥°©ªñ±úúÒÓŠÊ?ᙈRÌBÏÂUA›O:=­¨ü¸¢¢d!¤“¥¦sìR¬6FmÓèÓŠ²•æž„6Zîù½I‘ôiEåÇ{ÔŤ;ôyIÌã½Úë·LT9>€>žQ÷ödÅm~¿lá Ýõ4Ëë?­¨òxFEš{ éÌRÍØ[;×NW.=­¨ò´¢b¥¼Æ žÈ覻©ŒéÓŠ*O+ Êñ} a«éE5nêÒÓŠ*O+*³I¬Ô\Iv¨k?ø¦RO+ªüÄÅt±‹Ç(#ÓÍ”¬D .?­¨ò´¢ËÁY¸ŸkœÛö×g"†ËO+ª<­¨È<)q!yJ×|(ž]©ú´¢Êc<ʋǴ0=>Ó<®æâêÌO+ª<žQ°y-ï WG;>ÙOóÓŠ*g*AxàÃ.¿¥ -tÏ„€>¬(„)Ÿƒ\žÝÐkÞžŒ…,³ÿ°¢àr{xýÌK<¦?ú¹ÏÓ4?¬({=z&ÀX“i§]שÜ6×VT~XQ0½Ÿ\H`Ùã?Ë&’ÆE‚[_~XQH~~ºF"G…™ÂqŒm™b|)iyXQöz´újÝ&šJ÷€nJF!qåaEÙë'Î(´ýùa&S«må¶Ð ¾~yXQöz<£.hƒUÛ| ÍèåÚ/+Ê^O+ [þ=àp¢;#ºl_¦â˜)+ MKoÑÌ8uLFÂ2ÍcöƒHú´¢Ìãe8¦ˆ»ßfn$Í@ƒ-Eå/O+Ê<®(VYÑn¼'ăZvôàS²<­(ó´¢àä3d@mˆŸ}ÒErzF}´ú,ý;VâpCÒ,’.+꿼üæëï¾ûþ‹ß|ñýWÿú÷/¾ÿîë|ñÃÛÿýÅþËÛoÞ¾úò‡|óÅ»¯^þúí·?¾ûñ?ñþóùo¿ùîÛŸøÛo¿úWþøí_¿yzäûŸäË¿þø—_óӲ姡,?¾ÿòÛ¯~æ¡wßþðÅŸ¿ûêíÓ3yûãÛ÷ß½ü†™¥÷,ý·ù”ß탟ñùðžõoå±{‹g'|·;{°»GÌá·?~ÃïöÁÏøL݇û*ã»}ð³ôZîÝG[à»}ð³´èÅ* ¾Ûç Ûmö²M*¾Û?KëÔþؽÙDß탟¥±|ì¾ò’‘ß탟…¥¸?†Î(ø#|·Ïé±{Ø‘ ïöaf<³ãÇcVƉ|dõs7´¹@0¾Û?KfðxÌË8Z/ýçeÜ`Úcþhâ×ÏaNÑцÿ(ÉÂ^úÏë?šêc¸…ÔÏé±{ØA¤‹ïöÁÏB®;+õ1Ä´êçðÈ$1ønŸÃœ‚oeCønü,¥Dã1+ÿˆ¥7ù¥ÿ¼þ£N´ßísÐ^Ä£øâÆ[?§4Ô^úç0Y ö&ìžú9¡Ý£²N|·~ÏñXu|·ÏéM‹h+¾Ûç ½®Ž:¾Ûç0 è‚åïö9,tüç ¾Û‡„ä!9|·Ïa@Еƒh°“ëç„êcHF©ŸÓc±>†›gýœK²|Ùæ«~Ë×"Ž{l½ôŸ×q+²sã»};9(ò ‡gϦ—þó"›¯ºïö9Œ #±tñÝ>‡åŒÛ2æßís˜Sx}ðVønŸÃ›‚~è£þ„ß탟ñ™«;¾Ûç$[’u‰ïö9¬StD'¾Û‡mŸ©ãtSà0Áwûàgá5GÛ%›(¾Ûç°©Â ŠÚX|·~–zÙñ˜•áÄwû†CJ|·ÏaxAÄqñÝ>§W¢­ønŸÓ!+ÚýÝ>'´T»¿ÛçôX5ÄwûÔ2ÔßísØ‘ÀeƒßísøGÁ÷ÄÇîïö9=VußísзXußís˜¬XOc|·ÏaÃõ4Æwû á9L3¾Ûç0õ wÁPâ»}ËX'–¾Ûçd˜•: ÷wûìèø#|·Ïé1#o†ïö9¼iª£Žïö9¡U›ßíszÌËÖ‚ïö9l5à ‡6à»}’êiŒïö9 /±­à»}[Mª£Žïö9ÌBªºïö9¬İpTà»}Ç9²aßß탟ÿ  ù®´¢ïïö9èx¬±_à»}{¸'`sà»}v|FJ|·ÏaxsÕ}|·ÏI¶$÷|·Ïé¾EqðÝ>EBŒŒoZ^úçð¦¥ê>¾Ûç€^elønŸÃæPꨓô~Nw'Ö¾Ûç` –j™2͵~ÃK~bü£á¥RêiŒïö9½i’‡ïö9,ÀRm"|·ÏéMëýßísºV\—\+˜yõŸ·Ç ÿ¦=_ãçåEÂ:Ö$×ëç°NQûÅ›BÓõsº]¾¢ùþ9¢¹×ƒ±~ËWò ±ޝ¸—±ð¤~ûª¢x?Qýœî§Wá*"5DýV*—ø"pS?§ô¾,ËÝÙôÏéî\Û…ã»}NÃ[»5“_¤~NS_;ðâ»}޲IT¶â©ŸÓ»öŸÄwû6üÖR‘nìú9M}m>GÖú9¾i©oZúçô¦µõ¾ÛçäÁ¨oðÝ>G4éfBºžú9[m&ïö9ìH­A¾Ûçˆ&ôô¬n¬ŸÃ•§±ƒã»}N³PÉ™ñÝ>ÇÇ„p—ÝÁëç°[6U|·Ïé±Ê¶‰ïö9lªA‘e¬nü¼HepÃwûœ–Leå"cQýe6Ú¨ŸÃ%¥‘²ó´~Žh±Êûç([ª>®Ô?‹’/žço¿ûV¡GÇóò·›ßyù{í©]þòàW^ž8¸•Wñ^åÈOÊqð)ŸäX]ʇg†Gõ“¨‰„ŸØ24/5’R'ùêå7 €ãw “ Àô€å¦×.nÐ|Ù^‡¸=àù€ë5…Ûø^¸=ù@èµ~Û‰Ä^··=ù@ê5xÛ…ä^O·=`è +½6n€N°«×¹íÀýj9ó0’ì{ŒÄoó0’†áa×kÉöàìBY—yI¸{QßÕj¼öàà2±×kíÀµ…doó0’pî:V-<Œ$ܺd2#ÉFÝöêõMût$š^«´?À.²¶×í`$ÑlÔ>Œ$ëØ˜ßù0’pÙ¢®§Õöì`$™(ó0’pÓ¢Þ¦ÕÜì`$AphF’îYtâ´#IÇ,ÈrÜÃHÒ%ëL¯QÙ 3Ööz“ýŒ$êIÜÃHÒ‹þÃîa$éz½Ÿj5ûI𳸇‘¤»ô’îa$éh‡€{IÏ^Æ¥×@ì`$ï§Z=ÃþFÒ›^›°?À®æ¶×ìСízÍÀþF’Õ{#É*z.ÿþF’å# w)òë[Žýþ“Hsϗ߀‹yï-÷}€ÙâWÏcßÀH’2îa$›ÎÛž_¾?ÀV÷®çŠï0(à{Þ÷þF’=ÃFŽOäb·|ìýŒäýTË­ÞÀH‚1<Œ$ÜœŽ%Ê#™{{õüåýŒ$LJ‘„S9Å-¯x#I ¯‡‘„#¹¾-ßw€•Ðsw÷0’¨‹# ·%òi[NíþË/rÏÝ`§òû©–ëº?€‘DûÝô0’pO"ÿ´å î`$Ñh:=Œ$\’È m¹¡ûÉû©–ç¹?€‘$Aá“ÄàTìù—ûI4O# §#r"[^äöÜÈol9ŽûIÐ^凑„‹y‡-÷p#y?Õò÷0’H•Ê# ‡"rûZ~ßþF2‡ž«·?€‘ig~²'àK=‡n#‰:Ãü0’p"¯­å¶í`$ËÕóÔö0’Åôœ³ýŒ$¨HÊÃHÂ5ˆ<°– ¶?€‘¼Ÿjy]ûÉû©–£µ?À*ûØó­ö0’÷S-wj€AÒÜó ¶ “¥JÏiêü—vUºïNýó_|ùÃo¿ùׯÿñê¾Díÿ3OOõöÇ/ß}ýƒ~—¶ïÞñÕÛ{÷íÛ¯úÃOß¼½ÿê õÁÓÓß}ýîß¿ûæíûwž~ã¾!ÊŸý‚ßúówýöÇöVOÃðÅ¿¿ýöUÿ¡ÉÓ8ðñï¾ûþ‹·ÿþýÛ~x÷Ý·?÷x½½ñõ»Dœ—ÿé÷ÿô‡]&ââ©_67ãñÿñýÛíñß¾ûJ</xðËoIûˆðí—ßìæ u?ýÊýV÷„¾ÿÇþþ•¿ÿÓ?þéÿ<þÊ7_þøþÝßÿ?ó¿îÿ?þ•WWÿß/øM;~óúu¿é~åoþííŸüîý,ê¯ü7­zÏ_ù›ö?7Bö?ýžö?ýžî?ýžî?=ŸN½ç/¡úžî'ÞsqÃ}ùׯÞýøÅû·{‡uþÅ_n”ïÞß{Ìû¯Þ}ûå×/?ýÔW_þøå½}ûã½ ¿à ýéç¿ùòÿ¿÷ÒöÇ?÷ð»oùÃýn‘n)ÌËo?ãØüõýÛ{Cýêí׿½÷œ”ð»ËüÿÆŸAvÚ•w¹ßYÿòw|ÙöÆüîJ¿3nÚâÑêŽÞ‡Rm;?ó;ý¦_þ¹ßÙ'ãå ýOc|ÿþ»¿Ý{êûYÖ÷o¿ÿî‡wåŸùåÓýÛ?~ûîÇw÷ ¼ûõÛ/xûs|õö‡?¿÷}ÝjåüœÒþûûïþúýϨ¬<³ëO=üKµ[žæÿ…æôê·ÿïÛ÷Ôâûîý½º_þüÝ7ßýîËoÿŒ¹ ·_û+c<~øîß~ü/ß¿•ãmúùÏ_ß'ô»{÷g9·¦¿ù[ž~÷#·sÿc¾mªÿñ»ÿöÏŸþËËo)àË¿þõÝ×÷_üûoo ì(gýë÷´”¾y[í’þµíýý_¾üáþ­¡!øk·Íðß¾üú–ò·ÞAØWþýÕ{ýû/(Óìœßž¢÷ƒøÅO@ñ¡%q~H'ºeZó¾·‡¾þò_o¡¿üúÇŸBúþ/ïžÿî‡wÐ8SÃ,Òñƒ—ßO6®ß”ªeh˜iMOYjðÁK@æ=ú¿3 ÄŒÛø×€K=ìñ—åï«òþªù½.þåÍŸþøÉ§üÃˇøãëß2Hqo±ÿq¿Ò¼ãÀ®‡<ÿð¡.libcifpp-7.0.9/examples/CMakeLists.txt0000644000175000017500000000027014746170722017546 0ustar maartenmaartencmake_minimum_required(VERSION 3.15) project(cifpp_example LANGUAGES CXX) find_package(cifpp REQUIRED) add_executable(example example.cpp) target_link_libraries(example cifpp::cifpp)libcifpp-7.0.9/examples/example.cpp0000644000175000017500000000153214746170722017147 0ustar maartenmaarten#include #include #include namespace fs = std::filesystem; int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage: example \n"; exit(1); } cif::file file(argv[1]); if (file.empty()) { std::cerr << "Empty file\n"; exit(1); } auto &db = file.front(); auto &atom_site = db["atom_site"]; auto n = atom_site.find(cif::key("label_atom_id") == "OXT").size(); std::cout << "File contains " << atom_site.size() << " atoms of which " << n << (n == 1 ? " is" : " are") << " OXT\n" << "residues with an OXT are:\n"; for (const auto &[asym, comp, seqnr] : atom_site.find( cif::key("label_atom_id") == "OXT", "label_asym_id", "label_comp_id", "label_seq_id")) { std::cout << asym << ' ' << comp << ' ' << seqnr << '\n'; } return 0; } libcifpp-7.0.9/include/0000755000175000017500000000000014746170722014614 5ustar maartenmaartenlibcifpp-7.0.9/include/cif++.hpp0000644000175000017500000000325314746170722016217 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/utilities.hpp" #include "cif++/file.hpp" #include "cif++/parser.hpp" #include "cif++/format.hpp" #include "cif++/compound.hpp" #include "cif++/point.hpp" #include "cif++/symmetry.hpp" #include "cif++/model.hpp" #include "cif++/pdb.hpp" #include "cif++/gzio.hpp"libcifpp-7.0.9/include/cif++/0000755000175000017500000000000014746170722015503 5ustar maartenmaartenlibcifpp-7.0.9/include/cif++/atom_type.hpp0000644000175000017500000002472414746170722020226 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** \file atom_type.hpp * * This file contains information about all known elements */ #pragma once #include "cif++/exports.hpp" #include #include #include #include namespace cif { /** Atom type as an integer. All known elements are available as a constant. */ enum atom_type : uint8_t { Nn = 0, ///< Unknown H = 1, ///< Hydro­gen He = 2, ///< He­lium Li = 3, ///< Lith­ium Be = 4, ///< Beryl­lium B = 5, ///< Boron C = 6, ///< Carbon N = 7, ///< Nitro­gen O = 8, ///< Oxy­gen F = 9, ///< Fluor­ine Ne = 10, ///< Neon Na = 11, ///< So­dium Mg = 12, ///< Magne­sium Al = 13, ///< Alumin­ium Si = 14, ///< Sili­con P = 15, ///< Phos­phorus S = 16, ///< Sulfur Cl = 17, ///< Chlor­ine Ar = 18, ///< Argon K = 19, ///< Potas­sium Ca = 20, ///< Cal­cium Sc = 21, ///< Scan­dium Ti = 22, ///< Tita­nium V = 23, ///< Vana­dium Cr = 24, ///< Chrom­ium Mn = 25, ///< Manga­nese Fe = 26, ///< Iron Co = 27, ///< Cobalt Ni = 28, ///< Nickel Cu = 29, ///< Copper Zn = 30, ///< Zinc Ga = 31, ///< Gallium Ge = 32, ///< Germa­nium As = 33, ///< Arsenic Se = 34, ///< Sele­nium Br = 35, ///< Bromine Kr = 36, ///< Kryp­ton Rb = 37, ///< Rubid­ium Sr = 38, ///< Stront­ium Y = 39, ///< Yttrium Zr = 40, ///< Zirco­nium Nb = 41, ///< Nio­bium Mo = 42, ///< Molyb­denum Tc = 43, ///< Tech­netium Ru = 44, ///< Ruthe­nium Rh = 45, ///< Rho­dium Pd = 46, ///< Pallad­ium Ag = 47, ///< Silver Cd = 48, ///< Cad­mium In = 49, ///< Indium Sn = 50, ///< Tin Sb = 51, ///< Anti­mony Te = 52, ///< Tellurium I = 53, ///< Iodine Xe = 54, ///< Xenon Cs = 55, ///< Cae­sium Ba = 56, ///< Ba­rium La = 57, ///< Lan­thanum Hf = 72, ///< Haf­nium Ta = 73, ///< Tanta­lum W = 74, ///< Tung­sten Re = 75, ///< Rhe­nium Os = 76, ///< Os­mium Ir = 77, ///< Iridium Pt = 78, ///< Plat­inum Au = 79, ///< Gold Hg = 80, ///< Mer­cury Tl = 81, ///< Thallium Pb = 82, ///< Lead Bi = 83, ///< Bis­muth Po = 84, ///< Polo­nium At = 85, ///< Asta­tine Rn = 86, ///< Radon Fr = 87, ///< Fran­cium Ra = 88, ///< Ra­dium Ac = 89, ///< Actin­ium Rf = 104, ///< Ruther­fordium Db = 105, ///< Dub­nium Sg = 106, ///< Sea­borgium Bh = 107, ///< Bohr­ium Hs = 108, ///< Has­sium Mt = 109, ///< Meit­nerium Ds = 110, ///< Darm­stadtium Rg = 111, ///< Roent­genium Cn = 112, ///< Coper­nicium Nh = 113, ///< Nihon­ium Fl = 114, ///< Flerov­ium Mc = 115, ///< Moscov­ium Lv = 116, ///< Liver­morium Ts = 117, ///< Tenness­ine Og = 118, ///< Oga­nesson Ce = 58, ///< Cerium Pr = 59, ///< Praseo­dymium Nd = 60, ///< Neo­dymium Pm = 61, ///< Prome­thium Sm = 62, ///< Sama­rium Eu = 63, ///< Europ­ium Gd = 64, ///< Gadolin­ium Tb = 65, ///< Ter­bium Dy = 66, ///< Dyspro­sium Ho = 67, ///< Hol­mium Er = 68, ///< Erbium Tm = 69, ///< Thulium Yb = 70, ///< Ytter­bium Lu = 71, ///< Lute­tium Th = 90, ///< Thor­ium Pa = 91, ///< Protac­tinium U = 92, ///< Ura­nium Np = 93, ///< Neptu­nium Pu = 94, ///< Pluto­nium Am = 95, ///< Ameri­cium Cm = 96, ///< Curium Bk = 97, ///< Berkel­ium Cf = 98, ///< Califor­nium Es = 99, ///< Einstei­nium Fm = 100, ///< Fer­mium Md = 101, ///< Mende­levium No = 102, ///< Nobel­ium Lr = 103, ///< Lawren­cium D = 119, ///< Deuterium }; // -------------------------------------------------------------------- /// An enum used to select the desired radius for an atom. /// All values are collected from the wikipedia pages on atom radii enum class radius_type { calculated, ///< Calculated radius from theoretical models empirical, ///< Empirically measured covalent radii /// @deprecated It is a bit unclear where these values came from. So, better not use them covalent_empirical, single_bond, ///< Bond length for a single covalent bond calculated using statistically analysis double_bond, ///< Bond length for a double covalent bond calculated using statistically analysis triple_bond, ///< Bond length for a triple covalent bond calculated using statistically analysis van_der_waals, ///< Radius of an imaginary hard sphere representing the distance of closest approach for another atom type_count ///< Number of radii }; /// @brief The number of radii per element which can be requested from atom_type_info constexpr std::size_t kRadiusTypeCount = static_cast(radius_type::type_count); /// An enum used to select either the effective or the crystal radius of an ion. /// See explanation on Wikipedia: https://en.wikipedia.org/wiki/Ionic_radius enum class ionic_radius_type { effective, ///< Based on distance between ions in a crystal structure as determined by X-ray crystallography crystal ///< Calculated ion radius based on a function of ionic charge and spin }; /// Requests for an unknown radius value return kNA constexpr float kNA = std::numeric_limits::quiet_NaN(); /// A struct holding the known information for all elements defined in atom_type struct atom_type_info { /// The type as an atom_type atom_type type; /// The official name for this element std::string name; /// The official symbol for this element std::string symbol; /// The weight of this element float weight; /// A flag indicating whether the element is a metal bool metal; /// Array containing all known radii for this element. A value of kNA is /// stored for unknown values float radii[kRadiusTypeCount]; }; /// Array of atom_type_info struct for each of the defined elements in atom_type extern CIFPP_EXPORT const atom_type_info kKnownAtoms[]; // -------------------------------------------------------------------- // AtomTypeTraits /// A traits class to access information for known elements class atom_type_traits { public: /// Constructor taking an atom_type \a a atom_type_traits(atom_type a); /// Constructor based on the element as a string in \a symbol atom_type_traits(const std::string &symbol); atom_type type() const { return m_info->type; } ///< Returns the atom_type std::string name() const { return m_info->name; } ///< Returns the name of the element std::string symbol() const { return m_info->symbol; } ///< Returns the symbol of the element float weight() const { return m_info->weight; } ///< Returns the average weight of the element bool is_metal() const { return m_info->metal; } ///< Returns true if the element is a metal /// Return true if the symbol in \a symbol actually exists in the list of known elements in atom_type static bool is_element(const std::string &symbol); /// Return true if the symbol in \a symbol exists and is a metal static bool is_metal(const std::string &symbol); /// @brief Return the radius for the element, use \a type to select which radius to return /// @param type The selector for which radius to return /// @return The requested radius or kNA if not known (or applicable) float radius(radius_type type = radius_type::single_bond) const { if (type >= radius_type::type_count) throw std::invalid_argument("invalid radius requested"); return m_info->radii[static_cast(type)] / 100.f; } /// \brief Return the radius for a charged version of this atom in a solid crystal /// /// \param charge The charge of the ion /// \return The radius of the ion float crystal_ionic_radius(int charge) const; /// \brief Return the radius for a charged version of this atom in a non-solid environment /// /// \param charge The charge of the ion /// \return The radius of the ion float effective_ionic_radius(int charge) const; /// \brief Return the radius for a charged version of this atom, returns the effective radius by default /// /// \param charge The charge of the ion /// \param type The requested ion radius type /// \return The radius of the ion float ionic_radius(int charge, ionic_radius_type type = ionic_radius_type::effective) const { return type == ionic_radius_type::effective ? effective_ionic_radius(charge) : crystal_ionic_radius(charge); } /** * @brief data type encapsulating the scattering factors * in a simplified form (only a and b). */ struct SFData { /** @cond */ double a[6], b[6]; /** @endcond */ }; /// @brief to get the Cval and Siva scattering factor values, use this constant as charge: static constexpr int kWKSFVal = -99; /// @brief Return the Waasmaier & Kirfel scattering factor values for the element /// /// The coefficients from Waasmaier & Kirfel (1995), Acta Cryst. A51, 416-431. /// /// @param charge The charge for which the structure values should be returned, use kWSKFVal to return the *Cval* and *Siva* values /// @return The scattering factors as a SFData struct const SFData &wksf(int charge = 0) const; /// @brief Return the electron scattering factor values for the element /// /// @return The scattering factors as a SFData struct const SFData &elsf() const; /// Clipper doesn't like atoms with charges that do not have a scattering factor. And /// rightly so, but we need to know in advance if this is the case bool has_sf(int charge) const; private: const struct atom_type_info *m_info; }; } // namespace cif libcifpp-7.0.9/include/cif++/category.hpp0000644000175000017500000013130014746170722020027 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/forward_decl.hpp" #include "cif++/condition.hpp" #include "cif++/iterator.hpp" #include "cif++/row.hpp" #include "cif++/text.hpp" #include "cif++/validate.hpp" #include /** \file category.hpp * Documentation for the cif::category class * * The category class should meet the requirements of Container and * SequenceContainer. * * TODO: implement all of: * https://en.cppreference.com/w/cpp/named_req/Container * https://en.cppreference.com/w/cpp/named_req/SequenceContainer * and more? */ namespace cif { // -------------------------------------------------------------------- // special exceptions /// @brief A duplicate_key_error is thrown when an attempt is made /// to insert a row with values that would introduce a duplicate key /// in the index. Of course, this can only happen if a @ref category_validator /// has been defined for this category. class duplicate_key_error : public std::runtime_error { public: /** * @brief Construct a new duplicate key error object */ duplicate_key_error(const std::string &msg) : std::runtime_error(msg) { } }; /// @brief A missing_key_error is thrown when an attempt is made /// to create an index when one of the key items is missing. class missing_key_error : public std::runtime_error { public: /** * @brief Construct a new duplicate key error object */ missing_key_error(const std::string &msg, const std::string &key) : std::runtime_error(msg) , m_key(key) { } const std::string &get_key() const noexcept { return m_key; } private: std::string m_key; }; /// @brief A multiple_results_error is throw when you request a single /// row using a query but the query contains more than exactly one row. class multiple_results_error : public std::runtime_error { public: /** * @brief Construct a new multiple results error object */ multiple_results_error() : std::runtime_error("query should have returned exactly one row") { } }; // -------------------------------------------------------------------- // These should be moved elsewhere, one day. /// \cond template inline constexpr bool is_optional_v = false; template inline constexpr bool is_optional_v> = true; /// \endcond // -------------------------------------------------------------------- /// The class category is a sequence container for rows of data values. /// You could think of it as a std::vector like class. /// /// A @ref category_validator can be assigned to an object of category /// after which this class can validate contained data and use an /// index to keep key values unique. class category { public: /// \cond friend class row_handle; template friend class iterator_impl; using value_type = row_handle; using reference = value_type; using const_reference = const value_type; using iterator = iterator_impl; using const_iterator = iterator_impl; /// \endcond category() = default; ///< Default constructor category(std::string_view name); ///< Constructor taking a \a name category(const category &rhs); ///< Copy constructor category(category &&rhs) noexcept ///< Move constructor { swap(*this, rhs); } category &operator=(category rhs) ///< assignement operator { swap(*this, rhs); return *this; } /// @brief Destructor /// @note Please note that the destructor is not virtual. It is assumed that /// you will not derive from this class. ~category(); friend void swap(category &a, category &b) noexcept; // -------------------------------------------------------------------- const std::string &name() const { return m_name; } ///< Returns the name of the category [[deprecated("use key_items instead")]] iset key_fields() const; ///< Returns the cif::iset of key item names. Retrieved from the @ref category_validator for this category iset key_items() const; ///< Returns the cif::iset of key item names. Retrieved from the @ref category_validator for this category [[deprecated("use key_item_indices instead")]] std::set key_field_indices() const; ///< Returns a set of indices for the key items. std::set key_item_indices() const; ///< Returns a set of indices for the key items. /// @brief Set the validator for this category to @a v /// @param v The category_validator to assign. A nullptr value is allowed. /// @param db The enclosing @ref datablock void set_validator(const validator *v, datablock &db); /// @brief Update the links in this category /// @param db The enclosing @ref datablock void update_links(const datablock &db); /// @brief Return the global @ref validator for the data /// @return The @ref validator or nullptr if not assigned const validator *get_validator() const { return m_validator; } /// @brief Return the category validator for this category /// @return The @ref category_validator or nullptr if not assigned const category_validator *get_cat_validator() const { return m_cat_validator; } /// @brief Validate the data stored using the assigned @ref category_validator /// @return Returns true is all validations pass bool is_valid() const; /// @brief Validate links, that means, values in this category should have an /// accompanying value in parent categories. /// /// @note /// The code makes one exception when validating missing links and that's between /// *atom_site* and a parent *pdbx_poly_seq_scheme* or *entity_poly_seq*. /// This particular case should be skipped because it is wrong: /// there are atoms that are not part of a polymer, and thus will have no /// parent in those categories. /// /// @return Returns true is all validations pass bool validate_links() const; /// @brief Equality operator, returns true if @a rhs is equal to this /// @param rhs The object to compare with /// @return True if the data contained is equal bool operator==(const category &rhs) const; /// @brief Unequality operator, returns true if @a rhs is not equal to this /// @param rhs The object to compare with /// @return True if the data contained is not equal bool operator!=(const category &rhs) const { return not operator==(rhs); } // -------------------------------------------------------------------- /// @brief Return a reference to the first row in this category. /// @return Reference to the first row in this category. The result is undefined if /// the category is empty. reference front() { return { *this, *m_head }; } /// @brief Return a const reference to the first row in this category. /// @return const reference to the first row in this category. The result is undefined if /// the category is empty. const_reference front() const { return { const_cast(*this), const_cast(*m_head) }; } /// @brief Return a reference to the last row in this category. /// @return Reference to the last row in this category. The result is undefined if /// the category is empty. reference back() { return { *this, *m_tail }; } /// @brief Return a const reference to the last row in this category. /// @return const reference to the last row in this category. The result is undefined if /// the category is empty. const_reference back() const { return { const_cast(*this), const_cast(*m_tail) }; } /// Return an iterator to the first row iterator begin() { return { *this, m_head }; } /// Return an iterator pointing past the last row iterator end() { return { *this, nullptr }; } /// Return a const iterator to the first row const_iterator begin() const { return { *this, m_head }; } /// Return a const iterator pointing past the last row const_iterator end() const { return { *this, nullptr }; } /// Return a const iterator to the first row const_iterator cbegin() const { return { *this, m_head }; } /// Return an iterator pointing past the last row const_iterator cend() const { return { *this, nullptr }; } /// Return a count of the rows in this container std::size_t size() const { return std::distance(cbegin(), cend()); } /// Return the theoretical maximum number or rows that can be stored std::size_t max_size() const { return std::numeric_limits::max(); // this is a bit optimistic, I guess } /// Return true if the category is empty bool empty() const { return m_head == nullptr; } // -------------------------------------------------------------------- // A category can have a key, as defined by the validator/dictionary /// @brief The key type using key_type = row_initializer; /// @brief Return a row_handle for the row specified by \a key /// @param key The value for the key, items specified in the dictionary should have a value /// @return The row found in the index, or an undefined row_handle row_handle operator[](const key_type &key); /// @brief Return a const row_handle for the row specified by \a key /// @param key The value for the key, items specified in the dictionary should have a value /// @return The row found in the index, or an undefined row_handle const row_handle operator[](const key_type &key) const { return const_cast(this)->operator[](key); } // -------------------------------------------------------------------- /// @brief Return a special const iterator for all rows in this category. /// This iterator can be used in a structured binding context. E.g.: /// /// @code{.cpp} /// for (const auto &[name, value] : cat.rows("item_name", "item_value")) /// std::cout << name << ": " << value << '\n'; /// @endcode /// /// @tparam Ts The types for the items requested /// @param names The names for the items requested template iterator_proxy rows(Ns... names) const { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return iterator_proxy(*this, begin(), { names... }); } /// @brief Return a special iterator for all rows in this category. /// This iterator can be used in a structured binding context. E.g.: /// /// @code{.cpp} /// for (const auto &[name, value] : cat.rows("item_name", "item_value")) /// std::cout << name << ": " << value << '\n'; /// /// // or in case we only need one item: /// /// for (int id : cat.rows("id")) /// std::cout << id << '\n'; /// @endcode /// /// @tparam Ts The types for the items requested /// @param names The names for the items requested template iterator_proxy rows(Ns... names) { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return iterator_proxy(*this, begin(), { names... }); } // -------------------------------------------------------------------- /// @brief Return a special iterator to loop over all rows that conform to @a cond /// /// @code{.cpp} /// for (row_handle rh : cat.find(cif::key("first_name") == "John" and cif::key("last_name") == "Doe")) /// .. // do something with rh /// @endcode /// /// @param cond The condition for the query /// @return A special iterator that loops over all elements that match. The iterator can be dereferenced /// to a @ref row_handle conditional_iterator_proxy find(condition &&cond) { return find(begin(), std::move(cond)); } /// @brief Return a special iterator to loop over all rows that conform to @a cond /// starting at @a pos /// /// @param pos Where to start searching /// @param cond The condition for the query /// @return A special iterator that loops over all elements that match. The iterator can be dereferenced /// to a @ref row_handle conditional_iterator_proxy find(iterator pos, condition &&cond) { return { *this, pos, std::move(cond) }; } /// @brief Return a special const iterator to loop over all rows that conform to @a cond /// /// @param cond The condition for the query /// @return A special iterator that loops over all elements that match. The iterator can be dereferenced /// to a const @ref row_handle conditional_iterator_proxy find(condition &&cond) const { return find(cbegin(), std::move(cond)); } /// @brief Return a special const iterator to loop over all rows that conform to @a cond /// starting at @a pos /// /// @param pos Where to start searching /// @param cond The condition for the query /// @return A special iterator that loops over all elements that match. The iterator can be dereferenced /// to a const @ref row_handle conditional_iterator_proxy find(const_iterator pos, condition &&cond) const { return conditional_iterator_proxy{ *this, pos, std::move(cond) }; } /// @brief Return a special iterator to loop over all rows that conform to @a cond. The resulting /// iterator can be used in a structured binding context. /// /// @code{.cpp} /// for (const auto &[name, value] : cat.find(cif::key("item_value") > 10, "item_name", "item_value")) /// std::cout << name << ": " << value << '\n'; /// @endcode /// /// @param cond The condition for the query /// @tparam Ts The types for the items requested /// @param names The names for the items requested /// @return A special iterator that loops over all elements that match. template conditional_iterator_proxy find(condition &&cond, Ns... names) { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return find(cbegin(), std::move(cond), std::forward(names)...); } /// @brief Return a special const iterator to loop over all rows that conform to @a cond. The resulting /// iterator can be used in a structured binding context. /// /// @param cond The condition for the query /// @tparam Ts The types for the items requested /// @param names The names for the items requested /// @return A special iterator that loops over all elements that match. template conditional_iterator_proxy find(condition &&cond, Ns... names) const { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return find(cbegin(), std::move(cond), std::forward(names)...); } /// @brief Return a special iterator to loop over all rows that conform to @a cond starting at @a pos. /// The resulting iterator can be used in a structured binding context. /// /// @param pos Iterator pointing to the location where to start /// @param cond The condition for the query /// @tparam Ts The types for the items requested /// @param names The names for the items requested /// @return A special iterator that loops over all elements that match. template conditional_iterator_proxy find(const_iterator pos, condition &&cond, Ns... names) { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return { *this, pos, std::move(cond), std::forward(names)... }; } /// @brief Return a special const iterator to loop over all rows that conform to @a cond starting at @a pos. /// The resulting iterator can be used in a structured binding context. /// /// @param pos Iterator pointing to the location where to start /// @param cond The condition for the query /// @tparam Ts The types for the items requested /// @param names The names for the items requested /// @return A special iterator that loops over all elements that match. template conditional_iterator_proxy find(const_iterator pos, condition &&cond, Ns... names) const { static_assert(sizeof...(Ts) == sizeof...(Ns), "The number of item names should be equal to the number of types to return"); return { *this, pos, std::move(cond), std::forward(names)... }; } // -------------------------------------------------------------------- // if you only expect a single row /// @brief Return the row handle for the row that matches @a cond Throws @a multiple_results_error if /// there are is not exactly one row matching @a cond /// @param cond The condition to search for /// @return Row handle to the row found row_handle find1(condition &&cond) { return find1(begin(), std::move(cond)); } /// @brief Return the row handle for the row that matches @a cond starting at @a pos /// Throws @a multiple_results_error if there are is not exactly one row matching @a cond /// @param pos The position to start the search /// @param cond The condition to search for /// @return Row handle to the row found row_handle find1(iterator pos, condition &&cond) { auto h = find(pos, std::move(cond)); if (h.size() != 1) throw multiple_results_error(); return *h.begin(); } /// @brief Return the const row handle for the row that matches @a cond Throws @a multiple_results_error if /// there are is not exactly one row matching @a cond /// @param cond The condition to search for /// @return Row handle to the row found const row_handle find1(condition &&cond) const { return find1(cbegin(), std::move(cond)); } /// @brief Return const the row handle for the row that matches @a cond starting at @a pos /// Throws @a multiple_results_error if there are is not exactly one row matching @a cond /// @param pos The position to start the search /// @param cond The condition to search for /// @return Row handle to the row found const row_handle find1(const_iterator pos, condition &&cond) const { auto h = find(pos, std::move(cond)); if (h.size() != 1) throw multiple_results_error(); return *h.begin(); } /// @brief Return value for the item named @a item for the single row that /// matches @a cond. Throws @a multiple_results_error if there are is not exactly one row /// @tparam The type to use for the result /// @param cond The condition to search for /// @param item The name of the item to return the value for /// @return The value found template T find1(condition &&cond, std::string_view item) const { return find1(cbegin(), std::move(cond), item); } /// @brief Return value for the item named @a item for the single row that /// matches @a cond when starting to search at @a pos. /// Throws @a multiple_results_error if there are is not exactly one row /// @tparam The type to use for the result /// @param pos The location to start the search /// @param cond The condition to search for /// @param item The name of the item to return the value for /// @return The value found template , int> = 0> T find1(const_iterator pos, condition &&cond, std::string_view item) const { auto h = find(pos, std::move(cond), item); if (h.size() != 1) throw multiple_results_error(); return *h.begin(); } /// @brief Return a value of type std::optional for the item named @a item for the single row that /// matches @a cond when starting to search at @a pos. /// If the row was not found, an empty value is returned. /// @tparam The type to use for the result /// @param pos The location to start the search /// @param cond The condition to search for /// @param item The name of the item to return the value for /// @return The value found, can be empty if no row matches the condition template , int> = 0> T find1(const_iterator pos, condition &&cond, std::string_view item) const { auto h = find(pos, std::move(cond), item); if (h.size() > 1) throw multiple_results_error(); if (h.empty()) return {}; return *h.begin(); } /// @brief Return a std::tuple for the values for the items named in @a items /// for the single row that matches @a cond /// Throws @a multiple_results_error if there are is not exactly one row /// @tparam The types to use for the resulting tuple /// @param cond The condition to search for /// @param items The names of the items to return the value for /// @return The values found as a single tuple of type std::tuple template > std::tuple find1(condition &&cond, Cs... items) const { static_assert(sizeof...(Ts) == sizeof...(Cs), "The number of item names should be equal to the number of types to return"); // static_assert(std::is_same_v..., "The item names should be const char"); return find1(cbegin(), std::move(cond), std::forward(items)...); } /// @brief Return a std::tuple for the values for the items named in @a items /// for the single row that matches @a cond when starting to search at @a pos /// Throws @a multiple_results_error if there are is not exactly one row /// @tparam The types to use for the resulting tuple /// @param pos The location to start the search /// @param cond The condition to search for /// @param items The names of the items to return the value for /// @return The values found as a single tuple of type std::tuple template > std::tuple find1(const_iterator pos, condition &&cond, Cs... items) const { static_assert(sizeof...(Ts) == sizeof...(Cs), "The number of item names should be equal to the number of types to return"); auto h = find(pos, std::move(cond), std::forward(items)...); if (h.size() != 1) throw multiple_results_error(); return *h.begin(); } // -------------------------------------------------------------------- // if you want only a first hit /// @brief Return a row handle to the first row that matches @a cond /// @param cond The condition to search for /// @return The handle to the row that matches or an empty row_handle row_handle find_first(condition &&cond) { return find_first(begin(), std::move(cond)); } /// @brief Return a row handle to the first row that matches @a cond starting at @a pos /// @param pos The location to start searching /// @param cond The condition to search for /// @return The handle to the row that matches or an empty row_handle row_handle find_first(iterator pos, condition &&cond) { auto h = find(pos, std::move(cond)); return h.empty() ? row_handle{} : *h.begin(); } /// @brief Return a const row handle to the first row that matches @a cond /// @param cond The condition to search for /// @return The const handle to the row that matches or an empty row_handle const row_handle find_first(condition &&cond) const { return find_first(cbegin(), std::move(cond)); } /// @brief Return a const row handle to the first row that matches @a cond starting at @a pos /// @param pos The location to start searching /// @param cond The condition to search for /// @return The const handle to the row that matches or an empty row_handle const row_handle find_first(const_iterator pos, condition &&cond) const { auto h = find(pos, std::move(cond)); return h.empty() ? row_handle{} : *h.begin(); } /// @brief Return the value for item @a item for the first row that matches condition @a cond /// @tparam The type of the value to return /// @param cond The condition to search for /// @param item The item for which the value should be returned /// @return The value found or a default constructed value if not found template T find_first(condition &&cond, std::string_view item) const { return find_first(cbegin(), std::move(cond), item); } /// @brief Return the value for item @a item for the first row that matches condition @a cond /// when starting the search at @a pos /// @tparam The type of the value to return /// @param pos The location to start searching /// @param cond The condition to search for /// @param item The item for which the value should be returned /// @return The value found or a default constructed value if not found template T find_first(const_iterator pos, condition &&cond, std::string_view item) const { auto h = find(pos, std::move(cond), item); return h.empty() ? T{} : *h.begin(); } /// @brief Return a tuple containing the values for the items @a items for the first row that matches condition @a cond /// @tparam The types of the values to return /// @param cond The condition to search for /// @param items The items for which the values should be returned /// @return The values found or default constructed values if not found template > std::tuple find_first(condition &&cond, Cs... items) const { static_assert(sizeof...(Ts) == sizeof...(Cs), "The number of item names should be equal to the number of types to return"); // static_assert(std::is_same_v..., "The item names should be const char"); return find_first(cbegin(), std::move(cond), std::forward(items)...); } /// @brief Return a tuple containing the values for the items @a items for the first row that matches condition @a cond /// when starting the search at @a pos /// @tparam The types of the values to return /// @param pos The location to start searching /// @param cond The condition to search for /// @param items The items for which the values should be returned /// @return The values found or default constructed values if not found template > std::tuple find_first(const_iterator pos, condition &&cond, Cs... items) const { static_assert(sizeof...(Ts) == sizeof...(Cs), "The number of item names should be equal to the number of types to return"); auto h = find(pos, std::move(cond), std::forward(items)...); return h.empty() ? std::tuple{} : *h.begin(); } // -------------------------------------------------------------------- /// @brief Return the maximum value for item @a item for all rows that match condition @a cond /// @tparam The type of the value to return /// @param item The item to use for the value /// @param cond The condition to search for /// @return The value found or the minimal value for the type template , int> = 0> T find_max(std::string_view item, condition &&cond) const { T result = std::numeric_limits::min(); for (auto v : find(std::move(cond), item)) { if (result < v) result = v; } return result; } /// @brief Return the maximum value for item @a item for all rows /// @tparam The type of the value to return /// @param item The item to use for the value /// @return The value found or the minimal value for the type template , int> = 0> T find_max(std::string_view item) const { return find_max(item, all()); } /// @brief Return the minimum value for item @a item for all rows that match condition @a cond /// @tparam The type of the value to return /// @param item The item to use for the value /// @param cond The condition to search for /// @return The value found or the maximum value for the type template , int> = 0> T find_min(std::string_view item, condition &&cond) const { T result = std::numeric_limits::max(); for (auto v : find(std::move(cond), item)) { if (result > v) result = v; } return result; } /// @brief Return the maximum value for item @a item for all rows /// @tparam The type of the value to return /// @param item The item to use for the value /// @return The value found or the maximum value for the type template , int> = 0> T find_min(std::string_view item) const { return find_min(item, all()); } /// @brief Return whether a row exists that matches condition @a cond /// @param cond The condition to match /// @return True if a row exists [[deprecated("Use contains instead")]] bool exists(condition &&cond) const { return contains(std::move(cond)); } /// @brief Return whether a row exists that matches condition @a cond /// @param cond The condition to match /// @return True if a row exists bool contains(condition &&cond) const { bool result = false; if (cond) { cond.prepare(*this); auto sh = cond.single(); if (sh.has_value() and *sh) result = true; else { for (auto r : *this) { if (cond(r)) { result = true; break; } } } } return result; } /// @brief Return the total number of rows that match condition @a cond /// @param cond The condition to match /// @return The count std::size_t count(condition &&cond) const { std::size_t result = 0; if (cond) { cond.prepare(*this); auto sh = cond.single(); if (sh.has_value() and *sh) result = 1; else { for (auto r : *this) { if (cond(r)) ++result; } } } return result; } // -------------------------------------------------------------------- /// Using the relations defined in the validator, return whether the row /// in @a r has any children in other categories bool has_children(row_handle r) const; /// Using the relations defined in the validator, return whether the row /// in @a r has any parents in other categories bool has_parents(row_handle r) const; /// Using the relations defined in the validator, return the row handles /// for all rows in @a childCat that are linked to row @a r std::vector get_children(row_handle r, const category &childCat) const; /// Using the relations defined in the validator, return the row handles /// for all rows in @a parentCat that are linked to row @a r std::vector get_parents(row_handle r, const category &parentCat) const; /// Using the relations defined in the validator, return the row handles /// for all rows in @a cat that are in any way linked to row @a r std::vector get_linked(row_handle r, const category &cat) const; // -------------------------------------------------------------------- // void insert(const_iterator pos, const row_initializer &row) // { // insert_impl(pos, row); // } // void insert(const_iterator pos, row_initializer &&row) // { // insert_impl(pos, std::move(row)); // } /// Erase the row pointed to by @a pos and return the iterator to the /// row following pos. iterator erase(iterator pos); /// Erase row @a rh void erase(row_handle rh) { erase(iterator(*this, rh.m_row)); } /// @brief Erase all rows that match condition @a cond /// @param cond The condition /// @return The number of rows that have been erased std::size_t erase(condition &&cond); /// @brief Erase all rows that match condition @a cond calling /// the visitor function @a visit for each before actually erasing it. /// @param cond The condition /// @param visit The visitor function /// @return The number of rows that have been erased std::size_t erase(condition &&cond, std::function &&visit); /// @brief Emplace the values in @a ri in a new row /// @param ri An object containing the values to insert /// @return iterator to the newly created row iterator emplace(row_initializer &&ri) { return this->emplace(ri.begin(), ri.end()); } /// @brief Create a new row and emplace the values in the range @a b to @a e in it /// @param b Iterator to the beginning of the range of @ref item_value /// @param e Iterator to the end of the range of @ref item_value /// @return iterator to the newly created row template iterator emplace(ItemIter b, ItemIter e) { row *r = this->create_row(); try { for (auto i = b; i != e; ++i) { // item_value *new_item = this->create_item(*i); r->append(add_item(i->name()), { i->value() }); } } catch (...) { if (r != nullptr) this->delete_row(r); throw; } return insert_impl(cend(), r); } /// @brief Completely erase all rows contained in this category void clear(); // -------------------------------------------------------------------- /// \brief generate a new, unique ID. Pass it an ID generating function /// based on a sequence number. This function will be called until the /// result is unique in the context of this category std::string get_unique_id(std::function generator = cif::cif_id_for_number); /// @brief Generate a new, unique ID based on a string prefix followed by a number /// @param prefix The string prefix /// @return a new unique ID std::string get_unique_id(const std::string &prefix) { return get_unique_id([prefix](int nr) { return prefix + std::to_string(nr + 1); }); } /// @brief Generate a new, unique value for a item named @a item_name /// @param item_name The name of the item /// @return a new unique value std::string get_unique_value(std::string_view item_name); // -------------------------------------------------------------------- using value_provider_type = std::function; /// \brief Update a single item named @a item_name in the rows that match /// \a cond to values provided by a callback function \a value_provider /// making sure the linked categories are updated according to the link. /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. void update_value(condition &&cond, std::string_view item_name, value_provider_type &&value_provider) { auto rs = find(std::move(cond)); std::vector rows; std::copy(rs.begin(), rs.end(), std::back_inserter(rows)); update_value(rows, item_name, std::move(value_provider)); } /// \brief Update a single item named @a item_name in the rows \a rows /// to values provided by a callback function \a value_provider /// making sure the linked categories are updated according to the link. /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. void update_value(const std::vector &rows, std::string_view item_name, value_provider_type &&value_provider); /// \brief Update a single item named @a item_name in the rows that match \a cond to value \a value /// making sure the linked categories are updated according to the link. /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. void update_value(condition &&cond, std::string_view item_name, std::string_view value) { auto rs = find(std::move(cond)); std::vector rows; std::copy(rs.begin(), rs.end(), std::back_inserter(rows)); update_value(rows, item_name, value); } /// \brief Update a single item named @a item_name in @a rows to value \a value /// making sure the linked categories are updated according to the link. /// That means, child categories are updated if the links are absolute /// and unique. If they are not, the child category rows are split. void update_value(const std::vector &rows, std::string_view item_name, std::string_view value) { update_value(rows, item_name, [value](std::string_view) { return value; }); } // -------------------------------------------------------------------- // Naming used to be very inconsistent. For backward compatibility, // the old function names are here as deprecated variants. /// \brief Return the index number for \a column_name [[deprecated("Use get_item_ix instead")]] uint16_t get_column_ix(std::string_view column_name) const { return get_item_ix(column_name); } /// @brief Return the name for column with index @a ix /// @param ix The index number /// @return The name of the column [[deprecated("use get_item_name instead")]] std::string_view get_column_name(uint16_t ix) const { return get_item_name(ix); } /// @brief Make sure a item with name @a item_name is known and return its index number /// @param item_name The name of the item /// @return The index number of the item [[deprecated("use add_item instead")]] uint16_t add_column(std::string_view item_name) { return add_item(item_name); } /** @brief Remove column name @a colum_name * @param column_name The column to be removed */ [[deprecated("use remove_item instead")]] void remove_column(std::string_view column_name) { remove_item(column_name); } /** @brief Rename column @a from_name to @a to_name */ [[deprecated("use rename_item instead")]] void rename_column(std::string_view from_name, std::string_view to_name) { rename_item(from_name, to_name); } /// @brief Return whether a column with name @a name exists in this category /// @param name The name of the column /// @return True if the column exists [[deprecated("use has_item instead")]] bool has_column(std::string_view name) const { return has_item(name); } /// @brief Return the cif::iset of columns in this category [[deprecated("use get_items instead")]] iset get_columns() const { return get_items(); } // -------------------------------------------------------------------- /// \brief Return the index number for \a item_name uint16_t get_item_ix(std::string_view item_name) const { uint16_t result; for (result = 0; result < m_items.size(); ++result) { if (iequals(item_name, m_items[result].m_name)) break; } if (VERBOSE > 0 and result == m_items.size() and m_cat_validator != nullptr) // validate the name, if it is known at all (since it was not found) { auto iv = m_cat_validator->get_validator_for_item(item_name); if (iv == nullptr) std::cerr << "Invalid name used '" << item_name << "' is not a known item in " + m_name << '\n'; } return result; } /// @brief Return the name for item with index @a ix /// @param ix The index number /// @return The name of the item std::string_view get_item_name(uint16_t ix) const { if (ix >= m_items.size()) throw std::out_of_range("item index is out of range"); return m_items[ix].m_name; } /// @brief Make sure a item with name @a item_name is known and return its index number /// @param item_name The name of the item /// @return The index number of the item uint16_t add_item(std::string_view item_name) { using namespace std::literals; uint16_t result = get_item_ix(item_name); if (result == m_items.size()) { const item_validator *item_validator = nullptr; if (m_cat_validator != nullptr) { item_validator = m_cat_validator->get_validator_for_item(item_name); if (item_validator == nullptr) m_validator->report_error(validation_error::item_not_allowed_in_category, m_name, item_name, false); } m_items.emplace_back(item_name, item_validator); } return result; } /** @brief Remove item name @a colum_name * @param item_name The item to be removed */ void remove_item(std::string_view item_name); /** @brief Rename item @a from_name to @a to_name */ void rename_item(std::string_view from_name, std::string_view to_name); /// @brief Return whether a item with name @a name exists in this category /// @param name The name of the item /// @return True if the item exists bool has_item(std::string_view name) const { return get_item_ix(name) < m_items.size(); } /// @brief Return the cif::iset of items in this category iset get_items() const; // -------------------------------------------------------------------- /// @brief Sort the rows using comparator function @a f /// @param f The comparator function taking two row_handles and returning /// an int indicating whether the first is smaller, equal or larger than /// the second. ( respectively a value <0, 0, or >0 ) void sort(std::function f); /// @brief Reorder the rows in the category using the index defined by /// the @ref category_validator void reorder_by_index(); // -------------------------------------------------------------------- /// This function returns effectively the list of fully qualified item /// names, that is category_name + '.' + item_name for each item [[deprecated("use get_item_order instead")]] std::vector get_tag_order() const { return get_item_order(); } /// This function returns effectively the list of fully qualified item /// names, that is category_name + '.' + item_name for each item std::vector get_item_order() const; /// Write the contents of the category to the std::ostream @a os void write(std::ostream &os) const; /// @brief Write the contents of the category to the std::ostream @a os and /// use @a order as the order of the items. If @a addMissingItems is /// false, items that do not contain any value will be suppressed /// @param os The std::ostream to write to /// @param order The order in which the items should appear /// @param addMissingItems When false, empty items are suppressed from the output void write(std::ostream &os, const std::vector &order, bool addMissingItems = true); private: void write(std::ostream &os, const std::vector &order, bool includeEmptyItems) const; public: /// friend function to make it possible to do: /// @code {.cpp} /// std::cout << my_category; /// @endcode friend std::ostream &operator<<(std::ostream &os, const category &cat) { cat.write(os); return os; } private: void update_value(row *row, uint16_t item, std::string_view value, bool updateLinked, bool validate = true); void erase_orphans(condition &&cond, category &parent); using allocator_type = std::allocator; constexpr allocator_type get_allocator() const { return {}; } using char_allocator_type = typename std::allocator_traits::template rebind_alloc; using char_allocator_traits = std::allocator_traits; using row_allocator_type = typename std::allocator_traits::template rebind_alloc; using row_allocator_traits = std::allocator_traits; row_allocator_traits::pointer get_row() { row_allocator_type ra(get_allocator()); return row_allocator_traits::allocate(ra, 1); } row *create_row() { auto p = this->get_row(); row_allocator_type ra(get_allocator()); row_allocator_traits::construct(ra, p); return p; } row *clone_row(const row &r); void delete_row(row *r); row_handle create_copy(row_handle r); struct item_entry { std::string m_name; const item_validator *m_validator; item_entry(std::string_view name, const item_validator *validator) : m_name(name) , m_validator(validator) { } }; struct link { link(category *linked, const link_validator *v) : linked(linked) , v(v) { } // TODO: NEED TO FIX THIS! category *linked; const link_validator *v; }; // proxy methods for every insertion iterator insert_impl(const_iterator pos, row *n); iterator erase_impl(const_iterator pos); // -------------------------------------------------------------------- condition get_parents_condition(row_handle rh, const category &parentCat) const; condition get_children_condition(row_handle rh, const category &childCat) const; // -------------------------------------------------------------------- void swap_item(uint16_t item_ix, row_handle &a, row_handle &b); // -------------------------------------------------------------------- std::string m_name; std::vector m_items; const validator *m_validator = nullptr; const category_validator *m_cat_validator = nullptr; std::vector m_parent_links, m_child_links; bool m_cascade = true; uint32_t m_last_unique_num = 0; class category_index *m_index = nullptr; row *m_head = nullptr, *m_tail = nullptr; }; } // namespace cif libcifpp-7.0.9/include/cif++/compound.hpp0000644000175000017500000003204714746170722020046 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020-2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/atom_type.hpp" #include "cif++/datablock.hpp" #include "cif++/exports.hpp" #include "cif++/point.hpp" #include "cif++/utilities.hpp" #include #include #include #include /// \file compound.hpp /// This file contains the definition for the class compound, encapsulating /// the information found for compounds in the CCD. /// /// The data is loaded by default from a file called `components.cif`. This file /// is located using load_resource. (See documentation on cif::load_resource for more information) /// /// Note that since version 6 the CCP4 monomer library is no longer used. /// See also :doc:`/compound` for more information. namespace cif { // -------------------------------------------------------------------- class compound; struct compound_atom; class compound_factory_impl; /// \brief The bond type or bond order as defined in the CCD, possible values taken from the mmcif_pdbx file enum class bond_type { sing, ///< single bond doub, ///< double bond trip, ///< triple bond quad, ///< quadruple bond arom, ///< aromatic bond poly, ///< polymeric bond delo, ///< delocalized double bond pi, ///< pi bond }; /// @brief return the string representation of @a bondType std::string bond_type_to_string(bond_type bondType); /// @brief return the cif::bond_type for the string representation @a bondType bond_type parse_bond_type_from_string(const std::string &bondType); /// \brief The possible stereo config values for a compound_atom. /// /// As the site https://psiberg.com/r-s-nomenclature/ states: /// /// > RS nomenclature is currently the preferred system for assigning absolute /// > configuration to chiral molecules. The letters R and S come from the Latin /// > words ‘Rectus‘ and ‘Sinister‘ meaning ‘right’ and ‘left’. Molecules that /// > rotate the plane of polarized light to right are referred to as ‘R isomers’ /// > and the molecules that rotate the plane of polarized light to left are /// > referred to ‘S isomers’. enum class stereo_config_type : uint8_t { N = 'N', ///< Not polarizing R = 'R', ///< Rectus S = 'S' ///< Sinister }; /// @brief return the string representation of @a stereo_config std::string to_string(stereo_config_type stereo_config); /// @brief return the cif::stereo_config_type for the string representation @a stereo_config stereo_config_type parse_stereo_config_from_string(const std::string &stereo_config); /// -------------------------------------------------------------------- /// \brief struct containing information about an atom in a chemical compound. /// This is a subset of the available information. Contact the author if you need more fields. struct compound_atom { std::string id; ///< Identifier for each atom in the chemical component atom_type type_symbol; ///< The element type for each atom in the chemical component. int charge = 0; ///< The formal charge assigned to each atom in the chemical component. bool aromatic = false; ///< Defines atoms in an aromatic moiety bool leaving_atom = false; ///< Flags atoms with "leaving" capability stereo_config_type stereo_config = stereo_config_type::N; ///< Defines the stereochemical configuration of the chiral center atom. float x, ///< The x component of the coordinates for each atom specified as orthogonal angstroms. y, ///< The y component of the coordinates for each atom specified as orthogonal angstroms. z; ///< The z component of the coordinates for each atom specified as orthogonal angstroms. /// Return the location of the atom as a point point get_location() const { return { x, y, z }; } }; /// -------------------------------------------------------------------- /// \brief struct containing information about the bonds struct compound_bond { std::string atom_id[2]; ///< The ID's of the two atoms that define the bond. bond_type type; ///< The bond order of the chemical bond associated with the specified atoms. bool aromatic = false, ///< Defines aromatic bonds. stereo_config = false; ///< Defines stereochemical bonds. }; /// -------------------------------------------------------------------- /// \brief a class that contains information about a chemical compound. /// This information is derived from the CDD by default. /// /// To create compounds, you use the factory method. You can add your own /// compound definitions by calling the push_dictionary function and /// pass it a valid CCD formatted file. class compound { public: // accessors std::string id() const { return m_id; } ///< Return the alphanumeric code for the chemical component. std::string name() const { return m_name; } ///< Return the name of the chemical component. std::string type() const { return m_type; } ///< Return the type of monomer. std::string formula() const { return m_formula; } ///< Return the chemical formula of the chemical component. float formula_weight() const { return m_formula_weight; } ///< Return the formula mass of the chemical component in Daltons. int formal_charge() const { return m_formal_charge; } ///< Return the formal charge on the chemical component. const std::vector &atoms() const { return m_atoms; } ///< Return the list of atoms for this compound const std::vector &bonds() const { return m_bonds; } ///< Return the list of bonds for this compound compound_atom get_atom_by_atom_id(const std::string &atom_id) const; ///< Return the atom with id @a atom_id bool atoms_bonded(const std::string &atomId_1, const std::string &atomId_2) const; ///< Return true if @a atomId_1 is bonded to @a atomId_2 float bond_length(const std::string &atomId_1, const std::string &atomId_2) const; ///< Return the bond length between @a atomId_1 and @a atomId_2 bool is_water() const ///< Return if the compound is actually a water { return m_id == "HOH" or m_id == "H2O" or m_id == "WAT"; } /** \brief Return whether this compound has a type of either 'peptide linking' or 'L-peptide linking' */ bool is_peptide() const; /** \brief Return whether this compound has a type of either 'DNA linking' or 'RNA linking' */ bool is_base() const; char one_letter_code() const { return m_one_letter_code; }; ///< Return the one letter code to use in a canonical sequence. If unknown the value '\0' is returned std::string parent_id() const { return m_parent_id; }; ///< Return the parent id code in case a parent is specified (e.g. MET for MSE) private: friend class compound_factory_impl; friend class local_compound_factory_impl; compound(cif::datablock &db); std::string m_id; std::string m_name; std::string m_type; std::string m_formula; char m_one_letter_code = 0; std::string m_parent_id; float m_formula_weight = 0; int m_formal_charge = 0; std::vector m_atoms; std::vector m_bonds; }; // -------------------------------------------------------------------- // Factory class for compound and Link objects /// Use the compound_factory singleton instance to create compound objects class compound_factory { public: /// \brief Initialise a singleton instance. /// /// If you have a multithreaded application and want to have different /// compounds in each thread (e.g. a web service processing user requests /// with different sets of compounds) you can set the \a useThreadLocalInstanceOnly /// flag to true. static void init(bool useThreadLocalInstanceOnly); /// Return the singleton instance. If initialized with local threads, this is the /// instance for the current thread. static compound_factory &instance(); /// Delete and reset the singleton instance. If initialized with local threads, this is the /// instance for the current thread. static void clear(); /// Set the default dictionary file to @a inDictFile void set_default_dictionary(const std::filesystem::path &inDictFile); /// Override any previously loaded dictionary with @a inDictFile void push_dictionary(const std::filesystem::path &inDictFile); /** @brief Override any previously loaded dictionary with the data in @a file * * @note experimental feature * * Load the file @a file as a source for compound information. This may * be e.g. a regular mmCIF file with extra files containing compound * information. * * Be carefull to remove the block again, best use @ref cif::compound_source * as a stack based object. */ void push_dictionary(const file &file); /// Remove the last pushed dictionary void pop_dictionary(); /// Return whether @a res_name is a valid and known peptide [[deprecated("use is_peptide or is_std_peptide instead)")]] bool is_known_peptide(const std::string &res_name) const; /// Return whether @a res_name is a valid and known base [[deprecated("use is_base or is_std_base instead)")]] bool is_known_base(const std::string &res_name) const; /// Return whether @a res_name is a peptide bool is_peptide(std::string_view res_name) const; /// Return whether @a res_name is a base bool is_base(std::string_view res_name) const; /// Return whether @a res_name is one of the standard peptides bool is_std_peptide(std::string_view res_name) const; /// Return whether @a res_name is one of the standard bases bool is_std_base(std::string_view res_name) const; /// Return whether @a res_name is a monomer (either base or peptide) bool is_monomer(std::string_view res_name) const; /// Return whether @a res_name is one of the standard bases or peptides bool is_std_monomer(std::string_view res_name) const { return is_std_base(res_name) or is_std_peptide(res_name); } bool is_water(std::string_view res_name) const { return res_name == "HOH" or res_name == "H2O" or res_name == "WAT"; } /// \brief Create the compound object for \a id /// /// This will create the compound instance for \a id if it doesn't exist already. /// The result is owned by this factory and should not be deleted by the user. /// \param id The compound ID, a three letter code usually /// \result The compound, or nullptr if it could not be created (missing info) const compound *create(std::string_view id); ~compound_factory(); CIFPP_EXPORT static const std::map kAAMap, ///< Globally accessible static list of the default amino acids kBaseMap; ///< Globally accessible static list of the default bases void report_missing_compound(std::string_view compound_id); private: compound_factory(); compound_factory(const compound_factory &) = delete; compound_factory &operator=(const compound_factory &) = delete; static std::unique_ptr s_instance; static thread_local std::unique_ptr tl_instance; static bool s_use_thread_local_instance; std::shared_ptr m_impl; }; // -------------------------------------------------------------------- /** * @brief Stack based source for compound info. * * Use this class to temporarily add a compound source to the * compound_factory. * * @code{.cpp} * cif::file f("1cbs-with-custom-rea.cif"); * cif::compound_source cs(f); * * auto &cf = cif::compound_factory::instance(); * auto rea_compound = cf.create("REA"); * @endcode */ class compound_source { public: compound_source(const cif::file &file) { cif::compound_factory::instance().push_dictionary(file); } ~compound_source() { cif::compound_factory::instance().pop_dictionary(); } }; } // namespace cif libcifpp-7.0.9/include/cif++/condition.hpp0000644000175000017500000010170214746170722020203 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/row.hpp" #include #include #include #include #include #include /** \file condition.hpp * This file contains code to create conditions: object encapsulating a * query you can use to find rows in a @ref cif::category * * Conditions are created as standard C++ expressions. That means * you can use the standard comparison operators to compare item * contents with a value and boolean operators to chain everything * together. * * To create a query that simply compares one item with one value: * * @code {.cpp} * cif::condition c = cif::key("id") == 1; * @endcode * * That will find rows where the ID item contains the number 1. If * using cif::key is a bit too much typing, you can also write: * * @code{.cpp} * using namespace cif::literals; * * cif::condition c2 = "id"_key == 1; * @endcode * * Now if you want both ID = 1 and ID = 2 in the result: * * @code{.cpp} * auto c3 = "id"_key == 1 or "id"_key == 2; * @endcode * * There are some special values you can use. To find rows with item that * do not have a value: * * @code{.cpp} * auto c4 = "type"_key == cif::null; * @endcode * * Of if it should not be NULL: * * @code{.cpp} * auto c5 = "type"_key != cif::null; * @endcode * * There's even a way to find all records: * * @code{.cpp} * auto c6 = cif::all; * @endcode * * And when you want to search for any item containing the value 'foo': * * @code{.cpp} * auto c7 = cif::any == "foo"; * @endcode * * All these conditions can be chained together again: * * @code{.cpp} * auto c8 = std::move(c3) and std::move(c5); * @endcode */ namespace cif { // -------------------------------------------------------------------- /// let's make life easier, since @ref cif::category is not known yet, /// we declare a function to access its contents /** * @brief Get the items that can be used as key in conditions for a category * * @param cat The category whose items to return * @return iset The set of key item names */ [[deprecated("use get_category_items instead")]] iset get_category_fields(const category &cat); /** * @brief Get the items that can be used as key in conditions for a category * * @param cat The category whose items to return * @return iset The set of key field names */ iset get_category_items(const category &cat); /** * @brief Get the item index for item @a col in category @a cat * * @param cat The category * @param col The name of the item * @return uint16_t The index */ uint16_t get_item_ix(const category &cat, std::string_view col); /** * @brief Return whether the item @a col in category @a cat has a primitive type of *uchar* * * @param cat The category * @param col The item name * @return true If the primitive type is of type *uchar* * @return false If the primitive type is not of type *uchar* */ bool is_item_type_uchar(const category &cat, std::string_view col); // -------------------------------------------------------------------- // some more templates to be able to do querying namespace detail { struct condition_impl { virtual ~condition_impl() {} virtual condition_impl *prepare(const category &) { return this; } virtual bool test(row_handle) const = 0; virtual void str(std::ostream &) const = 0; virtual std::optional single() const { return {}; }; virtual bool equals([[maybe_unused]] const condition_impl *rhs) const { return false; } }; struct all_condition_impl : public condition_impl { bool test(row_handle) const override { return true; } void str(std::ostream &os) const override { os << "*"; } }; struct or_condition_impl; struct and_condition_impl; struct not_condition_impl; } // namespace detail /** * @brief The interface class for conditions. This uses the bridge pattern, * which means the implementation is in the member m_impl */ class condition { public: /** @cond */ using condition_impl = detail::condition_impl; /** @endcond */ /** * @brief Construct a new, empty condition object * */ condition() : m_impl(nullptr) { } /** * @brief Construct a new condition object with implementation @a impl * * @param impl The implementation to use */ explicit condition(condition_impl *impl) : m_impl(impl) { } condition(const condition &) = delete; /** * @brief Construct a new condition object moving the data from @a rhs */ condition(condition &&rhs) noexcept : m_impl(nullptr) { std::swap(m_impl, rhs.m_impl); } condition &operator=(const condition &) = delete; /** * @brief Assignment operator moving the data from @a rhs */ condition &operator=(condition &&rhs) noexcept { std::swap(m_impl, rhs.m_impl); return *this; } ~condition() { delete m_impl; m_impl = nullptr; } /** * @brief Prepare the condition to be used on category @a c. This will * take care of setting the correct indices for items e.g. * * @param c The category this query should act upon */ void prepare(const category &c); /** * @brief This operator returns true if the row referenced by @a r is * a match for this condition. * * @param r The reference to a row. * @return true If there is a match * @return false If there is no match */ bool operator()(row_handle r) const { assert(this->m_impl != nullptr); assert(this->m_prepared); return m_impl ? m_impl->test(r) : false; } /** * @brief Return true if the condition is not empty */ explicit operator bool() { return not empty(); } /** * @brief Return true if the condition is empty, has no condition */ bool empty() const { return m_impl == nullptr; } /** * @brief If the prepare step found out there is only one hit * this single hit can be returned by this method. * * @return std::optional The result will contain * a row reference if there is a single hit, it will be empty otherwise */ std::optional single() const { return m_impl ? m_impl->single() : std::optional(); } friend condition operator||(condition &&a, condition &&b); /**< Return a condition which is the logical OR or condition @a and @b */ friend condition operator&&(condition &&a, condition &&b); /**< Return a condition which is the logical AND or condition @a and @b */ /// @cond friend struct detail::or_condition_impl; friend struct detail::and_condition_impl; friend struct detail::not_condition_impl; /// @endcond /** * @brief Swap two conditions */ void swap(condition &rhs) { std::swap(m_impl, rhs.m_impl); std::swap(m_prepared, rhs.m_prepared); } /** * @brief Operator to use to write out a condition to @a os, for debugging purposes * * @param os The std::ostream to write to * @param cond The condition to write * @return std::ostream& The same as @a os */ friend std::ostream &operator<<(std::ostream &os, const condition &cond) { if (cond.m_impl) cond.m_impl->str(os); return os; } private: void optimise(condition_impl *&impl); condition_impl *m_impl; bool m_prepared = false; }; namespace detail { struct key_is_empty_condition_impl : public condition_impl { key_is_empty_condition_impl(const std::string &item_name) : m_item_name(item_name) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); return this; } bool test(row_handle r) const override { return r[m_item_ix].empty(); } void str(std::ostream &os) const override { os << m_item_name << " IS NULL"; } std::string m_item_name; uint16_t m_item_ix = 0; }; struct key_is_not_empty_condition_impl : public condition_impl { key_is_not_empty_condition_impl(const std::string &item_name) : m_item_name(item_name) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); return this; } bool test(row_handle r) const override { return not r[m_item_ix].empty(); } void str(std::ostream &os) const override { os << m_item_name << " IS NOT NULL"; } std::string m_item_name; uint16_t m_item_ix = 0; }; struct key_equals_condition_impl : public condition_impl { key_equals_condition_impl(item &&i) : m_item_name(i.name()) , m_value(std::forward(i).value()) { } condition_impl *prepare(const category &c) override; bool test(row_handle r) const override { return m_single_hit.has_value() ? *m_single_hit == r : r[m_item_ix].compare(m_value, m_icase) == 0; } void str(std::ostream &os) const override { os << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value; } virtual std::optional single() const override { return m_single_hit; } virtual bool equals(const condition_impl *rhs) const override { if (typeid(*rhs) == typeid(key_equals_condition_impl)) { auto ri = static_cast(rhs); if (m_single_hit.has_value() or ri->m_single_hit.has_value()) return m_single_hit == ri->m_single_hit; else // watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category) return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name; } return this == rhs; } std::string m_item_name; uint16_t m_item_ix = 0; bool m_icase = false; std::string m_value; std::optional m_single_hit; }; struct key_equals_or_empty_condition_impl : public condition_impl { key_equals_or_empty_condition_impl(key_equals_condition_impl *equals) : m_item_name(equals->m_item_name) , m_value(equals->m_value) , m_icase(equals->m_icase) , m_single_hit(equals->m_single_hit) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); m_icase = is_item_type_uchar(c, m_item_name); return this; } bool test(row_handle r) const override { bool result = false; if (m_single_hit.has_value()) result = *m_single_hit == r; else result = r[m_item_ix].empty() or r[m_item_ix].compare(m_value, m_icase) == 0; return result; } void str(std::ostream &os) const override { os << '(' << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value << " OR " << m_item_name << " IS NULL)"; } virtual std::optional single() const override { return m_single_hit; } virtual bool equals(const condition_impl *rhs) const override { if (typeid(*rhs) == typeid(key_equals_or_empty_condition_impl)) { auto ri = static_cast(rhs); if (m_single_hit.has_value() or ri->m_single_hit.has_value()) return m_single_hit == ri->m_single_hit; else // watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category) return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name; } return this == rhs; } std::string m_item_name; uint16_t m_item_ix = 0; std::string m_value; bool m_icase = false; std::optional m_single_hit; }; struct key_equals_number_condition_impl : public condition_impl { key_equals_number_condition_impl(const std::string &name, double v) : m_item_name(name) , m_value(v) { } condition_impl *prepare(const category &c) override; bool test(row_handle r) const override { return m_single_hit.has_value() ? *m_single_hit == r : r[m_item_ix].compare(m_value) == 0; } void str(std::ostream &os) const override { os << m_item_name << " == " << m_value; } virtual std::optional single() const override { return m_single_hit; } virtual bool equals(const condition_impl *rhs) const override { if (typeid(*rhs) == typeid(key_equals_number_condition_impl)) { auto ri = static_cast(rhs); if (m_single_hit.has_value() or ri->m_single_hit.has_value()) return m_single_hit == ri->m_single_hit; else // watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category) return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name; } return this == rhs; } std::string m_item_name; uint16_t m_item_ix = 0; double m_value; std::optional m_single_hit; }; struct key_equals_number_or_empty_condition_impl : public condition_impl { key_equals_number_or_empty_condition_impl(key_equals_number_condition_impl *equals) : m_item_name(equals->m_item_name) , m_value(equals->m_value) , m_single_hit(equals->m_single_hit) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); return this; } bool test(row_handle r) const override { bool result = false; if (m_single_hit.has_value()) result = *m_single_hit == r; else result = r[m_item_ix].empty() or r[m_item_ix].compare(m_value) == 0; return result; } void str(std::ostream &os) const override { os << '(' << m_item_name << " == " << m_value << " OR " << m_item_name << " IS NULL)"; } virtual std::optional single() const override { return m_single_hit; } virtual bool equals(const condition_impl *rhs) const override { if (typeid(*rhs) == typeid(key_equals_number_or_empty_condition_impl)) { auto ri = static_cast(rhs); if (m_single_hit.has_value() or ri->m_single_hit.has_value()) return m_single_hit == ri->m_single_hit; else // watch out, both m_item_ix might be the same while item_names might be diffent (in case they both do not exist in the category) return m_item_ix == ri->m_item_ix and m_value == ri->m_value and m_item_name == ri->m_item_name; } return this == rhs; } std::string m_item_name; uint16_t m_item_ix = 0; double m_value; std::optional m_single_hit; }; struct key_compare_condition_impl : public condition_impl { template key_compare_condition_impl(const std::string &item_name, COMP &&comp, const std::string &s) : m_item_name(item_name) , m_compare(std::move(comp)) , m_str(s) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); m_icase = is_item_type_uchar(c, m_item_name); return this; } bool test(row_handle r) const override { return m_compare(r, m_icase); } void str(std::ostream &os) const override { os << m_item_name << (m_icase ? "^ " : " ") << m_str; } std::string m_item_name; uint16_t m_item_ix = 0; bool m_icase = false; std::function m_compare; std::string m_str; }; struct key_matches_condition_impl : public condition_impl { key_matches_condition_impl(const std::string &item_name, const std::regex &rx) : m_item_name(item_name) , m_item_ix(0) , mRx(rx) { } condition_impl *prepare(const category &c) override { m_item_ix = get_item_ix(c, m_item_name); return this; } bool test(row_handle r) const override { std::string_view txt = r[m_item_ix].text(); return std::regex_match(txt.begin(), txt.end(), mRx); } void str(std::ostream &os) const override { os << m_item_name << " =~ expression"; } std::string m_item_name; uint16_t m_item_ix; std::regex mRx; }; template struct any_is_condition_impl : public condition_impl { typedef T valueType; any_is_condition_impl(const valueType &value) : mValue(value) { } bool test(row_handle r) const override { auto &c = r.get_category(); bool result = false; for (auto &f : get_category_items(c)) { try { if (r[f].compare(mValue) == 0) { result = true; break; } } catch (...) { } } return result; } void str(std::ostream &os) const override { os << " == " << mValue; } valueType mValue; }; struct any_matches_condition_impl : public condition_impl { any_matches_condition_impl(const std::regex &rx) : mRx(rx) { } bool test(row_handle r) const override { auto &c = r.get_category(); bool result = false; for (auto &f : get_category_items(c)) { try { std::string_view txt = r[f].text(); if (std::regex_match(txt.begin(), txt.end(), mRx)) { result = true; break; } } catch (...) { } } return result; } void str(std::ostream &os) const override { os << " =~ expression"; } std::regex mRx; }; // TODO: Optimize and_condition by having a list of sub items. // That way you can also collapse multiple _is_ conditions in // case they make up an indexed tuple. struct and_condition_impl : public condition_impl { and_condition_impl() = default; and_condition_impl(condition &&a, condition &&b) { if (typeid(*a.m_impl) == typeid(*this)) { and_condition_impl *ai = static_cast(a.m_impl); std::swap(m_sub, ai->m_sub); m_sub.emplace_back(std::exchange(b.m_impl, nullptr)); } else if (typeid(*b.m_impl) == typeid(*this)) { and_condition_impl *bi = static_cast(b.m_impl); std::swap(m_sub, bi->m_sub); m_sub.emplace_back(std::exchange(a.m_impl, nullptr)); } else { m_sub.emplace_back(std::exchange(a.m_impl, nullptr)); m_sub.emplace_back(std::exchange(b.m_impl, nullptr)); } } ~and_condition_impl() { for (auto sub : m_sub) delete sub; } condition_impl *prepare(const category &c) override { for (auto &sub : m_sub) sub = sub->prepare(c); return this; } bool test(row_handle r) const override { bool result = true; for (auto sub : m_sub) { if (sub->test(r)) continue; result = false; break; } return result; } void str(std::ostream &os) const override { os << '('; bool first = true; for (auto sub : m_sub) { if (first) first = false; else os << " AND "; sub->str(os); } os << ')'; } virtual std::optional single() const override { std::optional result; for (auto sub : m_sub) { auto s = sub->single(); if (not result.has_value()) { result = s; continue; } if (s == result) continue; result.reset(); break; } return result; } static condition_impl *combine_equal(std::vector &subs, or_condition_impl *oc); std::vector m_sub; }; struct or_condition_impl : public condition_impl { or_condition_impl(condition &&a, condition &&b) { if (typeid(*a.m_impl) == typeid(*this)) { or_condition_impl *ai = static_cast(a.m_impl); std::swap(m_sub, ai->m_sub); m_sub.emplace_back(std::exchange(b.m_impl, nullptr)); } else if (typeid(*b.m_impl) == typeid(*this)) { or_condition_impl *bi = static_cast(b.m_impl); std::swap(m_sub, bi->m_sub); m_sub.emplace_back(std::exchange(a.m_impl, nullptr)); } else { m_sub.emplace_back(std::exchange(a.m_impl, nullptr)); m_sub.emplace_back(std::exchange(b.m_impl, nullptr)); } } ~or_condition_impl() { for (auto sub : m_sub) delete sub; } condition_impl *prepare(const category &c) override; bool test(row_handle r) const override { bool result = false; for (auto sub : m_sub) { if (not sub->test(r)) continue; result = true; break; } return result; } void str(std::ostream &os) const override { bool first = true; os << '('; for (auto sub : m_sub) { if (first) first = false; else os << " OR "; sub->str(os); } os << ')'; } virtual std::optional single() const override { std::optional result; for (auto sub : m_sub) { auto s = sub->single(); if (not result.has_value()) { result = s; continue; } if (s == result) continue; result.reset(); break; } return result; } std::vector m_sub; }; struct not_condition_impl : public condition_impl { not_condition_impl(condition &&a) : mA(nullptr) { std::swap(mA, a.m_impl); } ~not_condition_impl() { delete mA; } condition_impl *prepare(const category &c) override { mA = mA->prepare(c); return this; } bool test(row_handle r) const override { return not mA->test(r); } void str(std::ostream &os) const override { os << "NOT ("; mA->str(os); os << ')'; } condition_impl *mA; }; } // namespace detail /** * @brief Create a condition containing the logical AND of conditions @a a and @a b */ inline condition operator and(condition &&a, condition &&b) { if (a.m_impl and b.m_impl) return condition(new detail::and_condition_impl(std::move(a), std::move(b))); if (a.m_impl) return condition(std::move(a)); return condition(std::move(b)); } /** * @brief Create a condition containing the logical OR of conditions @a a and @a b */ inline condition operator or(condition &&a, condition &&b) { if (a.m_impl and b.m_impl) { if (typeid(*a.m_impl) == typeid(detail::key_equals_condition_impl) and typeid(*b.m_impl) == typeid(detail::key_is_empty_condition_impl)) { auto ci = static_cast(a.m_impl); auto ce = static_cast(b.m_impl); if (ci->m_item_name == ce->m_item_name) return condition(new detail::key_equals_or_empty_condition_impl(ci)); } if (typeid(*b.m_impl) == typeid(detail::key_equals_condition_impl) and typeid(*a.m_impl) == typeid(detail::key_is_empty_condition_impl)) { auto ci = static_cast(b.m_impl); auto ce = static_cast(a.m_impl); if (ci->m_item_name == ce->m_item_name) return condition(new detail::key_equals_or_empty_condition_impl(ci)); } if (typeid(*a.m_impl) == typeid(detail::key_equals_number_condition_impl) and typeid(*b.m_impl) == typeid(detail::key_is_empty_condition_impl)) { auto ci = static_cast(a.m_impl); auto ce = static_cast(b.m_impl); if (ci->m_item_name == ce->m_item_name) return condition(new detail::key_equals_number_or_empty_condition_impl(ci)); } if (typeid(*b.m_impl) == typeid(detail::key_equals_number_condition_impl) and typeid(*a.m_impl) == typeid(detail::key_is_empty_condition_impl)) { auto ci = static_cast(b.m_impl); auto ce = static_cast(a.m_impl); if (ci->m_item_name == ce->m_item_name) return condition(new detail::key_equals_number_or_empty_condition_impl(ci)); } return condition(new detail::or_condition_impl(std::move(a), std::move(b))); } if (a.m_impl) return condition(std::move(a)); return condition(std::move(b)); } /** * @brief A helper class to make it possible to search for empty items (NULL) * * @code{.cpp} * "id"_key == cif::empty_type(); * @endcode */ struct empty_type { }; /** * @brief A helper to make it possible to have conditions like * * @code{.cpp} * "id"_key == cif::null; * @endcode */ inline constexpr empty_type null = empty_type(); /** * @brief Class to use in creating conditions, creates a reference to a item or item * */ struct key { /** * @brief Construct a new key object using @a item_name as name * * @param item_name */ explicit key(const std::string &item_name) : m_item_name(item_name) { } /** * @brief Construct a new key object using @a item_name as name * * @param item_name */ explicit key(const char *item_name) : m_item_name(item_name) { } /** * @brief Construct a new key object using @a item_name as name * * @param item_name */ explicit key(std::string_view item_name) : m_item_name(item_name) { } key(const key &) = delete; key &operator=(const key &) = delete; std::string m_item_name; ///< The item name }; template concept Numeric = ((std::is_floating_point_v or std::is_integral_v) and not std::is_same_v); /** * @brief Operator to create an equals condition based on a key @a key and a numeric value @a v */ template condition operator==(const key &key, const T &v) { return condition(new detail::key_equals_number_condition_impl(key.m_item_name, v)); } /** * @brief Operator to create an equals condition based on a key @a key and a value @a value */ inline condition operator==(const key &key, std::string_view value) { if (not value.empty()) return condition(new detail::key_equals_condition_impl({ key.m_item_name, value })); else return condition(new detail::key_is_empty_condition_impl(key.m_item_name)); } /** * @brief Operator to create an equals condition based on a key @a key and a value @a value */ template requires std::is_same_v inline condition operator==(const key &key, T value) { return condition(new detail::key_equals_condition_impl({ key.m_item_name, value ? "y" : "n" })); } /** * @brief Operator to create a not equals condition based on a key @a key and a value @a v */ template condition operator!=(const key &key, const T &v) { return condition(new detail::not_condition_impl(operator==(key, v))); } /** * @brief Operator to create a not equals condition based on a key @a key and a value @a value */ inline condition operator!=(const key &key, std::string_view value) { return condition(new detail::not_condition_impl(operator==(key, value))); } /** * @brief Operator to create a greater than condition based on a key @a key and a value @a v */ template condition operator>(const key &key, const T &v) { std::ostringstream s; s << " > " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v) > 0; }, s.str())); } /** * @brief Operator to create a greater than or equals condition based on a key @a key and a value @a v */ template condition operator>=(const key &key, const T &v) { std::ostringstream s; s << " >= " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v) >= 0; }, s.str())); } /** * @brief Operator to create a less than condition based on a key @a key and a value @a v */ template condition operator<(const key &key, const T &v) { std::ostringstream s; s << " < " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v) < 0; }, s.str())); } /** * @brief Operator to create a less than or equals condition based on a key @a key and a value @a v */ template condition operator<=(const key &key, const T &v) { std::ostringstream s; s << " <= " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v) <= 0; }, s.str())); } /** * @brief Operator to create a greater than condition based on a key @a key and a value @a v */ inline condition operator>(const key &key, std::string_view v) { std::ostringstream s; s << " > " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v, icase) > 0; }, s.str())); } /** * @brief Operator to create a greater than or equals condition based on a key @a key and a value @a v */ inline condition operator>=(const key &key, std::string_view v) { std::ostringstream s; s << " >= " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v, icase) >= 0; }, s.str())); } /** * @brief Operator to create a less than condition based on a key @a key and a value @a v */ inline condition operator<(const key &key, std::string_view v) { std::ostringstream s; s << " < " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v, icase) < 0; }, s.str())); } /** * @brief Operator to create a less than or equals condition based on a key @a key and a value @a v */ inline condition operator<=(const key &key, std::string_view v) { std::ostringstream s; s << " <= " << v; return condition(new detail::key_compare_condition_impl( key.m_item_name, [item_name = key.m_item_name, v](row_handle r, bool icase) { return r[item_name].compare(v, icase) <= 0; }, s.str())); } /** * @brief Operator to create a condition based on a key @a key and a regular expression @a rx */ inline condition operator==(const key &key, const std::regex &rx) { return condition(new detail::key_matches_condition_impl(key.m_item_name, rx)); } /** * @brief Operator to create a condition based on a key @a key which should be empty/null */ inline condition operator==(const key &key, const empty_type &) { return condition(new detail::key_is_empty_condition_impl(key.m_item_name)); } /** * @brief Operator to create a condition based on a key @a key which should be not empty/null */ inline condition operator!=(const key &key, const empty_type &) { return condition(new detail::key_is_not_empty_condition_impl(key.m_item_name)); } /** * @brief Create a condition to search any item for a value @a v if @a v contains a value * compare to null if not. */ template condition operator==(const key &key, const std::optional &v) { if (v.has_value()) return condition(new detail::key_equals_condition_impl({ key.m_item_name, *v })); else return condition(new detail::key_is_empty_condition_impl(key.m_item_name)); } /** * @brief Create a condition to search any item for a value @a v if @a v contains a value * compare to null if not. */ template condition operator!=(const key &key, const std::optional &v) { if (v.has_value()) return condition(new detail::not_condition_impl(condition(new detail::key_equals_condition_impl({ key.m_item_name, *v })))); else return condition(new detail::not_condition_impl(condition(new detail::key_is_empty_condition_impl(key.m_item_name)))); } /** * @brief Operator to create a boolean opposite of the condition in @a rhs */ inline condition operator not(condition &&rhs) { return condition(new detail::not_condition_impl(std::move(rhs))); } /** @cond */ struct any_type { }; /** @endcond */ /** * @brief A helper for any item constructs */ inline constexpr any_type any = any_type{}; /** * @brief Create a condition to search any item for a value @a v */ template condition operator==(const any_type &, const T &v) { return condition(new detail::any_is_condition_impl(v)); } /** * @brief Create a condition to search any item for a regular expression @a rx */ inline condition operator==(const any_type &, const std::regex &rx) { return condition(new detail::any_matches_condition_impl(rx)); } /** * @brief Create a condition to return all rows */ inline condition all() { return condition(new detail::all_condition_impl()); } namespace literals { /** * @brief Return a cif::key for the item name @a text * * @param text The name of the item * @param length The length of @a text * @return key The cif::key created */ inline key operator""_key(const char *text, std::size_t length) { return key(std::string(text, length)); } } // namespace literals } // namespace cif libcifpp-7.0.9/include/cif++/datablock.hpp0000644000175000017500000001513614746170722020146 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/category.hpp" #include "cif++/forward_decl.hpp" /** \file datablock.hpp * Each valid mmCIF file contains at least one @ref cif::datablock. * A datablock has a name and can contain one or more @ref cif::category "categories" */ namespace cif { // -------------------------------------------------------------------- /** * @brief A datablock is a list of category objects with some additional features * */ class datablock : public std::list { public: datablock() = default; /** * @brief Construct a new datablock object with name @a name * * @param name The name for the new datablock */ datablock(std::string_view name) : m_name(name) { } /** @cond */ datablock(const datablock &); datablock(datablock &&db) noexcept { swap_(*this, db); } datablock &operator=(datablock db) { swap_(*this, db); return *this; } /** @endcond */ friend void swap_(datablock &a, datablock &b) noexcept { std::swap(a.m_name, b.m_name); std::swap(a.m_validator, b.m_validator); std::swap(static_cast&>(a), static_cast&>(b)); } // -------------------------------------------------------------------- /** * @brief Return the name of this datablock */ const std::string &name() const { return m_name; } /** * @brief Set the name of this datablock to @a name * * @param name The new name */ void set_name(std::string_view name) { m_name = name; } /** * @brief Set the validator object to @a v * * @param v The new validator object, may be null */ void set_validator(const validator *v); /** * @brief Get the validator object * * @return const validator* The validator or nullptr if there is none */ const validator *get_validator() const; /** * @brief Validates the content of this datablock and all its content * * @return true If the content is valid * @return false If the content is not valid */ bool is_valid() const; /** * @brief Validates the content of this datablock and all its content * and updates or removes the audit_conform category to match the result. * * @return true If the content is valid * @return false If the content is not valid */ bool is_valid(); /** * @brief Validates all contained data for valid links between parents and children * as defined in the validator * * @return true If all links are valid * @return false If all links are not valid */ bool validate_links() const; // -------------------------------------------------------------------- /** * @brief Return the category named @a name, will create a new and empty * category named @a name if it does not exist. * * @param name The name of the category to return * @return category& Reference to the named category */ category &operator[](std::string_view name); /** * @brief Return the const category named @a name, will return a reference * to a static empty category if it was not found. * * @param name The name of the category to return * @return category& Reference to the named category */ const category &operator[](std::string_view name) const; /** * @brief Return a pointer to the category named @a name or nullptr if * it does not exist. * * @param name The name of the category * @return category* Pointer to the category found or nullptr */ category *get(std::string_view name); /** * @brief Return a pointer to the category named @a name or nullptr if * it does not exist. * * @param name The name of the category * @return category* Pointer to the category found or nullptr */ const category *get(std::string_view name) const; /** * @brief Tries to find a category with name @a name and will create a * new one if it is not found. The result is a tuple of an iterator * pointing to the category and a boolean indicating whether the category * was created or not. * * @param name The name for the category * @return std::tuple A tuple containing an iterator pointing * at the category and a boolean indicating whether the category was newly * created. */ std::tuple emplace(std::string_view name); /** * @brief Get the preferred order of the categories when writing them */ [[deprecated("use get_item_order instead")]] std::vector get_tag_order() const { return get_item_order(); } /** * @brief Get the preferred order of the categories when writing them */ std::vector get_item_order() const; /** * @brief Write out the contents to @a os */ void write(std::ostream &os) const; /** * @brief Write out the contents to @a os using the order defined in @a item_name_order */ void write(std::ostream &os, const std::vector &item_name_order); /** * @brief Friend operator<< to write datablock @a db to std::ostream @a os */ friend std::ostream &operator<<(std::ostream &os, const datablock &db) { db.write(os); return os; } // -------------------------------------------------------------------- /** * @brief Comparison operator to compare two datablock for equal content */ bool operator==(const datablock &rhs) const; private: std::string m_name; const validator *m_validator = nullptr; }; } // namespace ciflibcifpp-7.0.9/include/cif++/dictionary_parser.hpp0000644000175000017500000000356014746170722021741 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/validate.hpp" /** * @file validate.hpp * * Functions to create and manipulate validator objects */ namespace cif { /** * @brief Parse the contents of @a is and create a new validator object with name @a name */ validator parse_dictionary(std::string_view name, std::istream &is); /** * @brief Extend the definitions in validator @a v with the contents of stream @a is */ void extend_dictionary(validator &v, std::istream &is); } // namespace cif libcifpp-7.0.9/include/cif++/file.hpp0000644000175000017500000001565414746170722017146 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include #include "cif++/datablock.hpp" #include "cif++/parser.hpp" /** \file file.hpp * * The file class defined here encapsulates the contents of an mmCIF file * It is mainly a list of @ref cif::datablock objects * * The class file has methods to load dictionaries. These dictionaries are * loaded from resources (if available) or from disk from several locations. * * See the documentation on load_resource() in file: utilities.hpp for more * information on how data is loaded. */ namespace cif { // -------------------------------------------------------------------- /** * @brief The class file is actually a list of datablock objects * */ class file : public std::list { public: file() = default; /** * @brief Construct a new file object using the data in the file @a p as content * * @param p Path to a file containing the data to load */ explicit file(const std::filesystem::path &p) { load(p); } /** * @brief Construct a new file object using the data in the std::istream @a is * * @param is The istream containing the data to load */ explicit file(std::istream &is) { load(is); } /** * @brief Construct a new file object with data in the constant string defined * by @a data and @a length * * @param data The pointer to the character string with data to load * @param length The length of the data */ explicit file(const char *data, std::size_t length) { struct membuf : public std::streambuf { membuf(char *text, std::size_t length) { this->setg(text, text, text + length); } } buffer(const_cast(data), length); std::istream is(&buffer); load(is); } /** @cond */ file(const file &rhs) : std::list(rhs) { } file(file &&rhs) { this->swap(rhs); } file &operator=(file f) { this->swap(f); return *this; } /** @endcond */ /** * @brief Set the validator object to @a v */ void set_validator(const validator *v); /** * @brief Get the validator object */ const validator *get_validator() const { return m_validator; } /** * @brief Validate the content and return true if everything was valid. * * Will throw an exception if there is no validator defined. * * If each category was valid, validate_links will also be called. * * @return true If the content is valid * @return false If the content is not valid */ bool is_valid() const; /** * @brief Validate the content and return true if everything was valid. * * Will attempt to load the referenced dictionary if none was specified. * * If each category was valid, validate_links will also be called. * * @return true If the content is valid * @return false If the content is not valid */ bool is_valid(); /** * @brief Validate the links for all datablocks contained. * * Will throw an exception if no validator was specified. * * @return true If all links were valid * @return false If all links were not valid */ bool validate_links() const; /** * @brief Attempt to load a dictionary (validator) based on * the contents of the *audit_conform* category, if available. */ void load_dictionary(); /** * @brief Attempt to load the named dictionary @a name and * create a validator based on it. * * Tje @a name can be the name of a single file, or even the * stem of that filename. So, e.g. mmcif_pdbx is valid. * * Since libcifpp can use extensions to validators, you * can add them to the name. So if you would like to add * the dssp extensions you would have to write: * * @code{cpp} * file.load_dictionary("mmcif_pdbx;dssp-extension"); * @endcode * * @param name The name of the dictionary to load */ void load_dictionary(std::string_view name); /** * @brief Return true if a datablock with the name @a name is part of this file */ bool contains(std::string_view name) const; /** * @brief return a reference to the first datablock in the file */ datablock &front() { assert(not empty()); return std::list::front(); } /** * @brief return a const reference to the first datablock in the file */ const datablock &front() const { assert(not empty()); return std::list::front(); } /** * @brief return a reference to the datablock named @a name */ datablock &operator[](std::string_view name); /** * @brief return a const reference to the datablock named @a name */ const datablock &operator[](std::string_view name) const; /** * @brief Tries to find a datablock with name @a name and will create a * new one if it is not found. The result is a tuple of an iterator * pointing to the datablock and a boolean indicating whether the datablock * was created or not. * * @param name The name for the datablock * @return std::tuple A tuple containing an iterator pointing * at the datablock and a boolean indicating whether the datablock was newly * created. */ std::tuple emplace(std::string_view name); /** Load the data from the file specified by @a p */ void load(const std::filesystem::path &p); /** Load the data from @a is */ void load(std::istream &is); /** Save the data to the file specified by @a p */ void save(const std::filesystem::path &p) const; /** Save the data to @a is */ void save(std::ostream &os) const; /** * @brief Friend operator<< to write file @a f to std::ostream @a os */ friend std::ostream &operator<<(std::ostream &os, const file &f) { f.save(os); return os; } private: const validator *m_validator = nullptr; }; } // namespace ciflibcifpp-7.0.9/include/cif++/format.hpp0000644000175000017500000001357314746170722017515 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include /** \file format.hpp * * File containing a basic reimplementation of boost::format * but then a bit more simplistic. Still this allowed me to move my code * from using boost::format to something without external dependency easily. */ namespace cif { namespace detail { template struct to_varg { using type = T; to_varg(const T &v) : m_value(v) { } type operator*() { return m_value; } T m_value; }; template <> struct to_varg { using type = const char *; to_varg(const char *v) : m_value(v) { } type operator*() { return m_value.c_str(); } std::string m_value; }; template <> struct to_varg { using type = const char *; to_varg(const std::string &v) : m_value(v) { } type operator*() { return m_value.c_str(); } std::string m_value; }; } // namespace /** @cond */ template class format_plus_arg { public: using args_vector_type = std::tuple...>; using vargs_vector_type = std::tuple::type...>; format_plus_arg(const format_plus_arg &) = delete; format_plus_arg &operator=(const format_plus_arg &) = delete; format_plus_arg(std::string_view fmt, Args... args) : m_fmt(fmt) , m_args(std::forward(args)...) { auto ix = std::make_index_sequence(); copy_vargs(ix); } std::string str() { char buffer[1024]; std::string::size_type r = std::apply(snprintf, std::tuple_cat(std::make_tuple(buffer, sizeof(buffer), m_fmt.c_str()), m_vargs)); return { buffer, r }; } friend std::ostream &operator<<(std::ostream &os, const format_plus_arg &f) { char buffer[1024]; std::string::size_type r = std::apply(snprintf, std::tuple_cat(std::make_tuple(buffer, sizeof(buffer), f.m_fmt.c_str()), f.m_vargs)); os.write(buffer, r); return os; } private: template void copy_vargs(std::index_sequence) { ((std::get(m_vargs) = *std::get(m_args)), ...); } std::string m_fmt; args_vector_type m_args; vargs_vector_type m_vargs; }; /** @endcond */ /** * @brief A simplistic reimplementation of boost::format, in fact it is * actually a way to call the C function snprintf to format the arguments * in @a args into the format string @a fmt * * The string in @a fmt should thus be a C style format string. * * TODO: Move to C++23 style of printing. * * @tparam Args The types of the arguments * @param fmt The format string * @param args The arguments * @return An object that can be written out to a std::ostream using operator<< */ template constexpr auto format(std::string_view fmt, Args... args) { return format_plus_arg(fmt, std::forward(args)...); } // -------------------------------------------------------------------- /// A streambuf that fills out lines with spaces up until a specified width class fill_out_streambuf : public std::streambuf { public: /** @cond */ using base_type = std::streambuf; using int_type = base_type::int_type; using char_type = base_type::char_type; using traits_type = base_type::traits_type; /** @endcond */ /** * @brief Construct a new fill out streambuf object based on ostream @a os and a * width to fill out to of @a width */ fill_out_streambuf(std::ostream &os, int width = 80) : m_os(os) , m_upstream(os.rdbuf()) , m_width(width) { } /** @cond */ ~fill_out_streambuf() { m_os.rdbuf(m_upstream); } /** @endcond */ /** * @brief The magic happens here. Write out a couple of spaces when * the last character to write is a newline to make the line as * wide as the requested width. */ virtual int_type overflow(int_type ic = traits_type::eof()) { char ch = traits_type::to_char_type(ic); int_type result = ic; if (ch == '\n') { for (int i = m_column_count; result != traits_type::eof() and i < m_width; ++i) result = m_upstream->sputc(' '); } if (result != traits_type::eof()) result = m_upstream->sputc(ch); if (result != traits_type::eof()) { if (ch == '\n') { m_column_count = 0; ++m_line_count; } else ++m_column_count; } return result; } /** Return the upstream streambuf */ std::streambuf *get_upstream() const { return m_upstream; } /** Return how many lines have been written */ int get_line_count() const { return m_line_count; } private: std::ostream &m_os; std::streambuf *m_upstream; int m_width; int m_line_count = 0; int m_column_count = 0; }; } // namespace pdbx libcifpp-7.0.9/include/cif++/forward_decl.hpp0000644000175000017500000000325614746170722020655 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include #include /** * @file forward_decl.hpp * * File containing only forward declarations * */ namespace cif { class category; class datablock; class file; class parser; class row; class row_handle; class item; struct item_handle; } // namespace ciflibcifpp-7.0.9/include/cif++/gzio.hpp0000644000175000017500000007102114746170722017165 0ustar maartenmaarten// Copyright Maarten L. Hekkelman, 2022 // 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) #pragma once #include #include #include #include #include #include /** \file gzio.hpp * * Single header file for the implementation of stream classes * that can transparently read and write compressed files. * * The gzio::istream_buf class sniffs the input and decides whether to use * a decompressor if a signature was recognized. * * There's also an ifstream and ofstream class here that can * read and write compressed files. In this case the decission * whether to use a compressions/decompression algorithm is * based on the extension of the \a filename argument. * * This is a stripped down version of the gxrio library from * https://github.com/mhekkel/gxrio.git * Most notably, the lzma support has been removed since getting * that to work in Windows proved to be too much work. */ namespace cif::gzio { /** The default buffer size to use */ const std::size_t kDefaultBufferSize = 256; // -------------------------------------------------------------------- /// \brief A base class for the streambuf classes in gzio /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// /// The base class for all streambuf classes in this library. /// It maintains the pointer to the upstream streambuf. template class basic_streambuf : public std::basic_streambuf { public: /** @cond */ using char_type = CharT; using traits_type = Traits; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; using streambuf_type = std::basic_streambuf; basic_streambuf() = default; basic_streambuf(const basic_streambuf &) = delete; basic_streambuf(basic_streambuf &&rhs) : streambuf_type(std::move(rhs)) { m_upstream = std::exchange(rhs.m_upstream, nullptr); } basic_streambuf &operator=(const basic_streambuf &) = delete; basic_streambuf &operator=(basic_streambuf &&rhs) { m_upstream = std::exchange(rhs.m_upstream, nullptr); return *this; } /** @endcond */ /** Set the upstream streambuf to @a upstream */ void set_upstream(streambuf_type *upstream) { m_upstream = upstream; } /** @cond */ virtual basic_streambuf *init(streambuf_type *sb) = 0; virtual basic_streambuf *close() = 0; /** @endcond */ protected: /// \brief The upstream streambuf object, usually this is a basic_filebuf streambuf_type *m_upstream = nullptr; }; // -------------------------------------------------------------------- /// \brief A streambuf class that can be used to decompress gzipped data /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// \tparam BufferSize The size of the internal buffers. /// /// This implementation of streambuf can decompress (inflate) data compressed /// using zlib. template class basic_igzip_streambuf : public basic_streambuf { public: /** @cond */ static_assert(sizeof(CharT) == 1, "Unfortunately, support for wide characters is not implemented yet."); using char_type = CharT; using traits_type = Traits; using streambuf_type = std::basic_streambuf; using base_type = basic_streambuf; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; basic_igzip_streambuf() = default; basic_igzip_streambuf(const basic_igzip_streambuf &) = delete; /** @endcond */ /// \brief Move constructor basic_igzip_streambuf(basic_igzip_streambuf &&rhs) : base_type(std::move(rhs)) { std::swap(m_zstream, rhs.m_zstream); std::swap(m_gzheader, rhs.m_gzheader); auto p = std::copy(rhs.gptr(), rhs.egptr(), m_out_buffer.data()); this->setg(m_out_buffer.data(), m_out_buffer.data(), p); if (m_zstream and m_zstream->avail_in > 0) { auto next_in_offset = m_zstream->next_in - rhs.m_in_buffer.data(); std::copy(rhs.m_in_buffer.data() + next_in_offset, rhs.m_in_buffer.data() + next_in_offset + m_zstream->avail_in, m_in_buffer.data()); m_zstream->next_in = m_in_buffer.data(); } } /** @cond */ basic_igzip_streambuf &operator=(const basic_igzip_streambuf &) = delete; /// \brief Move operator= implementation basic_igzip_streambuf &operator=(basic_igzip_streambuf &&rhs) { base_type::operator=(std::move(rhs)); std::swap(m_zstream, rhs.m_zstream); std::swap(m_gzheader, rhs.m_gzheader); auto p = std::copy(rhs.gptr(), rhs.egptr(), m_out_buffer.data()); this->setg(m_out_buffer.data(), m_out_buffer.data(), p); if (m_zstream and m_zstream->avail_in > 0) { auto next_in_offset = m_zstream->next_in - reinterpret_cast(rhs.m_in_buffer.data()); std::copy(rhs.m_in_buffer.data() + next_in_offset, rhs.m_in_buffer.data() + next_in_offset + m_zstream->avail_in, m_in_buffer.data()); m_zstream->next_in = reinterpret_cast(m_in_buffer.data()); } return *this; } ~basic_igzip_streambuf() { close(); } /** @endcond */ /// \brief This closes the zlib stream and sets the get pointers to null. base_type *close() override { if (m_zstream) { ::inflateEnd(m_zstream.get()); m_zstream.reset(nullptr); m_gzheader.reset(nullptr); } this->setg(nullptr, nullptr, nullptr); return this; } /// \brief Initialize a zlib stream and set the upstream. /// /// \param upstream The upstream streambuf /// /// The zstream is constructed and an optional header is /// read from upstream. The contents of the header are ignored /// but we must maintain that structure. base_type *init(streambuf_type *upstream) override { this->set_upstream(upstream); close(); m_zstream.reset(new z_stream_s); m_gzheader.reset(new gz_header_s); auto &zstream = *m_zstream.get(); zstream = z_stream_s{}; auto &header = *m_gzheader.get(); header = gz_header_s{}; int err = ::inflateInit2(&zstream, 47); if (err == Z_OK) { zstream.next_in = reinterpret_cast(m_in_buffer.data()); zstream.avail_in = static_cast(this->m_upstream->sgetn(m_in_buffer.data(), m_in_buffer.size())); err = ::inflateGetHeader(&zstream, &header); if (err != Z_OK) ::inflateEnd(&zstream); } if (err != Z_OK) zstream = z_stream_s{}; return err == Z_OK ? this : nullptr; } private: /// \brief The actual work is done here. int_type underflow() override { if (m_zstream and this->m_upstream) { auto &zstream = *m_zstream.get(); const std::streamsize kBufferByteSize = m_out_buffer.size(); while (this->gptr() == this->egptr()) { zstream.next_out = reinterpret_cast(m_out_buffer.data()); zstream.avail_out = static_cast(kBufferByteSize); if (zstream.avail_in == 0) { zstream.next_in = reinterpret_cast(m_in_buffer.data()); zstream.avail_in = static_cast(this->m_upstream->sgetn(m_in_buffer.data(), m_in_buffer.size())); } if (zstream.avail_in == 0) break; int err = ::inflate(&zstream, Z_SYNC_FLUSH); std::streamsize n = kBufferByteSize - zstream.avail_out; if (n > 0) { this->setg( m_out_buffer.data(), m_out_buffer.data(), m_out_buffer.data() + n); break; } if (err == Z_STREAM_END and zstream.avail_in > 0) err = ::inflateReset2(&zstream, 47); if (err < Z_OK) break; } } return this->gptr() != this->egptr() ? traits_type::to_int_type(*this->gptr()) : traits_type::eof(); } private: /// \brief The zlib internal structures are mainained as pointers to avoid having /// to copy their content in move constructors. std::unique_ptr m_zstream; /// \brief The zlib internal structures are mainained as pointers to avoid having /// to copy their content in move constructors. std::unique_ptr m_gzheader; /// \brief Input buffer, this is the input for zlib std::array m_in_buffer; /// \brief Output buffer, where the ostream finds the data std::array m_out_buffer; }; // -------------------------------------------------------------------- /// \brief A streambuf class that can be used to compress data using zlib /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// \tparam BufferSize The size of the internal buffers. /// /// This implementation of streambuf can compress (deflate) data using zlib. template class basic_ogzip_streambuf : public basic_streambuf { public: /** @cond */ static_assert(sizeof(CharT) == 1, "Unfortunately, support for wide characters is not implemented yet."); using char_type = CharT; using traits_type = Traits; using streambuf_type = std::basic_streambuf; using base_type = basic_streambuf; using int_type = typename traits_type::int_type; using pos_type = typename traits_type::pos_type; using off_type = typename traits_type::off_type; basic_ogzip_streambuf() = default; basic_ogzip_streambuf(const basic_ogzip_streambuf &) = delete; /// \brief Move constructor basic_ogzip_streambuf(basic_ogzip_streambuf &&rhs) : base_type(std::move(rhs)) { std::swap(m_zstream, rhs.m_zstream); std::swap(m_gzheader, rhs.m_gzheader); this->setp(m_in_buffer.data(), m_in_buffer.data() + m_in_buffer.size()); this->sputn(rhs.pbase(), rhs.pptr() - rhs.pbase()); rhs.setp(nullptr, nullptr); } basic_ogzip_streambuf &operator=(const basic_ogzip_streambuf &) = delete; /** @endcond */ /// \brief Move operator= basic_ogzip_streambuf &operator=(basic_ogzip_streambuf &&rhs) { base_type::operator=(std::move(rhs)); std::swap(m_zstream, rhs.m_zstream); std::swap(m_gzheader, rhs.m_gzheader); this->setp(m_in_buffer.data(), m_in_buffer.data() + m_in_buffer.size()); this->sputn(rhs.pbase(), rhs.pptr() - rhs.pbase()); rhs.setp(nullptr, nullptr); return *this; } ~basic_ogzip_streambuf() { close(); } /// \brief This closes the zlib stream and sets the put pointers to null. base_type *close() override { if (m_zstream) { overflow(traits_type::eof()); ::deflateEnd(m_zstream.get()); m_zstream.reset(nullptr); m_gzheader.reset(nullptr); } this->setp(nullptr, nullptr); return this; } /// \brief Initialize the internal zlib structures /// /// \param upstream The upstream streambuf /// /// The zlib stream is initialized as one that can accept /// a gzip header. base_type *init(streambuf_type *upstream) override { this->set_upstream(upstream); close(); m_zstream.reset(new z_stream_s); m_gzheader.reset(new gz_header_s); auto &zstream = *m_zstream.get(); zstream = z_stream_s{}; auto &header = *m_gzheader.get(); header = gz_header_s{}; const int WINDOW_BITS = 15, GZIP_ENCODING = 16; int err = deflateInit2(&zstream, Z_BEST_COMPRESSION, Z_DEFLATED, WINDOW_BITS | GZIP_ENCODING, Z_DEFLATED, Z_DEFAULT_STRATEGY); if (err == Z_OK) err = ::deflateSetHeader(&zstream, &header); if (err == Z_OK) this->setp(this->m_in_buffer.data(), this->m_in_buffer.data() + this->m_in_buffer.size()); else zstream = z_stream_s{}; return err == Z_OK ? this : nullptr; } private: /// \brief The actual work is done here /// /// \param ch The character that did not fit, in case it is eof we need to flush /// int_type overflow(int_type ch) override { if (not m_zstream) return traits_type::eof(); auto &zstream = *m_zstream; zstream.next_in = reinterpret_cast(this->pbase()); zstream.avail_in = static_cast(this->pptr() - this->pbase()); char_type buffer[BufferSize]; for (;;) { zstream.next_out = reinterpret_cast(buffer); zstream.avail_out = sizeof(buffer); int err = ::deflate(&zstream, ch == traits_type::eof() ? Z_FINISH : Z_NO_FLUSH); std::streamsize n = sizeof(buffer) - zstream.avail_out; if (n > 0) { auto r = this->m_upstream->sputn(reinterpret_cast(buffer), n); if (r != n) return traits_type::eof(); } if (zstream.avail_out == 0) continue; if (err == Z_OK and ch == traits_type::eof()) continue; break; } this->setp(this->m_in_buffer.data(), this->m_in_buffer.data() + this->m_in_buffer.size()); if (not traits_type::eq_int_type(ch, traits_type::eof())) { *this->pptr() = traits_type::to_char_type(ch); this->pbump(1); } return ch; } private: /// \brief The zlib internal structures are mainained as pointers to avoid having /// to copy their content in move constructors. std::unique_ptr m_zstream; /// \brief The zlib internal structures are mainained as pointers to avoid having /// to copy their content in move constructors. std::unique_ptr m_gzheader; /// \brief Input buffer, this is the input for zlib std::array m_in_buffer; }; // -------------------------------------------------------------------- /// \brief An istream implementation that wraps a streambuf with a decompressing streambuf /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// /// This is an istream implementation that can take a source streambuf and then wraps /// this streambuf with a decompressing streambuf class defined above. /// The class inherits from std::basic_istream and offers all the associated functionality. template class basic_istream : public std::basic_istream { public: /** @cond */ using base_type = std::basic_istream; using traits_type = Traits; using char_type = typename traits_type::char_type; using int_type = typename traits_type::int_type; using z_streambuf_type = basic_streambuf; using upstreambuf_type = std::basic_streambuf; using gzip_streambuf_type = basic_igzip_streambuf; /** @endcond */ /// \brief Regular move constructor basic_istream(basic_istream &&rhs) : base_type(std::move(rhs)) { m_gziobuf = std::move(rhs.m_gziobuf); if (m_gziobuf) this->rdbuf(m_gziobuf.get()); else this->rdbuf(nullptr); } /// \brief Regular move operator= basic_istream &operator=(basic_istream &&rhs) { base_type::operator=(std::move(rhs)); m_gziobuf = std::move(rhs.m_gziobuf); if (m_gziobuf) this->rdbuf(m_gziobuf.get()); else this->rdbuf(nullptr); return *this; } /// \brief Construct an istream with the passed in streambuf \a buf /// /// \param buf The streambuf that provides the compressed data /// /// This constructor will initialize the zlib code with the \a buf streambuf. explicit basic_istream(upstreambuf_type *buf) : base_type(nullptr) { init_z(buf); } protected: basic_istream() : base_type(nullptr) {} /// \brief Initialise internals with streambuf \a sb /// \param sb The upstream streambuf class /// /// This will sniff the content in \a sb and decide upon what is found /// what implementation is used. If it doesn't look like compressed data /// the \a sb streambuf is used without any decompression being done. void init_z(upstreambuf_type *sb) { int_type ch = sb->sgetc(); if (ch == 0x1f) { sb->sbumpc(); ch = sb->sgetc(); sb->sungetc(); if (ch == 0x8b) // Read gzip header m_gziobuf.reset(new gzip_streambuf_type); } if (m_gziobuf) { if (not m_gziobuf->init(sb)) this->setstate(std::ios_base::failbit); else this->init(m_gziobuf.get()); } else this->init(sb); } protected: /// \brief Our streambuf class std::unique_ptr m_gziobuf; }; // -------------------------------------------------------------------- /// \brief Control input from files compressed with gzip. /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// /// This is an ifstream implementation that can read from named files compressed with /// gzip directly. The class inherits from std::basic_istream and offers all the /// associated functionality. template class basic_ifstream : public basic_istream { public: /** @cond */ using base_type = basic_istream; using char_type = CharT; using traits_type = Traits; using filebuf_type = std::basic_filebuf; using gzip_streambuf_type = typename base_type::gzip_streambuf_type; /// \brief Default constructor, does not open a file since none is specified basic_ifstream() = default; ~basic_ifstream() { close(); } /** @endcond */ /// \brief Construct an ifstream /// \param filename Null terminated string specifying the file to open /// \param mode The mode in which to open the file explicit basic_ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in) { open(filename, mode); } /// \brief Construct an ifstream /// \param filename std::string specifying the file to open /// \param mode The mode in which to open the file explicit basic_ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) { open(filename, mode); } /// \brief Construct an ifstream /// \param filename std::filesystem::path specifying the file to open /// \param mode The mode in which to open the file explicit basic_ifstream(const std::filesystem::path &filename, std::ios_base::openmode mode = std::ios_base::in) { open(filename, mode); } /// \brief Move constructor basic_ifstream(basic_ifstream &&rhs) : base_type(std::move(rhs)) { m_filebuf = std::move(rhs.m_filebuf); if (this->m_gziobuf) this->m_gziobuf->set_upstream(&m_filebuf); else this->rdbuf(&m_filebuf); } /** @cond */ basic_ifstream(const basic_ifstream &) = delete; basic_ifstream &operator=(const basic_ifstream &) = delete; /** @endcond */ /// \brief Move version of operator= basic_ifstream &operator=(basic_ifstream &&rhs) { base_type::operator=(std::move(rhs)); m_filebuf = std::move(rhs.m_filebuf); if (this->m_gziobuf) this->m_gziobuf->set_upstream(&m_filebuf); else this->rdbuf(&m_filebuf); return *this; } /// \brief Open the file \a filename with mode \a mode /// \param filename std::filesystem::path specifying the file to open /// \param mode The mode in which to open the file void open(const std::filesystem::path &filename, std::ios_base::openmode mode = std::ios_base::in) { if (not m_filebuf.open(filename, mode | std::ios::binary)) this->setstate(std::ios_base::failbit); else { if (filename.extension() == ".gz") this->m_gziobuf.reset(new gzip_streambuf_type); if (not this->m_gziobuf) { this->rdbuf(&m_filebuf); this->clear(); } else if (not this->m_gziobuf->init(&m_filebuf)) this->setstate(std::ios_base::failbit); else { this->rdbuf(this->m_gziobuf.get()); this->clear(); } } } /// \brief Open the file \a filename with mode \a mode /// \param filename std::string specifying the file to open /// \param mode The mode in which to open the file void open(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) { open(std::filesystem::path{filename}, mode); } /// \brief Open the file \a filename with mode \a mode /// \param filename Null terminated string specifying the file to open /// \param mode The mode in which to open the file void open(const char *filename, std::ios_base::openmode mode = std::ios_base::in) { open(std::filesystem::path{filename}, mode); } /// \brief Return true if the file is open /// \return m_filebuf.is_open() bool is_open() const { return m_filebuf.is_open(); } /// \brief Close the file /// /// Calls m_filebuf.close(). If that fails, the failbit is set. void close() { if (this->m_gziobuf and not this->m_gziobuf->close()) this->setstate(std::ios_base::failbit); if (not m_filebuf.close()) this->setstate(std::ios_base::failbit); } /// \brief Swap the contents with those of \a rhs /// \param rhs The ifstream to swap with void swap(basic_ifstream &rhs) { base_type::swap(rhs); m_filebuf.swap(rhs.m_filebuf); if (this->m_gziobuf) { this->m_gziobuf.set_upstream(&m_filebuf); this->rdbuf(this->m_gziobuf.get()); } else this->rdbuf(&m_filebuf); if (rhs.m_gziobuf) { rhs.m_gziobuf.set_upstream(&rhs.m_filebuf); rhs.rdbuf(rhs.m_gziobuf.get()); } else rhs.rdbuf(&rhs.m_filebuf); } private: /// \brief The filebuf filebuf_type m_filebuf; }; // -------------------------------------------------------------------- /// \brief An ostream implementation that wraps a streambuf with a compressing streambuf /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// /// This is an ostream implementation that can take an upstream streambuf and then wraps /// this streambuf with a compressing streambuf class defined above. /// The class inherits from std::basic_ostream and offers all the associated functionality. template class basic_ostream : public std::basic_ostream { public: /** @cond */ using base_type = std::basic_ostream; using char_type = CharT; using traits_type = Traits; using z_streambuf_type = basic_streambuf; using upstreambuf_type = std::basic_streambuf; /** @endcond */ /// \brief Regular move constructor basic_ostream(basic_ostream &&rhs) : base_type(std::move(rhs)) { m_gziobuf = std::move(rhs.m_gziobuf); this->rdbuf(m_gziobuf.get()); } /// \brief Regular move operator= basic_ostream &operator=(basic_ostream &&rhs) { base_type::operator=(std::move(rhs)); m_gziobuf = std::move(rhs.m_gziobuf); this->rdbuf(m_gziobuf.get()); return *this; } // One might expect a constructor taking a streambuf pointer // as the regular std::ostream class does. However, that's not // going to work since no information is known at this time // what compression to use. // // explicit basic_ostream(upstreambuf_type *buf) // { // init_z(buf); // this->init(m_gziobuf.get()); // } protected: basic_ostream() : base_type(nullptr) {} /// \brief Initialise internals with streambuf \a sb void init_z(std::streambuf *sb) { if (not m_gziobuf->init(sb)) this->setstate(std::ios_base::failbit); } protected: /// \brief Our streambuf class std::unique_ptr m_gziobuf; }; // -------------------------------------------------------------------- /// \brief Control output to files compressing the contents with gzip. /// /// \tparam CharT Type of the character stream. /// \tparam Traits Traits for character type, defaults to char_traits<_CharT>. /// /// This is an ofstream implementation that can writeto named files compressing the content /// with gzip directly. The class inherits from std::basic_ostream and offers all the /// associated functionality. template class basic_ofstream : public basic_ostream { public: /** @cond */ using base_type = basic_ostream; using char_type = CharT; using traits_type = Traits; using filebuf_type = std::basic_filebuf; using gzip_streambuf_type = basic_ogzip_streambuf; basic_ofstream() = default; ~basic_ofstream() { close(); } /** @endcond */ /// \brief Construct an ofstream /// \param filename Null terminated string specifying the file to open /// \param mode The mode in which to open the file explicit basic_ofstream(const char *filename, std::ios_base::openmode mode = std::ios_base::out) { open(filename, mode); } /// \brief Construct an ofstream /// \param filename std::string specifying the file to open /// \param mode The mode in which to open the file explicit basic_ofstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::out) { open(filename, mode); } /// \brief Construct an ofstream /// \param filename std::filesystem::path specifying the file to open /// \param mode The mode in which to open the file explicit basic_ofstream(const std::filesystem::path &filename, std::ios_base::openmode mode = std::ios_base::out) { open(filename, mode); } /// \brief Move constructor basic_ofstream(basic_ofstream &&rhs) : base_type(std::move(rhs)) { m_filebuf = std::move(rhs.m_filebuf); if (this->m_gziobuf) this->m_gziobuf->set_upstream(&m_filebuf); else this->rdbuf(&m_filebuf); } /** @cond */ basic_ofstream(const basic_ofstream &) = delete; basic_ofstream &operator=(const basic_ofstream &) = delete; /** @endcond */ /// \brief Move operator= basic_ofstream &operator=(basic_ofstream &&rhs) { base_type::operator=(std::move(rhs)); m_filebuf = std::move(rhs.m_filebuf); if (this->m_gziobuf) this->m_gziobuf->set_upstream(&m_filebuf); else this->rdbuf(&m_filebuf); return *this; } /// \brief Open the file \a filename with mode \a mode /// \param filename std::filesystem::path specifying the file to open /// \param mode The mode in which to open the file /// /// A compression algorithm is chosen upon the contents of the /// extension() of \a filename with .gz mapping to gzip compression /// and .xz to xz compression. void open(const std::filesystem::path &filename, std::ios_base::openmode mode = std::ios_base::out) { if (not m_filebuf.open(filename, mode | std::ios::binary)) this->setstate(std::ios_base::failbit); else { if (filename.extension() == ".gz") this->m_gziobuf.reset(new gzip_streambuf_type); else this->m_gziobuf.reset(nullptr); if (this->m_gziobuf) { if (not this->m_gziobuf->init(&m_filebuf)) this->setstate(std::ios_base::failbit); else { this->rdbuf(this->m_gziobuf.get()); this->clear(); } } else { this->rdbuf(&m_filebuf); this->clear(); } } } /// \brief Open the file \a filename with mode \a mode /// \param filename std::string specifying the file to open /// \param mode The mode in which to open the file /// /// A compression algorithm is chosen upon the contents of the /// extension of \a filename with .gz mapping to gzip compression /// and .xz to xz compression. void open(const std::string &filename, std::ios_base::openmode mode = std::ios_base::out) { open(std::filesystem::path{filename}, mode); } /// \brief Open the file \a filename with mode \a mode /// \param filename Null terminated string specifying the file to open /// \param mode The mode in which to open the file /// /// A compression algorithm is chosen upon the contents of the /// extension of \a filename with .gz mapping to gzip compression /// and .xz to xz compression. void open(const char *filename, std::ios_base::openmode mode = std::ios_base::out) { open(std::filesystem::path{filename}, mode); } /// \brief Return true if the file is open /// \return m_filebuf.is_open() bool is_open() const { return m_filebuf.is_open(); } /// \brief Close the file /// /// Calls m_filebuf.close(). If that fails, the failbit is set. void close() { if (this->m_gziobuf and not this->m_gziobuf->close()) this->setstate(std::ios_base::failbit); if (not m_filebuf.close()) this->setstate(std::ios_base::failbit); } /// \brief Swap the contents with those of \a rhs /// \param rhs The ifstream to swap with void swap(basic_ofstream &rhs) { base_type::swap(rhs); m_filebuf.swap(rhs.m_filebuf); if (this->m_gziobuf) { this->m_gziobuf.set_upstream(&m_filebuf); this->rdbuf(this->m_gziobuf.get()); } else this->rdbuf(&m_filebuf); if (rhs.m_gziobuf) { rhs.m_gziobuf.set_upstream(&rhs.m_filebuf); rhs.rdbuf(rhs.m_gziobuf.get()); } else rhs.rdbuf(&rhs.m_filebuf); } private: /// \brief The filebuf filebuf_type m_filebuf; }; // -------------------------------------------------------------------- /// \brief Convenience typedef for a regular istream using istream = basic_istream>; /// \brief Convenience typedef for a file ifstream using ifstream = basic_ifstream>; /// \brief Convenience typedef for a file ofstream using ofstream = basic_ofstream>; } // namespace gzio libcifpp-7.0.9/include/cif++/item.hpp0000644000175000017500000004733314746170722017164 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/exports.hpp" #include "cif++/forward_decl.hpp" #include "cif++/text.hpp" #include "cif++/utilities.hpp" #include #include #include #include #include #include #include #include #include /** \file item.hpp * * This file contains the declaration of item but also the item_value and item_handle * These handle the storage of and access to the data for a single data item. */ namespace cif { // -------------------------------------------------------------------- /** @brief item is a transient class that is used to pass data into rows * but it also takes care of formatting data. * * * * The class cif::item is often used implicitly when creating a row in a category * using the emplace function. * * @code{.cpp} * cif::category cat("my-cat"); * cat.emplace({ * { "item-1", 1 }, // <- stores an item with value 1 * { "item-2", 1.0, 2 }, // <- stores an item with value 1.00 * { "item-3", std::optional() }, // <- stores an item with value ? * { "item-4", std::make_optional(42) }, // <- stores an item with value 42 * { "item-5" } // <- stores an item with value . * }); * * std::cout << cat << '\n'; * @endcode * * Will result in: * * @code{.txt} * _my-cat.item-1 1 * _my-cat.item-2 1.00 * _my-cat.item-3 ? * _my-cat.item-4 42 * _my-cat.item-5 . * @endcode */ class item { public: /// \brief Default constructor, empty item item() = default; /// \brief constructor for an item with name \a name and as /// content the character '.', i.e. an inapplicable value. item(std::string_view name) : m_name(name) , m_value({ '.' }) { } /// \brief constructor for an item with name \a name and as /// content a single character string with content \a value item(std::string_view name, char value) : m_name(name) , m_value({ value }) { } /// \brief constructor for an item with name \a name and as /// content the formatted floating point value \a value with /// precision \a precision template , int> = 0> item(std::string_view name, const T &value, int precision) : m_name(name) { using namespace std; using namespace cif; char buffer[32]; auto r = to_chars(buffer, buffer + sizeof(buffer) - 1, value, chars_format::fixed, precision); if ((bool)r.ec) throw std::runtime_error("Could not format number"); m_value.assign(buffer, r.ptr - buffer); } /// \brief constructor for an item with name \a name and as /// content a formatted floating point value \a value with /// so-called general formatting template , int> = 0> item(const std::string_view name, const T &value) : m_name(name) { using namespace std; using namespace cif; char buffer[32]; auto r = to_chars(buffer, buffer + sizeof(buffer) - 1, value, chars_format::general); if ((bool)r.ec) throw std::runtime_error("Could not format number"); m_value.assign(buffer, r.ptr - buffer); } /// \brief constructor for an item with name \a name and as /// content the formatted integral value \a value template and not std::is_same_v, int> = 0> item(const std::string_view name, const T &value) : m_name(name) { char buffer[32]; auto r = std::to_chars(buffer, buffer + sizeof(buffer) - 1, value); if ((bool)r.ec) throw std::runtime_error("Could not format number"); m_value.assign(buffer, r.ptr - buffer); } /// \brief constructor for an item with name \a name and as /// content the formatted boolean value \a value template , int> = 0> item(const std::string_view name, const T &value) : m_name(name) { m_value.assign(value ? "y" : "n"); } /// \brief constructor for an item with name \a name and as /// content value \a value item(const std::string_view name, std::string_view value) : m_name(name) , m_value(value) { } /// \brief constructor for an item with name \a name and as /// content value \a value template, int> = 0> item(const std::string_view name, T &&value) : m_name(name) , m_value(std::move(value)) { } /// \brief constructor for an item with name \a name and as /// content the optional value \a value template item(const std::string_view name, const std::optional &value) : m_name(name) { if (value.has_value()) { item tmp(name, *value); std::swap(tmp.m_value, m_value); } else m_value.assign("?"); } /// \brief constructor for an item with name \a name and as /// content the formatted floating point value \a value with /// precision \a precision template , int> = 0> item(std::string_view name, const std::optional &value, int precision) : m_name(name) { if (value.has_value()) { item tmp(name, *value, precision); std::swap(tmp.m_value, m_value); } else m_value.assign("?"); } /** @cond */ item(const item &rhs) = default; item(item &&rhs) noexcept = default; item &operator=(const item &rhs) = default; item &operator=(item &&rhs) noexcept = default; /** @endcond */ std::string_view name() const { return m_name; } ///< Return the name of the item std::string_view value() const & { return m_value; } ///< Return the value of the item std::string value() const && { return std::move(m_value); } ///< Return the value of the item /// \brief replace the content of the stored value with \a v void value(std::string_view v) { m_value = v; } /// \brief empty means either null or unknown bool empty() const { return m_value.empty(); } /// \brief returns true if the item contains '.' bool is_null() const { return m_value == "."; } /// \brief returns true if the item contains '?' bool is_unknown() const { return m_value == "?"; } /// \brief the length of the value string std::size_t length() const { return m_value.length(); } /// \brief support for structured binding template decltype(auto) get() const { if constexpr (N == 0) return name(); else if constexpr (N == 1) return value(); } private: std::string_view m_name; std::string m_value; }; // -------------------------------------------------------------------- /// \brief the internal storage for items in a category /// /// Internal storage, strictly forward linked list with minimal space /// requirements. Strings of size 7 or shorter are stored internally. /// Typically, more than 99% of the strings in an mmCIF file are less /// than 8 bytes in length. struct item_value { /** @cond */ item_value() = default; /** @endcond */ /// \brief constructor item_value(std::string_view text) : m_length(text.length()) , m_storage(0) { if (m_length >= kBufferSize) { m_data = new char[m_length + 1]; std::copy(text.begin(), text.end(), m_data); m_data[m_length] = 0; } else { std::copy(text.begin(), text.end(), m_local_data); m_local_data[m_length] = 0; } } /** @cond */ item_value(item_value &&rhs) noexcept : m_length(std::exchange(rhs.m_length, 0)) , m_storage(std::exchange(rhs.m_storage, 0)) { } item_value &operator=(item_value &&rhs) noexcept { std::swap(m_length, rhs.m_length); std::swap(m_storage, rhs.m_storage); return *this; } ~item_value() { if (m_length >= kBufferSize) delete[] m_data; m_storage = 0; m_length = 0; } item_value(const item_value &) = delete; item_value &operator=(const item_value &) = delete; /** @endcond */ /** operator bool, allows easy checking for empty items */ explicit operator bool() const { return m_length != 0; } std::size_t m_length = 0; ///< Length of the data union { char m_local_data[8]; ///< Storage area for small strings (strings smaller than kBufferSize) char *m_data; ///< Pointer to a string stored in the heap uint64_t m_storage; ///< Alternative storage of the data, used in move operations }; /** The maximum length of locally stored strings */ static constexpr std::size_t kBufferSize = sizeof(m_local_data); // By using std::string_view instead of c_str we obain a // nice performance gain since we avoid many calls to strlen. /** Return the content of the item as a std::string_view */ constexpr inline std::string_view text() const { return { m_length >= kBufferSize ? m_data : m_local_data, m_length }; } }; // -------------------------------------------------------------------- // Transient object to access stored data /// \brief This is item_handle, it is used to access the data stored in item_value. struct item_handle { public: /** @cond */ // conversion helper class template struct item_value_as; /** @endcond */ /** * @brief Assign value @a value to the item referenced * * @tparam T Type of the value * @param value The value * @return reference to this item_handle */ template item_handle &operator=(const T &value) { assign_value(item{ "", value }.value()); return *this; } /** * @brief Assign value @a value to the item referenced * * @tparam T Type of the value * @param value The value * @return reference to this item_handle */ template item_handle &operator=(T &&value) { assign_value(item{ "", std::forward(value) }.value()); return *this; } /** * @brief Assign value @a value to the item referenced * * @tparam T Type of the value * @param value The value * @return reference to this item_handle */ template item_handle &operator=(const char (&value)[N]) { assign_value(item{ "", std::move(value) }.value()); return *this; } /** * @brief A method with a variable number of arguments that will be concatenated and * assigned as a string. Use it like this: * * @code{.cpp} * cif::item_handle ih; * is.os("The result of ", 1, " * ", 42, " is of course ", 42); * @endcode * * And the content will then be `The result of 1 * 42 is of course 42`. * * @tparam Ts Types of the parameters * @param v The parameters to concatenate */ template void os(const Ts &...v) { std::ostringstream ss; ((ss << v), ...); this->operator=(ss.str()); } /** Swap contents of this and @a b */ void swap(item_handle &b); /** Return the contents of this item as type @tparam T */ template auto as() const -> T { using value_type = std::remove_cv_t>; return item_value_as::convert(*this); } /** Return the contents of this item as type @tparam T or, if not * set, use @a dv as the default value. */ template auto value_or(const T &dv) const { return empty() ? dv : this->as(); } /** * @brief Compare the contents of this item with value @a value * optionally ignoring character case, if @a icase is true. * Returns 0 if both are equal, -1 if this sorts before @a value * and 1 if this sorts after @a value * * @tparam T Type of the value @a value * @param value The value to compare with * @param icase Flag indicating if we should compare character case sensitive * @return -1, 0 or 1 */ template int compare(const T &value, bool icase = true) const { return item_value_as::compare(*this, value, icase); } /** * @brief Compare the value contained with the value @a value and * return true if both are equal. */ template bool operator==(const T &value) const { // TODO: icase or not icase? return item_value_as::compare(*this, value, true) == 0; } // We may not have C++20 yet... /** * @brief Compare the value contained with the value @a value and * return true if both are not equal. */ template bool operator!=(const T &value) const { return not operator==(value); } /** * @brief Returns true if the content string is empty or * only contains '.' meaning null or '?' meaning unknown * in a mmCIF context */ bool empty() const { auto txt = text(); return txt.empty() or (txt.length() == 1 and (txt.front() == '.' or txt.front() == '?')); } /** Easy way to test for an empty item */ explicit operator bool() const { return not empty(); } /// is_null return true if the item contains '.' bool is_null() const { auto txt = text(); return txt.length() == 1 and txt.front() == '.'; } /// is_unknown returns true if the item contains '?' bool is_unknown() const { auto txt = text(); return txt.length() == 1 and txt.front() == '?'; } /** Return a std::string_view for the contents */ std::string_view text() const; /** * @brief Construct a new item handle object * * @param item Item index * @param row Reference to the row */ item_handle(uint16_t item, row_handle &row) : m_item_ix(item) , m_row_handle(row) { } /** A variable holding an empty item */ CIFPP_EXPORT static const item_handle s_null_item; /** friend to swap two item handles */ friend void swap(item_handle a, item_handle b) { a.swap(b); } private: item_handle(); uint16_t m_item_ix; row_handle &m_row_handle; void assign_value(std::string_view value); }; // So sad that older gcc implementations of from_chars did not support floats yet... /** @cond */ template struct item_handle::item_value_as and not std::is_same_v>> { using value_type = std::remove_reference_t>; static value_type convert(const item_handle &ref) { value_type result = {}; if (not ref.empty()) { auto txt = ref.text(); auto b = txt.data(); auto e = txt.data() + txt.size(); std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) ? selected_charconv::from_chars(b + 1, e, result) : selected_charconv::from_chars(b, e, result); if ((bool)r.ec or r.ptr != e) { result = {}; if (cif::VERBOSE) { if (r.ec == std::errc::invalid_argument) std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n"; else if (r.ec == std::errc::result_out_of_range) std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n"; else std::cerr << "Not a valid number " << std::quoted(txt) << '\n'; } } } return result; } static int compare(const item_handle &ref, const T &value, bool icase) { int result = 0; auto txt = ref.text(); if (ref.empty()) result = 1; else { value_type v = {}; auto b = txt.data(); auto e = txt.data() + txt.size(); std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) ? selected_charconv::from_chars(b + 1, e, v) : selected_charconv::from_chars(b, e, v); if ((bool)r.ec or r.ptr != e) { if (cif::VERBOSE) { if (r.ec == std::errc::invalid_argument) std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n"; else if (r.ec == std::errc::result_out_of_range) std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n"; else std::cerr << "Not a valid number " << std::quoted(txt) << '\n'; } result = 1; } else if (std::abs(v - value) <= std::numeric_limits::epsilon()) result = 0; else if (v < value) result = -1; else if (v > value) result = 1; } return result; } }; template struct item_handle::item_value_as> { static std::optional convert(const item_handle &ref) { std::optional result; if (ref) result = ref.as(); return result; } static int compare(const item_handle &ref, std::optional value, bool icase) { if (ref.empty() and not value) return 0; if (ref.empty()) return -1; else if (not value) return 1; else return ref.compare(*value, icase); } }; template struct item_handle::item_value_as>> { static bool convert(const item_handle &ref) { bool result = false; if (not ref.empty()) result = iequals(ref.text(), "y"); return result; } static int compare(const item_handle &ref, bool value, bool icase) { bool rv = convert(ref); return value && rv ? 0 : (rv < value ? -1 : 1); } }; template struct item_handle::item_value_as { static std::string convert(const item_handle &ref) { if (ref.empty()) return {}; return { ref.text().data(), ref.text().size() }; } static int compare(const item_handle &ref, const char (&value)[N], bool icase) { return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); } }; template struct item_handle::item_value_as>> { static std::string convert(const item_handle &ref) { if (ref.empty()) return {}; return { ref.text().data(), ref.text().size() }; } static int compare(const item_handle &ref, const char *value, bool icase) { return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); } }; template struct item_handle::item_value_as>> { static std::string convert(const item_handle &ref) { if (ref.empty()) return {}; return { ref.text().data(), ref.text().size() }; } static int compare(const item_handle &ref, const std::string_view &value, bool icase) { return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); } }; template struct item_handle::item_value_as>> { static std::string convert(const item_handle &ref) { if (ref.empty()) return {}; return { ref.text().data(), ref.text().size() }; } static int compare(const item_handle &ref, const std::string &value, bool icase) { return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value); } }; /** @endcond */ } // namespace cif namespace std { /** @cond */ template <> struct tuple_size<::cif::item> : public std::integral_constant { }; template <> struct tuple_element<0, ::cif::item> { using type = decltype(std::declval<::cif::item>().name()); }; template <> struct tuple_element<1, ::cif::item> { using type = decltype(std::declval<::cif::item>().value()); }; /** @endcond */ } // namespace stdlibcifpp-7.0.9/include/cif++/iterator.hpp0000644000175000017500000004725314746170722020060 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/row.hpp" #include /** * @file iterator.hpp * * This file contains several implementations of generic iterators. * * Using partial specialization we can have implementation for * iterators that return row_handles, a single value or tuples of * multiple values. * */ namespace cif { // -------------------------------------------------------------------- /** * @brief Implementation of an iterator that can return * multiple values in a tuple. Of course, that tuple can * then be used in structured binding to receive the values * in a for loop e.g. * * @tparam Category The category for this iterator * @tparam Ts The types this iterator can be dereferenced to */ template class iterator_impl { public: /** @cond */ template friend class iterator_impl; friend class category; /** @endcond */ /** variable that contains the number of elements in the tuple */ static constexpr std::size_t N = sizeof...(Ts); /** @cond */ using category_type = std::remove_cv_t; using row_type = std::conditional_t, const row, row>; using tuple_type = std::tuple; using iterator_category = std::forward_iterator_tag; using value_type = tuple_type; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type &; iterator_impl() = default; iterator_impl(const iterator_impl &rhs) = default; iterator_impl(iterator_impl &&rhs) = default; template iterator_impl(const iterator_impl &rhs) : m_current(const_cast(rhs.m_current)) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { } template iterator_impl(iterator_impl &rhs) : m_current(const_cast(rhs.m_current)) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { m_value = get(std::make_index_sequence()); } template iterator_impl(const iterator_impl &rhs, const std::array &cix) : m_current(const_cast(rhs.m_current)) , m_item_ix(cix) { m_value = get(std::make_index_sequence()); } iterator_impl &operator=(iterator_impl i) { std::swap(m_current, i.m_current); std::swap(m_item_ix, i.m_item_ix); std::swap(m_value, i.m_value); return *this; } virtual ~iterator_impl() = default; reference operator*() { return m_value; } pointer operator->() { return &m_value; } operator const row_handle() const { return m_current; } operator row_handle() { return m_current; } iterator_impl &operator++() { if (m_current) m_current.m_row = m_current.m_row->m_next; m_value = get(std::make_index_sequence()); return *this; } iterator_impl operator++(int) { iterator_impl result(*this); this->operator++(); return result; } bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } template bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } template bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } /** @endcond */ private: template tuple_type get(std::index_sequence) const { return m_current ? tuple_type{ m_current[m_item_ix[Is]].template as()... } : tuple_type{}; } row_handle m_current; value_type m_value; std::array m_item_ix; }; /** * @brief Implementation of an iterator that returns * only row_handles * * @tparam Category The category for this iterator */ template class iterator_impl { public: /** @cond */ template friend class iterator_impl; friend class category; using category_type = std::remove_cv_t; using row_type = std::conditional_t, const row, row>; using iterator_category = std::forward_iterator_tag; using value_type = row_handle; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type &; iterator_impl() = default; iterator_impl(const iterator_impl &rhs) = default; iterator_impl(iterator_impl &&rhs) = default; template iterator_impl(const iterator_impl &rhs) : m_current(const_cast(rhs.m_current)) { } iterator_impl(Category &cat, row *current) : m_current(cat, *current) { } template iterator_impl(const iterator_impl &rhs, const std::array &) : m_current(const_cast(rhs.m_current)) { } iterator_impl &operator=(iterator_impl i) { std::swap(m_current, i.m_current); return *this; } virtual ~iterator_impl() = default; reference operator*() { return m_current; } pointer operator->() { return &m_current; } operator const row_handle() const { return m_current; } operator row_handle() { return m_current; } iterator_impl &operator++() { if (m_current) m_current.m_row = m_current.m_row->m_next; return *this; } iterator_impl operator++(int) { iterator_impl result(*this); this->operator++(); return result; } bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } template bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } template bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } /** @endcond */ private: row_handle m_current; }; /** * @brief Implementation of an iterator that can return * a single value. * * @tparam Category The category for this iterator * @tparam T The type this iterator can be dereferenced to */ template class iterator_impl { public: /** @cond */ template friend class iterator_impl; friend class category; using category_type = std::remove_cv_t; using row_type = std::conditional_t, const row, row>; using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type &; iterator_impl() = default; iterator_impl(const iterator_impl &rhs) = default; iterator_impl(iterator_impl &&rhs) = default; template iterator_impl(const iterator_impl &rhs) : m_current(rhs.m_current) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { } template iterator_impl(iterator_impl &rhs) : m_current(const_cast(rhs.m_current)) , m_value(rhs.m_value) , m_item_ix(rhs.m_item_ix) { m_value = get(); } template iterator_impl(const iterator_impl &rhs, const std::array &cix) : m_current(const_cast(rhs.m_current)) , m_item_ix(cix[0]) { m_value = get(); } iterator_impl &operator=(iterator_impl i) { std::swap(m_current, i.m_current); std::swap(m_item_ix, i.m_item_ix); std::swap(m_value, i.m_value); return *this; } virtual ~iterator_impl() = default; reference operator*() { return m_value; } pointer operator->() { return &m_value; } operator const row_handle() const { return m_current; } operator row_handle() { return m_current; } iterator_impl &operator++() { if (m_current) m_current.m_row = m_current.m_row->m_next; m_value = get(); return *this; } iterator_impl operator++(int) { iterator_impl result(*this); this->operator++(); return result; } bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } template bool operator==(const iterator_impl &rhs) const { return m_current == rhs.m_current; } template bool operator!=(const iterator_impl &rhs) const { return m_current != rhs.m_current; } /** @endcond */ private: value_type get() const { return m_current ? m_current[m_item_ix].template as() : value_type{}; } row_handle m_current; value_type m_value; uint16_t m_item_ix; }; // -------------------------------------------------------------------- // iterator proxy /** * @brief An iterator_proxy is used as a result type for methods that * return a range of values you want to iterate over. * * E.g. the class cif::category contains the method cif::category::rows() * that returns an iterator_proxy that allows you to iterate over * all the rows in the category. * * @tparam Category The category for the iterators * @tparam Ts The types the iterators return. See class: iterator */ template class iterator_proxy { public: /** @cond */ static constexpr const std::size_t N = sizeof...(Ts); using category_type = Category; using row_type = std::conditional_t, const row, row>; using iterator = iterator_impl; using row_iterator = iterator_impl; iterator_proxy(category_type &cat, row_iterator pos, char const *const items[N]); iterator_proxy(category_type &cat, row_iterator pos, std::initializer_list items); iterator_proxy(iterator_proxy &&p); iterator_proxy &operator=(iterator_proxy &&p); iterator_proxy(const iterator_proxy &) = delete; iterator_proxy &operator=(const iterator_proxy &) = delete; /** @endcond */ iterator begin() const { return iterator(m_begin, m_item_ix); } ///< Return the iterator pointing to the first row iterator end() const { return iterator(m_end, m_item_ix); } ///< Return the iterator pointing past the last row bool empty() const { return m_begin == m_end; } ///< Return true if the range is empty explicit operator bool() const { return not empty(); } ///< Easy way to detect if the range is empty std::size_t size() const { return std::distance(begin(), end()); } ///< Return size of the range // row front() { return *begin(); } // row back() { return *(std::prev(end())); } category_type &category() const { return *m_category; } ///< Return the category the iterator belong to /** swap */ void swap(iterator_proxy &rhs) { std::swap(m_category, rhs.m_category); std::swap(m_begin, rhs.m_begin); std::swap(m_end, rhs.m_end); std::swap(m_item_ix, rhs.m_item_ix); } private: category_type *m_category; row_iterator m_begin, m_end; std::array m_item_ix; }; // -------------------------------------------------------------------- // conditional iterator proxy /** * @brief A conditional iterator proxy is similar to an iterator_proxy * in that it can be used to return a range of rows you can iterate over. * In the case of an conditional_iterator_proxy a cif::condition is used * to filter out only those rows that match the condition. * * @tparam CategoryType The category the iterators belong to * @tparam Ts The types to which the iterators can be dereferenced */ template class conditional_iterator_proxy { public: /** @cond */ static constexpr const std::size_t N = sizeof...(Ts); using category_type = std::remove_cv_t; using base_iterator = iterator_impl; using value_type = typename base_iterator::value_type; using row_type = typename base_iterator::row_type; using row_iterator = iterator_impl; class conditional_iterator_impl { public: using iterator_category = std::forward_iterator_tag; using value_type = conditional_iterator_proxy::value_type; using difference_type = std::ptrdiff_t; using pointer = value_type *; using reference = value_type; conditional_iterator_impl(CategoryType &cat, row_iterator pos, const condition &cond, const std::array &cix); conditional_iterator_impl(const conditional_iterator_impl &i) = default; conditional_iterator_impl &operator=(const conditional_iterator_impl &i) = default; virtual ~conditional_iterator_impl() = default; reference operator*() { return *m_begin; } pointer operator->() { m_current = *m_begin; return &m_current; } conditional_iterator_impl &operator++() { while (m_begin != m_end) { if (++m_begin == m_end) break; if (m_condition->operator()(m_begin)) break; } return *this; } conditional_iterator_impl operator++(int) { conditional_iterator_impl result(*this); this->operator++(); return result; } bool operator==(const conditional_iterator_impl &rhs) const { return m_begin == rhs.m_begin; } bool operator!=(const conditional_iterator_impl &rhs) const { return m_begin != rhs.m_begin; } bool operator==(const row_iterator &rhs) const { return m_begin == rhs; } bool operator!=(const row_iterator &rhs) const { return m_begin != rhs; } template bool operator==(const iterator_impl &rhs) const { return m_begin == rhs; } template bool operator!=(const iterator_impl &rhs) const { return m_begin != rhs; } private: CategoryType *m_cat; base_iterator m_begin, m_end; value_type m_current; const condition *m_condition; }; using iterator = conditional_iterator_impl; using reference = typename iterator::reference; template conditional_iterator_proxy(CategoryType &cat, row_iterator pos, condition &&cond, Ns... names); conditional_iterator_proxy(conditional_iterator_proxy &&p); conditional_iterator_proxy &operator=(conditional_iterator_proxy &&p); conditional_iterator_proxy(const conditional_iterator_proxy &) = delete; conditional_iterator_proxy &operator=(const conditional_iterator_proxy &) = delete; /** @endcond */ iterator begin() const; ///< Return the iterator pointing to the first row iterator end() const; ///< Return the iterator pointing past the last row bool empty() const; ///< Return true if the range is empty explicit operator bool() const { return not empty(); } ///< Easy way to detect if the range is empty std::size_t size() const { return std::distance(begin(), end()); } ///< Return size of the range row_handle front() { return *begin(); } ///< Return reference to the first row // row_handle back() { return *begin(); } CategoryType &category() const { return *m_cat; } ///< Category the iterators belong to /** swap */ void swap(conditional_iterator_proxy &rhs); private: CategoryType *m_cat; condition m_condition; row_iterator mCBegin, mCEnd; std::array mCix; }; // -------------------------------------------------------------------- /** @cond */ template iterator_proxy::iterator_proxy(Category &cat, row_iterator pos, char const *const items[N]) : m_category(&cat) , m_begin(pos) , m_end(cat.end()) { for (uint16_t i = 0; i < N; ++i) m_item_ix[i] = m_category->get_item_ix(items[i]); } template iterator_proxy::iterator_proxy(Category &cat, row_iterator pos, std::initializer_list items) : m_category(&cat) , m_begin(pos) , m_end(cat.end()) { // static_assert(items.size() == N, "The list of item names should be exactly the same as the list of requested items"); std::uint16_t i = 0; for (auto item : items) m_item_ix[i++] = m_category->get_item_ix(item); } // -------------------------------------------------------------------- template conditional_iterator_proxy::conditional_iterator_impl::conditional_iterator_impl( Category &cat, row_iterator pos, const condition &cond, const std::array &cix) : m_cat(&cat) , m_begin(pos, cix) , m_end(cat.end(), cix) , m_condition(&cond) { if (m_condition == nullptr or m_condition->empty()) m_begin = m_end; } template conditional_iterator_proxy::conditional_iterator_proxy(conditional_iterator_proxy &&p) : m_cat(nullptr) , mCBegin(p.mCBegin) , mCEnd(p.mCEnd) , mCix(p.mCix) { std::swap(m_cat, p.m_cat); std::swap(mCix, p.mCix); m_condition.swap(p.m_condition); } template template conditional_iterator_proxy::conditional_iterator_proxy(Category &cat, row_iterator pos, condition &&cond, Ns... names) : m_cat(&cat) , m_condition(std::move(cond)) , mCBegin(pos) , mCEnd(cat.end()) { static_assert(sizeof...(Ts) == sizeof...(Ns), "Number of item names should be equal to number of requested value types"); if (m_condition) { m_condition.prepare(cat); while (mCBegin != mCEnd and not m_condition(*mCBegin)) ++mCBegin; } else mCBegin = mCEnd; uint16_t i = 0; ((mCix[i++] = m_cat->get_item_ix(names)), ...); } template conditional_iterator_proxy &conditional_iterator_proxy::operator=(conditional_iterator_proxy &&p) { swap(p); return *this; } template typename conditional_iterator_proxy::iterator conditional_iterator_proxy::begin() const { return iterator(*m_cat, mCBegin, m_condition, mCix); } template typename conditional_iterator_proxy::iterator conditional_iterator_proxy::end() const { return iterator(*m_cat, mCEnd, m_condition, mCix); } template bool conditional_iterator_proxy::empty() const { return mCBegin == mCEnd; } template void conditional_iterator_proxy::swap(conditional_iterator_proxy &rhs) { std::swap(m_cat, rhs.m_cat); m_condition.swap(rhs.m_condition); std::swap(mCBegin, rhs.mCBegin); std::swap(mCEnd, rhs.mCEnd); std::swap(mCix, rhs.mCix); } /** @endcond */ } // namespace ciflibcifpp-7.0.9/include/cif++/matrix.hpp0000644000175000017500000004572414746170722017534 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #include #include /** * @file matrix.hpp * * Some basic matrix operations and classes to hold matrices. * * We're using expression templates for optimal performance. * */ namespace cif { // -------------------------------------------------------------------- // We're using expression templates here /** * @brief Base for the matrix expression templates * This all uses the Curiously recurring template pattern * * @tparam M The type of the derived class */ template class matrix_expression { public: constexpr std::size_t dim_m() const { return static_cast(*this).dim_m(); } ///< Return the size (dimension) in direction m constexpr std::size_t dim_n() const { return static_cast(*this).dim_n(); } ///< Return the size (dimension) in direction n constexpr bool empty() const { return dim_m() == 0 or dim_n() == 0; } ///< Convenient way to test for empty matrices /** Return a reference to element [ @a i, @a j ] */ constexpr auto &operator()(std::size_t i, std::size_t j) { return static_cast(*this).operator()(i, j); } /** Return the value of element [ @a i, @a j ] */ constexpr auto operator()(std::size_t i, std::size_t j) const { return static_cast(*this).operator()(i, j); } /** Swap the contents of rows @a r1 and @a r2 */ void swap_row(std::size_t r1, std::size_t r2) { for (std::size_t c = 0; c < dim_m(); ++c) { auto v = operator()(r1, c); operator()(r1, c) = operator()(r2, c); operator()(r2, c) = v; } } /** Swap the contents of columns @a c1 and @a c2 */ void swap_col(std::size_t c1, std::size_t c2) { for (std::size_t r = 0; r < dim_n(); ++r) { auto &a = operator()(r, c1); auto &b = operator()(r, c2); std::swap(a, b); } } /** write the matrix @a m to std::ostream @a os */ friend std::ostream &operator<<(std::ostream &os, const matrix_expression &m) { os << '['; for (std::size_t i = 0; i < m.dim_m(); ++i) { os << '['; for (std::size_t j = 0; j < m.dim_n(); ++j) { os << m(i, j); if (j + 1 < m.dim_n()) os << ", "; } if (i + 1 < m.dim_m()) os << ", "; os << ']'; } os << ']'; return os; } }; // -------------------------------------------------------------------- /** * @brief Storage class implementation of matrix_expression. * * @tparam F The type of the stored values * * matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n * element m i,j is mapped to [i * n + j] and thus storage is row major */ template class matrix : public matrix_expression> { public: /** The value type */ using value_type = F; /** * @brief Copy construct a new matrix object using @a m * * @tparam M2 Type of @a m * @param m The matrix expression to copy values from */ template matrix(const matrix_expression &m) : m_m(m.dim_m()) , m_n(m.dim_n()) , m_data(m_m * m_n) { for (std::size_t i = 0; i < m_m; ++i) { for (std::size_t j = 0; j < m_n; ++j) operator()(i, j) = m(i, j); } } /** * @brief Construct a new matrix object with dimension @a m and @a n * setting the values to @a v * * @param m Requested dimension M * @param n Requested dimension N * @param v Value to store in each element */ matrix(std::size_t m, std::size_t n, value_type v = 0) : m_m(m) , m_n(n) , m_data(m_m * m_n) { std::fill(m_data.begin(), m_data.end(), v); } /** @cond */ matrix() = default; matrix(matrix &&m) = default; matrix(const matrix &m) = default; matrix &operator=(matrix &&m) = default; matrix &operator=(const matrix &m) = default; /** @endcond */ constexpr std::size_t dim_m() const { return m_m; } ///< Return dimension m constexpr std::size_t dim_n() const { return m_n; } ///< Return dimension n /** Return the value of element [ @a i, @a j ] */ constexpr value_type operator()(std::size_t i, std::size_t j) const { assert(i < m_m); assert(j < m_n); return m_data[i * m_n + j]; } /** Return a reference to element [ @a i, @a j ] */ constexpr value_type &operator()(std::size_t i, std::size_t j) { assert(i < m_m); assert(j < m_n); return m_data[i * m_n + j]; } private: std::size_t m_m = 0, m_n = 0; std::vector m_data; }; // -------------------------------------------------------------------- // special case, 3x3 matrix /** * @brief Storage class implementation of matrix_expression * with compile time fixed size. * * @tparam F The type of the stored values * * matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n * element m i,j is mapped to [i * n + j] and thus storage is row major */ template class matrix_fixed : public matrix_expression> { public: /** The value type */ using value_type = F; /** The storage size */ static constexpr std::size_t kSize = M * N; /** Copy constructor */ template matrix_fixed(const M2 &m) { assert(M == m.dim_m() and N == m.dim_n()); for (std::size_t i = 0; i < M; ++i) { for (std::size_t j = 0; j < N; ++j) operator()(i, j) = m(i, j); } } /** default constructor */ matrix_fixed(value_type v = 0) { m_data.fill(v); } /** Alternate constructor taking an array of values to store */ matrix_fixed(const F (&v)[kSize]) { fill(v, std::make_index_sequence{}); } /** @cond */ matrix_fixed(matrix_fixed &&m) = default; matrix_fixed(const matrix_fixed &m) = default; matrix_fixed &operator=(matrix_fixed &&m) = default; matrix_fixed &operator=(const matrix_fixed &m) = default; /** @endcond */ /** Store the values in @a a in the matrix */ template matrix_fixed& fill(const F (&a)[kSize], std::index_sequence) { m_data = { a[Ixs]... }; return *this; } constexpr std::size_t dim_m() const { return M; } ///< Return dimension m constexpr std::size_t dim_n() const { return N; } ///< Return dimension n /** Return the value of element [ @a i, @a j ] */ constexpr value_type operator()(std::size_t i, std::size_t j) const { assert(i < M); assert(j < N); return m_data[i * N + j]; } /** Return a reference to element [ @a i, @a j ] */ constexpr value_type &operator()(std::size_t i, std::size_t j) { assert(i < M); assert(j < N); return m_data[i * N + j]; } private: std::array m_data; }; /** typedef of a fixed matrix of size 3x3 */ template using matrix3x3 = matrix_fixed; /** typedef of a fixed matrix of size 4x4 */ template using matrix4x4 = matrix_fixed; // -------------------------------------------------------------------- /** * @brief Storage class implementation of symmetric matrix_expression * * @tparam F The type of the stored values * * matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n * element m i,j is mapped to [i * n + j] and thus storage is row major */ template class symmetric_matrix : public matrix_expression> { public: /** The value type */ using value_type = F; /** constructor for a matrix of size @a n x @a n elements with value @a v */ symmetric_matrix(std::size_t n, value_type v = 0) : m_n(n) , m_data((m_n * (m_n + 1)) / 2) { std::fill(m_data.begin(), m_data.end(), v); } /** @cond */ symmetric_matrix() = default; symmetric_matrix(symmetric_matrix &&m) = default; symmetric_matrix(const symmetric_matrix &m) = default; symmetric_matrix &operator=(symmetric_matrix &&m) = default; symmetric_matrix &operator=(const symmetric_matrix &m) = default; /** @endcond */ constexpr std::size_t dim_m() const { return m_n; } ///< Return dimension m constexpr std::size_t dim_n() const { return m_n; } ///< Return dimension n /** Return the value of element [ @a i, @a j ] */ constexpr value_type operator()(std::size_t i, std::size_t j) const { return i < j ? m_data[(j * (j + 1)) / 2 + i] : m_data[(i * (i + 1)) / 2 + j]; } /** Return a reference to element [ @a i, @a j ] */ constexpr value_type &operator()(std::size_t i, std::size_t j) { if (i > j) std::swap(i, j); assert(j < m_n); return m_data[(j * (j + 1)) / 2 + i]; } private: std::size_t m_n; std::vector m_data; }; // -------------------------------------------------------------------- /** * @brief Storage class implementation of symmetric matrix_expression * with compile time fixed size. * * @tparam F The type of the stored values * * matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n * element m i,j is mapped to [i * n + j] and thus storage is row major */ template class symmetric_matrix_fixed : public matrix_expression> { public: /** The value type */ using value_type = F; /** constructor with all elements set to value @a v */ symmetric_matrix_fixed(value_type v = 0) { std::fill(m_data.begin(), m_data.end(), v); } /** @cond */ symmetric_matrix_fixed(symmetric_matrix_fixed &&m) = default; symmetric_matrix_fixed(const symmetric_matrix_fixed &m) = default; symmetric_matrix_fixed &operator=(symmetric_matrix_fixed &&m) = default; symmetric_matrix_fixed &operator=(const symmetric_matrix_fixed &m) = default; /** @endcond */ constexpr std::size_t dim_m() const { return M; } ///< Return dimension m constexpr std::size_t dim_n() const { return M; } ///< Return dimension n /** Return the value of element [ @a i, @a j ] */ constexpr value_type operator()(std::size_t i, std::size_t j) const { return i < j ? m_data[(j * (j + 1)) / 2 + i] : m_data[(i * (i + 1)) / 2 + j]; } /** Return a reference to element [ @a i, @a j ] */ constexpr value_type &operator()(std::size_t i, std::size_t j) { if (i > j) std::swap(i, j); assert(j < M); return m_data[(j * (j + 1)) / 2 + i]; } private: std::array m_data; }; /** typedef of a fixed symmetric matrix of size 3x3 */ template using symmetric_matrix3x3 = symmetric_matrix_fixed; /** typedef of a fixed symmetric matrix of size 4x4 */ template using symmetric_matrix4x4 = symmetric_matrix_fixed; // -------------------------------------------------------------------- /** * @brief implementation of symmetric matrix_expression with a value * of 1 for the diagonal values and 0 for all the others. * * @tparam F The type of the stored values * * matrix is m x n, addressing i,j is 0 <= i < m and 0 <= j < n * element m i,j is mapped to [i * n + j] and thus storage is row major */ template class identity_matrix : public matrix_expression> { public: /** the value type */ using value_type = F; /** constructor taking a dimension @a n */ identity_matrix(std::size_t n) : m_n(n) { } constexpr std::size_t dim_m() const { return m_n; } ///< Return dimension m constexpr std::size_t dim_n() const { return m_n; } ///< Return dimension n /** Return the value of element [ @a i, @a j ] */ constexpr value_type operator()(std::size_t i, std::size_t j) const { return static_cast(i == j ? 1 : 0); } private: std::size_t m_n; }; // -------------------------------------------------------------------- // matrix functions, implemented as expression templates /** * @brief Implementation of a substraction operation as a matrix expression * * @tparam M1 Type of matrix 1 * @tparam M2 Type of matrix 2 */ template class matrix_subtraction : public matrix_expression> { public: /** constructor */ matrix_subtraction(const M1 &m1, const M2 &m2) : m_m1(m1) , m_m2(m2) { assert(m_m1.dim_m() == m_m2.dim_m()); assert(m_m1.dim_n() == m_m2.dim_n()); } constexpr std::size_t dim_m() const { return m_m1.dim_m(); } ///< Return dimension m constexpr std::size_t dim_n() const { return m_m1.dim_n(); } ///< Return dimension n /** Access to the value of element [ @a i, @a j ] */ constexpr auto operator()(std::size_t i, std::size_t j) const { return m_m1(i, j) - m_m2(i, j); } private: const M1 &m_m1; const M2 &m_m2; }; /** operator to subtract two matrices and return a matrix expression */ template auto operator-(const matrix_expression &m1, const matrix_expression &m2) { return matrix_subtraction(m1, m2); } /** * @brief Implementation of a multiplication operation as a matrix expression * * @tparam M1 Type of matrix 1 * @tparam M2 Type of matrix 2 */ template class matrix_matrix_multiplication : public matrix_expression> { public: /** constructor */ matrix_matrix_multiplication(const M1 &m1, const M2 &m2) : m_m1(m1) , m_m2(m2) { assert(m1.dim_m() == m2.dim_n()); } constexpr std::size_t dim_m() const { return m_m1.dim_m(); } ///< Return dimension m constexpr std::size_t dim_n() const { return m_m1.dim_n(); } ///< Return dimension n /** Access to the value of element [ @a i, @a j ] */ constexpr auto operator()(std::size_t i, std::size_t j) const { using value_type = decltype(m_m1(0, 0)); value_type result = {}; for (std::size_t k = 0; k < m_m1.dim_m(); ++k) result += m_m1(i, k) * m_m2(k, j); return result; } private: const M1 &m_m1; const M2 &m_m2; }; /** * @brief Implementation of a multiplication operation of a matrix and a scalar value as a matrix expression * * @tparam M1 Type of matrix * @tparam M2 Type of scalar value */ template class matrix_scalar_multiplication : public matrix_expression> { public: /** value type */ using value_type = T; /** constructor */ matrix_scalar_multiplication(const M &m, value_type v) : m_m(m) , m_v(v) { } constexpr std::size_t dim_m() const { return m_m.dim_m(); } ///< Return dimension m constexpr std::size_t dim_n() const { return m_m.dim_n(); } ///< Return dimension n /** Access to the value of element [ @a i, @a j ] */ constexpr auto operator()(std::size_t i, std::size_t j) const { return m_m(i, j) * m_v; } private: const M &m_m; value_type m_v; }; /** First implementation of operator*, enabled if the second parameter is a scalar */ template , int> = 0> auto operator*(const matrix_expression &m, T v) { return matrix_scalar_multiplication(m, v); } /** First implementation of operator*, enabled if the second parameter is not a scalar and thus must be a matrix, right? */ template , int> = 0> auto operator*(const matrix_expression &m1, const matrix_expression &m2) { return matrix_matrix_multiplication(m1, m2); } // -------------------------------------------------------------------- /** Generic routine to calculate the determinant of a matrix * * @note This is currently only implemented for fixed matrices of size 3x3 */ template auto determinant(const M &m); /** Implementation of the determinant function for fixed size matrices of size 3x3 */ template auto determinant(const matrix3x3 &m) { return (m(0, 0) * (m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1)) + m(0, 1) * (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) + m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0))); } /** Generic routine to calculate the inverse of a matrix * * @note This is currently only implemented for fixed matrices of size 3x3 */ template M inverse(const M &m); /** Implementation of the inverse function for fixed size matrices of size 3x3 */ template matrix3x3 inverse(const matrix3x3 &m) { F det = determinant(m); matrix3x3 result; result(0, 0) = (m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1)) / det; result(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) / det; result(2, 0) = (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0)) / det; result(0, 1) = (m(2, 1) * m(0, 2) - m(2, 2) * m(0, 1)) / det; result(1, 1) = (m(2, 2) * m(0, 0) - m(2, 0) * m(0, 2)) / det; result(2, 1) = (m(2, 0) * m(0, 1) - m(2, 1) * m(0, 0)) / det; result(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) / det; result(1, 2) = (m(0, 2) * m(1, 0) - m(0, 0) * m(1, 2)) / det; result(2, 2) = (m(0, 0) * m(1, 1) - m(0, 1) * m(1, 0)) / det; return result; } // -------------------------------------------------------------------- /** * @brief Implementation of a cofactor calculation as a matrix expression * * @tparam M Type of matrix */ template class matrix_cofactors : public matrix_expression> { public: /** constructor */ matrix_cofactors(const M &m) : m_m(m) { } constexpr std::size_t dim_m() const { return m_m.dim_m(); } ///< Return dimension m constexpr std::size_t dim_n() const { return m_m.dim_n(); } ///< Return dimension n /** Access to the value of element [ @a i, @a j ] */ constexpr auto operator()(std::size_t i, std::size_t j) const { const std::size_t ixs[4][3] = { { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 }, { 0, 1, 2 } }; const std::size_t *ix = ixs[i]; const std::size_t *iy = ixs[j]; auto result = m_m(ix[0], iy[0]) * m_m(ix[1], iy[1]) * m_m(ix[2], iy[2]) + m_m(ix[0], iy[1]) * m_m(ix[1], iy[2]) * m_m(ix[2], iy[0]) + m_m(ix[0], iy[2]) * m_m(ix[1], iy[0]) * m_m(ix[2], iy[1]) - m_m(ix[0], iy[2]) * m_m(ix[1], iy[1]) * m_m(ix[2], iy[0]) - m_m(ix[0], iy[1]) * m_m(ix[1], iy[0]) * m_m(ix[2], iy[2]) - m_m(ix[0], iy[0]) * m_m(ix[1], iy[2]) * m_m(ix[2], iy[1]); return (i + j) % 2 == 1 ? -result : result; } private: const M &m_m; }; } // namespace cif libcifpp-7.0.9/include/cif++/model.hpp0000644000175000017500000011277014746170722017324 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/atom_type.hpp" #include "cif++/datablock.hpp" #include "cif++/point.hpp" #include #include #if __cpp_lib_format #include #endif /** @file model.hpp * * This file contains code to work with models of molecules. * * The classes available encapsulate the real world concepts of * atoms, residues, monomers, polymers and everything is then * bound together in a structure. * * This code is not finished yet, ideally it would be a high * level interface to manipulate macro molecular structures * and an attempt has been made to start work on this. But * there's still a lot that needs to be implemented. * * However, the code that is here is still useful in * manipulating the underlying mmCIF data model. * */ namespace cif::mm { class atom; class residue; class monomer; class polymer; class structure; // -------------------------------------------------------------------- /** * @brief The class atom encapsulates the data in _atom_site and * _atom_site_anisotrop * * The class atom is a kind of flyweight class. It can be copied * with low overhead. All data is stored in the underlying mmCIF * categories but some very often used items are cached in the * impl. * * It is also possible to have symmetry copies of atoms. They * share the same data in the cif::category but their location * differs by using a symmetry operator. */ class atom { private: /** @cond */ struct atom_impl : public std::enable_shared_from_this { atom_impl(const datablock &db, std::string_view id) : m_db(db) , m_cat(db["atom_site"]) , m_id(id) { auto r = row(); if (r) tie(m_location.m_x, m_location.m_y, m_location.m_z) = r.get("Cartn_x", "Cartn_y", "Cartn_z"); } // constructor for a symmetry copy of an atom atom_impl(const atom_impl &impl, const point &loc, const std::string &sym_op) : atom_impl(impl) { m_location = loc; m_symop = sym_op; } atom_impl(const atom_impl &i) = default; void prefetch(); int compare(const atom_impl &b) const; // bool getAnisoU(float anisou[6]) const; int get_charge() const; void moveTo(const point &p); // const compound *compound() const; std::string get_property(std::string_view name) const; int get_property_int(std::string_view name) const; float get_property_float(std::string_view name) const; void set_property(const std::string_view name, const std::string &value); row_handle row() { return m_cat[{ { "id", m_id } }]; } const row_handle row() const { return m_cat[{ { "id", m_id } }]; } row_handle row_aniso() { auto cat = m_db.get("atom_site_anisotrop"); return cat ? cat->operator[]({ { "id", m_id } }) : row_handle{}; } const row_handle row_aniso() const { auto cat = m_db.get("atom_site_anisotrop"); return cat ? cat->operator[]({ { "id", m_id } }) : row_handle{}; } const datablock &m_db; const category &m_cat; std::string m_id; point m_location; std::string m_symop = "1_555"; }; /** @endcond */ public: /** * @brief Construct a new, empty atom object */ atom() {} /** * @brief Construct a new atom object using @a impl as impl * * @param impl The implementation objectt */ atom(std::shared_ptr impl) : m_impl(impl) { } /** * @brief Copy construct a new atom object */ atom(const atom &rhs) : m_impl(rhs.m_impl) { } /** * @brief Construct a new atom object based on a cif::row * * @param db The datablock where the _atom_site category resides * @param row The row containing the data for this atom */ atom(const datablock &db, const row_handle &row) : atom(std::make_shared(db, row["id"].as())) { } /** * @brief A special constructor to create symmetry copies * * @param rhs The original atom to copy * @param symmmetry_location The symmetry location * @param symmetry_operation The symmetry operator used */ atom(const atom &rhs, const point &symmmetry_location, const std::string &symmetry_operation) : atom(std::make_shared(*rhs.m_impl, symmmetry_location, symmetry_operation)) { } /// \brief To quickly test if the atom has data explicit operator bool() const { return (bool)m_impl; } /// \brief Copy assignement operator atom &operator=(const atom &rhs) = default; /// \brief Return the item named @a name in the _atom_site category for this atom std::string get_property(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property(name); } /// \brief Return the item named @a name in the _atom_site category for this atom cast to an int int get_property_int(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property_int(name); } /// \brief Return the item named @a name in the _atom_site category for this atom cast to a float float get_property_float(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property_float(name); } /// \brief Set value for the item named @a name in the _atom_site category to @a value void set_property(const std::string_view name, const std::string &value) { if (not m_impl) throw std::logic_error("Error trying to modify an uninitialized atom"); m_impl->set_property(name, value); } /// \brief Set value for the item named @a name in the _atom_site category to @a value template , int> = 0> void set_property(const std::string_view name, const T &value) { set_property(name, std::to_string(value)); } /** Return the ID of the _atom_site record. * * @note Although I've never seen anything other than integers, * the standard says this should be a string and so we use that. */ const std::string &id() const { return impl().m_id; } /// \brief Return the type of the atom cif::atom_type get_type() const { return atom_type_traits(get_property("type_symbol")).type(); } /// \brief Return the cached location of this atom point get_location() const { return impl().m_location; } /// \brief Set the location of this atom, will set both the cached data as well as the data in the underlying _atom_site category void set_location(point p) { if (not m_impl) throw std::logic_error("Error trying to modify an uninitialized atom"); m_impl->moveTo(p); } /// \brief Translate the position of this atom by \a t void translate(point t) { set_location(get_location() + t); } /// \brief Rotate the position of this atom by \a q void rotate(quaternion q) { auto loc = get_location(); loc.rotate(q); set_location(loc); } /// \brief rotate the coordinates of this atom by \a q around point \a p void rotate(quaternion q, point p) { auto loc = get_location(); loc.rotate(q, p); set_location(loc); } /// \brief Translate and rotate the position of this atom by \a t and \a q void translate_and_rotate(point t, quaternion q) { auto loc = get_location(); loc += t; loc.rotate(q); set_location(loc); } /// \brief Translate, rotate and translate again the coordinates this atom by \a t1 , \a q and \a t2 void translate_rotate_and_translate(point t1, quaternion q, point t2) { auto loc = get_location(); loc += t1; loc.rotate(q); loc += t2; set_location(loc); } /// for direct access to underlying data, be careful! const row_handle get_row() const { return impl().row(); } /// for direct access to underlying data, be careful! const row_handle get_row_aniso() const { return impl().row_aniso(); } /// Return if the atom is actually a symmetry copy or the original one bool is_symmetry_copy() const { return impl().m_symop != "1_555"; } /// Return the symmetry operator used std::string symmetry() const { return impl().m_symop; } /// Return true if this atom is part of a water molecule bool is_water() const { auto comp_id = get_label_comp_id(); return comp_id == "HOH" or comp_id == "H2O" or comp_id == "WAT"; } /// Return the charge int get_charge() const { return impl().get_charge(); } /// Return the occupancy float get_occupancy() const { return get_property_float("occupancy"); } // specifications std::string get_label_asym_id() const { return get_property("label_asym_id"); } ///< Return the label_asym_id property int get_label_seq_id() const { return get_property_int("label_seq_id"); } ///< Return the label_seq_id property std::string get_label_atom_id() const { return get_property("label_atom_id"); } ///< Return the label_atom_id property std::string get_label_alt_id() const { return get_property("label_alt_id"); } ///< Return the label_alt_id property std::string get_label_comp_id() const { return get_property("label_comp_id"); } ///< Return the label_comp_id property std::string get_label_entity_id() const { return get_property("label_entity_id"); } ///< Return the label_entity_id property std::string get_auth_asym_id() const { return get_property("auth_asym_id"); } ///< Return the auth_asym_id property std::string get_auth_seq_id() const { return get_property("auth_seq_id"); } ///< Return the auth_seq_id property std::string get_auth_atom_id() const { return get_property("auth_atom_id"); } ///< Return the auth_atom_id property std::string get_auth_alt_id() const { return get_property("auth_alt_id"); } ///< Return the auth_alt_id property std::string get_auth_comp_id() const { return get_property("auth_comp_id"); } ///< Return the auth_comp_id property std::string get_pdb_ins_code() const { return get_property("pdbx_PDB_ins_code"); } ///< Return the pdb_ins_code property /// Return true if this atom is an alternate bool is_alternate() const { if (auto alt_id = get_label_alt_id(); alt_id.empty() or alt_id == ".") return false; return true; } /// Convenience method to return a string that might be ID in PDB space std::string pdb_id() const { return get_label_comp_id() + '_' + get_auth_asym_id() + '_' + get_auth_seq_id() + get_pdb_ins_code(); } /// Compare two atoms bool operator==(const atom &rhs) const { if (m_impl == rhs.m_impl) return true; if (not(m_impl and rhs.m_impl)) return false; return &m_impl->m_db == &rhs.m_impl->m_db and m_impl->m_id == rhs.m_impl->m_id; } /// Compare two atoms bool operator!=(const atom &rhs) const { return not operator==(rhs); } /// Is this atom a backbone atom bool is_back_bone() const { auto atomID = get_label_atom_id(); return atomID == "N" or atomID == "O" or atomID == "C" or atomID == "CA"; } /// swap void swap(atom &b) { std::swap(m_impl, b.m_impl); } /// Compare this atom with @a b int compare(const atom &b) const { return impl().compare(*b.m_impl); } /// Should this atom sort before @a rhs bool operator<(const atom &rhs) const { return compare(rhs) < 0; } /// Write the atom to std::ostream @a os friend std::ostream &operator<<(std::ostream &os, const atom &atom); private: friend class structure; const atom_impl &impl() const { if (not m_impl) throw std::runtime_error("Uninitialized atom, not found?"); return *m_impl; } std::shared_ptr m_impl; }; /** swap */ inline void swap(atom &a, atom &b) { a.swap(b); } /** Calculate the distance between atoms @a and @a b in Ã¥ngström */ inline float distance(const atom &a, const atom &b) { return distance(a.get_location(), b.get_location()); } /** Calculate the square of the distance between atoms @a and @a b in Ã¥ngström * * @note Use this whenever possible instead of simply using distance since * this function does not have to calculate a square root which is expensive. */ inline float distance_squared(const atom &a, const atom &b) { return distance_squared(a.get_location(), b.get_location()); } // -------------------------------------------------------------------- /** * @brief The entity types that can be found in a mmCIF file * */ enum class EntityType { Polymer, ///< entity is a polymer NonPolymer, ///< entity is not a polymer Macrolide, ///< entity is a macrolide Water, ///< water in the solvent model Branched ///< entity is branched }; // -------------------------------------------------------------------- /** * @brief The class residue is a collection of atoms forming a molecule * * This class is used to store ligand e.g. Derived classes are monomer * and sugar. */ class residue { public: friend class structure; /** * @brief Construct a new residue object based on key items */ residue(structure &structure, const std::string &compoundID, const std::string &asymID, int seqID, const std::string &authAsymID, const std::string &authSeqID, const std::string &pdbInsCode) : m_structure(&structure) , m_compound_id(compoundID) , m_asym_id(asymID) , m_seq_id(seqID) , m_auth_asym_id(authAsymID) , m_auth_seq_id(authSeqID) , m_pdb_ins_code(pdbInsCode) { } /** Construct a new residue in structure with the atoms in @a atoms */ residue(structure &structure, const std::vector &atoms); /** @cond */ residue(const residue &rhs) = delete; residue &operator=(const residue &rhs) = delete; residue(residue &&rhs) = default; residue &operator=(residue &&rhs) = default; virtual ~residue() = default; /** @endcond */ /** Return the entity_id of this residue */ std::string get_entity_id() const; /** Return the entity type of this residue */ EntityType entity_type() const; const std::string &get_asym_id() const { return m_asym_id; } ///< Return the asym_id int get_seq_id() const { return m_seq_id; } ///< Return the seq_id const std::string get_auth_asym_id() const { return m_auth_asym_id; } ///< Return the auth_asym_id const std::string get_auth_seq_id() const { return m_auth_seq_id; } ///< Return the auth_seq_id std::string get_pdb_ins_code() const { return m_pdb_ins_code; } ///< Return the pdb_ins_code const std::string &get_compound_id() const { return m_compound_id; } ///< Return the compound_id void set_compound_id(const std::string &id) { m_compound_id = id; } ///< Set the compound_id to @a id /** Return the structure this residue belongs to */ structure *get_structure() const { return m_structure; } /** Return a list of the atoms for this residue */ std::vector &atoms() { return m_atoms; } /** Return a const list of the atoms for this residue */ const std::vector &atoms() const { return m_atoms; } /** Add atom @a atom to the atoms in this residue */ void add_atom(atom &atom); /// \brief Unique atoms returns only the atoms without alternates and the first of each alternate atom id. std::vector unique_atoms() const; /// \brief Return the atom with atom_id @a atomID atom get_atom_by_atom_id(const std::string &atomID) const; /// \brief Return the list of atoms having ID \a atomID /// /// This includes all alternate atoms with this ID /// whereas get_atom_by_atom_id only returns the first unique atom std::vector get_atoms_by_id(const std::string &atomID) const; /// \brief Is this residue a single entity? bool is_entity() const; /// \brief Is this residue a water molecule? bool is_water() const { return m_compound_id == "HOH"; } /// \brief Return true if this residue has alternate atoms bool has_alternate_atoms() const; /// \brief Return true if this residue has alternate atoms for the atom \a atomID bool has_alternate_atoms_for(const std::string &atomID) const; /// \brief Return the list of unique alt ID's present in this residue std::set get_alternate_ids() const; /// \brief Return the list of unique atom ID's std::set get_atom_ids() const; /// \brief Return a tuple containing the center location and the radius for the atoms of this residue std::tuple center_and_radius() const; /// \brief Write the residue @a res to the std::ostream @a os friend std::ostream &operator<<(std::ostream &os, const residue &res); /// \brief Return true if this residue is equal to @a rhs bool operator==(const residue &rhs) const { return this == &rhs or (m_structure == rhs.m_structure and m_seq_id == rhs.m_seq_id and m_asym_id == rhs.m_asym_id and m_compound_id == rhs.m_compound_id and m_auth_seq_id == rhs.m_auth_seq_id); } /// @brief Create a new atom and add it to the list /// @return newly created atom virtual atom create_new_atom(atom_type inType, const std::string &inAtomID, point inLocation); protected: /** @cond */ residue() {} structure *m_structure = nullptr; std::string m_compound_id, m_asym_id; int m_seq_id = 0; std::string m_auth_asym_id, m_auth_seq_id, m_pdb_ins_code; std::vector m_atoms; /** @endcond */ }; // -------------------------------------------------------------------- /** * @brief a monomer models a single residue in a protein chain * */ class monomer : public residue { public: monomer(const monomer &rhs) = delete; monomer &operator=(const monomer &rhs) = delete; /// \brief Move constructor monomer(monomer &&rhs); /// \brief Move assignment operator monomer &operator=(monomer &&rhs); /// \brief constructor with actual values monomer(const polymer &polymer, std::size_t index, int seqID, const std::string &authSeqID, const std::string &pdbInsCode, const std::string &compoundID); bool is_first_in_chain() const; ///< Return if this residue is the first residue in the chain bool is_last_in_chain() const; ///< Return if this residue is the last residue in the chain // convenience bool has_alpha() const; ///< Return if a alpha value can be calculated (depends on location in chain) bool has_kappa() const; ///< Return if a kappa value can be calculated (depends on location in chain) // Assuming this is really an amino acid... float phi() const; ///< Return the phi value for this residue float psi() const; ///< Return the psi value for this residue float alpha() const; ///< Return the alpha value for this residue float kappa() const; ///< Return the kappa value for this residue float tco() const; ///< Return the tco value for this residue float omega() const; ///< Return the omega value for this residue // torsion angles std::size_t nr_of_chis() const; ///< Return how many torsion angles can be calculated float chi(std::size_t i) const; ///< Return torsion angle @a i bool is_cis() const; ///< Return true if this residue is in a cis conformation /// \brief Returns true if the four atoms C, CA, N and O are present bool is_complete() const; /// \brief Returns true if any of the backbone atoms has an alternate bool has_alternate_backbone_atoms() const; atom CAlpha() const { return get_atom_by_atom_id("CA"); } ///< Return the CAlpha atom atom C() const { return get_atom_by_atom_id("C"); } ///< Return the C atom atom N() const { return get_atom_by_atom_id("N"); } ///< Return the N atom atom O() const { return get_atom_by_atom_id("O"); } ///< Return the O atom atom H() const { return get_atom_by_atom_id("H"); } ///< Return the H atom /// \brief Return true if this monomer is bonded to monomer @a rhs bool is_bonded_to(const monomer &rhs) const { return this != &rhs and are_bonded(*this, rhs); } /** * @brief Return true if the distance between the CA atoms of the * two monomers @a a and @a b are within the expected range with * an error margin of @a errorMargin. * * The expected distance is 3.0 Ã¥ngström for a cis conformation * and 3.8 Ã¥ngström for trans. */ static bool are_bonded(const monomer &a, const monomer &b, float errorMargin = 0.5f); /// \brief Return true if the bond between @a a and @a b is cis static bool is_cis(const monomer &a, const monomer &b); /// \brief Return the omega angle between @a a and @a b static float omega(const monomer &a, const monomer &b); /// \brief Return the chiral volume, only for LEU and VAL float chiral_volume() const; /// \brief Compare this monomer with \a rhs bool operator==(const monomer &rhs) const { return m_polymer == rhs.m_polymer and m_index == rhs.m_index; } atom create_new_atom(atom_type inType, const std::string &inAtomID, point inLocation) override; private: const polymer *m_polymer; std::size_t m_index; }; // -------------------------------------------------------------------- /** * @brief A polymer is simply a list of monomers * */ class polymer : public std::vector { public: /// \brief Constructor polymer(structure &s, const std::string &entityID, const std::string &asymID, const std::string &auth_asym_id); polymer(const polymer &) = delete; polymer &operator=(const polymer &) = delete; structure *get_structure() const { return m_structure; } ///< Return the structure std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id std::string get_auth_asym_id() const { return m_auth_asym_id; } ///< Return the PDB chain ID, actually std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id private: structure *m_structure; std::string m_entity_id; std::string m_asym_id; std::string m_auth_asym_id; }; // -------------------------------------------------------------------- // sugar and branch, to describe glycosylation sites class branch; /** * @brief A sugar is a residue that is part of a glycosylation site * */ class sugar : public residue { public: /// \brief constructor sugar(branch &branch, const std::string &compoundID, const std::string &asymID, int authSeqID); /** @cond */ sugar(sugar &&rhs); sugar &operator=(sugar &&rhs); /** @endcond */ /** * @brief Return the sugar number in the glycosylation tree * * To store the sugar number, the auth_seq_id item has been overloaded * in the specification. But since a sugar number should be, ehm, a number * and auth_seq_id is specified to contain a string, we do a check here * to see if it really is a number. * * @return The sugar number */ int num() const { int result; auto r = std::from_chars(m_auth_seq_id.data(), m_auth_seq_id.data() + m_auth_seq_id.length(), result); if ((bool)r.ec) throw std::runtime_error("The auth_seq_id should be a number for a sugar"); return result; } /// \brief Return the name of this sugar std::string name() const; /// \brief Return the atom the C1 is linked to atom get_link() const { return m_link; } /// \brief Set the link atom C1 is linked to to @a link void set_link(atom link) { m_link = link; } /// \brief Return the sugar number of the sugar linked to C1 std::size_t get_link_nr() const { std::size_t result = 0; if (m_link) result = m_link.get_property_int("auth_seq_id"); return result; } /// \brief Construct an atom based on the info in @a atom_info and add it to this sugar atom add_atom(row_initializer atom_info); private: branch *m_branch; atom m_link; }; /** * @brief A branch is a list of sugars * * A list is how it is stored, but a branch is like a branch in a tree, * with potentially lots of sub branches. Each sugar is linked to a sugar * up in the branch with its (almost always) C1 atom. * */ class branch : public std::vector { public: /// \brief constructor branch(structure &structure, const std::string &asym_id, const std::string &entity_id); branch(const branch &) = delete; branch &operator=(const branch &) = delete; /** @cond */ branch(branch &&) = default; branch &operator=(branch &&) = default; /** @endcond */ /// \brief Update the link atoms in all sugars in this branch void link_atoms(); /// \brief Return the name of the branch std::string name() const; /// \brief Return the weight of the branch based on the formulae of the sugars float weight() const; std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id structure &get_structure() { return *m_structure; } ///< Return the structure structure &get_structure() const { return *m_structure; } ///< Return the structure /// \brief Return a reference to the sugar with number @a num sugar &get_sugar_by_num(int nr); /// \brief Return a const reference to the sugar with number @a num const sugar &get_sugar_by_num(int nr) const { return const_cast(this)->get_sugar_by_num(nr); } /// \brief Construct a new sugar with compound ID @a compound_id in this branch /// and return a reference to the newly created sugar. Use this to create a first /// sugar in a branch. sugar &construct_sugar(const std::string &compound_id); /// \brief Construct a new sugar with compound ID @a compound_id in this branch /// and return a reference to the newly created sugar. The newly created sugar /// will be connected to an already created sugar in the branch using the /// information in @a atom_id, @a linked_sugar_nr and @a linked_atom_id sugar &construct_sugar(const std::string &compound_id, const std::string &atom_id, int linked_sugar_nr, const std::string &linked_atom_id); private: friend sugar; std::string name(const sugar &s) const; structure *m_structure; std::string m_asym_id, m_entity_id; }; // -------------------------------------------------------------------- /// \brief A still very limited set of options for reading structures enum class StructureOpenOptions { SkipHydrogen = 1 << 0 ///< Do not include hydrogen atoms in the structure object }; /// \brief A way to combine two options. Not very useful as there is only one... constexpr inline bool operator&(StructureOpenOptions a, StructureOpenOptions b) { return static_cast(a) bitand static_cast(b); } // -------------------------------------------------------------------- /** * @brief A structure is the combination of polymers, ligand and sugar branches found * in the mmCIF file. This will always contain one model, the first model is taken * if not otherwise specified. * */ class structure { public: /// \brief Read the structure from cif::file @a p structure(file &p, std::size_t modelNr = 1, StructureOpenOptions options = {}); /// \brief Load the structure from already parsed mmCIF data in @a db structure(datablock &db, std::size_t modelNr = 1, StructureOpenOptions options = {}); /** @cond */ structure(structure &&s) = default; /** @endcond */ // structures cannot be copied. structure(const structure &) = delete; structure &operator=(const structure &) = delete; ~structure() = default; /// \brief Return the model number std::size_t get_model_nr() const { return m_model_nr; } /// \brief Return a list of all the atoms in this structure const std::vector &atoms() const { return m_atoms; } EntityType get_entity_type_for_entity_id(const std::string entityID) const; ///< Return the entity type for the entity with id @a entity_id EntityType get_entity_type_for_asym_id(const std::string asymID) const; ///< Return the entity type for the asym with id @a asym_id const std::list &polymers() const { return m_polymers; } ///< Return the list of polymers std::list &polymers() { return m_polymers; } ///< Return the list of polymers polymer &get_polymer_by_asym_id(const std::string &asymID); ///< Return the polymer having asym ID @a asymID const polymer &get_polymer_by_asym_id(const std::string &asymID) const ///< Return the polymer having asym ID @a asymID { return const_cast(this)->get_polymer_by_asym_id(asymID); } const std::list &branches() const { return m_branches; } ///< Return the list of all branches std::list &branches() { return m_branches; } ///< Return the list of all branches branch &get_branch_by_asym_id(const std::string &asymID); ///< Return the branch having asym ID @a asymID const branch &get_branch_by_asym_id(const std::string &asymID) const; ///< Return the branch having asym ID @a asymID const std::vector &non_polymers() const { return m_non_polymers; } ///< Return the list of non-polymers, actually the list of ligands bool has_atom_id(const std::string &id) const; ///< Return true if an atom with ID @a id exists in this structure atom get_atom_by_id(const std::string &id) const; ///< Return the atom with ID @a id /// \brief Return the atom identified by the label_ values specified atom get_atom_by_label(const std::string &atomID, const std::string &asymID, const std::string &compID, int seqID, const std::string &altID = ""); /// \brief Return the atom closest to point \a p atom get_atom_by_position(point p) const; /// \brief Return the atom closest to point \a p with atom type \a type in a residue of type \a res_type atom get_atom_by_position_and_type(point p, std::string_view type, std::string_view res_type) const; /// \brief Create a non-poly residue based on atoms already present in this structure. residue &create_residue(const std::vector &atoms); /// \brief Get a non-poly residue for an asym with id \a asymID residue &get_residue(const std::string &asymID) { return get_residue(asymID, 0, ""); } /// \brief Get a non-poly residue for an asym with id \a asymID const residue &get_residue(const std::string &asymID) const { return get_residue(asymID, 0, ""); } /// \brief Get a residue for an asym with id \a asymID seq id \a seqID and authSeqID \a authSeqID residue &get_residue(const std::string &asymID, int seqID, const std::string &authSeqID); /// \brief Get a the single residue for an asym with id \a asymID seq id \a seqID and authSeqID \a authSeqID const residue &get_residue(const std::string &asymID, int seqID, const std::string &authSeqID) const { return const_cast(this)->get_residue(asymID, seqID, authSeqID); } /// \brief Get a residue for an asym with id \a asymID, compound id \a compID, seq id \a seqID and authSeqID \a authSeqID residue &get_residue(const std::string &asymID, const std::string &compID, int seqID, const std::string &authSeqID); /// \brief Get a residue for an asym with id \a asymID, compound id \a compID, seq id \a seqID and authSeqID \a authSeqID const residue &get_residue(const std::string &asymID, const std::string &compID, int seqID, const std::string &authSeqID) const { return const_cast(this)->get_residue(asymID, compID, seqID, authSeqID); } /// \brief Get a the residue for atom \a atom residue &get_residue(const atom &atom) { return get_residue(atom.get_label_asym_id(), atom.get_label_comp_id(), atom.get_label_seq_id(), atom.get_auth_seq_id()); } /// \brief Get a the residue for atom \a atom const residue &get_residue(const atom &atom) const { return get_residue(atom.get_label_asym_id(), atom.get_label_comp_id(), atom.get_label_seq_id(), atom.get_auth_seq_id()); } // Actions. Originally a lot more actions were expected here /// \brief Remove atom @a a void remove_atom(atom &a) { remove_atom(a, true); } void swap_atoms(atom a1, atom a2); ///< swap the labels for these atoms void move_atom(atom a, point p); ///< move atom to a new location /** * @brief Change residue @a res to a new compound ID optionally * remapping atoms. * * A new chem_comp entry as well as an entity is created if needed and * if the list of @a remappedAtoms is not empty it is used to remap. * * The array in @a remappedAtoms contains tuples of strings, both * strings contain an atom_id. The first is the one in the current * residue and the second is the atom_id that should be used instead. * If the second string is empty, the atom is removed from the residue. * * @param res * @param newcompound * @param remappedAtoms */ void change_residue(residue &res, const std::string &newcompound, const std::vector> &remappedAtoms); /// \brief Remove a residue, can be monomer or nonpoly /// /// \param asym_id The asym ID /// \param seq_id The sequence ID /// \param auth_seq_id The auth sequence ID void remove_residue(const std::string &asym_id, int seq_id, const std::string &auth_seq_id); /// \brief Create a new non-polymer entity, returns new ID /// \param mon_id The mon_id for the new nonpoly, must be an existing and known compound from CCD /// \return The ID of the created entity std::string create_non_poly_entity(const std::string &mon_id); /// \brief Create a new NonPolymer struct_asym with atoms constructed from \a atoms, returns asym_id. /// This method assumes you are copying data from one cif file to another. /// /// \param entity_id The entity ID of the new nonpoly /// \param atoms The array of atom_site rows containing the data. /// \return The newly create asym ID std::string create_non_poly(const std::string &entity_id, const std::vector &atoms); /// \brief Create a new NonPolymer struct_asym with atoms constructed from info in \a atom_info, returns asym_id. /// This method creates new atom records filled with info from the info. /// /// \param entity_id The entity ID of the new nonpoly /// \param atoms The array of sets of item data containing the data for the atoms. /// \return The newly create asym ID std::string create_non_poly(const std::string &entity_id, std::vector atoms); /// \brief Create a new water with atom constructed from info in \a atom_info /// This method creates a new atom record filled with info from the info. /// /// \param atom The set of item data containing the data for the atoms. void create_water(row_initializer atom); /// \brief Create a new and empty (sugar) branch branch &create_branch(); // /// \brief Create a new (sugar) branch with one first NAG containing atoms constructed from \a atoms // branch &create_branch(std::vector atoms); // /// \brief Extend an existing (sugar) branch identified by \a asymID with one sugar containing atoms constructed from \a atom_info // /// // /// \param asym_id The asym id of the branch to extend // /// \param atom_info Array containing the info for the atoms to construct for the new sugar // /// \param link_sugar The sugar to link to, note: this is the sugar number (1 based) // /// \param link_atom The atom id of the atom linked in the sugar // branch &extend_branch(const std::string &asym_id, std::vector atom_info, // int link_sugar, const std::string &link_atom); /// \brief Remove \a branch void remove_branch(branch &branch); /// \brief Remove residue \a res /// /// \param res The residue to remove void remove_residue(residue &res); /// \brief Translate the coordinates of all atoms in the structure by \a t void translate(point t); /// \brief Rotate the coordinates of all atoms in the structure by \a q void rotate(quaternion t); /// \brief Translate and rotate the coordinates of all atoms in the structure by \a t and \a q void translate_and_rotate(point t, quaternion q); /// \brief Translate, rotate and translate again the coordinates of all atoms in the structure by \a t1 , \a q and \a t2 void translate_rotate_and_translate(point t1, quaternion q, point t2); /// \brief Remove all categories that have no rows left void cleanup_empty_categories(); /// \brief Direct access to underlying data category &get_category(std::string_view name) const { return m_db[name]; } /// \brief Direct access to underlying data datablock &get_datablock() const { return m_db; } /// \brief Check if all atoms are part of either a polymer, a branch or one of the non-polymer residues void validate_atoms() const; /// \brief emplace a newly created atom using @a args template atom &emplace_atom(Args &...args) { return emplace_atom(atom{ std::forward(args)... }); } /// \brief emplace the moved atom @a atom atom &emplace_atom(atom &&atom); /// \brief Reorder atom_site atoms based on 'natural' ordering void reorder_atoms(); private: friend polymer; friend residue; void load_atoms_for_model(StructureOpenOptions options); std::string insert_compound(const std::string &compoundID, bool is_entity); std::string create_entity_for_branch(branch &branch); void load_data(); void remove_atom(atom &a, bool removeFromResidue); void remove_sugar(sugar &sugar); datablock &m_db; std::size_t m_model_nr; std::vector m_atoms; std::vector m_atom_index; std::list m_polymers; std::list m_branches; std::vector m_non_polymers; }; } // namespace cif::mm libcifpp-7.0.9/include/cif++/parser.hpp0000644000175000017500000002216514746170722017516 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/row.hpp" #include /** * @file parser.hpp * * This file contains the declaration of an mmCIF parser */ namespace cif { // -------------------------------------------------------------------- /** Exception that is thrown when the mmCIF file contains a parsing error */ class parse_error : public std::runtime_error { public: /// \brief constructor parse_error(uint32_t line_nr, const std::string &message) : std::runtime_error("parse error at line " + std::to_string(line_nr) + ": " + message) { } }; // -------------------------------------------------------------------- /** * @brief The sac_parser is a similar to SAX parsers (Simple API for XML, * in our case it is Simple API for CIF) * * This is a hand crafted, optimised parser for reading cif files, * both cif 1.0 and cif 1.1 is supported. But version 2.0 is not. * That means that the content of files strictly contains only * ASCII characters. Anything else will generate an error. * * This class is an abstract base class. Derived classes should * implement the produce_ methods. */ // TODO: Need to implement support for transformed long lines class sac_parser { public: /** @cond */ struct iless_op { bool operator()(std::string a, std::string b) const { to_upper(a); to_upper(b); return a < b; } }; using datablock_index = std::map; virtual ~sac_parser() = default; /** @endcond */ /// \brief The parser only supports ASCII so we can /// create a table with character properties. enum CharTraitsMask : uint8_t { kOrdinaryMask = 1 << 0, ///< The character is in the Ordinary class kNonBlankMask = 1 << 1, ///< The character is in the NonBlank class kTextLeadMask = 1 << 2, ///< The character is in the TextLead class kAnyPrintMask = 1 << 3 ///< The character is in the AnyPrint class }; /// \brief Return true if the character @a ch is a *space* character static constexpr bool is_space(int ch) { return ch == ' ' or ch == '\t' or ch == '\r' or ch == '\n'; } /// \brief Return true if the character @a ch is a *white* character static constexpr bool is_white(int ch) { return is_space(ch) or ch == '#'; } /// \brief Return true if the character @a ch is a *ordinary* character static constexpr bool is_ordinary(int ch) { return ch >= 0x20 and ch <= 0x7f and (kCharTraitsTable[ch - 0x20] & kOrdinaryMask) != 0; } /// \brief Return true if the character @a ch is a *non_blank* character static constexpr bool is_non_blank(int ch) { return ch > 0x20 and ch <= 0x7f and (kCharTraitsTable[ch - 0x20] & kNonBlankMask) != 0; } /// \brief Return true if the character @a ch is a *text_lead* character static constexpr bool is_text_lead(int ch) { return ch >= 0x20 and ch <= 0x7f and (kCharTraitsTable[ch - 0x20] & kTextLeadMask) != 0; } /// \brief Return true if the character @a ch is a *any_print* character static constexpr bool is_any_print(int ch) { return ch == '\t' or (ch >= 0x20 and ch <= 0x7f and (kCharTraitsTable[ch - 0x20] & kAnyPrintMask) != 0); } /// \brief Return true if the string in @a text can safely be written without quotation static bool is_unquoted_string(std::string_view text); protected: /** @cond */ static constexpr uint8_t kCharTraitsTable[128] = { // 0 1 2 3 4 5 6 7 8 9 a b c d e f 14, 15, 14, 14, 14, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, // 2 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 10, 15, 15, 15, 15, // 3 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // 4 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 14, 15, 14, // 5 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, // 6 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, // 7 }; enum class CIFToken { UNKNOWN, END_OF_FILE, DATA, LOOP, GLOBAL, SAVE_, SAVE_NAME, STOP, ITEM_NAME, VALUE }; static constexpr const char *get_token_name(CIFToken token) { switch (token) { case CIFToken::UNKNOWN: return "Unknown"; case CIFToken::END_OF_FILE: return "Eof"; case CIFToken::DATA: return "DATA"; case CIFToken::LOOP: return "LOOP"; case CIFToken::GLOBAL: return "GLOBAL"; case CIFToken::SAVE_: return "SAVE"; case CIFToken::SAVE_NAME: return "SAVE+name"; case CIFToken::STOP: return "STOP"; case CIFToken::ITEM_NAME: return "Tag"; case CIFToken::VALUE: return "Value"; default: return "Invalid token parameter"; } } // get_next_char takes the next character from the istream. // This function also does carriage/linefeed translation. int get_next_char(); // Put the last read character back into the istream void retract(); CIFToken get_next_token(); void match(CIFToken token); /** @endcond */ public: /** \brief Parse only a single datablock in the string @a datablock * The start of the datablock is first located and then data * is parsed up until the next start of a datablock or the end of * the data. * */ bool parse_single_datablock(const std::string &datablock); /** \brief Return an index for all the datablocks found, that is * the index will contain the names and offsets for each. */ datablock_index index_datablocks(); /** * @brief Parse the datablock named @a datablock * * This will first lookup the datablock's offset in the index @a index * and then start parsing from that location until the next datablock. * * @param datablock Name of the datablock to parse * @param index The index created using index_datablocks * @return true If the datablock was found * @return false If the datablock was not found */ bool parse_single_datablock(const std::string &datablock, const datablock_index &index); /** * @brief Parse the file * */ void parse_file(); protected: /** @cond */ sac_parser(std::istream &is, bool init = true); void parse_global(); void parse_datablock(); virtual void parse_save_frame(); void error(const std::string &msg) { if (cif::VERBOSE > 0) std::cerr << "Error parsing mmCIF: " << msg << '\n'; throw parse_error(m_line_nr, msg); } void warning(const std::string &msg) { if (cif::VERBOSE > 0) std::cerr << "parser warning at line " << m_line_nr << ": " << msg << '\n'; } // production methods, these are pure virtual here virtual void produce_datablock(std::string_view name) = 0; virtual void produce_category(std::string_view name) = 0; virtual void produce_row() = 0; virtual void produce_item(std::string_view category, std::string_view item, std::string_view value) = 0; protected: enum class State { Start, White, Esc, Comment, QuestionMark, Dot, QuotedString, QuotedStringQuote, UnquotedString, ItemName, TextItem, TextItemNL, Reserved, Value }; std::streambuf &m_source; // Parser state uint32_t m_line_nr; bool m_bol; CIFToken m_lookahead; // token buffer std::vector m_token_buffer; std::string_view m_token_value; /** @endcond */ }; // -------------------------------------------------------------------- /** * @brief An actual implementation of a sac_parser generating data in a file * * This parser will create the cif::file, cif::datablock and cif::category * objects required to contain all data */ class parser : public sac_parser { public: /// \brief constructor, generates data into @a file from @a is parser(std::istream &is, file &file) : sac_parser(is) , m_file(file) { } /** @cond */ void produce_datablock(std::string_view name) override; void produce_category(std::string_view name) override; void produce_row() override; void produce_item(std::string_view category, std::string_view item, std::string_view value) override; protected: file &m_file; datablock *m_datablock = nullptr; category *m_category = nullptr; row_handle m_row; /** @endcond */ }; } // namespace cif libcifpp-7.0.9/include/cif++/pdb.hpp0000644000175000017500000001664114746170722016771 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/file.hpp" #include /** * @file pdb.hpp * * This file presents the API to read and write files in the * legacy and ancient PDB format. * * The code works on the basis of best effort since it is * impossible to have correct round trip fidelity. * */ namespace cif::pdb { /// -------------------------------------------------------------------- // PDB to mmCIF /** @brief Read a file in either mmCIF or PDB format from file @a file, * compressed or not, depending on the content. */ file read(const std::filesystem::path &file); /** @brief Read a file in either mmCIF or PDB format from std::istream @a is, * compressed or not, depending on the content. */ file read(std::istream &is); /** * @brief Read a file in legacy PDB format from std::istream @a is and * put the data into @a cifFile */ file read_pdb_file(std::istream &pdbFile); // mmCIF to PDB /** @brief Write out the data in @a db in legacy PDB format * to std::ostream @a os */ void write(std::ostream &os, const datablock &db); /** @brief Write out the data in @a f in legacy PDB format * to std::ostream @a os */ inline void write(std::ostream &os, const file &f) { write(os, f.front()); } /** @brief Write out the data in @a db to file @a file * in legacy PDB format or mmCIF format, depending on the * filename extension. * * If extension of @a file is *.gz* the resulting file will * be written in gzip compressed format. */ void write(const std::filesystem::path &file, const datablock &db); /** @brief Write out the data in @a f to file @a file * in legacy PDB format or mmCIF format, depending on the * filename extension. * * If extension of @a file is *.gz* the resulting file will * be written in gzip compressed format. */ inline void write(const std::filesystem::path &p, const file &f) { write(p, f.front()); } // -------------------------------------------------------------------- /** \brief Reconstruct all missing categories for an assumed PDBx file. * * Some people believe that simply dumping some atom records is enough. * * \param file The cif::file that hopefully contains some valid data * \param dictionary The mmcif dictionary to use * \result Returns true if the resulting file is valid */ bool reconstruct_pdbx(file &pdbx_file, std::string_view dictionary = "mmcif_pdbx"); /** \brief This is an extension to cif::validator, use the logic in common * PDBx files to see if the file is internally consistent. * * This function for now checks if the following categories are consistent: * * atom_site -> pdbx_poly_seq_scheme -> entity_poly_seq -> entity_poly -> entity * * Use the common \ref cif::VERBOSE flag to turn on diagnostic messages. * * This function throws a std::system_error in case of an error * * \param file The input file * \param dictionary The mmcif dictionary to use * \result Returns true if the file was valid and consistent */ bool is_valid_pdbx_file(const file &pdbx_file, std::string_view dictionary = "mmcif_pdbx"); /** \brief This is an extension to cif::validator, use the logic in common * PDBx files to see if the file is internally consistent. * * This function for now checks if the following categories are consistent: * * atom_site -> pdbx_poly_seq_scheme -> entity_poly_seq -> entity_poly -> entity * * Use the common \ref cif::VERBOSE flag to turn on diagnostic messages. * * The dictionary is assumed to be specified in the file or to be the * default mmcif_pdbx.dic dictionary. * * \param file The input file * \param ec The error_code in case something was wrong * \result Returns true if the file was valid and consistent */ bool is_valid_pdbx_file(const file &pdbx_file, std::error_code &ec); /** \brief This is an extension to cif::validator, use the logic in common * PDBx files to see if the file is internally consistent. * * This function for now checks if the following categories are consistent: * * atom_site -> pdbx_poly_seq_scheme -> entity_poly_seq -> entity_poly -> entity * * Use the common \ref cif::VERBOSE flag to turn on diagnostic messages. * * \param file The input file * \param dictionary The dictionary to use * \param ec The error_code in case something was wrong * \result Returns true if the file was valid and consistent */ bool is_valid_pdbx_file(const file &pdbx_file, std::string_view dictionary, std::error_code &ec); // -------------------------------------------------------------------- // Other I/O related routines /** @brief Return the HEADER line for the data in @a data * * The line returned should be compatible with the legacy PDB * format and is e.g. used in the DSSP program. * * @param data The datablock to use as source for the requested data * @param truncate_at The maximum length of the line returned */ std::string get_HEADER_line(const datablock &data, std::string::size_type truncate_at = 127); /** @brief Return the COMPND line for the data in @a data * * The line returned should be compatible with the legacy PDB * format and is e.g. used in the DSSP program. * * @param data The datablock to use as source for the requested data * @param truncate_at The maximum length of the line returned */ std::string get_COMPND_line(const datablock &data, std::string::size_type truncate_at = 127); /** @brief Return the SOURCE line for the data in @a data * * The line returned should be compatible with the legacy PDB * format and is e.g. used in the DSSP program. * * @param data The datablock to use as source for the requested data * @param truncate_at The maximum length of the line returned */ std::string get_SOURCE_line(const datablock &data, std::string::size_type truncate_at = 127); /** @brief Return the AUTHOR line for the data in @a data * * The line returned should be compatible with the legacy PDB * format and is e.g. used in the DSSP program. * * @param data The datablock to use as source for the requested data * @param truncate_at The maximum length of the line returned */ std::string get_AUTHOR_line(const datablock &data, std::string::size_type truncate_at = 127); } // namespace cif::pdb libcifpp-7.0.9/include/cif++/pdb/0000755000175000017500000000000014746170722016250 5ustar maartenmaartenlibcifpp-7.0.9/include/cif++/pdb/cif2pdb.hpp0000644000175000017500000000310014746170722020264 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once /// \file cif2pdb.hpp /// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead #warning "Use of this file is deprecated, please use "cif++/pdb.hpp" libcifpp-7.0.9/include/cif++/pdb/io.hpp0000644000175000017500000000307614746170722017376 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once /// \file io.hpp /// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead #warning "Use of this file is deprecated, please use "cif++/pdb.hpp" libcifpp-7.0.9/include/cif++/pdb/pdb2cif.hpp0000644000175000017500000000307614746170722020300 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once /// \file pdb2cif.hpp /// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead #warning "Use of this file is deprecated, please use "cif++/pdb.hpp"libcifpp-7.0.9/include/cif++/pdb/tls.hpp0000644000175000017500000000302114746170722017557 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once /// \file tls.hpp /// \deprecated This code has been moved to libpdb-redo #warning "This code has been moved to libpdb-redo" libcifpp-7.0.9/include/cif++/point.hpp0000644000175000017500000006505014746170722017353 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #if __has_include() #define HAVE_LIBCLIPPER 1 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-qualifiers" #include #pragma GCC diagnostic pop #endif /** \file point.hpp * * This file contains the definition for *cif::point* as well as * lots of routines and classes that can manipulate points. */ namespace cif { // -------------------------------------------------------------------- /// \brief Our value for Pi const double kPI = 3.141592653589793238462643383279502884; // -------------------------------------------------------------------- /** * @brief A stripped down quaternion implementation, based on boost::math::quaternion * * We use quaternions to do rotations in 3d space. Quaternions are faster than * matrix calculations and they also suffer less from drift caused by rounding * errors. * * Like complex number, quaternions do have a meaningful notion of "real part", * but unlike them there is no meaningful notion of "imaginary part". * Instead there is an "unreal part" which itself is a quaternion, and usually * nothing simpler (as opposed to the complex number case). * However, for practicality, there are accessors for the other components * (these are necessary for the templated copy constructor, for instance). * * @note Quaternion multiplication is *NOT* commutative; * symbolically, "q *= rhs;" means "q = q * rhs;" * and "q /= rhs;" means "q = q * inverse_of(rhs);" */ template class quaternion_type { public: /// \brief the value type of the elements, usually this is float using value_type = T; /// \brief constructor with the four members constexpr explicit quaternion_type(value_type const &value_a = {}, value_type const &value_b = {}, value_type const &value_c = {}, value_type const &value_d = {}) : a(value_a) , b(value_b) , c(value_c) , d(value_d) { } /// \brief constructor taking two complex values as input constexpr explicit quaternion_type(std::complex const &z0, std::complex const &z1 = std::complex()) : a(z0.real()) , b(z0.imag()) , c(z1.real()) , d(z1.imag()) { } constexpr quaternion_type(quaternion_type const &) = default; ///< Copy constructor constexpr quaternion_type(quaternion_type &&) = default; ///< Copy constructor /// \brief Copy constructor accepting a quaternion with a different value_type template constexpr explicit quaternion_type(quaternion_type const &rhs) : a(static_cast(rhs.a)) , b(static_cast(rhs.b)) , c(static_cast(rhs.c)) , d(static_cast(rhs.d)) { } // accessors /// \brief See class description, return the *real* part of the quaternion constexpr value_type real() const { return a; } /// \brief See class description, return the *unreal* part of the quaternion constexpr quaternion_type unreal() const { return { 0, b, c, d }; } /// \brief swap constexpr void swap(quaternion_type &o) { std::swap(a, o.a); std::swap(b, o.b); std::swap(c, o.c); std::swap(d, o.d); } // assignment operators /// \brief Assignment operator accepting a quaternion with optionally another value_type template constexpr quaternion_type &operator=(quaternion_type const &rhs) { a = static_cast(rhs.a); b = static_cast(rhs.b); c = static_cast(rhs.c); d = static_cast(rhs.d); return *this; } /// \brief Assignment operator constexpr quaternion_type &operator=(quaternion_type const &rhs) { a = rhs.a; b = rhs.b; c = rhs.c; d = rhs.d; return *this; } /// \brief Assignment operator that sets the *real* part to @a rhs and the *unreal* parts to zero constexpr quaternion_type &operator=(value_type const &rhs) { a = rhs; b = c = d = static_cast(0); return *this; } /// \brief Assignment operator that sets the *real* part to the real part of @a rhs /// and the first *unreal* part to the imaginary part of of @a rhs. The other *unreal* // parts are set to zero. constexpr quaternion_type &operator=(std::complex const &rhs) { a = rhs.real(); b = rhs.imag(); c = d = static_cast(0); return *this; } // other assignment-related operators /// \brief operator += adding value @a rhs to the *real* part constexpr quaternion_type &operator+=(value_type const &rhs) { a += rhs; return *this; } /// \brief operator += adding the real part of @a rhs to the *real* part /// and the imaginary part of @a rhs to the first *unreal* part constexpr quaternion_type &operator+=(std::complex const &rhs) { a += std::real(rhs); b += std::imag(rhs); return *this; } /// \brief operator += adding the parts of @a rhs to the equivalent part of this template constexpr quaternion_type &operator+=(quaternion_type const &rhs) { a += rhs.a; b += rhs.b; c += rhs.c; d += rhs.d; return *this; } /// \brief operator -= subtracting value @a rhs from the *real* part constexpr quaternion_type &operator-=(value_type const &rhs) { a -= rhs; return *this; } /// \brief operator -= subtracting the real part of @a rhs from the *real* part /// and the imaginary part of @a rhs from the first *unreal* part constexpr quaternion_type &operator-=(std::complex const &rhs) { a -= std::real(rhs); b -= std::imag(rhs); return *this; } /// \brief operator -= subtracting the parts of @a rhs from the equivalent part of this template constexpr quaternion_type &operator-=(quaternion_type const &rhs) { a -= rhs.a; b -= rhs.b; c -= rhs.c; d -= rhs.d; return *this; } /// \brief multiply all parts with value @a rhs constexpr quaternion_type &operator*=(value_type const &rhs) { a *= rhs; b *= rhs; c *= rhs; d *= rhs; return *this; } /// \brief multiply with complex number @a rhs constexpr quaternion_type &operator*=(std::complex const &rhs) { value_type ar = rhs.real(); value_type br = rhs.imag(); quaternion_type result(a * ar - b * br, a * br + b * ar, c * ar + d * br, -c * br + d * ar); swap(result); return *this; } /// \brief multiply @a a with @a b and return the result friend constexpr quaternion_type operator*(const quaternion_type &a, const quaternion_type &b) { auto result = a; result *= b; return result; } /// \brief multiply with quaternion @a rhs template constexpr quaternion_type &operator*=(quaternion_type const &rhs) { value_type ar = static_cast(rhs.a); value_type br = static_cast(rhs.b); value_type cr = static_cast(rhs.c); value_type dr = static_cast(rhs.d); quaternion_type result(a * ar - b * br - c * cr - d * dr, a * br + b * ar + c * dr - d * cr, a * cr - b * dr + c * ar + d * br, a * dr + b * cr - c * br + d * ar); swap(result); return *this; } /// \brief divide all parts by @a rhs constexpr quaternion_type &operator/=(value_type const &rhs) { a /= rhs; b /= rhs; c /= rhs; d /= rhs; return *this; } /// \brief divide by complex number @a rhs constexpr quaternion_type &operator/=(std::complex const &rhs) { value_type ar = rhs.real(); value_type br = rhs.imag(); value_type denominator = ar * ar + br * br; quaternion_type result((+a * ar + b * br) / denominator, (-a * br + b * ar) / denominator, (+c * ar - d * br) / denominator, (+c * br + d * ar) / denominator); swap(result); return *this; } /// \brief divide by quaternion @a rhs template constexpr quaternion_type &operator/=(quaternion_type const &rhs) { value_type ar = static_cast(rhs.a); value_type br = static_cast(rhs.b); value_type cr = static_cast(rhs.c); value_type dr = static_cast(rhs.d); value_type denominator = ar * ar + br * br + cr * cr + dr * dr; quaternion_type result((+a * ar + b * br + c * cr + d * dr) / denominator, (-a * br + b * ar - c * dr + d * cr) / denominator, (-a * cr + b * dr + c * ar - d * br) / denominator, (-a * dr - b * cr + c * br + d * ar) / denominator); swap(result); return *this; } /// \brief normalise the values so that the length of the result is exactly 1 friend constexpr quaternion_type normalize(quaternion_type q) { std::valarray t(4); t[0] = q.a; t[1] = q.b; t[2] = q.c; t[3] = q.d; t *= t; value_type length = std::sqrt(t.sum()); if (length > 0.001) q /= static_cast(length); else q = quaternion_type(1, 0, 0, 0); return q; } /// \brief return the conjugate of this friend constexpr quaternion_type conj(quaternion_type q) { return quaternion_type{ +q.a, -q.b, -q.c, -q.d }; } constexpr value_type get_a() const { return a; } ///< Return part a constexpr value_type get_b() const { return b; } ///< Return part b constexpr value_type get_c() const { return c; } ///< Return part c constexpr value_type get_d() const { return d; } ///< Return part d /// \brief compare with @a rhs constexpr bool operator==(const quaternion_type &rhs) const { return a == rhs.a and b == rhs.b and c == rhs.c and d == rhs.d; } /// \brief compare with @a rhs constexpr bool operator!=(const quaternion_type &rhs) const { return a != rhs.a or b != rhs.b or c != rhs.c or d != rhs.d; } /// \brief test for all zero values constexpr operator bool() const { return a != 0 or b != 0 or c != 0 or d != 0; } private: value_type a, b, c, d; }; /** * @brief This code is similar to the code in boost so I copy the documentation as well: * * > spherical is a simple transposition of polar, it takes as inputs a (positive) * > magnitude and a point on the hypersphere, given by three angles. The first of * > these, theta has a natural range of -pi to +pi, and the other two have natural * > ranges of -pi/2 to +pi/2 (as is the case with the usual spherical coordinates in * > **R**3). Due to the many symmetries and periodicities, nothing untoward happens if * > the magnitude is negative or the angles are outside their natural ranges. The * > expected degeneracies (a magnitude of zero ignores the angles settings...) do * > happen however. */ template inline quaternion_type spherical(T const &rho, T const &theta, T const &phi1, T const &phi2) { T cos_phi1 = std::cos(phi1); T cos_phi2 = std::cos(phi2); T a = std::cos(theta) * cos_phi1 * cos_phi2; T b = std::sin(theta) * cos_phi1 * cos_phi2; T c = std::sin(phi1) * cos_phi2; T d = std::sin(phi2); quaternion_type result(a, b, c, d); result *= rho; return result; } /// \brief By default we use the float version of a quaternion using quaternion = quaternion_type; // -------------------------------------------------------------------- /** * @brief 3D point: a location with x, y and z coordinates as floating point. * * Note that you can simply use structured binding to get access to the * individual parts like so: * * @code{.cpp} * float x, y, z; * tie(x, y, z) = atom.get_location(); * @endcode */ template struct point_type { /// \brief the value type of the x, y and z members using value_type = F; value_type m_x, ///< The x part of the location m_y, ///< The y part of the location m_z; ///< The z part of the location /// \brief default constructor, initialises the values to zero constexpr point_type() : m_x(0) , m_y(0) , m_z(0) { } /// \brief constructor taking three values constexpr point_type(value_type x, value_type y, value_type z) : m_x(x) , m_y(y) , m_z(z) { } /// \brief Copy constructor template constexpr point_type(const point_type &pt) : m_x(static_cast(pt.m_x)) , m_y(static_cast(pt.m_y)) , m_z(static_cast(pt.m_z)) { } /// \brief constructor taking a tuple of three values constexpr point_type(const std::tuple &pt) : point_type(std::get<0>(pt), std::get<1>(pt), std::get<2>(pt)) { } #if HAVE_LIBCLIPPER /// \brief Construct a point using the values in clipper coordinate @a pt constexpr point_type(const clipper::Coord_orth &pt) : m_x(pt[0]) , m_y(pt[1]) , m_z(pt[2]) { } /// \brief Assign a point using the values in clipper coordinate @a rhs constexpr point_type &operator=(const clipper::Coord_orth &rhs) { m_x = rhs[0]; m_y = rhs[1]; m_z = rhs[2]; return *this; } #endif /// \brief Assignment operator template constexpr point_type &operator=(const point_type &rhs) { m_x = static_cast(rhs.m_x); m_y = static_cast(rhs.m_y); m_z = static_cast(rhs.m_z); return *this; } constexpr value_type &get_x() { return m_x; } ///< Get a reference to x constexpr value_type get_x() const { return m_x; } ///< Get the value of x constexpr void set_x(value_type x) { m_x = x; } ///< Set the value of x to @a x constexpr value_type &get_y() { return m_y; } ///< Get a reference to y constexpr value_type get_y() const { return m_y; } ///< Get the value of y constexpr void set_y(value_type y) { m_y = y; } ///< Set the value of y to @a y constexpr value_type &get_z() { return m_z; } ///< Get a reference to z constexpr value_type get_z() const { return m_z; } ///< Get the value of z constexpr void set_z(value_type z) { m_z = z; } ///< Set the value of z to @a z /// \brief add @a rhs constexpr point_type &operator+=(const point_type &rhs) { m_x += rhs.m_x; m_y += rhs.m_y; m_z += rhs.m_z; return *this; } /// \brief add @a d to all members constexpr point_type &operator+=(value_type d) { m_x += d; m_y += d; m_z += d; return *this; } /// \brief Add the points @a lhs and @a rhs and return the result template friend constexpr auto operator+(const point_type &lhs, const point_type &rhs) { return point_type>(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y, lhs.m_z + rhs.m_z); } /// \brief subtract @a rhs constexpr point_type &operator-=(const point_type &rhs) { m_x -= rhs.m_x; m_y -= rhs.m_y; m_z -= rhs.m_z; return *this; } /// \brief subtract @a d from all members constexpr point_type &operator-=(value_type d) { m_x -= d; m_y -= d; m_z -= d; return *this; } /// \brief Subtract the points @a lhs and @a rhs and return the result template friend constexpr auto operator-(const point_type &lhs, const point_type &rhs) { return point_type>(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y, lhs.m_z - rhs.m_z); } /// \brief Return the negative copy of @a pt friend constexpr point_type operator-(const point_type &pt) { return point_type(-pt.m_x, -pt.m_y, -pt.m_z); } /// \brief multiply all members with @a rhs constexpr point_type &operator*=(value_type rhs) { m_x *= rhs; m_y *= rhs; m_z *= rhs; return *this; } /// \brief multiply point @a pt with value @a f and return the result template friend constexpr auto operator*(const point_type &pt, F2 f) { return point_type>(pt.m_x * f, pt.m_y * f, pt.m_z * f); } /// \brief multiply point @a pt with value @a f and return the result template friend constexpr auto operator*(F2 f, const point_type &pt) { return point_type>(pt.m_x * f, pt.m_y * f, pt.m_z * f); } /// \brief divide all members by @a rhs constexpr point_type &operator/=(value_type rhs) { m_x /= rhs; m_y /= rhs; m_z /= rhs; return *this; } /// \brief divide point @a pt by value @a f and return the result template friend constexpr auto operator/(const point_type &pt, F2 f) { return point_type>(pt.m_x / f, pt.m_y / f, pt.m_z / f); } /** * @brief looking at this point as a vector, normalise it which * means dividing all members by the length making the length * effectively 1. * * @return The previous length of this vector */ constexpr value_type normalize() { auto length = m_x * m_x + m_y * m_y + m_z * m_z; if (length > 0) { length = std::sqrt(length); operator/=(length); } return length; } /// \brief Rotate this point using the quaterion @a q constexpr void rotate(const quaternion &q) { quaternion_type p(0, m_x, m_y, m_z); p = q * p * conj(q); m_x = p.get_b(); m_y = p.get_c(); m_z = p.get_d(); } /// \brief Rotate this point using the quaterion @a q by first /// moving the point to @a pivot and after rotating moving it /// back constexpr void rotate(const quaternion &q, point_type pivot) { operator-=(pivot); rotate(q); operator+=(pivot); } #if HAVE_LIBCLIPPER /// \brief Make it possible to pass a point to clipper functions expecting a clipper coordinate operator clipper::Coord_orth() const { return clipper::Coord_orth(m_x, m_y, m_z); } #endif /// \brief Allow access to this point as if it is a tuple of three const value_type's constexpr operator std::tuple() const { return std::make_tuple(std::ref(m_x), std::ref(m_y), std::ref(m_z)); } /// \brief Allow access to this point as if it is a tuple of three value_type's constexpr operator std::tuple() { return std::make_tuple(std::ref(m_x), std::ref(m_y), std::ref(m_z)); } #if defined(__cpp_impl_three_way_comparison) /// \brief a default spaceship operator constexpr auto operator<=>(const point_type &rhs) const = default; #else /// \brief a default equals operator constexpr bool operator==(const point_type &rhs) const { return m_x == rhs.m_x and m_y == rhs.m_y and m_z == rhs.m_z; } /// \brief a default not-equals operator constexpr bool operator!=(const point_type &rhs) const { return not operator==(rhs); } #endif // consider point as a vector... perhaps I should rename point? /// \brief looking at the point as if it is a vector, return the squared length constexpr value_type length_sq() const { return m_x * m_x + m_y * m_y + m_z * m_z; } /// \brief looking at the point as if it is a vector, return the length constexpr value_type length() const { return std::sqrt(length_sq()); } /// \brief Print out the point @a pt to @a os friend std::ostream &operator<<(std::ostream &os, const point_type &pt) { os << '(' << pt.m_x << ',' << pt.m_y << ',' << pt.m_z << ')'; return os; } }; /// \brief By default we use points with float value_type using point = point_type; // -------------------------------------------------------------------- // several standard 3d operations /// \brief return the squared distance between points @a a and @a b template constexpr auto distance_squared(const point_type &a, const point_type &b) { return (a.m_x - b.m_x) * (a.m_x - b.m_x) + (a.m_y - b.m_y) * (a.m_y - b.m_y) + (a.m_z - b.m_z) * (a.m_z - b.m_z); } /// \brief return the distance between points @a a and @a b template constexpr auto distance(const point_type &a, const point_type &b) { return std::sqrt( (a.m_x - b.m_x) * (a.m_x - b.m_x) + (a.m_y - b.m_y) * (a.m_y - b.m_y) + (a.m_z - b.m_z) * (a.m_z - b.m_z)); } /// \brief return the dot product between the vectors @a a and @a b template inline constexpr auto dot_product(const point_type &a, const point_type &b) { return a.m_x * b.m_x + a.m_y * b.m_y + a.m_z * b.m_z; } /// \brief return the cross product between the vectors @a a and @a b template inline constexpr auto cross_product(const point_type &a, const point_type &b) { return point_type>( a.m_y * b.m_z - b.m_y * a.m_z, a.m_z * b.m_x - b.m_z * a.m_x, a.m_x * b.m_y - b.m_x * a.m_y); } /// \brief return the angle in degrees between the vectors from point @a p2 to @a p1 and @a p2 to @a p3 template constexpr auto angle(const point_type &p1, const point_type &p2, const point_type &p3) { point_type v1 = p1 - p2; point_type v2 = p3 - p2; return std::acos(dot_product(v1, v2) / (v1.length() * v2.length())) * 180 / kPI; } /// \brief return the dihedral angle in degrees for the four points @a p1, @a p2, @a p3 and @a p4 /// /// See https://en.wikipedia.org/wiki/Dihedral_angle for an explanation of what a dihedral angle is template constexpr auto dihedral_angle(const point_type &p1, const point_type &p2, const point_type &p3, const point_type &p4) { point_type v12 = p1 - p2; // vector from p2 to p1 point_type v43 = p4 - p3; // vector from p3 to p4 point_type z = p2 - p3; // vector from p3 to p2 point_type p = cross_product(z, v12); point_type x = cross_product(z, v43); point_type y = cross_product(z, x); auto u = dot_product(x, x); auto v = dot_product(y, y); F result = 360; if (u > 0 and v > 0) { u = dot_product(p, x) / std::sqrt(u); v = dot_product(p, y) / std::sqrt(v); if (u != 0 or v != 0) result = std::atan2(v, u) * static_cast(180 / kPI); } return result; } /// \brief return the cosinus angle for the four points @a p1, @a p2, @a p3 and @a p4 template constexpr auto cosinus_angle(const point_type &p1, const point_type &p2, const point_type &p3, const point_type &p4) { point_type v12 = p1 - p2; point_type v34 = p3 - p4; auto x = dot_product(v12, v12) * dot_product(v34, v34); return x > 0 ? dot_product(v12, v34) / std::sqrt(x) : 0; } /// \brief return the distance from point @a p to the line from @a l1 to @a l2 template constexpr auto distance_point_to_line(const point_type &l1, const point_type &l2, const point_type &p) { auto line = l2 - l1; auto p_to_l1 = p - l1; auto p_to_l2 = p - l2; auto cross = cross_product(p_to_l1, p_to_l2); return cross.length() / line.length(); } // -------------------------------------------------------------------- /** * @brief For e.g. simulated annealing, returns a new point that is moved in * a random direction with a distance randomly chosen from a normal * distribution with a stddev of offset. */ point nudge(point p, float offset); // -------------------------------------------------------------------- /// \brief Return a quaternion created from angle @a angle and axis @a axis quaternion construct_from_angle_axis(float angle, point axis); /// \brief Return a tuple of an angle and an axis for quaternion @a q std::tuple quaternion_to_angle_axis(quaternion q); /// @brief Given four points and an angle, return the quaternion required to rotate /// point p4 along the p2-p3 axis and around point p3 to obtain the required within /// an accuracy of esd quaternion construct_for_dihedral_angle(point p1, point p2, point p3, point p4, float angle, float esd); /// \brief Return the point that is the centroid of all the points in @a pts point centroid(const std::vector &pts); /// \brief Move all the points in @a pts so that their centroid is at the origin /// (0, 0, 0) and return the offset used (the former centroid) point center_points(std::vector &pts); /// \brief Returns how the two sets of points \a a and \b b can be aligned /// /// \param a The first set of points /// \param b The second set of points /// \result The quaternion which should be applied to the points in \a a to /// obtain the best superposition. quaternion align_points(const std::vector &a, const std::vector &b); /// \brief The RMSd for the points in \a a and \a b double RMSd(const std::vector &a, const std::vector &b); // -------------------------------------------------------------------- /** * @brief Helper class to generate evenly divided points on a sphere * * We use a fibonacci sphere to calculate even distribution of the dots * * @tparam N The number of points on the sphere is 2 * N + 1 */ template class spherical_dots { public: /// \brief the number of points constexpr static int P = 2 * N * 1; /// \brief the *weight* of the fibonacci sphere constexpr static double W = (4 * kPI) / P; /// \brief the internal storage type using array_type = typename std::array; /// \brief iterator type using iterator = typename array_type::const_iterator; /// \brief singleton instance static spherical_dots &instance() { static spherical_dots sInstance; return sInstance; } /// \brief The number of points std::size_t size() const { return P; } /// \brief Access a point by index const point operator[](uint32_t inIx) const { return m_points[inIx]; } /// \brief iterator pointing to the first point iterator begin() const { return m_points.begin(); } /// \brief iterator pointing past the last point iterator end() const { return m_points.end(); } /// \brief return the *weight*, double weight() const { return W; } spherical_dots() { const double kGoldenRatio = (1 + std::sqrt(5.0)) / 2; auto p = m_points.begin(); for (int32_t i = -N; i <= N; ++i) { double lat = std::asin((2.0 * i) / P); double lon = std::fmod(i, kGoldenRatio) * 2 * kPI / kGoldenRatio; p->m_x = std::sin(lon) * std::cos(lat); p->m_y = std::cos(lon) * std::cos(lat); p->m_z = std::sin(lat); ++p; } } private: array_type m_points; }; } // namespace cif libcifpp-7.0.9/include/cif++/row.hpp0000644000175000017500000003006114746170722017023 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/item.hpp" #include /** * @file row.hpp * * The class cif::row should be an opaque type. It is used to store the * internal data per row in a category. You should use cif::row_handle * to get access to the contents in a row. * * One could think of rows as vectors of cif::item. But internally * that's not the case. * * You can access the values of stored items by name or index. * The return value of operator[] is an cif::item_handle object. * * @code {.cpp} * cif::category &atom_site = my_db["atom_site"]; * cif::row_handle rh = atom_site.front(); * * // by name: * std::string name = rh["label_atom_id"].as(); * * // by index: * uint16_t ix = atom_site.get_item_ix("label_atom_id"); * assert(rh[ix].as("label_atom_id", "cartn_x", "cartn_y", "cartn_z"); * @endcode * * * */ namespace cif { namespace detail { // some helper classes to help create tuple result types template struct get_row_result { static constexpr std::size_t N = sizeof...(C); get_row_result(const row_handle &r, std::array &&items) : m_row(r) , m_items(std::move(items)) { } const item_handle operator[](uint16_t ix) const { return m_row[m_items[ix]]; } template = 0> operator std::tuple() const { return get(std::index_sequence_for{}); } template std::tuple get(std::index_sequence) const { return std::tuple{ m_row[m_items[Is]].template as()... }; } const row_handle &m_row; std::array m_items; }; // we want to be able to tie some variables to a get_row_result, for this we use tiewraps template struct tie_wrap { tie_wrap(Ts... args) : m_value(args...) { } template void operator=(const RR &&rr) { // get_row_result will do the conversion, but only if the types // are compatible. That means the number of parameters to the get() // of the row should be equal to the number of items in the tuple // you are trying to tie. using RType = std::tuple::type...>; m_value = static_cast(rr); } std::tuple m_value; }; } // namespace detail /// \brief similar to std::tie, assign values to each element in @a v from the /// result of a get on a row_handle. template auto tie(Ts &...v) { return detail::tie_wrap(std::forward(v)...); } // -------------------------------------------------------------------- /// \brief the row class, this one is not directly accessible from the outside class row : public std::vector { public: row() = default; /** * @brief Return the item_value pointer for item at index @a ix */ item_value* get(uint16_t ix) { return ix < size() ? &data()[ix] : nullptr; } /** * @brief Return the const item_value pointer for item at index @a ix */ const item_value* get(uint16_t ix) const { return ix < size() ? &data()[ix] : nullptr; } private: friend class category; friend class category_index; template friend class iterator_impl; void append(uint16_t ix, item_value &&iv) { if (ix >= size()) resize(ix + 1); at(ix) = std::move(iv); } void remove(uint16_t ix) { if (ix < size()) at(ix) = item_value{}; } row *m_next = nullptr; }; // -------------------------------------------------------------------- /// \brief row_handle is the way to access data stored in rows class row_handle { public: /** @cond */ friend struct item_handle; friend class category; friend class category_index; friend class row_initializer; template friend class iterator_impl; row_handle() = default; row_handle(const row_handle &) = default; row_handle(row_handle &&) = default; row_handle &operator=(const row_handle &) = default; row_handle &operator=(row_handle &&) = default; /** @endcond */ /// \brief constructor taking a category @a cat and a row @a r row_handle(const category &cat, const row &r) : m_category(const_cast(&cat)) , m_row(const_cast(&r)) { } /// \brief return the category this row belongs to const category &get_category() const { return *m_category; } /// \brief Return true if the row is empty or uninitialised bool empty() const { return m_category == nullptr or m_row == nullptr; } /// \brief convenience method to test for empty() explicit operator bool() const { return not empty(); } /// \brief return a cif::item_handle to the item in item @a item_ix item_handle operator[](uint16_t item_ix) { return empty() ? item_handle::s_null_item : item_handle(item_ix, *this); } /// \brief return a const cif::item_handle to the item in item @a item_ix const item_handle operator[](uint16_t item_ix) const { return empty() ? item_handle::s_null_item : item_handle(item_ix, const_cast(*this)); } /// \brief return a cif::item_handle to the item in the item named @a item_name item_handle operator[](std::string_view item_name) { return empty() ? item_handle::s_null_item : item_handle(add_item(item_name), *this); } /// \brief return a const cif::item_handle to the item in the item named @a item_name const item_handle operator[](std::string_view item_name) const { return empty() ? item_handle::s_null_item : item_handle(get_item_ix(item_name), const_cast(*this)); } /// \brief Return an object that can be used in combination with cif::tie /// to assign the values for the items @a items template auto get(C... items) const { return detail::get_row_result(*this, { get_item_ix(items)... }); } /// \brief Return a tuple of values of types @a Ts for the items @a items template = 0> std::tuple get(C... items) const { return detail::get_row_result(*this, { get_item_ix(items)... }); } /// \brief Get the value of item @a item cast to type @a T template T get(const char *item) const { return operator[](get_item_ix(item)).template as(); } /// \brief Get the value of item @a item cast to type @a T template T get(std::string_view item) const { return operator[](get_item_ix(item)).template as(); } /// \brief assign each of the items named in @a values to their respective value void assign(const std::vector &values) { for (auto &value : values) assign(value, true); } /** \brief assign the value @a value to the item named @a name * * If updateLinked it true, linked records are updated as well. * That means that if item @a name is part of the link definition * and the link results in a linked record in another category * this record in the linked category is updated as well. * * If validate is true, which is default, the assigned value is * checked to see if it conforms to the rules defined in the dictionary */ void assign(std::string_view name, std::string_view value, bool updateLinked, bool validate = true) { assign(add_item(name), value, updateLinked, validate); } /** \brief assign the value @a value to item at index @a item * * If updateLinked it true, linked records are updated as well. * That means that if item @a item is part of the link definition * and the link results in a linked record in another category * this record in the linked category is updated as well. * * If validate is true, which is default, the assigned value is * checked to see if it conforms to the rules defined in the dictionary */ void assign(uint16_t item, std::string_view value, bool updateLinked, bool validate = true); /// \brief compare two rows bool operator==(const row_handle &rhs) const { return m_category == rhs.m_category and m_row == rhs.m_row; } /// \brief compare two rows bool operator!=(const row_handle &rhs) const { return m_category != rhs.m_category or m_row != rhs.m_row; } private: uint16_t get_item_ix(std::string_view name) const; std::string_view get_item_name(uint16_t ix) const; uint16_t add_item(std::string_view name); row *get_row() { return m_row; } const row *get_row() const { return m_row; } void assign(const item &i, bool updateLinked) { assign(i.name(), i.value(), updateLinked); } void swap(uint16_t item, row_handle &r); category *m_category = nullptr; row *m_row = nullptr; }; // -------------------------------------------------------------------- /** * @brief The class row_initializer is a list of cif::item's. * * This class is used to construct new rows, it allows to * group a list of item name and value pairs and pass it * in one go to the constructing function. */ class row_initializer : public std::vector { public: /** @cond */ friend class category; row_initializer() = default; row_initializer(const row_initializer &) = default; row_initializer(row_initializer &&) = default; row_initializer &operator=(const row_initializer &) = default; row_initializer &operator=(row_initializer &&) = default; /** @endcond */ /// \brief constructor taking a std::initializer_list of items row_initializer(std::initializer_list items) : std::vector(items) { } /// \brief constructor taking a range of items template , int> = 0> row_initializer(ItemIter b, ItemIter e) : std::vector(b, e) { } /// \brief constructor taking the values of an existing row row_initializer(row_handle rh); /// \brief set the value for item name @a name to @a value void set_value(std::string_view name, std::string_view value); /// \brief set the value for item based on @a i void set_value(const item &i) { set_value(i.name(), i.value()); } /// \brief set the value for item name @a name to @a value, but only if the item did not have a value already void set_value_if_empty(std::string_view name, std::string_view value); /// \brief set the value for item @a i, but only if the item did not have a value already void set_value_if_empty(const item &i) { set_value_if_empty(i.name(), i.value()); } }; } // namespace ciflibcifpp-7.0.9/include/cif++/symmetry.hpp0000644000175000017500000003754014746170722020116 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/exports.hpp" #include "cif++/matrix.hpp" #include "cif++/point.hpp" #include #include #include #if defined(__cpp_impl_three_way_comparison) #include #endif /** \file cif++/symmetry.hpp * * This file contains code to do symmetry operations based on the * operations as specified in the International Tables. */ namespace cif { // -------------------------------------------------------------------- /// \brief Apply matrix transformation @a m on point @a pt and return the result inline point operator*(const matrix3x3 &m, const point &pt) { return { m(0, 0) * pt.m_x + m(0, 1) * pt.m_y + m(0, 2) * pt.m_z, m(1, 0) * pt.m_x + m(1, 1) * pt.m_y + m(1, 2) * pt.m_z, m(2, 0) * pt.m_x + m(2, 1) * pt.m_y + m(2, 2) * pt.m_z }; } // -------------------------------------------------------------------- /// \brief the space groups we know enum class space_group_name { full, ///< The *full* spacegroup xHM, ///< The *xHM* spacegroup Hall ///< The *Hall* spacegroup }; /// \brief For each known spacegroup we define a structure like this struct space_group { const char *name; ///< The name according to *full* const char *xHM; ///< The name according to *xHM* const char *Hall; ///< The name according to *Hall* int nr; ///< The number for this spacegroup }; /// \brief Global list of spacegroups extern CIFPP_EXPORT const space_group kSpaceGroups[]; /// \brief Global for the size of the list of spacegroups extern CIFPP_EXPORT const std::size_t kNrOfSpaceGroups; // -------------------------------------------------------------------- /** * @brief Helper class to efficiently pack the data that * makes up a symmetry operation * */ struct symop_data { /// \brief constructor constexpr symop_data(const std::array &data) : m_packed((data[0] bitand 0x03ULL) << 34 bitor (data[1] bitand 0x03ULL) << 32 bitor (data[2] bitand 0x03ULL) << 30 bitor (data[3] bitand 0x03ULL) << 28 bitor (data[4] bitand 0x03ULL) << 26 bitor (data[5] bitand 0x03ULL) << 24 bitor (data[6] bitand 0x03ULL) << 22 bitor (data[7] bitand 0x03ULL) << 20 bitor (data[8] bitand 0x03ULL) << 18 bitor (data[9] bitand 0x07ULL) << 15 bitor (data[10] bitand 0x07ULL) << 12 bitor (data[11] bitand 0x07ULL) << 9 bitor (data[12] bitand 0x07ULL) << 6 bitor (data[13] bitand 0x07ULL) << 3 bitor (data[14] bitand 0x07ULL) << 0) { } /// \brief compare bool operator==(const symop_data &rhs) const { return m_packed == rhs.m_packed; } /// \brief sorting order bool operator<(const symop_data &rhs) const { return m_packed < rhs.m_packed; } /// \brief return an int representing the value stored in the two bits at offset @a offset inline constexpr int unpack3(int offset) const { int result = (m_packed >> offset) bitand 0x03; return result == 3 ? -1 : result; } /// \brief return an int representing the value stored in the three bits at offset @a offset inline constexpr int unpack7(int offset) const { return (m_packed >> offset) bitand 0x07; } /// \brief return an array of 15 ints representing the values stored constexpr std::array data() const { return { unpack3(34), unpack3(32), unpack3(30), unpack3(28), unpack3(26), unpack3(24), unpack3(22), unpack3(20), unpack3(18), unpack7(15), unpack7(12), unpack7(9), unpack7(6), unpack7(3), unpack7(0) }; } private: friend struct symop_datablock; const uint64_t kPackMask = (~0ULL >> (64 - 36)); symop_data(uint64_t v) : m_packed(v bitand kPackMask) { } uint64_t m_packed; }; /** * @brief For each symmetry operator defined in the international tables * we have an entry in this struct type. It contains the spacegroup * number, the symmetry operations and the rotational number. */ struct symop_datablock { /// \brief constructor constexpr symop_datablock(int spacegroup, int rotational_number, const std::array &rt_data) : m_v((spacegroup bitand 0xffffULL) << 48 bitor (rotational_number bitand 0xffULL) << 40 bitor symop_data(rt_data).m_packed) { } uint16_t spacegroup() const { return m_v >> 48; } ///< Return the spacegroup symop_data symop() const { return symop_data(m_v); } ///< Return the symmetry operation uint8_t rotational_number() const { return (m_v >> 40) bitand 0xff; } ///< Return the rotational_number private: uint64_t m_v; }; static_assert(sizeof(symop_datablock) == sizeof(uint64_t), "Size of symop_data is wrong"); /// \brief Global containing the list of known symmetry operations extern CIFPP_EXPORT const symop_datablock kSymopNrTable[]; /// \brief Size of the list of known symmetry operations extern CIFPP_EXPORT const std::size_t kSymopNrTableSize; // -------------------------------------------------------------------- // Some more symmetry related stuff here. class datablock; class cell; class spacegroup; class rtop; struct sym_op; /** @brief A class that encapsulates the symmetry operations as used in PDB files, * i.e. a rotational number and a translation vector. * * The syntax in string format follows the syntax as used in mmCIF files, i.e. * rotational number followed by underscore and the three translations where 5 is * no movement. * * So the string 1_555 means no symmetry movement at all since the rotational number * 1 always corresponds to the symmetry operation [x, y, z]. */ struct sym_op { public: /// \brief constructor sym_op(uint8_t nr = 1, uint8_t ta = 5, uint8_t tb = 5, uint8_t tc = 5) : m_nr(nr) , m_ta(ta) , m_tb(tb) , m_tc(tc) { } /// \brief construct a sym_op based on the contents encoded in string @a s explicit sym_op(std::string_view s); /** @cond */ sym_op(const sym_op &) = default; sym_op(sym_op &&) = default; sym_op &operator=(const sym_op &) = default; sym_op &operator=(sym_op &&) = default; /** @endcond */ /// \brief return true if this sym_op is the identity operator constexpr bool is_identity() const { return m_nr == 1 and m_ta == 5 and m_tb == 5 and m_tc == 5; } /// \brief quick test for unequal to identity explicit constexpr operator bool() const { return not is_identity(); } /// \brief return the content encoded in a string std::string string() const; #if defined(__cpp_impl_three_way_comparison) /// \brief a default spaceship operator constexpr auto operator<=>(const sym_op &rhs) const = default; #else /// \brief a default equals operator constexpr bool operator==(const sym_op &rhs) const { return m_nr == rhs.m_nr and m_ta == rhs.m_ta and m_tb == rhs.m_tb and m_tc == rhs.m_tc; } /// \brief a default not-equals operator constexpr bool operator!=(const sym_op &rhs) const { return not operator==(rhs); } #endif /// @cond uint8_t m_nr; uint8_t m_ta, m_tb, m_tc; /// @endcond }; static_assert(sizeof(sym_op) == 4, "Sym_op should be four bytes"); namespace literals { /** * @brief This operator allows you to write code like this: * * @code {.cpp} * using namespace cif::literals; * * cif::sym_op so = "1_555"_symop; * @endcode * */ inline sym_op operator""_symop(const char *text, std::size_t length) { return sym_op({ text, length }); } } // namespace literals // -------------------------------------------------------------------- // The transformation class /** * @brief A class you can use to apply symmetry transformations on points * * Transformations consist of two operations, a matrix transformation which * is often a rotation followed by a translation. * * In case the matrix transformation is a pure rotation a quaternion * is created to do the actual calculations. That's faster and more * precise. */ class transformation { public: /// \brief constructor taking a symop_data object @a data transformation(const symop_data &data); /// \brief constructor taking a rotation matrix @a r and a translation vector @a t transformation(const matrix3x3 &r, const cif::point &t); /** @cond */ transformation(const transformation &) = default; transformation(transformation &&) = default; transformation &operator=(const transformation &) = default; transformation &operator=(transformation &&) = default; /** @endcond */ /// \brief operator() to perform the transformation on point @a pt and return the result point operator()(point pt) const { if (m_q) pt.rotate(m_q); else pt = m_rotation * pt; return pt + m_translation; } /// \brief return a transformation object that is the result of applying @a rhs after @a lhs friend transformation operator*(const transformation &lhs, const transformation &rhs); /// \brief return the inverse transformation for @a t friend transformation inverse(const transformation &t); /// \brief return the inverse tranformation for this transformation operator-() const { return inverse(*this); } friend class spacegroup; private: // Most rotation matrices provided by the International Tables // are really rotation matrices, in those cases we can construct // a quaternion. Unfortunately, that doesn't work for all of them void try_create_quaternion(); matrix3x3 m_rotation; quaternion m_q; point m_translation; }; // -------------------------------------------------------------------- // class cell /** * @brief The cell class describes the dimensions and angles of a unit cell * in a crystal */ class cell { public: /// \brief constructor cell(float a, float b, float c, float alpha = 90.f, float beta = 90.f, float gamma = 90.f); /// \brief constructor that takes the appropriate values from the *cell* category in datablock @a db cell(const datablock &db); float get_a() const { return m_a; } ///< return dimension a float get_b() const { return m_b; } ///< return dimension b float get_c() const { return m_c; } ///< return dimension c float get_alpha() const { return m_alpha; } ///< return angle alpha float get_beta() const { return m_beta; } ///< return angle beta float get_gamma() const { return m_gamma; } ///< return angle gamma float get_volume() const; ///< return the calculated volume for this cell matrix3x3 get_orthogonal_matrix() const { return m_orthogonal; } ///< return the matrix to use to transform coordinates from fractional to orthogonal matrix3x3 get_fractional_matrix() const { return m_fractional; } ///< return the matrix to use to transform coordinates from orthogonal to fractional private: void init(); float m_a, m_b, m_c, m_alpha, m_beta, m_gamma; matrix3x3 m_orthogonal, m_fractional; }; // -------------------------------------------------------------------- /// \brief Return the spacegroup number from the *symmetry* category in datablock @a db int get_space_group_number(const datablock &db); /// \brief Return the spacegroup number for spacegroup named @a spacegroup int get_space_group_number(std::string_view spacegroup); /// \brief Return the spacegroup number for spacegroup named @a spacegroup assuming space_group_name @a type int get_space_group_number(std::string_view spacegroup, space_group_name type); /** * @brief class to encapsulate the list of transformations making up a spacegroup * */ class spacegroup : public std::vector { public: /// \brief constructor using the information in the *symmetry* category in datablock @a db spacegroup(const datablock &db) : spacegroup(get_space_group_number(db)) { } /// \brief constructor using the spacegroup named @a name spacegroup(std::string_view name) : spacegroup(get_space_group_number(name)) { } /// \brief constructor using the spacegroup named @a name assuming space_group_name @a type spacegroup(std::string_view name, space_group_name type) : spacegroup(get_space_group_number(name, type)) { } /// \brief constructor using the spacegroup number @a nr spacegroup(int nr); int get_nr() const { return m_nr; } ///< Return the nr std::string get_name() const; ///< Return the name /** \brief perform a spacegroup operation on point @a pt using * cell @a c and sym_op @a symop */ point operator()(const point &pt, const cell &c, sym_op symop) const; /** \brief perform an inverse spacegroup operation on point @a pt using * cell @a c and sym_op @a symop */ point inverse(const point &pt, const cell &c, sym_op symop) const; private: int m_nr; std::size_t m_index; }; // -------------------------------------------------------------------- /** * @brief A crystal combines a cell and a spacegroup. * * The information in cell and spacegroup together make up all * information you need to do symmetry calculations in a crystal */ class crystal { public: /// \brief constructor using the information found in datablock @a db crystal(const datablock &db) : m_cell(db) , m_spacegroup(db) { } /// \brief constructor using cell @a c and spacegroup @a sg crystal(const cell &c, const spacegroup &sg) : m_cell(c) , m_spacegroup(sg) { } /** @cond */ crystal(const crystal &) = default; crystal(crystal &&) = default; crystal &operator=(const crystal &) = default; crystal &operator=(crystal &&) = default; /** @endcond */ const cell &get_cell() const { return m_cell; } ///< Return the cell const spacegroup &get_spacegroup() const { return m_spacegroup; } ///< Return the spacegroup /// \brief Return the symmetry copy of point @a pt using symmetry operation @a symop point symmetry_copy(const point &pt, sym_op symop) const { return m_spacegroup(pt, m_cell, symop); } /// \brief Return the symmetry copy of point @a pt using the inverse of symmetry operation @a symop point inverse_symmetry_copy(const point &pt, sym_op symop) const { return m_spacegroup.inverse(pt, m_cell, symop); } /// \brief Return a tuple consisting of distance, new location and symmetry operation /// for the point @a b with respect to point @a a. std::tuple closest_symmetry_copy(point a, point b) const; private: cell m_cell; spacegroup m_spacegroup; }; // -------------------------------------------------------------------- // Symmetry operations on points /// \brief convenience function returning the fractional point @a pt in orthogonal coordinates for cell @a c inline point orthogonal(const point &pt, const cell &c) { return c.get_orthogonal_matrix() * pt; } /// \brief convenience function returning the orthogonal point @a pt in fractional coordinates for cell @a c inline point fractional(const point &pt, const cell &c) { return c.get_fractional_matrix() * pt; } // -------------------------------------------------------------------- } // namespace cif libcifpp-7.0.9/include/cif++/text.hpp0000644000175000017500000004105414746170722017204 0ustar maartenmaarten/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/exports.hpp" #include #include #include #include #include #include #include #include #if __has_include() #include namespace std_experimental = std::experimental; #else // A quick hack to work around the missing is_detected in MSVC namespace std_experimental { namespace detail { template class Op, class... Args> struct detector { using value_t = std::false_type; }; template