Compare commits

..

137 Commits

Author SHA1 Message Date
Maarten L. Hekkelman
86cec7f1c5 remove debug statement 2026-04-01 17:02:43 +02:00
Maarten L. Hekkelman
d4406b934c Removed own matrix implementation 2026-04-01 15:31:07 +02:00
Maarten L. Hekkelman
f105c64d85 All tests pass with new point type 2026-04-01 13:25:23 +02:00
Maarten L. Hekkelman
3805eb3905 Compiling, but not working yet 2026-04-01 12:33:55 +02:00
Maarten L. Hekkelman
4b05eec45a First steps 2026-04-01 10:58:33 +02:00
Maarten L. Hekkelman
187d7fc314 only use polymer entities to create pdbx_poly_seq_scheme 2026-03-30 08:20:50 +02:00
Maarten L. Hekkelman
6df8b435ec Fix reconstruction of pdbx_poly_seq_scheme, again... 2026-03-25 16:16:24 +01:00
Maarten L. Hekkelman
399827db41 Version bump 2026-03-25 09:59:53 +01:00
Maarten L. Hekkelman
57aaf36bde Fix regression in reconstruct 2026-03-25 09:58:45 +01:00
Maarten L. Hekkelman
a804f6d107 Merge branch 'develop' into trunk 2026-03-24 08:22:55 +01:00
Maarten L. Hekkelman
0ab75e866e If fastfloat is needed, it should be installed system wide 2026-03-24 08:15:17 +01:00
Maarten L. Hekkelman
a092760c1e include fast_float 2026-03-23 18:45:14 +01:00
Maarten L. Hekkelman
ac1eaa7ca0 FastFloat, again 2026-03-23 17:44:24 +01:00
Maarten L. Hekkelman
1d615eeff1 fast float for macOS (and FreeBSD?) 2026-03-23 17:36:35 +01:00
Maarten L. Hekkelman
d936b67e89 Fix for fastfloat, does not need linking 2026-03-23 16:38:31 +01:00
Maarten L. Hekkelman
5ec523a7c9 fix for missing from_chars on FreeBSD 2026-03-23 16:37:33 +01:00
Maarten L. Hekkelman
3fd8d1f507 Passing optional 2026-03-23 16:05:45 +01:00
Maarten L. Hekkelman
1a7fffd561 Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-03-23 13:56:00 +01:00
Maarten L. Hekkelman
95ea353c4e Better constructors allowing easier emplacement of new items in e.g. a category
Fix reconstruction of pdbx_poly_seq_scheme
2026-03-23 13:55:17 +01:00
Maarten L. Hekkelman
1e39c43cd2 Fix for removal of operator bool 2026-03-17 13:30:29 +01:00
Maarten L. Hekkelman
cb109bacc4 remove operator bool for item's. The result is too ambiguous. 2026-03-17 13:22:21 +01:00
Maarten L. Hekkelman
8e802331c9 Fix row_initializser, missing 'empty' data using wrong test 2026-03-17 13:17:41 +01:00
Maarten L. Hekkelman
eabcf437cd Add some more tests for I/O 2026-03-17 13:17:02 +01:00
Maarten L. Hekkelman
03e5a05238 Last change to create_water was not good 2026-03-17 13:16:31 +01:00
Maarten L. Hekkelman
ebdd556729 Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-03-17 11:53:03 +01:00
Maarten L. Hekkelman
5860c83306 swap item_handle 2026-03-17 11:52:50 +01:00
Maarten L. Hekkelman
bb6410260e fix adding waters, keep auth_seq_id 2026-03-11 16:17:13 +01:00
Maarten L. Hekkelman
3a8527e26d fix regression in output 2026-03-11 16:16:46 +01:00
Maarten L. Hekkelman
3976d9edb6 add transposed matrix 2026-03-05 17:52:06 +01:00
Maarten L. Hekkelman
2df904899d Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-03-04 20:28:12 +01:00
Maarten L. Hekkelman
6eb8857877 Fix matrix multiplication 2026-03-04 20:28:04 +01:00
Maarten L. Hekkelman
2f3a755ebd Better model, now include micro heterogeneity 2026-03-04 11:20:38 +01:00
Maarten L. Hekkelman
9c4256b0f0 Removed kPI, replaced with std::numbers::pi 2026-03-03 10:46:16 +01:00
Maarten L. Hekkelman
fdc3972ec0 Fix makefile for examples 2026-03-02 11:27:41 +01:00
Maarten L. Hekkelman
a322d2efb8 Update documentation, add more license and copyright info 2026-03-02 11:20:59 +01:00
Maarten L. Hekkelman
926b8c1069 Fix generating documentation 2026-03-02 09:14:15 +01:00
Maarten L. Hekkelman
1e1a5b4cc8 Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-03-02 07:30:52 +01:00
Maarten L. Hekkelman
3e220e69a7 macOS is behind, again 2026-02-26 10:54:37 +01:00
Maarten L. Hekkelman
d5fbf05b44 documenting 2026-02-25 17:16:09 +01:00
Maarten L. Hekkelman
3721922174 Remove last warnings 2026-02-25 15:11:17 +01:00
Maarten L. Hekkelman
3d7b732090 use ranges for iota 2026-02-25 11:17:15 +01:00
Maarten L. Hekkelman
ea890b3942 Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-02-24 21:15:55 +01:00
Maarten L. Hekkelman
92c335499d fix writing PDB file 2026-02-24 21:15:45 +01:00
Maarten L. Hekkelman
96f0ae694c Avoid some string copies 2026-02-21 14:25:13 +01:00
Maarten L. Hekkelman
c3e1c46b63 using enum and more 2026-02-21 14:12:13 +01:00
Maarten L. Hekkelman
5670417f12 using std::nullopt 2026-02-19 21:54:24 +01:00
Maarten L. Hekkelman
da7cb8741b Add operator<< to item_handle 2026-02-18 19:30:09 +01:00
Maarten L. Hekkelman
f2a28ed2d0 Better refactoring this time 2026-02-18 19:13:06 +01:00
Maarten L. Hekkelman
c91a9ff509 Revert "Refactoring again"
This reverts commit 64edb4ddfd.
2026-02-18 17:28:54 +01:00
Maarten L. Hekkelman
64edb4ddfd Refactoring again 2026-02-18 16:34:19 +01:00
Maarten L. Hekkelman
8bf4399181 change path of main include file 2026-02-18 15:10:12 +01:00
Maarten L. Hekkelman
889de19cd0 Fix for incorrect type 2026-02-17 16:37:01 +01:00
Maarten L. Hekkelman
b7680dcb92 More cleaning up 2026-02-17 13:50:08 +01:00
Maarten L. Hekkelman
43999dfade fixes, cartn's and type of ndb_seq_num 2026-02-16 13:05:00 +01:00
Maarten L. Hekkelman
845b549128 fix parser for float like strings 2026-02-16 09:48:34 +01:00
Maarten L. Hekkelman
b019ba9b91 ouch... 2026-02-14 11:31:26 +01:00
Maarten L. Hekkelman
7a49d064a1 Added forward declaration of file 2026-02-14 10:56:09 +01:00
Maarten L. Hekkelman
fd0a95221e add missing include 2026-02-13 20:36:22 +01:00
Maarten L. Hekkelman
32acd48ce9 Fix 2026-02-13 20:21:44 +01:00
Maarten L. Hekkelman
14be0638c1 removed forward_decl 2026-02-13 20:08:18 +01:00
Maarten L. Hekkelman
2a0e52dff6 IWYU 2026-02-13 20:05:36 +01:00
Maarten L. Hekkelman
0fe1178bdd why was this included? 2026-02-13 19:04:47 +01:00
Maarten L. Hekkelman
2f11d6d642 All tests pass 2026-02-13 16:26:38 +01:00
Maarten L. Hekkelman
a46a31ac52 first merge 2026-02-13 13:53:45 +01:00
Maarten L. Hekkelman
5ad38b4e9c Merge branch 'develop' of github.com:PDB-REDO/libcifpp into develop 2026-02-07 15:25:39 +01:00
Maarten L. Hekkelman
702323d83a a specialisation for find1 with parameter std::optional 2026-02-07 15:25:20 +01:00
Maarten L. Hekkelman
721857629e Reconstruction optimised 2026-02-04 11:58:49 +01:00
Maarten L. Hekkelman
9200acf8f4 fix, take 2 2026-01-28 19:57:36 +01:00
Maarten L. Hekkelman
6c1d60f5a5 fix 2026-01-28 19:36:52 +01:00
Maarten L. Hekkelman
56cfa09976 Fix typo, add depedency to cmake config file 2026-01-28 18:02:56 +01:00
Maarten L. Hekkelman
1c8571f02c category is now a valid std::ranges::input_range 2026-01-28 16:42:52 +01:00
Maarten L. Hekkelman
9cb5981f8d Merge branch 'trunk' of github.com:PDB-REDO/libcifpp into trunk 2026-01-27 14:26:26 +01:00
Maarten L. Hekkelman
06f1a308c3 update changelog 2026-01-27 14:25:01 +01:00
Maarten L. Hekkelman
f827682a45 update changelog 2026-01-27 14:23:13 +01:00
Maarten L. Hekkelman
1aed6a593c Remove windows link statements 2026-01-27 13:48:11 +01:00
Maarten L. Hekkelman
098f8f032a Flush progress bar to avoid garbage 2026-01-27 11:55:40 +01:00
Maarten L. Hekkelman
a539603c4f iwyu acting up again 2026-01-27 09:24:46 +01:00
Maarten L. Hekkelman
0dc4a34071 New progress bar 2026-01-26 17:43:21 +01:00
Maarten L. Hekkelman
b969df1194 Fixed pdb.hpp header file, contained a wrong signature 2026-01-23 13:12:14 +01:00
Maarten L. Hekkelman
2ef82c794e Silence some more windows warnings 2026-01-13 09:55:03 +01:00
Maarten L. Hekkelman
e157eb7700 fixed swap 2026-01-13 09:41:39 +01:00
Maarten L. Hekkelman
a079543594 residue copy constructor and the like 2026-01-13 09:28:00 +01:00
Maarten L. Hekkelman
9f3a0b5c4c cleaning up mostly done now 2026-01-13 08:59:40 +01:00
Maarten L. Hekkelman
dd37d87a33 sqlite stuff 2026-01-08 09:41:50 +01:00
Maarten L. Hekkelman
03342caf13 cleaning up code 2026-01-08 08:26:48 +01:00
Maarten L. Hekkelman
1e5050b221 clean up bugprone and cert warnings 2026-01-05 16:00:59 +01:00
Maarten L. Hekkelman
b34eb21d96 Modernizing code 2026-01-05 15:21:36 +01:00
Maarten L. Hekkelman
656c82838a macOS and Windows fixes,
start using clang-tidy
2026-01-05 12:59:59 +01:00
Maarten L. Hekkelman
2a265bb5c8 Finish removing sqlite code 2026-01-05 11:00:49 +01:00
Maarten L. Hekkelman
7ac0717944 Merge branch 'using-sqlite-for-delete' into develop 2026-01-05 10:55:33 +01:00
Maarten L. Hekkelman
f190bdfd64 embedded and patched sqlite no longer needed 2026-01-05 10:55:03 +01:00
Maarten L. Hekkelman
1d430e8c47 Merge branch 'with-sqlite' into develop 2026-01-05 08:31:32 +01:00
Maarten L. Hekkelman
56aab89176 Refactored validator_factory interface 2026-01-05 08:30:30 +01:00
Maarten L. Hekkelman
b9bcf07f84 Implement backslashed wrapping of long strings according to the cif 1.1 specification. 2025-12-31 16:08:17 +01:00
Maarten L. Hekkelman
e3e7648c5c change test 2025-12-30 15:36:07 +01:00
Maarten L. Hekkelman
83ee1984d8 null remains a problem, conceptually...
Added drop_empty_items
2025-12-30 15:28:51 +01:00
Maarten L. Hekkelman
f9741a27cd Keep track of modification 2025-12-30 10:39:42 +01:00
Maarten L. Hekkelman
0b002afb9f Add exec methods to connection 2025-12-30 10:27:15 +01:00
Maarten L. Hekkelman
a58e5a1bfc Rename function 2025-12-29 17:59:49 +01:00
Maarten L. Hekkelman
1229652444 Remove recursion 2025-12-29 17:19:03 +01:00
Maarten L. Hekkelman
05197a85c6 Fixed transactions by patching sqlite3 2025-12-29 17:11:17 +01:00
Maarten L. Hekkelman
71cd4958bd check complete sql 2025-12-29 15:52:02 +01:00
Maarten L. Hekkelman
61965c2391 More output options 2025-12-29 14:41:35 +01:00
Maarten L. Hekkelman
0f60f42f9e dirty marking, mcql work, load_dictionary 2025-12-29 12:45:56 +01:00
Maarten L. Hekkelman
2e61b330c4 Merge branch 'with-sqlite' of https://github.com/PDB-REDO/libcifpp into with-sqlite 2025-12-27 16:46:11 +01:00
Maarten L. Hekkelman
964e7620eb format output 2025-12-27 16:46:04 +01:00
Maarten L. Hekkelman
8a329e7c2d foreign key test 2025-12-24 14:07:14 +01:00
Maarten L. Hekkelman
8485747377 Merge branch 'trunk' into with-sqlite 2025-12-22 11:25:56 +01:00
Maarten L. Hekkelman
34af410d5e Use FetchContent to find sqlite3, if needed 2025-12-22 11:25:00 +01:00
UENO, M.
8f5b9eb631 Use find_package for FastFloat prior to FetchContent_Declare (#73)
* Use `find_package` for FastFloat prior to `FetchContent_Declare`

* Convert space to tab
2025-12-22 07:26:41 +01:00
Maarten L. Hekkelman
388eae211e Merge branch 'trunk' into with-sqlite 2025-12-20 08:53:23 +01:00
Maarten L. Hekkelman
8e2494532e order and supress empty output 2025-12-19 15:47:43 +01:00
Maarten L. Hekkelman
2f7f62bdce Implemented drop table 2025-12-18 14:58:22 +01:00
Maarten L. Hekkelman
bf9551a994 Better error reporting in cql 2025-12-18 14:36:44 +01:00
Maarten L. Hekkelman
30ff5bea36 Better validation message 2025-12-18 14:36:27 +01:00
Maarten L. Hekkelman
b6568664ea Execute multiple statements and more 2025-12-17 16:02:53 +01:00
Maarten L. Hekkelman
863f010a7c Renaming cql files 2025-12-17 13:45:00 +01:00
Maarten L. Hekkelman
da76bbae7c With transactions 2025-12-17 12:29:31 +01:00
Maarten L. Hekkelman
e2454a2e79 Insert, Delete and Update 2025-12-17 11:05:15 +01:00
Maarten L. Hekkelman
9503a7e9b4 Transaction stream working 2025-12-16 19:33:31 +01:00
Maarten L. Hekkelman
20784bdaf5 Working around NULL problem, for now 2025-12-16 16:14:10 +01:00
Maarten L. Hekkelman
9558085105 Working, but NULL is a problem 2025-12-16 15:40:50 +01:00
Maarten L. Hekkelman
429c31ae42 First working version 2025-12-16 12:56:29 +01:00
Maarten L. Hekkelman
58ac1ce033 backup 2025-12-15 18:43:02 +01:00
Maarten L. Hekkelman
23e575858c we can create tables now 2025-12-15 16:39:39 +01:00
Maarten L. Hekkelman
4a31878975 first steps with sqlite 2025-12-09 16:47:11 +01:00
Maarten L. Hekkelman
9f6e1e245b start value expression 2025-12-09 13:42:46 +01:00
Maarten L. Hekkelman
8a8ca9599d several simple select statements added 2025-12-09 11:28:39 +01:00
Maarten L. Hekkelman
00b0473438 backup 2025-12-03 16:22:44 +01:00
Maarten L. Hekkelman
f15a76e29b exclude from all for fast_float 2025-11-27 09:05:51 +01:00
Maarten L. Hekkelman
915a147449 backup 2025-11-26 16:35:30 +01:00
Maarten L. Hekkelman
edf24ca9ff work 2025-11-26 13:46:55 +01:00
Maarten L. Hekkelman
46a9318aa5 revert version string generator 2025-11-26 13:11:54 +01:00
Maarten L. Hekkelman
4a7f48eed8 some initial work 2025-11-19 16:36:44 +01:00
Maarten L. Hekkelman
42e66afd92 Merge branch 'develop' into cql 2025-11-19 13:29:35 +01:00
Maarten L. Hekkelman
ea8dea8cbd Merge branch 'develop' into cql 2025-10-01 16:46:27 +02:00
Maarten L. Hekkelman
ff2a233156 stap 1, een test 2025-10-01 15:48:16 +02:00
91 changed files with 26136 additions and 8144 deletions

18
.clang-tidy Normal file
View File

@@ -0,0 +1,18 @@
Checks: '-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
cert-*,
modernize*,
-modernize-use-trailing-return-type,
-modernize-avoid-c-arrays,
-modernize-use-designated-initializers,
performance
'
# HeaderFilterRegex: '.*'
ExcludeHeaderFilterRegex: 'Eigen|Eigen/Eigenvalues|eigen3/Eigen/Eigenvalues|sqlite3.h'
CheckOptions:
- key: bugprone-narrowing-conversions.WarnOnIntegerNarrowingConversion
value: false
- key: bugprone-narrowing-conversions.WarnOnIntegerToFloatingPointNarrowingConversion
value: false

View File

@@ -33,7 +33,7 @@ jobs:
- name: Install dependencies Ubuntu
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install mrc catch2
run: sudo apt-get update && sudo apt-get install mrc catch2 libsqlite3-dev
- name: Install dependencies Window
if: matrix.os == 'windows-latest'
@@ -43,7 +43,7 @@ jobs:
- name: Install Catch2 macOS
if: matrix.os == 'macos-latest'
run: >
brew install catch2
brew install catch2 fast_float
- name: Configure CMake
run: >

View File

@@ -1,22 +0,0 @@
version: 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

View File

@@ -24,16 +24,18 @@
cmake_minimum_required(VERSION 3.23)
cmake_policy(SET CMP0135 NEW)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
# set the project name
project(
libcifpp
VERSION 9.0.5
LANGUAGES CXX C)
libcifpp
VERSION 10.0.2
LANGUAGES CXX C)
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
@@ -47,515 +49,538 @@ include(VersionString)
# 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")
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"
)
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")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
# Build documentation?
set(BUILD_DOCUMENTATION OFF CACHE BOOL "Build the documentation")
# Build examples?
set(BUILD_EXAMPLES ON CACHE BOOL "Build the example applications")
# Optionally build a version to be installed inside CCP4
set(BUILD_FOR_CCP4 OFF CACHE BOOL "Build a version to be installed in CCP4")
# Create the cql/sqlite interface
set(BUILD_SQLITE_INTERFACE ON CACHE BOOL "Build the sqlite interface")
# Building shared libraries?
if(NOT(BUILD_FOR_CCP4 AND WIN32))
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build a shared library instead of a static one")
if(NOT (BUILD_FOR_CCP4 AND WIN32))
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build a shared library instead of a static one")
endif()
if(PROJECT_IS_TOP_LEVEL AND NOT BUILD_FOR_CCP4)
# Lots of code depend on the availability of the components.cif file
set(CIFPP_DOWNLOAD_CCD ON CACHE BOOL "Download the CCD file components.cif during installation")
# Lots of code depend on the availability of the components.cif file
set(CIFPP_DOWNLOAD_CCD ON CACHE BOOL "Download the CCD file components.cif during installation")
# An optional cron script can be installed to keep the data files up-to-date
if(UNIX AND NOT APPLE)
set(CIFPP_INSTALL_UPDATE_SCRIPT ON CACHE BOOL "Install the script to update CCD and dictionary files")
endif()
# An optional cron script can be installed to keep the data files up-to-date
if(UNIX AND NOT APPLE)
set(CIFPP_INSTALL_UPDATE_SCRIPT ON CACHE BOOL "Install the script to update CCD and dictionary files")
endif()
else()
unset(CIFPP_DOWNLOAD_CCD)
unset(CIFPP_INSTALL_UPDATE_SCRIPT)
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")
set(CIFPP_RECREATE_SYMOP_DATA ON CACHE BOOL "Recreate SymOp data table in case it is out of date")
set(CIFPP_RECREATE_SYMOP_DATA ON CACHE BOOL "Recreate SymOp data table in case it is out of date")
endif()
# CCP4 build
if(BUILD_FOR_CCP4)
if("$ENV{CCP4}" STREQUAL "" OR NOT EXISTS $ENV{CCP4})
message(FATAL_ERROR "cifpp: 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("$ENV{CCP4}" STREQUAL "" OR NOT EXISTS $ENV{CCP4})
message(FATAL_ERROR "cifpp: 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()
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()
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()
# We do not want to write an export file for all our symbols...
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# 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)
# 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$<$<CONFIG:Debug>:Debug>DLL")
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
# This is dubious...
if(BUILD_SHARED_LIBS)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
else()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endif()
# Libraries
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()
# 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)
# 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()
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()
foreach(search ${_ZLIB_SEARCHES})
find_library(
ZLIB_LIBRARY
NAMES zlibstatic NAMES_PER_DIR ${${search}}
PATH_SUFFIXES lib)
endforeach()
endif()
set(CMAKE_CXX_STANDARD 20)
# Using fast_float for float parsing, but only if needed
try_compile(STD_CHARCONV_COMPILING
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-charconv.cpp)
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-charconv.cpp
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON)
if(NOT STD_CHARCONV_COMPILING)
message(NOTICE "libcifpp: Using fast_float for std::from_chars")
FetchContent_Declare(fastfloat
GIT_REPOSITORY "https://github.com/fastfloat/fast_float"
GIT_TAG v8.0.2
EXCLUDE_FROM_ALL)
FetchContent_MakeAvailable(fastfloat)
message(NOTICE "libcifpp: Using fast_float for std::from_chars")
find_package(FastFloat 8.0 REQUIRED CONFIG)
endif()
find_package(Threads)
find_package(ZLIB QUIET)
if(NOT ZLIB_FOUND)
message(FATAL_ERROR "cifpp: 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)")
message(FATAL_ERROR "cifpp: 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()
include(FindPkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PCRE2 IMPORTED_TARGET libpcre2-8)
pkg_check_modules(PCRE2 IMPORTED_TARGET libpcre2-8)
endif()
if(NOT PCRE2_FOUND)
add_subdirectory(pcre2-simple)
add_subdirectory(pcre2-simple)
endif()
find_package(glm REQUIRED)
# 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)
get_target_property(EIGEN_INCLUDE_DIR Eigen3::Eigen
INTERFACE_INCLUDE_DIRECTORIES)
else()
# Use ExternalProject since FetchContent always tries to install the result...
ExternalProject_Add(my-eigen3
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
# Use ExternalProject since FetchContent always tries to install the result...
ExternalProject_Add(my-eigen3
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
ExternalProject_Get_Property(my-eigen3 SOURCE_DIR)
set(EIGEN_INCLUDE_DIR ${SOURCE_DIR})
ExternalProject_Get_Property(my-eigen3 SOURCE_DIR)
set(EIGEN_INCLUDE_DIR ${SOURCE_DIR})
endif()
# 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")
# 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)
target_compile_features(symop-map-generator PUBLIC cxx_std_20)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp
COMMAND
$<TARGET_FILE:symop-map-generator> $ENV{CLIBD}/syminfo.lib
$ENV{CLIBD}/symop.lib ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/symop_table_data.hpp
COMMAND
$<TARGET_FILE:symop-map-generator> $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")
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()
# Create a revision file, containing the current git version info
write_version_header("${CMAKE_CURRENT_SOURCE_DIR}/src/" LIB_NAME "LibCIFPP")
# 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)
# Sources
list(APPEND 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
)
list(APPEND project_headers
include/cif++.hpp
include/cif++/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++/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.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
)
if(BUILD_SQLITE_INTERFACE)
find_package(SQLite3 QUIET)
if(SQLite3_FOUND)
target_link_libraries(cifpp PRIVATE SQLite::SQLite3)
else()
FetchContent_Populate(SQLite3
URL https://sqlite.org/2025/sqlite-amalgamation-3510100.zip
URL_HASH SHA3_256=856b52ffe7383d779bb86a0ed1ddc19c41b0e5751fa14ce6312f27534e629b64
EXCLUDE_FROM_ALL)
list(APPEND project_sources $<BUILD_INTERFACE:${sqlite3_SOURCE_DIR}>/sqlite3.c)
target_include_directories(cifpp PRIVATE $<BUILD_INTERFACE:${sqlite3_SOURCE_DIR}>)
endif()
list(APPEND project_sources ${CMAKE_CURRENT_SOURCE_DIR}/src/cql.cpp)
list(APPEND project_headers include/cif++/cql.hpp)
endif()
if(TARGET my-eigen3)
add_dependencies(cifpp my-eigen3)
add_dependencies(cifpp my-eigen3)
endif()
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}
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)
target_compile_features(cifpp PUBLIC cxx_std_23)
generate_export_header(cifpp EXPORT_FILE_NAME
${CMAKE_CURRENT_SOURCE_DIR}/include/cif++/exports.hpp)
${CMAKE_CURRENT_SOURCE_DIR}/include/cif++/exports.hpp)
if(MSVC)
target_compile_definitions(cifpp PUBLIC NOMINMAX=1)
target_compile_definitions(cifpp PUBLIC NOMINMAX=1)
endif()
set_target_properties(cifpp PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(
cifpp
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE "${EIGEN_INCLUDE_DIR}")
cifpp
PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE "${EIGEN_INCLUDE_DIR}")
target_link_libraries(cifpp
PUBLIC Threads::Threads ZLIB::ZLIB $<$<TARGET_EXISTS:std::atomic>:std::atomic>)
PUBLIC Threads::Threads ZLIB::ZLIB $<$<TARGET_EXISTS:std::atomic>:std::atomic>)
if(PCRE2_FOUND)
target_include_directories(cifpp PRIVATE ${PCRE2_INCLUDE_DIRS})
target_link_libraries(cifpp PRIVATE ${PCRE2_LINK_LIBRARIES})
target_include_directories(cifpp PRIVATE ${PCRE2_INCLUDE_DIRS})
target_link_libraries(cifpp PRIVATE ${PCRE2_LINK_LIBRARIES})
else()
target_link_libraries(cifpp PRIVATE $<BUILD_INTERFACE:pcre2s>)
target_link_libraries(cifpp PRIVATE $<BUILD_INTERFACE:pcre2s>)
endif()
if(NOT STD_CHARCONV_COMPILING)
target_link_libraries(cifpp PRIVATE FastFloat::fast_float)
target_include_directories(cifpp PRIVATE ${FastFloat_INCLUDE_DIRS})
target_compile_definitions(cifpp PRIVATE USE_FAST_FLOAT)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
target_link_options(cifpp PRIVATE -undefined dynamic_lookup)
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)
# 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(EXISTS ${COMPONENTS_CIF})
file(SIZE ${COMPONENTS_CIF} CCD_FILE_SIZE)
if(CCD_FILE_SIZE EQUAL 0)
message(STATUS "cifpp: Removing empty ${COMPONENTS_CIF} file")
file(REMOVE "${COMPONENTS_CIF}")
endif()
endif()
if(CCD_FILE_SIZE EQUAL 0)
message(STATUS "cifpp: 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(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()
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_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()
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)
# 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 "cifpp: Error trying to download CCD file: ${CCD_FETCH_STATUS}")
endif()
endif()
if(CCD_FETCH_STATUS_CODE)
message(
FATAL_ERROR "cifpp: 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")
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")
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})
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)
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()
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}")
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")
set(CIFPP_ETC_DIR
"${CMAKE_INSTALL_FULL_SYSCONFDIR}"
CACHE PATH "The directory where the update configuration file is stored")
else()
unset(CIFPP_CACHE_DIR)
unset(CIFPP_CACHE_DIR)
endif()
# Install rules
install(TARGETS cifpp
EXPORT cifpp
FILE_SET cifpp_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
EXPORT cifpp
FILE_SET cifpp_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
if(MSVC AND BUILD_SHARED_LIBS)
install(
FILES $<TARGET_PDB_FILE:cifpp>
DESTINATION ${CMAKE_INSTALL_LIBDIR}
OPTIONAL)
install(
FILES $<TARGET_PDB_FILE:cifpp>
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)
${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cifpp/cifppConfig*.cmake
${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cifpp/cifppTargets*.cmake)
if(OLD_CONFIG_FILES)
message(
STATUS "cifpp: Installation will remove old config files: ${OLD_CONFIG_FILES}")
install(CODE "file(REMOVE ${OLD_CONFIG_FILES})")
message(
STATUS "cifpp: 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)
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)
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)
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)
${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)
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})
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)
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)
"${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)
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)
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 "cifpp: Don't know where to install the update script")
endif()
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 "cifpp: 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
# 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(\"cifpp: A configuration file has been written to ${CIFPP_ETC_DIR}/libcifpp.conf, please edit this file to enable automatic updates\")"
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libcifpp.conf
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR})
install(
CODE "message(\"cifpp: 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()
install(DIRECTORY DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/libcifpp/cache-update.d)
endif()
target_compile_definitions(cifpp PUBLIC CACHE_DIR="${CIFPP_CACHE_DIR}")
target_compile_definitions(cifpp PUBLIC CACHE_DIR="${CIFPP_CACHE_DIR}")
endif()
if(BUILD_DOCUMENTATION)
add_subdirectory(docs)
add_subdirectory(docs)
endif()
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

View File

@@ -38,9 +38,7 @@ The documentation can be found at [github.io](https://pdb-redo.github.io/libcifp
#include <filesystem>
#include <iostream>
#include <cif++.hpp>
namespace fs = std::filesystem;
#include <cif++/cif++.hpp>
int main(int argc, char *argv[])
{

View File

@@ -1,3 +1,24 @@
Version 10.0.2
- Fixed regression in reconstruction introduced in 10.0.1
Version 10.0.1
- Fixed some regressions, like assigning to items.
- At emplace time (in category) values that are
in the wrong type according to a dictionary
will be converted/casted. May fail of course.
Version 10.0.0
- Major rewrite of the internal storage of values.
Used to be strings only, now there are several
basic types.
- Modernised code and cleaned up warnings using lint
tools
Version 9.0.6
- Various small fixes
Version 10.0.0
- Added a SQLite interface.
Version 9.0.5
- Added exists to compound_factory
- Added sub_matrix, fix and extend determinant calculation

View File

@@ -1,3 +1,27 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 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.
# Simple check to see if we need a library for std::atomic
if(TARGET std::atomic)

View File

@@ -1,3 +1,27 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 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.
#
# The problem is, find_package(PCRE2) does not work
# and using pkg-config results in linking to a shared library
# causing all kinds of trouble later on

View File

@@ -1,4 +1,29 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 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.
#
#Look for an executable called sphinx-build
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
DOC "Path to sphinx-build executable")

View File

@@ -8,5 +8,6 @@ include(CMakeFindDependencyMacro)
find_dependency(Threads)
find_dependency(ZLIB REQUIRED)
find_dependency(SQLite3 REQUIRED)
check_required_components(cifpp)

View File

@@ -36,7 +36,8 @@ class version_info_base
}
protected:
version_info_base(const char *name, const char *version, int build_number, const char *git_tag, const char *revision_date, bool is_main)
version_info_base(const char *name, const char *version, int build_number,
const char *git_tag, const char *revision_date, bool is_main) noexcept
: m_name(name)
, m_version(version)
, m_build_number(build_number)
@@ -70,13 +71,13 @@ class version_info_base
using version_info_ptr = version_info_base *;
static version_info_ptr &registered_main()
static version_info_ptr &registered_main() noexcept
{
static version_info_ptr s_main = nullptr;
return s_main;
}
static version_info_ptr &registered_libraries()
static version_info_ptr &registered_libraries() noexcept
{
static version_info_ptr s_head = nullptr;
return s_head;
@@ -96,7 +97,9 @@ 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)
// NOLINTNEXTLINE
version_info(const char *name, const char *version, int build_number,
const char *git_tag, const char *revision_date, bool is_main) noexcept
: version_info_base(name, version, build_number, git_tag, revision_date, is_main)
{
}
@@ -114,8 +117,9 @@ inline void write_version_string(std::ostream &os, bool verbose)
const class version_info_@IDENT_PREFIX@impl : public version_info_v1_1::version_info<version_info_@IDENT_PREFIX@impl>
{
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@)
version_info_@IDENT_PREFIX@impl() noexcept
: 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;

View File

@@ -1,3 +1,29 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 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.
*/
#include <charconv>
#include <cassert>
#include <cstring>

View File

@@ -1,3 +1,27 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 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.
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
@@ -16,33 +40,35 @@ set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
add_custom_command(
OUTPUT ${DOXYGEN_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIR})
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})
DEPENDS ${CIFPP_PUBLIC_HEADERS} ${DOXYGEN_OUTPUT_DIR} ${DOXYFILE_OUT}
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}
MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}
COMMENT "Generating docs")
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")
add_custom_target("Sphinx-cifpp" ALL
COMMAND ${SPHINX_EXECUTABLE} -b html
-Dbreathe_projects.cifpp=${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)
set_property(TARGET Sphinx-cifpp APPEND PROPERTY ADDITIONAL_CLEAN_FILES
${DOXYGEN_OUTPUT_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/api)
install(
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sphinx/
DESTINATION ${CMAKE_INSTALL_DOCDIR}
PATTERN .doctrees EXCLUDE
PATTERN .buildinfo EXCLUDE)

View File

@@ -1,10 +1,67 @@
EXCLUDE_SYMBOLS = cif::detail::*, std*
# EXCLUDE_SYMBOLS = cif::detail::*, std*
FILE_PATTERNS = *.hpp
STRIP_FROM_PATH = @DOXYGEN_INPUT_DIR@
RECURSIVE = YES
GENERATE_XML = YES
GENERATE_LATEX = NO
MACRO_EXPANSION = YES
PREDEFINED += and=&& or=|| not=! CIFPP_EXPORT= HAVE_LIBCLIPPER=1
GENERATE_HTML = NO
GENERATE_TODOLIST = NO
INPUT = @DOXYGEN_INPUT_DIR@
# # Doxyfile
# PROJECT_NAME = "libcifpp"
# PROJECT_BRIEF = "C++ library for using mmCIF files"
# GENERATE_LATEX = NO
# EXTRACT_ALL = YES
# EXTRACT_STATIC = YES
# HIDE_UNDOC_CLASSES = NO
# SHOW_NAMESPACES = YES
# EXTRACT_TEMPLATE_PARAMS = YES
# CREATE_SUBDIRS = NO
# INLINE_GROPUED_CLASSES = YES
# INLINE_SIMPLE_STRUCTS = YES
# NUM_PROC_THREADS = 0
# EXTRACT_LOCAL_CLASSES = NO
# HIDE_IN_BODY_DOCS = YES
# SHOW_HEADERFILE = NO
# INPUT = @DOXYGEN_INPUT_DIR@/c++
# RECURSIVE = YES
# EXAMPLE_PATH = @DOXYGEN_INPUT_DIR@/../examples
# EXAMPLE_PATTERNS = *.cpp
# # USE_MDFILE_AS_MAINPAGE = ../README.md
# ENABLE_PREPROCESSING = YES
# MACRO_EXPANSION = YES
# INCLUDE_PATH = @DOXYGEN_INPUT_DIR@
# INCLUDE_FILE_PATTERNS = *.hpp
# SKIP_FUNCTION_MACROS = NO
# # CLANG_ASSISTED_PARSING = YES
# # CLANG_ADD_INC_PATHS = YES
# # CLANG_OPTIONS = -O0 -std=c++23
# # GENERATE_HTML = YES
# # GENERATE_TREEVIEW = YES
# GENERATE_XML = YES
# # EXCLUDE_SYMBOLS = cif::detail::*, std*
# # # FILE_PATTERNS = *.hpp
# # STRIP_FROM_PATH = @DOXYGEN_INPUT_DIR@
# # PREDEFINED += and=&& or=|| not=! CIFPP_EXPORT= HAVE_LIBCLIPPER=1
# # GENERATE_HTML = NO
# # GENERATE_TODOLIST = NO

View File

@@ -7,7 +7,7 @@ Reading a file is as simple as:
.. code-block:: cpp
#include <cif++.hpp>
#include <cif++/cif++.hpp>
cif::file f("/path/to/file.cif");
@@ -76,7 +76,7 @@ Categories contain rows of data and each row has fields or items. Referencing a
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<std::string>();
std::string atom_id = rh["label_atom_id"].get<std::string>();
// Get the x, y and z coordinates using structered binding
const auto &[x, y, z] = rh.get<float,float,float>("Cartn_x", "Cartn_y", "Cartn_z");
@@ -214,7 +214,7 @@ A simple case:
.. code-block:: cpp
#include <cif++.hpp>
#include <cif++/cif++.hpp>
cif::file f("1cbs.cif.gz");
f.load_dictionary("mmcif_pdbx.dic");

View File

@@ -1,5 +1,7 @@
# Configuration file
project = '@PROJECT_NAME@'
copyright = '2023, Maarten L. Hekkelman'
copyright = '2026, Maarten L. Hekkelman'
author = 'Maarten L. Hekkelman'
release = '@PROJECT_VERSION@'
@@ -12,7 +14,7 @@ extensions = [
]
breathe_projects = {
"@PROJECT_NAME@": "../build/docs/xml"
"@PROJECT_NAME@": "@DOXYGEN_OUTPUT_DIR@"
}
myst_enable_extensions = [ "colon_fence" ]
@@ -60,7 +62,7 @@ html_static_path = ['_static']
html_theme_options = {
}
cpp_index_common_prefix = [
'cif::'
]
# cpp_index_common_prefix = [
# 'cif::'
# ]

33
docs/cql.rst Normal file
View File

@@ -0,0 +1,33 @@
CQL
===
The structure of cif files (even of STAR files, from which this format is derived) looks suspiciously like a relational database. When you consider categories to be tables and items to be columns you're almost there. The only problem is linking tables, in common cif files this is done based on multiple columns and the rules are a bit fuzzy allowing for empty columns to still match related columns that do have a value.
An early version of the tool *mmCQL* contained a SQL like language interpreter to SELECT and UPDATE values in cif files. This functionality has been expanded by implementing a full SQL interface using the `SQLite <https://sqlite.org>`_ library. Libcifpp categories are exposed as virtual tables in a SQLite environment and can be queried and manipulated using SQL syntax.
The current limitation is that CREATE TABLE and ALTER TABLE are not supported yet. Since SQLite has no way of supporting this, we will have to write a preprocessor to intercept these statements. That's on the to-do list.
The new *mmcql* tools in `cif-tools <https://github.com/PDB-REDO/cif-tools>`_ uses this new backend and is a command line application you can use similar to the *sqlite* or e.g. *psql* tools for regular SQLite files and postgresql databanks respectively.
Synopsis
--------
.. literalinclude:: ../examples/example-cql.cpp
:language: c++
:start-at: #include <cif++/cif++.hpp>
Usage
-----
To start using CQL, you will first have to create a :cpp:class:`connection` to a :cpp:class:`cif::cql::datablock`. Using this connection you can create a :cpp:class:`transaction`. And with the transaction can execute SQL statements using :cpp:func:`cif::cql::transaction::exec`. Or you can use the :cpp:func:`cif::cql::transaction::stream` function to directly pull values from the result.
The result of :cpp:func:`cif::cql::transaction::exec` is a :cpp:class:`cif::cql::result` class which uses a :cpp:class:`cif::category` as storage class.
Implementation Details
----------------------
When the datablock contains a validator (i.e. you loaded a dictionary) the SQL engine knows about all possible items/columns that are allowed. It also knows about links/relations between categories, just like the regular libcifpp query mechanism. So, updating and deleting will cascade automatically.
Another point is data types. cif files can have numbers, strings or NULL values. Same goes for SQLite. However, when a file was loaded without a dictionary, the type of an item is dependent on its content. If something was parsed as being a number, the type will be numeric. If however, the file does contain a dictionary/validator, the type is determined by this dictionary. So, even if it looks like a number, it still might be a string internally. Good example is the ID field in atom_site, or the auth_seq_id/auth_seq_num fields. In the WHERE clause this may have unexpected results, so you may have to fall back to using `CAST <https://sqlite.org/lang_expr.html#castexpr>`_.
The API for this functionality is a bit new, there may be room for improvement. Ideas are welcome.

View File

@@ -38,6 +38,7 @@ Using *libcifpp* is easy, if you are familiar with modern C++:
basics.rst
compound.rst
model.rst
cql.rst
resources.rst
symmetry.rst
bitsandpieces.rst

View File

@@ -8,7 +8,7 @@ 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.
Many functions are available in :ref:`file_cif++_point.hpp` that work on points. There are functions to calculate the :cpp:func:`glm::distance` between two points and also function to calculate dot products, cross products and dihedral angles between sets of points.
Quaternions
-----------
@@ -91,7 +91,7 @@ To give an idea how this works, here's a piece of code copied from one of the un
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);
assert(glm::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

Binary file not shown.

View File

@@ -1,7 +1,36 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 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.15)
project(cifpp_example LANGUAGES CXX)
find_package(cifpp REQUIRED)
if(PROJECT_IS_TOP_LEVEL AND NOT (TARGET cifpp OR cifpp_FOUND))
find_package(cifpp REQUIRED)
endif()
add_executable(example example.cpp)
target_link_libraries(example cifpp::cifpp)
target_link_libraries(example cifpp::cifpp)
add_executable(example-cql example-cql.cpp)
target_link_libraries(example-cql cifpp::cifpp)

View File

@@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute
* Copyright (c) 2026 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:
@@ -23,10 +23,42 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cif++/cif++.hpp>
#include <filesystem>
#include <iostream>
#pragma once
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: example <inputfile>\n";
exit(1);
}
/// \file io.hpp
/// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead
cif::file file(argv[1]);
#warning "Use of this file is deprecated, please use "cif++/pdb.hpp"
if (file.empty())
{
std::cerr << "Empty file\n";
exit(1);
}
auto &db = file.front();
cif::cql::connection c(db);
cif::cql::transaction tx(c);
auto N = tx.exec("SELECT COUNT(*) FROM atom_site").one_field().get<std::size_t>();
auto M = tx.exec("SELECT COUNT(*) FROM atom_site WHERE label_atom_id = 'OXT'").one_field().get<std::size_t>();
std::cout << "File contains " << N << " atoms of which " << M << (M == 1 ? " is" : " are") << " OXT\n"
<< "residues with an OXT are:\n";
for (const auto &[asym, comp, seqnr] : tx.stream<std::string, std::string, int>(
"SELECT label_asym_id, label_comp_id, label_seq_id FROM atom_site WHERE label_atom_id = 'OXT'"))
{
std::cout << asym << ' ' << comp << ' ' << seqnr << '\n';
}
return 0;
}

View File

@@ -1,9 +1,33 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 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.
*/
#include <filesystem>
#include <iostream>
#include <cif++.hpp>
namespace fs = std::filesystem;
#include <cif++/cif++.hpp>
int main(int argc, char *argv[])
{

View File

@@ -26,16 +26,8 @@
#pragma once
#include "cif++/utilities.hpp"
#include "cif++/file.hpp"
#include "cif++/parser.hpp"
#include "cif++/format.hpp"
#warning "Using this file is deprecated, use #include <cif++/cif++.hpp> instead"
#include "cif++/compound.hpp"
#include "cif++/point.hpp"
#include "cif++/symmetry.hpp"
#include "cif++/model.hpp"
#include "cif++/pdb.hpp"
#include "cif++/gzio.hpp"
// IWYU pragma: begin_exports
#include "cif++/cif++.hpp"
// IWYU pragma: end_exports

View File

@@ -33,6 +33,7 @@
#include "cif++/exports.hpp"
#include <array>
#include <cstdint>
#include <limits>
#include <stdexcept>
@@ -235,7 +236,7 @@ struct atom_type_info
/// Array containing all known radii for this element. A value of kNA is
/// stored for unknown values
float radii[kRadiusTypeCount];
std::array<float, kRadiusTypeCount> radii;
};
/// Array of atom_type_info struct for each of the defined elements in atom_type
@@ -256,12 +257,12 @@ class atom_type_traits
/// 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
[[nodiscard]] atom_type type() const { return m_info->type; } ///< Returns the atom_type
[[nodiscard]] std::string name() const { return m_info->name; } ///< Returns the name of the element
[[nodiscard]] std::string symbol() const { return m_info->symbol; } ///< Returns the symbol of the element
[[nodiscard]] 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
[[nodiscard]] 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);
@@ -272,7 +273,7 @@ class atom_type_traits
/// @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
[[nodiscard]] float radius(radius_type type = radius_type::single_bond) const
{
if (type >= radius_type::type_count)
throw std::invalid_argument("invalid radius requested");
@@ -283,20 +284,20 @@ class atom_type_traits
///
/// \param charge The charge of the ion
/// \return The radius of the ion
float crystal_ionic_radius(int charge) const;
[[nodiscard]] 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;
[[nodiscard]] 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
[[nodiscard]] 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);
}
@@ -321,16 +322,16 @@ class atom_type_traits
///
/// @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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] bool has_sf(int charge) const;
private:
const struct atom_type_info *m_info;

View File

@@ -26,16 +26,31 @@
#pragma once
#include "cif++/forward_decl.hpp"
// #include "cif++/condition.hpp"
#include "cif++/condition.hpp"
#include "cif++/item.hpp"
#include "cif++/iterator.hpp"
#include "cif++/row.hpp"
#include "cif++/text.hpp"
// #include <array>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iosfwd>
#include <iterator>
#include <limits>
#include <memory>
#include <optional>
#include <ranges>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
/** \file category.hpp
* Documentation for the cif::category class
@@ -52,6 +67,7 @@
namespace cif
{
class datablock;
class validator;
struct category_validator;
struct item_validator;
@@ -84,13 +100,14 @@ class missing_key_error : public std::runtime_error
/**
* @brief Construct a new duplicate key error object
*/
missing_key_error(const std::string &msg, const std::string &key)
missing_key_error(const std::string &msg, std::string key)
: std::runtime_error(msg)
, m_key(key)
, m_key(std::move(key))
{
}
const std::string &get_key() const noexcept { return m_key; }
/// Return the name of the key that was missing
[[nodiscard]] const std::string &get_key() const noexcept { return m_key; }
private:
std::string m_key;
@@ -104,7 +121,7 @@ class multiple_results_error : public std::runtime_error
/**
* @brief Construct a new multiple results error object
*/
multiple_results_error()
multiple_results_error() // NOLINT
: std::runtime_error("query should have returned exactly one row")
{
}
@@ -126,14 +143,17 @@ class category
friend class row_handle;
template <typename, typename...>
friend class iterator_impl;
template <bool, typename...>
friend class iterator_impl_base;
using value_type = row_handle;
using reference = value_type;
using const_reference = const value_type;
using iterator = iterator_impl<category>;
using const_iterator = iterator_impl<const category>;
using reference = row_handle;
using const_reference = const_row_handle;
using iterator = iterator_impl<>;
using const_iterator = const_iterator_impl<>;
static_assert(std::input_iterator<iterator>);
static_assert(std::input_iterator<const_iterator>);
/// \endcond
@@ -156,7 +176,8 @@ class category
swap(*this, rhs);
}
category &operator=(category rhs) ///< assignement operator
/// Assignment operator
category &operator=(category rhs)
{
swap(*this, rhs);
return *this;
@@ -167,15 +188,41 @@ class category
/// you will not derive from this class.
~category();
/// Swap two categories
friend void swap(category &a, category &b) noexcept;
// --------------------------------------------------------------------
const std::string &name() const { return m_name; } ///< Returns the name of the category
[[nodiscard]] const std::string &name() const { return m_name; } ///< Returns the name of the category
iset key_items() const; ///< Returns the cif::iset of key item names. Retrieved from the @ref category_validator for this category
/// \brief Rename category to @a new_name
void name(std::string_view new_name)
{
m_name = new_name;
m_dirty = true;
}
std::set<uint16_t> key_item_indices() const; ///< Returns a set of indices for the key items.
/// \brief Return true if the category has been modified since last open/save
[[nodiscard]] constexpr bool is_dirty() const
{
return m_dirty;
}
/// \brief Mark the category as modified according to @a dirty
void set_dirty(bool dirty)
{
m_dirty = dirty;
}
// --------------------------------------------------------------------
[[deprecated("use key_items instead")]] [[nodiscard]] iset key_fields() const; ///< Returns the cif::iset of key item names. Retrieved from the @ref category_validator for this category
[[nodiscard]] 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")]] [[nodiscard]] std::set<uint16_t> key_field_indices() const; ///< Returns a set of indices for the key items.
[[nodiscard]] std::set<uint16_t> 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.
@@ -188,15 +235,15 @@ class category
/// @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; }
[[nodiscard]] 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; }
[[nodiscard]] 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;
[[nodiscard]] bool is_valid() const;
/// @brief Validate links, that means, values in this category should have an
/// accompanying value in parent categories.
@@ -209,7 +256,7 @@ class category
/// parent in those categories.
///
/// @return Returns true is all validations pass
bool validate_links() const;
[[nodiscard]] bool validate_links() const;
/**
* @brief Strip removes items from this category that are invalid according to the assigned validator
@@ -236,15 +283,17 @@ class category
/// the category is empty.
reference front()
{
assert(size() > 0);
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
[[nodiscard]] const_reference front() const
{
return { const_cast<category &>(*this), const_cast<row &>(*m_head) };
assert(size() > 0);
return { *this, *m_head };
}
/// @brief Return a reference to the last row in this category.
@@ -252,15 +301,17 @@ class category
/// the category is empty.
reference back()
{
assert(size() > 0);
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
[[nodiscard]] const_reference back() const
{
return { const_cast<category &>(*this), const_cast<row &>(*m_tail) };
assert(size() > 0);
return { *this, *m_tail };
}
/// Return an iterator to the first row
@@ -276,43 +327,43 @@ class category
}
/// Return a const iterator to the first row
const_iterator begin() const
[[nodiscard]] const_iterator begin() const
{
return { *this, m_head };
}
/// Return a const iterator pointing past the last row
const_iterator end() const
[[nodiscard]] const_iterator end() const
{
return { *this, nullptr };
}
/// Return a const iterator to the first row
const_iterator cbegin() const
[[nodiscard]] const_iterator cbegin() const
{
return { *this, m_head };
}
/// Return an iterator pointing past the last row
const_iterator cend() const
[[nodiscard]] const_iterator cend() const
{
return { *this, nullptr };
}
/// Return a count of the rows in this container
std::size_t size() const
[[nodiscard]] 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
[[nodiscard]] std::size_t max_size() const
{
return std::numeric_limits<std::size_t>::max(); // this is a bit optimistic, I guess
}
/// Return true if the category is empty
bool empty() const
[[nodiscard]] bool empty() const
{
return m_head == nullptr;
}
@@ -336,13 +387,10 @@ class category
/// @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
/// @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<category *>(this)->operator[](key);
}
const_row_handle operator[](const key_type &key) const;
// --------------------------------------------------------------------
@@ -358,10 +406,10 @@ class category
/// @param names The names for the items requested
template <typename... Ts, typename... Ns>
iterator_proxy<const category, Ts...> rows(Ns... names) const
[[nodiscard]] const_iterator_proxy<Ts...> 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<const category, Ts...>(*this, begin(), { names... });
return const_iterator_proxy<Ts...>(*this, begin(), { names... });
}
/// @brief Return a special iterator for all rows in this category.
@@ -381,10 +429,10 @@ class category
/// @param names The names for the items requested
template <typename... Ts, typename... Ns>
iterator_proxy<category, Ts...> rows(Ns... names)
iterator_proxy<Ts...> 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<category, Ts...>(*this, begin(), { names... });
return iterator_proxy<Ts...>(*this, begin(), { names... });
}
// --------------------------------------------------------------------
@@ -400,7 +448,7 @@ class category
/// @return A special iterator that loops over all elements that match. The iterator can be dereferenced
/// to a @ref row_handle
conditional_iterator_proxy<category> find(condition &&cond)
conditional_iterator_proxy<> find(condition &&cond)
{
return find(begin(), std::move(cond));
}
@@ -413,7 +461,7 @@ class category
/// @return A special iterator that loops over all elements that match. The iterator can be dereferenced
/// to a @ref row_handle
conditional_iterator_proxy<category> find(iterator pos, condition &&cond)
conditional_iterator_proxy<> find(iterator pos, condition &&cond)
{
return { *this, pos, std::move(cond) };
}
@@ -424,7 +472,7 @@ class category
/// @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<const category> find(condition &&cond) const
const_conditional_iterator_proxy<> find(condition &&cond) const
{
return find(cbegin(), std::move(cond));
}
@@ -437,9 +485,9 @@ class category
/// @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<const category> find(const_iterator pos, condition &&cond) const
const_conditional_iterator_proxy<> find(const_iterator pos, condition &&cond) const
{
return conditional_iterator_proxy<const category>{ *this, pos, std::move(cond) };
return const_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
@@ -456,10 +504,10 @@ class category
/// @return A special iterator that loops over all elements that match.
template <typename... Ts, typename... Ns>
conditional_iterator_proxy<category, Ts...> find(condition &&cond, Ns... names)
conditional_iterator_proxy<Ts...> 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<Ts...>(cbegin(), std::move(cond), std::forward<Ns>(names)...);
return find<Ts...>(begin(), std::move(cond), std::forward<Ns>(names)...);
}
/// @brief Return a special const iterator to loop over all rows that conform to @a cond. The resulting
@@ -471,7 +519,7 @@ class category
/// @return A special iterator that loops over all elements that match.
template <typename... Ts, typename... Ns>
conditional_iterator_proxy<const category, Ts...> find(condition &&cond, Ns... names) const
const_conditional_iterator_proxy<Ts...> 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<Ts...>(cbegin(), std::move(cond), std::forward<Ns>(names)...);
@@ -487,7 +535,7 @@ class category
/// @return A special iterator that loops over all elements that match.
template <typename... Ts, typename... Ns>
conditional_iterator_proxy<category, Ts...> find(const_iterator pos, condition &&cond, Ns... names)
conditional_iterator_proxy<Ts...> find(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<Ns>(names)... };
@@ -503,7 +551,7 @@ class category
/// @return A special iterator that loops over all elements that match.
template <typename... Ts, typename... Ns>
conditional_iterator_proxy<const category, Ts...> find(const_iterator pos, condition &&cond, Ns... names) const
const_conditional_iterator_proxy<Ts...> 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<Ns>(names)... };
@@ -540,7 +588,7 @@ class category
/// 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
const_row_handle find1(condition &&cond) const
{
return find1(cbegin(), std::move(cond));
}
@@ -550,7 +598,7 @@ class category
/// @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
const_row_handle find1(const_iterator pos, condition &&cond) const
{
auto h = find(pos, std::move(cond));
@@ -568,10 +616,30 @@ class category
/// @return The value found
template <typename T>
T find1(condition &&cond, std::string_view item) const
requires(not is_optional_v<T>)
{
return find1<T>(cbegin(), std::move(cond), item);
}
/// @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 <typename T>
T find1(condition &&cond, std::string_view item) const
requires(is_optional_v<T>)
{
auto h = find<typename T::value_type>(cbegin(), std::move(cond), item);
if (h.size() == 1)
return *h.begin();
return T{};
}
/// @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
@@ -580,8 +648,9 @@ class category
/// @param cond The condition to search for
/// @param item The name of the item to return the value for
/// @return The value found
template <typename T, std::enable_if_t<not is_optional_v<T>, int> = 0>
template <typename T>
T find1(const_iterator pos, condition &&cond, std::string_view item) const
requires(not is_optional_v<T>)
{
auto h = find<T>(pos, std::move(cond), item);
@@ -599,8 +668,9 @@ class category
/// @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 <typename T, std::enable_if_t<is_optional_v<T>, int> = 0>
template <typename T>
T find1(const_iterator pos, condition &&cond, std::string_view item) const
requires(is_optional_v<T>)
{
auto h = find<typename T::value_type>(pos, std::move(cond), item);
@@ -608,7 +678,7 @@ class category
throw multiple_results_error();
if (h.empty())
return {};
return std::nullopt;
return *h.begin();
}
@@ -673,7 +743,7 @@ class category
/// @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
const_row_handle find_first(condition &&cond) const
{
return find_first(cbegin(), std::move(cond));
}
@@ -682,11 +752,11 @@ class category
/// @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
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();
return h.empty() ? const_row_handle{} : *h.begin();
}
/// @brief Return the value for item @a item for the first row that matches condition @a cond
@@ -751,8 +821,9 @@ class category
/// @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 <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
template <typename T>
T find_max(std::string_view item, condition &&cond) const
requires(std::is_arithmetic_v<T>)
{
T result = std::numeric_limits<T>::min();
@@ -769,8 +840,9 @@ class category
/// @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 <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
T find_max(std::string_view item) const
template <typename T>
[[nodiscard]] T find_max(std::string_view item) const
requires(std::is_arithmetic_v<T>)
{
return find_max<T>(item, all());
}
@@ -780,8 +852,9 @@ class category
/// @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 <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
T find_min(std::string_view item, condition &&cond) const
template <typename T>
[[nodiscard]] T find_min(std::string_view item, condition &&cond) const
requires(std::is_arithmetic_v<T>)
{
T result = std::numeric_limits<T>::max();
@@ -798,8 +871,9 @@ class category
/// @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 <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
T find_min(std::string_view item) const
template <typename T>
[[nodiscard]] T find_min(std::string_view item) const
requires(std::is_arithmetic_v<T>)
{
return find_min<T>(item, all());
}
@@ -819,10 +893,8 @@ class category
{
bool result = false;
if (cond)
if (cond and cond.prepare(*this))
{
cond.prepare(*this);
auto sh = cond.single();
if (sh.has_value() and *sh)
@@ -850,10 +922,8 @@ class category
{
std::size_t result = 0;
if (cond)
if (cond and cond.prepare(*this))
{
cond.prepare(*this);
auto sh = cond.single();
if (sh.has_value() and *sh)
@@ -875,36 +945,38 @@ class category
/// 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;
[[nodiscard]] bool has_children(const_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;
[[nodiscard]] bool has_parents(const_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<row_handle> get_children(row_handle r, const category &childCat) const;
[[nodiscard]] std::vector<const_row_handle> get_children(const_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<row_handle> get_parents(row_handle r, const category &parentCat) const;
[[nodiscard]] std::vector<const_row_handle> get_parents(const_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<row_handle> get_linked(row_handle r, const category &cat) const;
[[nodiscard]] std::vector<const_row_handle> get_linked(const_row_handle r, const category &cat) 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
[[nodiscard]] std::vector<row_handle> get_children(row_handle r, category &childCat);
/// Using the relations defined in the validator, return the row handles
/// for all rows in @a parentCat that are linked to row @a r
[[nodiscard]] std::vector<row_handle> get_parents(row_handle r, category &parentCat);
/// 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
[[nodiscard]] std::vector<row_handle> get_linked(row_handle r, category &cat);
// --------------------------------------------------------------------
// 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);
@@ -936,8 +1008,8 @@ class category
}
/// @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
/// @param b Iterator to the beginning of the range of item_values
/// @param e Iterator to the end of the range of item_values
/// @return iterator to the newly created row
template <typename ItemIter>
iterator emplace(ItemIter b, ItemIter e)
@@ -949,7 +1021,7 @@ class category
for (auto i = b; i != e; ++i)
{
// item_value *new_item = this->create_item(*i);
r->append(add_item(i->name()), i->value());
r->set(add_item(i->name()), i->value());
}
}
catch (...)
@@ -962,6 +1034,7 @@ class category
return insert_impl(cend(), r);
}
/// Create rows with the content of the data in [ @a b, to @a e)
void emplace(const_iterator b, const_iterator e)
{
while (b != e)
@@ -993,6 +1066,8 @@ class category
// --------------------------------------------------------------------
/// The type for a function that provides a value to insert based on a
/// value that is the default or the previous value
using value_provider_type = std::function<item_value(const item_value &)>;
/// \brief Update a single item named @a item_name in the rows that match
@@ -1006,6 +1081,7 @@ class category
{
auto rs = find(std::move(cond));
std::vector<row_handle> rows;
// NOLINTNEXTLINE
std::copy(rs.begin(), rs.end(), std::back_inserter(rows));
update_value(rows, item_name, std::move(value_provider));
}
@@ -1024,10 +1100,11 @@ class category
/// 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, item_value value)
void update_value(condition &&cond, std::string_view item_name, const item_value &value)
{
auto rs = find(std::move(cond));
std::vector<row_handle> rows;
// NOLINTNEXTLINE
std::copy(rs.begin(), rs.end(), std::back_inserter(rows));
update_value(rows, item_name, value);
}
@@ -1037,21 +1114,21 @@ class category
/// 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<row_handle> &rows, std::string_view item_name, item_value value)
void update_value(const std::vector<row_handle> &rows, std::string_view item_name, const item_value &value)
{
update_value(rows, item_name, [value](const item_value &v)
update_value(rows, item_name, [value](const item_value &)
{ return value; });
}
// --------------------------------------------------------------------
/// \brief Return the index number for \a item_name
uint16_t get_item_ix(std::string_view item_name) const;
[[nodiscard]] uint16_t get_item_ix(std::string_view item_name) const;
/// @brief Return the name for item with index @a ix
/// @param ix The index number
/// @return The name of the item
const std::string &get_item_name(uint16_t ix) const
[[nodiscard]] const std::string &get_item_name(uint16_t ix) const
{
if (ix >= m_items.size())
throw std::out_of_range("item index is out of range");
@@ -1069,19 +1146,28 @@ class category
*/
void remove_item(std::string_view item_name);
/// \brief Drop items in this category that contain empty values in all rows.
void drop_empty_items();
/** @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
[[nodiscard]] 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 Return the items in this category
[[nodiscard]] std::vector<std::string> get_items() const;
/// @brief Return the number of items (colums) in this category
[[nodiscard]] constexpr uint16_t get_item_count() const noexcept
{
return m_items.size();
}
// --------------------------------------------------------------------
@@ -1096,31 +1182,55 @@ class category
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<std::string> 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<std::string> get_item_order() const;
[[nodiscard]] std::vector<std::string> get_item_order() const;
/// Write the contents of the category to the std::ostream @a os
void write(std::ostream &os) const;
/// \brief Various supported output formats
enum class output_format
{
cif, // Output in mmCIF format
csv, // comma separated values
tsv, // tab separated values
list, // values delimited by a '|' character
column, // output in columns
markdown, //
table, // ascii art table
box, // table with unicode line characters
};
/// @brief
/// @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. Use this version
/// to write out
/// @param os The std::ostream to write to
/// @param fmt The format to use
/// @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, output_format fmt,
const std::vector<std::string> &order, bool addMissingItems = true);
/// @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<std::string> &order, bool addMissingItems = true);
void write(std::ostream &os, const std::vector<std::string> &order, bool addMissingItems = true)
{
write(os, output_format::cif, order, addMissingItems);
}
private:
void write(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_cif(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_delimited(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems,
std::string_view delimiter, bool aligned, bool header) const;
void write_markdown(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_table(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems, bool ascii) const;
public:
/// friend function to make it possible to do:
@@ -1140,7 +1250,7 @@ class category
using allocator_type = std::allocator<void>;
constexpr allocator_type get_allocator() const
[[nodiscard]] constexpr allocator_type get_allocator() const
{
return {};
}
@@ -1169,6 +1279,8 @@ class category
void delete_row(row *r);
iterator erase(iterator pos, bool cascade);
row_handle create_copy(row_handle r);
struct item_entry
@@ -1191,7 +1303,7 @@ class category
{
}
// TODO: NEED TO FIX THIS!
// TODO: NEED TO FIX THIS! (but what was it that needs to be fixed?)
category *linked;
const link_validator *v;
};
@@ -1202,8 +1314,8 @@ class category
// --------------------------------------------------------------------
condition get_parents_condition(row_handle rh, const category &parentCat) const;
condition get_children_condition(row_handle rh, const category &childCat) const;
[[nodiscard]] condition get_parents_condition(const_row_handle rh, const category &parentCat) const;
[[nodiscard]] condition get_children_condition(const_row_handle rh, const category &childCat) const;
// --------------------------------------------------------------------
@@ -1216,10 +1328,13 @@ class category
const validator *m_validator = nullptr;
const category_validator *m_cat_validator = nullptr;
std::vector<link> 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;
bool m_dirty = false; // Keep track of modifications
};
static_assert(std::ranges::input_range<category>);
} // namespace cif

View File

@@ -26,7 +26,27 @@
#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"
// IWYU pragma: begin_exports
#include "cif++/atom_type.hpp"
#include "cif++/category.hpp"
#include "cif++/compound.hpp"
#include "cif++/condition.hpp"
#include "cif++/cql.hpp"
#include "cif++/datablock.hpp"
#include "cif++/dictionary_parser.hpp"
#include "cif++/exports.hpp"
#include "cif++/file.hpp"
#include "cif++/format.hpp"
#include "cif++/gzio.hpp"
#include "cif++/item.hpp"
#include "cif++/iterator.hpp"
#include "cif++/model.hpp"
#include "cif++/parser.hpp"
#include "cif++/pdb.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"
// IWYU pragma: end_exports

View File

@@ -26,15 +26,16 @@
#pragma once
#include "cif++/atom_type.hpp"
#include "cif++/datablock.hpp"
#include "cif++/exports.hpp"
#include "cif++/point.hpp"
#include "cif++/utilities.hpp"
#include <cstdint>
#include <filesystem>
#include <map>
#include <set>
#include <tuple>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
/// \file compound.hpp
@@ -53,9 +54,10 @@ namespace cif
// --------------------------------------------------------------------
class compound;
struct compound_atom;
class compound_factory_impl;
class datablock;
class file;
enum atom_type : uint8_t;
/// \brief The bond type or bond order as defined in the CCD, possible values taken from the mmcif_pdbx file
enum class bond_type
@@ -116,7 +118,7 @@ struct compound_atom
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
[[nodiscard]] point get_location() const
{
return { x, y, z };
}
@@ -146,34 +148,37 @@ 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.
[[nodiscard]] std::string id() const { return m_id; } ///< Return the alphanumeric code for the chemical component.
[[nodiscard]] std::string name() const { return m_name; } ///< Return the name of the chemical component.
[[nodiscard]] std::string type() const { return m_type; } ///< Return the type of monomer.
[[nodiscard]] std::string formula() const { return m_formula; } ///< Return the chemical formula of the chemical component.
[[nodiscard]] float formula_weight() const { return m_formula_weight; } ///< Return the formula mass of the chemical component in Daltons.
[[nodiscard]] int formal_charge() const { return m_formal_charge; } ///< Return the formal charge on the chemical component.
const std::vector<compound_atom> &atoms() const { return m_atoms; } ///< Return the list of atoms for this compound
const std::vector<compound_bond> &bonds() const { return m_bonds; } ///< Return the list of bonds for this compound
[[nodiscard]] const std::vector<compound_atom> &atoms() const { return m_atoms; } ///< Return the list of atoms for this compound
[[nodiscard]] const std::vector<compound_bond> &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
[[nodiscard]] 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
[[nodiscard]] 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
[[nodiscard]] 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
[[nodiscard]] 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;
[[nodiscard]] bool is_peptide() const;
/** \brief Return whether this compound has a type of either 'DNA linking' or 'RNA linking' */
bool is_base() const;
[[nodiscard]] 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)
/// Return the one letter code to use in a canonical sequence. If unknown the value '\0' is returned
[[nodiscard]] char one_letter_code() const { return m_one_letter_code; };
/// Return the parent id code in case a parent is specified (e.g. MET for MSE)
[[nodiscard]] std::string parent_id() const { return m_parent_id; };
private:
friend class compound_factory_impl;
@@ -201,6 +206,9 @@ class compound
class compound_factory
{
public:
compound_factory(const compound_factory &) = delete;
compound_factory &operator=(const compound_factory &) = delete;
/// \brief Initialise a singleton instance.
///
/// If you have a multithreaded application and want to have different
@@ -243,41 +251,43 @@ class compound_factory
/// 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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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
[[nodiscard]] bool is_std_monomer(std::string_view res_name) const
{
return is_std_base(res_name) or is_std_peptide(res_name);
}
/// Return whether @a res_name is water
bool is_water(std::string_view res_name) const
[[nodiscard]] bool is_water(std::string_view res_name) const
{
return res_name == "HOH" or res_name == "H2O" or res_name == "WAT";
}
/// Return whether @a res_name already exists, without creating it.
bool exists(std::string_view res_name) const;
[[nodiscard]] bool exists(std::string_view res_name) const;
/// \brief Create the compound object for \a id
///
@@ -287,15 +297,18 @@ class compound_factory
/// \result The compound, or nullptr if it could not be created (missing info)
const compound *create(std::string_view id);
~compound_factory();
~compound_factory() = default;
CIFPP_EXPORT static const std::map<std::string, char> kAAMap, ///< Globally accessible static list of the default amino acids
kBaseMap; ///< Globally accessible static list of the default bases
/// Print out a message for a missing compound
void report_missing_compound(std::string_view compound_id);
bool get_report_missing() const { return m_report_missing; }
/// Return a flag indicating if we need to print out a report
[[nodiscard]] bool get_report_missing() const { return m_report_missing; }
/// Set a flag indicating if we need to print out a report
void set_report_missing(bool report)
{
m_report_missing = report;
@@ -304,9 +317,6 @@ class compound_factory
private:
compound_factory();
compound_factory(const compound_factory &) = delete;
compound_factory &operator=(const compound_factory &) = delete;
static std::unique_ptr<compound_factory> s_instance;
static thread_local std::unique_ptr<compound_factory> tl_instance;
static bool s_use_thread_local_instance;
@@ -335,6 +345,7 @@ class compound_factory
class compound_source
{
public:
/// Constructor
compound_source(const file &file)
{
compound_factory::instance().push_dictionary(file);

View File

@@ -26,15 +26,24 @@
#pragma once
#include "cif++/item.hpp"
#include "cif++/row.hpp"
#include "cif++/format.hpp"
#include "cif++/text.hpp"
#include <cassert>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <format>
#include <functional>
#include <iostream>
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
/** \file condition.hpp
* This file contains code to create conditions: object encapsulating a
@@ -127,9 +136,9 @@ iset get_category_items(const category &cat);
*
* @param cat The category
* @param col The name of the item
* @return uint16_t The index
* @return uint16_t The index, if item is found
*/
uint16_t get_item_ix(const category &cat, std::string_view col);
std::optional<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*
@@ -143,24 +152,25 @@ bool is_item_type_uchar(const category &cat, std::string_view col);
// --------------------------------------------------------------------
// some more templates to be able to do querying
/// @cond
namespace detail
{
struct condition_impl
{
virtual ~condition_impl() {}
virtual ~condition_impl() = default;
virtual condition_impl *prepare(const category &) { return this; }
virtual bool test(row_handle) const = 0;
[[nodiscard]] virtual bool test(const_row_handle) const = 0;
virtual void str(std::ostream &) const = 0;
virtual std::optional<row_handle> single() const { return {}; };
[[nodiscard]] virtual std::optional<const_row_handle> single() const { return std::nullopt; };
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; }
[[nodiscard]] bool test(const_row_handle) const override { return true; }
void str(std::ostream &os) const override { os << "*"; }
};
@@ -169,6 +179,8 @@ namespace detail
struct not_condition_impl;
} // namespace detail
/// @endcond
/**
* @brief The interface class for conditions. This uses the bridge pattern,
* which means the implementation is in the member m_impl
@@ -207,7 +219,7 @@ class condition
condition(condition &&rhs) noexcept
: m_impl(nullptr)
{
std::swap(m_impl, rhs.m_impl);
swap(*this, rhs);
}
condition &operator=(const condition &) = delete;
@@ -217,7 +229,7 @@ class condition
*/
condition &operator=(condition &&rhs) noexcept
{
std::swap(m_impl, rhs.m_impl);
swap(*this, rhs);
return *this;
}
@@ -232,8 +244,9 @@ class condition
* take care of setting the correct indices for items e.g.
*
* @param c The category this query should act upon
* @result Returns true if the condition might result in rows
*/
void prepare(const category &c);
bool prepare(const category &c);
/**
* @brief This operator returns true if the row referenced by @a r is
@@ -243,33 +256,32 @@ class condition
* @return true If there is a match
* @return false If there is no match
*/
bool operator()(row_handle r) const
bool operator()(const_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(); }
explicit operator bool() const { return not empty(); }
/**
* @brief Return true if the condition is empty, has no condition
*/
bool empty() const { return m_impl == nullptr; }
[[nodiscard]] 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<row_handle> The result will contain
* @return std::optional<const_row_handle> The result will contain
* a row reference if there is a single hit, it will be empty otherwise
*/
std::optional<row_handle> single() const
[[nodiscard]] std::optional<const_row_handle> single() const
{
return m_impl ? m_impl->single() : std::optional<row_handle>();
return m_impl ? m_impl->single() : std::optional<const_row_handle>();
}
friend condition operator||(condition &&a, condition &&b); /**< Return a condition which is the logical OR or condition @a and @b */
@@ -284,10 +296,9 @@ class condition
/**
* @brief Swap two conditions
*/
void swap(condition &rhs)
friend void swap(condition &lhs, condition &rhs) noexcept
{
std::swap(m_impl, rhs.m_impl);
std::swap(m_prepared, rhs.m_prepared);
std::swap(lhs.m_impl, rhs.m_impl);
}
/**
@@ -308,27 +319,33 @@ class condition
void optimise(condition_impl *&impl);
condition_impl *m_impl;
bool m_prepared = false;
};
namespace detail
{
/// @cond
struct key_is_empty_condition_impl : public condition_impl
{
key_is_empty_condition_impl(const std::string &item_name)
: m_item_name(item_name)
key_is_empty_condition_impl(std::string item_name)
: m_item_name(std::move(item_name))
{
}
condition_impl *prepare(const category &c) override
{
m_item_ix = get_item_ix(c, m_item_name);
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
m_item_ix = *ix;
else
m_missing_key = true;
return this;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
return r[m_item_ix].empty();
return m_missing_key or r[m_item_ix].empty();
}
void str(std::ostream &os) const override
@@ -338,22 +355,28 @@ namespace detail
std::string m_item_name;
uint16_t m_item_ix = 0;
bool m_missing_key = false;
};
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)
key_is_not_empty_condition_impl(std::string item_name)
: m_item_name(std::move(item_name))
{
}
condition_impl *prepare(const category &c) override
{
m_item_ix = get_item_ix(c, m_item_name);
return this;
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
return this;
}
return nullptr;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
return not r[m_item_ix].empty();
}
@@ -377,7 +400,7 @@ namespace detail
condition_impl *prepare(const category &c) override;
bool test(row_handle r) const override
[[nodiscard]] bool test(const_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;
}
@@ -387,12 +410,12 @@ namespace detail
os << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value;
}
virtual std::optional<row_handle> single() const override
[[nodiscard]] std::optional<const_row_handle> single() const override
{
return m_single_hit;
}
virtual bool equals(const condition_impl *rhs) const override
bool equals(const condition_impl *rhs) const override
{
if (typeid(*rhs) == typeid(key_equals_condition_impl))
{
@@ -410,7 +433,7 @@ namespace detail
uint16_t m_item_ix = 0;
bool m_icase = false;
item_value m_value;
std::optional<row_handle> m_single_hit;
std::optional<const_row_handle> m_single_hit;
};
struct key_equals_or_empty_condition_impl : public condition_impl
@@ -425,18 +448,29 @@ namespace detail
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);
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
m_icase = is_item_type_uchar(c, m_item_name);
}
else
m_key_is_missing = true;
return this;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
bool result = false;
if (m_single_hit.has_value())
if (m_key_is_missing)
result = true;
else 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;
}
@@ -445,12 +479,12 @@ namespace detail
os << '(' << m_item_name << (m_icase ? "^ " : " ") << " == " << m_value << " OR " << m_item_name << " IS NULL)";
}
virtual std::optional<row_handle> single() const override
[[nodiscard]] std::optional<const_row_handle> single() const override
{
return m_single_hit;
}
virtual bool equals(const condition_impl *rhs) const override
[[nodiscard]] bool equals(const condition_impl *rhs) const override
{
if (typeid(*rhs) == typeid(key_equals_or_empty_condition_impl))
{
@@ -466,29 +500,35 @@ namespace detail
std::string m_item_name;
uint16_t m_item_ix = 0;
item_value &m_value;
item_value m_value;
bool m_icase = false;
std::optional<row_handle> m_single_hit;
bool m_key_is_missing = false;
std::optional<const_row_handle> m_single_hit;
};
struct key_compare_condition_impl : public condition_impl
{
template <typename COMP>
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)
key_compare_condition_impl(std::string item_name, COMP &&comp, std::string s)
: m_item_name(std::move(item_name))
, m_compare(std::forward<COMP>(comp))
, m_str(std::move(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;
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
m_icase = is_item_type_uchar(c, m_item_name);
return this;
}
return nullptr;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
return m_compare(r, m_icase);
}
@@ -501,26 +541,30 @@ namespace detail
std::string m_item_name;
uint16_t m_item_ix = 0;
bool m_icase = false;
std::function<bool(row_handle, bool)> m_compare;
std::function<bool(const_row_handle, bool)> 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)
key_matches_condition_impl(std::string item_name, std::regex rx)
: m_item_name(std::move(item_name))
, mRx(std::move(rx))
{
}
condition_impl *prepare(const category &c) override
{
m_item_ix = get_item_ix(c, m_item_name);
return this;
auto ix = get_item_ix(c, m_item_name);
if (ix.has_value())
{
m_item_ix = *ix;
return this;
}
return nullptr;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
auto txt = r[m_item_ix].get<std::string>();
return std::regex_match(txt.begin(), txt.end(), mRx);
@@ -532,37 +576,31 @@ namespace detail
}
std::string m_item_name;
uint16_t m_item_ix;
uint16_t m_item_ix{};
std::regex mRx;
};
template <typename T>
struct any_is_condition_impl : public condition_impl
{
typedef T valueType;
using valueType = T;
any_is_condition_impl(const valueType &value)
: mValue(value)
{
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_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 (...)
if (r[f].compare(mValue) == 0)
{
result = true;
break;
}
}
@@ -579,12 +617,12 @@ namespace detail
struct any_matches_condition_impl : public condition_impl
{
any_matches_condition_impl(const std::regex &rx)
: mRx(rx)
any_matches_condition_impl(std::regex rx)
: mRx(std::move(rx))
{
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
auto &c = r.get_category();
@@ -600,7 +638,7 @@ namespace detail
break;
}
}
catch (...)
catch (const std::exception &ex) // NOLINT(bugprone-empty-catch)
{
}
}
@@ -627,14 +665,14 @@ namespace detail
{
if (typeid(*a.m_impl) == typeid(*this))
{
and_condition_impl *ai = static_cast<and_condition_impl *>(a.m_impl);
auto *ai = static_cast<and_condition_impl *>(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<and_condition_impl *>(b.m_impl);
auto *bi = static_cast<and_condition_impl *>(b.m_impl);
std::swap(m_sub, bi->m_sub);
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
@@ -646,7 +684,7 @@ namespace detail
}
}
~and_condition_impl()
~and_condition_impl() override // NOLINT(modernize-use-equals-default)
{
for (auto sub : m_sub)
delete sub;
@@ -654,7 +692,7 @@ namespace detail
condition_impl *prepare(const category &c) override;
bool test(row_handle r) const override;
[[nodiscard]] bool test(const_row_handle r) const override;
void str(std::ostream &os) const override
{
@@ -674,9 +712,9 @@ namespace detail
os << ')';
}
virtual std::optional<row_handle> single() const override
[[nodiscard]] std::optional<const_row_handle> single() const override
{
std::optional<row_handle> result;
std::optional<const_row_handle> result;
for (auto sub : m_sub)
{
@@ -701,7 +739,7 @@ namespace detail
static condition_impl *combine_equal(std::vector<and_condition_impl *> &subs, or_condition_impl *oc);
std::vector<condition_impl *> m_sub;
std::optional<row_handle> m_single; // Potential result of index lookup
std::optional<const_row_handle> m_single; // Potential result of index lookup
};
struct or_condition_impl : public condition_impl
@@ -710,14 +748,14 @@ namespace detail
{
if (typeid(*a.m_impl) == typeid(*this))
{
or_condition_impl *ai = static_cast<or_condition_impl *>(a.m_impl);
auto *ai = static_cast<or_condition_impl *>(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<or_condition_impl *>(b.m_impl);
auto *bi = static_cast<or_condition_impl *>(b.m_impl);
std::swap(m_sub, bi->m_sub);
m_sub.emplace_back(std::exchange(a.m_impl, nullptr));
@@ -729,7 +767,7 @@ namespace detail
}
}
~or_condition_impl()
~or_condition_impl() override // NOLINT(modernize-use-equals-default)
{
for (auto sub : m_sub)
delete sub;
@@ -737,7 +775,7 @@ namespace detail
condition_impl *prepare(const category &c) override;
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
bool result = false;
@@ -768,9 +806,9 @@ namespace detail
os << ')';
}
virtual std::optional<row_handle> single() const override
[[nodiscard]] std::optional<const_row_handle> single() const override
{
std::optional<row_handle> result;
std::optional<const_row_handle> result;
for (auto sub : m_sub)
{
@@ -798,23 +836,21 @@ namespace detail
struct not_condition_impl : public condition_impl
{
not_condition_impl(condition &&a)
: mA(nullptr)
{
std::swap(mA, a.m_impl);
}
~not_condition_impl()
~not_condition_impl() override
{
delete mA;
}
condition_impl *prepare(const category &c) override
{
mA = mA->prepare(c);
return this;
return mA->prepare(c) ? this : nullptr;
}
bool test(row_handle r) const override
[[nodiscard]] bool test(const_row_handle r) const override
{
return not mA->test(r);
}
@@ -826,9 +862,11 @@ namespace detail
os << ')';
}
condition_impl *mA;
condition_impl *mA = nullptr;
};
/// @endcond
} // namespace detail
/**
@@ -839,8 +877,8 @@ 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));
return a;
return b;
}
/**
@@ -874,9 +912,9 @@ inline condition operator or(condition &&a, condition &&b)
}
if (a.m_impl)
return condition(std::move(a));
return a;
return condition(std::move(b));
return b;
}
/**
@@ -912,8 +950,8 @@ struct key
*
* @param item_name
*/
explicit key(const std::string &item_name)
: m_item_name(item_name)
explicit key(std::string item_name)
: m_item_name(std::move(item_name))
{
}
@@ -943,13 +981,14 @@ struct key
std::string m_item_name; ///< The item name
};
/// concept to check for numeric data
template <typename T>
concept Numeric = ((std::is_floating_point_v<T> or std::is_integral_v<T>) and not std::is_same_v<T, bool>);
/**
* @brief Operator to create an equals condition based on a key @a key and a value @a value
*/
inline condition operator==(const key &key, item_value value)
inline condition operator==(const key &key, const item_value &value)
{
if (not value.empty())
return condition(new detail::key_equals_condition_impl({ key.m_item_name, value }));
@@ -957,19 +996,10 @@ inline condition operator==(const key &key, item_value value)
return condition(new detail::key_is_empty_condition_impl(key.m_item_name));
}
/**
* @brief Operator to create a not equals condition based on a key @a key and a value @a v
*/
template <typename T>
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)
inline condition operator!=(const key &key, const item_value &value)
{
return condition(new detail::not_condition_impl(operator==(key, value)));
}
@@ -981,9 +1011,9 @@ template <Numeric T>
condition operator>(const key &key, const T &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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) > 0; },
cif::format(" > {}", v)));
std::format(" > {}", v)));
}
/**
@@ -993,9 +1023,9 @@ template <Numeric T>
condition operator>=(const key &key, const T &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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) >= 0; },
cif::format(" >= {}", v)));
std::format(" >= {}", v)));
}
/**
@@ -1005,9 +1035,9 @@ template <Numeric T>
condition operator<(const key &key, const T &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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) < 0; },
cif::format(" < {}", v)));
std::format(" < {}", v)));
}
/**
@@ -1017,9 +1047,9 @@ template <Numeric T>
condition operator<=(const key &key, const T &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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v) <= 0; },
cif::format(" <= {}", v)));
std::format(" <= {}", v)));
}
/**
@@ -1028,9 +1058,9 @@ condition operator<=(const key &key, const T &v)
inline condition operator>(const key &key, std::string_view 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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) > 0; },
cif::format(" > {}", v)));
std::format(" > {}", v)));
}
/**
@@ -1039,9 +1069,9 @@ inline condition operator>(const key &key, std::string_view v)
inline condition operator>=(const key &key, std::string_view 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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) >= 0; },
cif::format(" >= {}", v)));
std::format(" >= {}", v)));
}
/**
@@ -1050,9 +1080,9 @@ inline condition operator>=(const key &key, std::string_view v)
inline condition operator<(const key &key, std::string_view 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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) < 0; },
cif::format(" < {}", v)));
std::format(" < {}", v)));
}
/**
@@ -1061,9 +1091,9 @@ inline condition operator<(const key &key, std::string_view v)
inline condition operator<=(const key &key, std::string_view 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)
key.m_item_name, [item_name = key.m_item_name, v](const_row_handle r, bool icase)
{ return r[item_name].compare(v, icase) <= 0; },
cif::format(" <= {}", v)));
std::format(" <= {}", v)));
}
/**

507
include/cif++/cql.hpp Normal file
View File

@@ -0,0 +1,507 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 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 cql.hpp
*
* This file contains code to access stored data as if it were
* a relation database. The underlying code uses SQLite as engine.
* categories are exposed as virtual tables.
*/
#include "cif++/category.hpp"
#include "cif++/item.hpp"
#include "cif++/iterator.hpp"
#include "cif++/row.hpp"
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
// --------------------------------------------------------------------
namespace cif::cql
{
class connection;
struct result_impl;
// --------------------------------------------------------------------
/// Reference to a field in the result set
class field_ref final
{
public:
/// The name of the field
[[nodiscard]] std::string_view name() const &
{
return m_row.get_category().get_item_name(m_index);
}
/// The index number of the field
[[nodiscard]] constexpr size_t num() const noexcept
{
return m_index;
}
/** Return the contents of this item as type @tparam T */
template <typename T = std::string>
[[nodiscard]] auto get() const -> T
{
return m_row[m_index].get<T>();
}
/// Returns true if the field contains NULL
[[nodiscard]] bool is_null() const
{
return m_row[m_index].is_null();
}
/** Return the contents of this item as type @tparam T or, if not
* set, use @a dv as the default value.
*/
template <typename T>
auto value_or(const T &dv) const
{
return m_row[m_index].value_or(dv);
}
/// Constructor
field_ref(const_row_handle rh, uint16_t col, std::shared_ptr<result_impl> result_impl)
: m_row(std::move(rh))
, m_index(col)
, m_result_impl(std::move(result_impl))
{
}
/// Copy constructor
field_ref(const field_ref &) = default;
/// Move constructor
field_ref(field_ref &&) = default;
/// Copy assignment
field_ref &operator=(const field_ref &) = default;
/// Move assignment
field_ref &operator=(field_ref &&) = default;
private:
const_row_handle m_row;
uint16_t m_index;
std::shared_ptr<result_impl> m_result_impl;
};
// --------------------------------------------------------------------
/// A reference to a row in the result set
class row_ref final
{
public:
/// Iterator for the items in this row
class const_field_iterator
{
public:
friend class result;
/// @cond
using iterator_category = std::forward_iterator_tag;
using value_type = const field_ref;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
const_field_iterator(const const_field_iterator &) = default;
const_field_iterator(const_field_iterator &&) = default;
const_field_iterator &operator=(const const_field_iterator &) = default;
const_field_iterator &operator=(const_field_iterator &&) = default;
reference operator*()
{
return m_current;
}
pointer operator->()
{
return &m_current;
}
const_field_iterator &operator++()
{
if (m_row)
{
++m_col;
m_current = field_ref(m_row, m_col, m_result_impl);
}
return *this;
}
const_field_iterator operator++(int)
{
const_field_iterator result(*this);
this->operator++();
return result;
}
bool operator==(const const_field_iterator &rhs) const
{
return m_row == rhs.m_row and m_col == rhs.m_col;
}
bool operator!=(const const_field_iterator &rhs) const
{
return m_row != rhs.m_row or m_col != rhs.m_col;
}
private:
friend class row_ref;
const_field_iterator(const_row_handle row, uint16_t column, std::shared_ptr<result_impl> result_impl)
: m_row(std::move(row))
, m_col(column)
, m_current(m_row, m_col, result_impl)
, m_result_impl(result_impl)
{
}
const_row_handle m_row;
uint16_t m_col;
field_ref m_current;
std::shared_ptr<result_impl> m_result_impl;
/// @endcond
};
// --------------------------------------------------------------------
row_ref() = default;
/// Constructor
row_ref(const_row_handle rh, std::shared_ptr<result_impl> result_impl)
: m_row(std::move(rh))
, m_result_impl(std::move(result_impl))
{
}
/// @cond
row_ref(const row_ref &) = default;
row_ref &operator=(const row_ref &) = default;
/// @endcond
// --------------------------------------------------------------------
[[nodiscard]] const_field_iterator begin() const noexcept { return { m_row, 0, m_result_impl }; } ///< Return begin field iterator
[[nodiscard]] const_field_iterator cbegin() const noexcept { return { m_row, 0, m_result_impl }; } ///< Return cbegin field iterator
[[nodiscard]] const_field_iterator end() const noexcept { return { m_row, static_cast<uint16_t>(size()), m_result_impl }; } ///< Return end field iterator
[[nodiscard]] const_field_iterator cend() const noexcept { return { m_row, static_cast<uint16_t>(size()), m_result_impl }; } ///< Return cend field iterator
[[nodiscard]] field_ref front() const noexcept { return { m_row, 0, m_result_impl }; } ///< return reference to front field
[[nodiscard]] field_ref back() const noexcept { return { m_row, static_cast<uint16_t>(size() - 1), m_result_impl }; } ///< return reference to back field
[[nodiscard]] size_t size() const noexcept; ///< return number of items in the row
[[nodiscard]] bool empty() const noexcept { return size() == 0; } ///< return if the row contains no items at all
[[nodiscard]] field_ref operator[](uint16_t index) const noexcept { return { m_row, index, m_result_impl }; } ///< access field by index
[[nodiscard]] field_ref operator[](std::string_view name) const; ///< access field by name
// --------------------------------------------------------------------
/// @cond
bool operator==(const row_ref &rhs) const { return m_row == rhs.m_row; }
bool operator!=(const row_ref &rhs) const { return m_row != rhs.m_row; }
private:
const_row_handle m_row;
std::shared_ptr<result_impl> m_result_impl;
/// @endcond
};
// --------------------------------------------------------------------
/// The result set, containing the result of a query
class result
{
public:
// --------------------------------------------------------------------
/// iterator to the rows in the result set
class iterator
{
public:
friend class view;
/// @cond
using iterator_category = std::forward_iterator_tag;
using value_type = const row_ref;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
// const_row_iterator() = default;
iterator(std::shared_ptr<result_impl> result_impl, category::const_iterator cat_iter)
: m_iter(std::move(cat_iter))
, m_current(*m_iter, result_impl)
, m_result_impl(result_impl)
{
}
iterator(const iterator &) = default;
iterator(iterator &&) = default;
// const_row_iterator &operator=(const const_row_iterator &) = default;
// const_row_iterator &operator=(const_row_iterator &&) = default;
reference operator*()
{
return m_current;
}
pointer operator->()
{
return &m_current;
}
iterator &operator++()
{
++m_iter;
m_current = { *m_iter, m_result_impl };
return *this;
}
iterator operator++(int)
{
iterator result(*this);
this->operator++();
return result;
}
bool operator==(const iterator &rhs) const
{
return m_result_impl == rhs.m_result_impl and m_iter == rhs.m_iter;
}
bool operator!=(const iterator &rhs) const
{
return m_result_impl != rhs.m_result_impl or m_iter != rhs.m_iter;
}
private:
category::const_iterator m_iter;
row_ref m_current;
std::shared_ptr<result_impl> m_result_impl;
/// @endcond
};
// --------------------------------------------------------------------
/// @cond
result() = delete;
result(result const &rhs) noexcept = default;
result(result &&rhs) noexcept = default;
result &operator=(result const &rhs) noexcept = default;
result &operator=(result &&rhs) noexcept = default;
result(category &&data, const std::string &query = "");
~result() = default;
/// @endcond
/// Return the row if and only if the result set contains exactly one row, throws otherwise
[[nodiscard]] row_ref one_row() const
{
if (size() != 1)
throw std::runtime_error("Expected one row");
return front();
}
/// Return the row if and only if the result set contains exactly one row,
/// and this row also contains only one field, throws otherwise
[[nodiscard]] field_ref one_field() const
{
expect_columns(1);
if (size() != 1)
throw std::runtime_error("Expected one row");
return one_row().front();
}
// --------------------------------------------------------------------
/// @cond
[[nodiscard]] iterator begin() const noexcept;
[[nodiscard]] iterator cbegin() const noexcept;
[[nodiscard]] iterator end() const noexcept;
[[nodiscard]] iterator cend() const noexcept;
[[nodiscard]] row_ref front() const;
[[nodiscard]] row_ref back() const;
[[nodiscard]] size_t size() const noexcept;
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
/// @endcond
/// Return the number of colums/fields in each row
[[nodiscard]] size_t column_count() const;
/// Return the result set as a cif::category
[[nodiscard]] category &get_category() const;
/// Test to see if the result set contains at least the number of fields/columns
/// but only when not empty
void expect_columns(size_t cols) const
{
if (auto actual = column_count(); size() > 0 and cols != actual)
throw std::runtime_error("Unexpected number of columns");
}
// --------------------------------------------------------------------
/// Print out the result set, for debugging mostly
friend std::ostream &operator<<(std::ostream &os, const result &r)
{
os << r.get_category();
return os;
}
private:
friend class transaction;
friend class SelectStatement;
std::shared_ptr<result_impl> m_impl;
};
// --------------------------------------------------------------------
/// Helper class to allow access to the data as a stream
template <typename... Ts>
class cql_iterator_proxy : public cif::iterator_proxy<Ts...>
{
public:
/// Constructor
cql_iterator_proxy(result &&res)
: cif::iterator_proxy<Ts...>(res.get_category())
, m_result(std::forward<result>(res))
{
m_result.expect_columns(cif::iterator_proxy<Ts...>::N);
}
private:
result m_result;
};
// --------------------------------------------------------------------
/// Transaction class.
/// At construction, this class starts a transaction on the connection
/// and at exit an automatic rollback is done, unless commit was called.
class transaction final
{
public:
/// Constructor
transaction(connection &conn);
/// @cond
~transaction();
transaction(const transaction &) = delete;
transaction &operator=(const transaction &) = delete;
/// @endcond
/// \brief Execute the sql in @a query returning an iterable result
result exec(std::string query);
/// \brief Execute the sql in @a query returning an iterable result.
/// Updates @a tail with what remains after the first statement in @a query
result exec(std::string query, std::string &tail);
/// Execute the sql in @a sql and return the result as a stream
template <typename... Ts>
cql_iterator_proxy<Ts...> stream(const std::string &sql)
{
return cql_iterator_proxy<Ts...>{ exec(sql) };
}
/// Commit the result of the operations
void commit();
/// Rollback the result of the operations, the underlying data is
/// restored to the state before the construction of this transaction.
void rollback();
private:
connection &m_conn;
bool m_transaction_active = false;
};
// --------------------------------------------------------------------
/// This connection class creates a SQLite environment with the data in
/// the provided datablock as tables.
class connection final
{
public:
/// Constructor
connection(datablock &db);
/// Destructor
~connection();
friend class transaction;
/// \brief Return true if the string @a sql contains a complete statement.
[[nodiscard]] bool is_complete_statement(const std::string &sql) const;
/// \brief Execute the sql in @a query returning an iterable result
result exec(std::string query);
/// \brief Execute the sql in @a query returning an iterable result.
/// Updates @a tail with what remains after the first statement in @a query
result exec(std::string query, std::string &tail);
/// \brief Return true if the underlying data was modified by any query.
[[nodiscard]] bool is_modified() const;
private:
struct connection_impl *m_impl;
};
} // namespace cif::cql

View File

@@ -27,9 +27,14 @@
#pragma once
#include "cif++/category.hpp"
#include "cif++/forward_decl.hpp"
#include <iosfwd>
#include <list>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
/** \file datablock.hpp
* Each valid mmCIF file contains at least one @ref cif::datablock.
@@ -39,6 +44,8 @@
namespace cif
{
class validator;
// --------------------------------------------------------------------
/**
@@ -76,6 +83,7 @@ class datablock : public std::list<category>
}
/** @endcond */
/// Swap two datablocks
friend void swap_(datablock &a, datablock &b) noexcept
{
std::swap(a.m_name, b.m_name);
@@ -88,7 +96,7 @@ class datablock : public std::list<category>
/**
* @brief Return the name of this datablock
*/
const std::string &name() const { return m_name; }
[[nodiscard]] const std::string &name() const { return m_name; }
/**
* @brief Set the name of this datablock to @a name
@@ -106,6 +114,12 @@ class datablock : public std::list<category>
*/
void load_dictionary();
/**
* @brief Attempt to load the dictionary @a dict
*
*/
void load_dictionary(std::string_view dict);
/**
* @brief Set the validator object to @a v
*
@@ -118,7 +132,7 @@ class datablock : public std::list<category>
*
* @return const validator* The validator or nullptr if there is none
*/
const validator *get_validator() const;
[[nodiscard]] const validator *get_validator() const;
/**
* @brief Validates the content of this datablock and all its content
@@ -126,7 +140,7 @@ class datablock : public std::list<category>
* @return true If the content is valid
* @return false If the content is not valid
*/
bool is_valid() const;
[[nodiscard]] bool is_valid() const;
/**
* @brief Validates all contained data for valid links between parents and children
@@ -135,7 +149,7 @@ class datablock : public std::list<category>
* @return true If all links are valid
* @return false If all links are not valid
*/
bool validate_links() const;
[[nodiscard]] bool validate_links() const;
/**
* @brief Strip removes all categories and items that are invalid according
@@ -181,12 +195,12 @@ class datablock : public std::list<category>
* @param name The name of the category
* @return category* Pointer to the category found or nullptr
*/
const category *get(std::string_view name) const;
[[nodiscard]] const category *get(std::string_view name) const;
/**
* @brief Return true if this datablock contains a non-empty category
*/
bool contains(std::string_view name) const
[[nodiscard]] bool contains(std::string_view name) const
{
return get(name) != nullptr;
}
@@ -207,16 +221,7 @@ class datablock : public std::list<category>
/**
* @brief Get the preferred order of the categories when writing them
*/
[[deprecated("use get_item_order instead")]]
std::vector<std::string> get_tag_order() const
{
return get_item_order();
}
/**
* @brief Get the preferred order of the categories when writing them
*/
std::vector<std::string> get_item_order() const;
[[nodiscard]] std::vector<std::string> get_item_order() const;
/**
* @brief Write out the contents to @a os

View File

@@ -27,6 +27,7 @@
#pragma once
#include "cif++/validate.hpp"
#include <iosfwd>
/**
* @file validate.hpp
@@ -37,6 +38,8 @@
namespace cif
{
class validator;
/**
* @brief Parse the contents of @a is and place content in validator @a v
*/

View File

@@ -26,21 +26,26 @@
#pragma once
#include <list>
#include "cif++/datablock.hpp"
#include "cif++/parser.hpp"
#include <cassert>
#include <cstddef>
#include <filesystem>
#include <istream>
#include <list>
#include <string_view>
#include <tuple>
/** \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.
* information on how data is loaded.
*/
namespace cif
@@ -50,7 +55,7 @@ namespace cif
/**
* @brief The class file is actually a list of datablock objects
*
*
*/
class file : public std::list<datablock>
@@ -60,7 +65,7 @@ class file : public std::list<datablock>
/**
* @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)
@@ -70,7 +75,7 @@ class file : public std::list<datablock>
/**
* @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)
@@ -81,7 +86,7 @@ class file : public std::list<datablock>
/**
* @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
*/
@@ -100,7 +105,7 @@ class file : public std::list<datablock>
}
/** @cond */
file(const file &rhs)
file(const file &rhs) // NOLINT
: std::list<datablock>(rhs)
{
}
@@ -120,23 +125,23 @@ class file : public std::list<datablock>
/**
* @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;
[[nodiscard]] 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
*/
@@ -144,18 +149,18 @@ class file : public std::list<datablock>
/**
* @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;
[[nodiscard]] bool validate_links() const;
/**
* @brief Return true if a datablock with the name @a name is part of this file
*/
bool contains(std::string_view name) const;
[[nodiscard]] bool contains(std::string_view name) const;
/**
* @brief return a reference to the first datablock in the file
@@ -169,7 +174,7 @@ class file : public std::list<datablock>
/**
* @brief return a const reference to the first datablock in the file
*/
const datablock &front() const
[[nodiscard]] const datablock &front() const
{
assert(not empty());
return std::list<datablock>::front();
@@ -190,7 +195,7 @@ class file : public std::list<datablock>
* 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<iterator, bool> A tuple containing an iterator pointing
* at the datablock and a boolean indicating whether the datablock was newly
@@ -204,12 +209,6 @@ class file : public std::list<datablock>
/** Load the data from @a is */
void load(std::istream &is);
/** Load the data from the file specified by @a p using validator @a v */
void load(const std::filesystem::path &p, const validator &v);
/** Load the data from @a is using validator @a v */
void load(std::istream &is, const validator &v);
/** Save the data to the file specified by @a p */
void save(const std::filesystem::path &p) const;

View File

@@ -26,29 +26,18 @@
#pragma once
#if __has_include(<format>)
#include <format>
#define USE_STD_FORMAT 1
#else
#include <fmt/format.h>
#endif
#include <ostream>
#include <streambuf>
#include <string>
/** \file format.hpp
*
* Now using cif::format instead of a home grown rip off
* Now using std::format instead of a home grown rip off
*/
namespace cif
{
#if USE_STD_FORMAT
using std::format;
#else
using fmt::format;
#endif
// --------------------------------------------------------------------
/// A streambuf that fills out lines with spaces up until a specified width
@@ -78,7 +67,7 @@ class fill_out_streambuf : public std::streambuf
/** @cond */
~fill_out_streambuf()
~fill_out_streambuf() override
{
m_os.rdbuf(m_upstream);
}
@@ -91,8 +80,7 @@ class fill_out_streambuf : public std::streambuf
* wide as the requested width.
*/
virtual int_type
overflow(int_type ic = traits_type::eof())
int_type overflow(int_type ic = traits_type::eof()) override
{
char ch = traits_type::to_char_type(ic);
@@ -122,10 +110,10 @@ class fill_out_streambuf : public std::streambuf
}
/** Return the upstream streambuf */
std::streambuf *get_upstream() const { return m_upstream; }
[[nodiscard]] std::streambuf *get_upstream() const { return m_upstream; }
/** Return how many lines have been written */
int get_line_count() const { return m_line_count; }
[[nodiscard]] int get_line_count() const { return m_line_count; }
private:
std::ostream &m_os;

View File

@@ -1,53 +0,0 @@
/*-
* 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 <string>
#include <vector>
/**
* @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 cif

View File

@@ -209,7 +209,7 @@ class basic_igzip_streambuf : public basic_streambuf<CharT, Traits>
return *this;
}
~basic_igzip_streambuf()
~basic_igzip_streambuf() override
{
close();
}
@@ -245,8 +245,8 @@ class basic_igzip_streambuf : public basic_streambuf<CharT, Traits>
close();
m_zstream.reset(new z_stream_s);
m_gzheader.reset(new gz_header_s);
m_zstream = std::make_unique<z_stream_s>();
m_gzheader = std::make_unique<gz_header_s>();
auto &zstream = *m_zstream.get();
zstream = z_stream_s{};
@@ -396,7 +396,7 @@ class basic_ogzip_streambuf : public basic_streambuf<CharT, Traits>
return *this;
}
~basic_ogzip_streambuf()
~basic_ogzip_streambuf() override
{
close();
}
@@ -431,8 +431,8 @@ class basic_ogzip_streambuf : public basic_streambuf<CharT, Traits>
close();
m_zstream.reset(new z_stream_s);
m_gzheader.reset(new gz_header_s);
m_zstream = std::make_unique<z_stream_s>();
m_gzheader = std::make_unique<gz_header_s>();
auto &zstream = *m_zstream.get();
zstream = z_stream_s{};
@@ -658,7 +658,7 @@ class basic_ifstream : public basic_istream<CharT, Traits>
/// \brief Default constructor, does not open a file since none is specified
basic_ifstream() = default;
~basic_ifstream()
~basic_ifstream() override
{
close();
}
@@ -774,7 +774,7 @@ class basic_ifstream : public basic_istream<CharT, Traits>
/// \brief Return true if the file is open
/// \return m_filebuf.is_open()
bool is_open() const
[[nodiscard]] bool is_open() const
{
return m_filebuf.is_open();
}
@@ -922,7 +922,7 @@ class basic_ofstream : public basic_ostream<CharT, Traits>
basic_ofstream() = default;
~basic_ofstream()
~basic_ofstream() override
{
close();
}
@@ -1054,7 +1054,7 @@ class basic_ofstream : public basic_ostream<CharT, Traits>
/// \brief Return true if the file is open
/// \return m_filebuf.is_open()
bool is_open() const
[[nodiscard]] bool is_open() const
{
return m_filebuf.is_open();
}

View File

@@ -26,24 +26,22 @@
#pragma once
#include "cif++/exports.hpp"
#include "cif++/forward_decl.hpp"
#include "cif++/text.hpp"
#include "cif++/utilities.hpp"
#include <algorithm>
#include <cassert>
#include <charconv>
#include <compare>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <ios>
#include <iostream>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>
#include <utility>
#include <version>
/** \file item.hpp
*
@@ -54,54 +52,37 @@
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<int>() }, // <- stores an item with value ?
* { "item-4", std::make_optional<int>(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 category;
class row;
// --------------------------------------------------------------------
/**
* The primitive types as known in libcifpp.
*/
enum class item_value_type
{
BOOLEAN,
/// Integer, stored as int64_t
INT,
/// Floating point, stored as double
FLOAT,
/// Character string
TEXT,
MISSING,
EMPTY // This is the real NULL in SQL terms
/// A value is not applicable, no data is stored, output is '.'
INAPPLICABLE,
/// A values is now known or missing, no data is stored, output is '?'
MISSING
};
template <typename T>
concept BooleanType = std::is_same_v<std::remove_cvref_t<T>, bool>;
// --------------------------------------------------------------------
/// @cond
template <typename T>
concept IntegralType = (std::is_integral_v<std::remove_cvref_t<T>> and not std::is_same_v<std::remove_cvref_t<T>, bool>);
concept IntegralType = (std::is_integral_v<std::remove_cvref_t<T>>);
template <typename T>
concept FloatType = std::is_floating_point_v<std::remove_cvref_t<T>>;
@@ -109,39 +90,56 @@ concept FloatType = std::is_floating_point_v<std::remove_cvref_t<T>>;
template <typename T>
concept StringType = (std::is_assignable_v<std::string, T> and not std::is_integral_v<T> and not std::is_floating_point_v<T>);
// --------------------------------------------------------------------
/// \cond
template <typename _Tp>
template <typename T>
inline constexpr bool is_optional_v = false;
template <typename _Tp>
inline constexpr bool is_optional_v<std::optional<_Tp>> = true;
/// \endcond
template <typename T>
inline constexpr bool is_optional_v<std::optional<T>> = true;
/// @endcond
/// The data is stored in this item_value type. It contains one of
/// the five types from @ref cif::item_value_type.
/// Data is stored internally in the structure, except for strings
/// larger than 7 characters. 99% of the character strings in a typical
/// mmCIF file are 7 bytes or less, so this saves a lot of memory allocations.
class item_value
{
public:
/// Construct an item_value containing the @ref INAPPLICABLE value.
item_value() noexcept
{
m_data.m_type = item_value_type::EMPTY;
m_data.m_type = item_value_type::INAPPLICABLE;
}
/// Construct an item_value containing the @ref MISSING value.
item_value(std::nullptr_t)
{
m_data.m_type = item_value_type::MISSING;
}
/// Create an item_value containing the type specified by @a type
/// and a default value for this type.
item_value(item_value_type type) noexcept
: m_data(type)
{
}
/// Copy constructor
item_value(const item_value &rhs)
{
m_data.m_type = rhs.m_data.m_type;
switch (m_data.m_type)
{
case item_value_type::BOOLEAN: m_data.m_value = rhs.m_data.m_value.m_boolean; break;
case item_value_type::INT: m_data.m_value = rhs.m_data.m_value.m_integer; break;
case item_value_type::FLOAT: m_data.m_value = rhs.m_data.m_value.m_float; break;
case item_value_type::TEXT:
using enum item_value_type;
case INT:
m_data.m_value = rhs.m_data.m_value.m_integer;
break;
case FLOAT:
m_data.m_len = rhs.m_data.m_len;
m_data.m_value = rhs.m_data.m_value.m_float;
break;
case TEXT:
m_data.m_len = rhs.m_data.m_len;
m_data.m_value = rhs.m_data.sv();
break;
@@ -149,41 +147,41 @@ class item_value
}
}
item_value(std::nullptr_t)
{
m_data.m_type = item_value_type::EMPTY;
}
template <BooleanType T>
item_value(T v)
{
m_data.m_type = item_value_type::BOOLEAN;
m_data.m_value = v;
}
/// Construct an item_value containing the string @a s
item_value(std::string_view s)
{
m_data.m_type = item_value_type::TEXT;
m_data.m_len = s.length();
m_data.m_value = s;
if (s == ".")
m_data.m_type = item_value_type::INAPPLICABLE;
else if (s == "?")
m_data.m_type = item_value_type::MISSING;
else
{
m_data.m_type = item_value_type::TEXT;
m_data.m_len = s.length();
m_data.m_value = s;
}
}
/// Construct an item_value containing the string @a s
template <size_t N>
item_value(const char(s)[N])
: item_value(std::string_view{ s, N })
{
}
/// Construct an item_value containing the string @a s
item_value(const char *s)
: item_value(std::string_view{ s })
{
}
/// Construct an item_value containing the string @a s
item_value(const std::string &s)
: item_value(std::string_view{ s })
{
}
/// Construct an item_value containing the integer @a v
template <IntegralType T>
item_value(T v)
{
@@ -191,31 +189,70 @@ class item_value
m_data.m_value = static_cast<int64_t>(v);
}
/// Construct an item_value containing the double @a v
/// No precission is recorded, so output will be as
/// long as required to contain value.
template <FloatType T>
item_value(T v, int precision = 0)
item_value(T v)
{
m_data.m_type = item_value_type::FLOAT;
m_data.m_value = static_cast<double>(v);
m_data.m_len = 0;
}
/// Construct an item_value containing the double @a v
/// If precision is not zero, output of this value to a file
/// will use this precision. When parsing files, this precision
/// is taken from the parsed string which will guarantee that
/// the output of the data is the same as the input.
template <FloatType T>
item_value(T v, int precision)
{
m_data.m_type = item_value_type::FLOAT;
m_data.m_value = static_cast<double>(v);
m_data.m_len = precision;
}
/// Construct an item_value containing either the value @a v
/// or a MISSING type.
template <typename T>
item_value(std::optional<T> v)
{
if (v.has_value())
{
item_value iv{ *v };
item_value iv{ *v };
swap(*this, iv);
}
else
m_data.m_type = item_value_type::EMPTY;
m_data.m_type = item_value_type::MISSING;
}
/// Construct an item_value containing the double @a v
/// If precision is not zero, output of this value to a file
/// will use this precision. When parsing files, this precision
/// is taken from the parsed string which will guarantee that
/// the output of the data is the same as the input.
/// If v is emtpy, the proper MISSING value type will be used.
template <FloatType T>
item_value(std::optional<T> v, int precision)
{
if (v.has_value())
{
item_value iv{ *v };
swap(*this, iv);
}
else
m_data.m_type = item_value_type::MISSING;
m_data.m_len = precision;
}
/// Move constructor
item_value(item_value &&rhs) noexcept
{
swap(*this, rhs);
}
/// We're using modern move semantics
item_value &operator=(item_value rhs) noexcept
{
swap(*this, rhs);
@@ -224,40 +261,42 @@ class item_value
// --------------------------------------------------------------------
constexpr bool is_null() const noexcept { return m_data.m_type == item_value_type::MISSING; }
constexpr bool is_empty() const noexcept { return m_data.m_type == item_value_type::EMPTY; }
constexpr bool is_string() const noexcept { return m_data.m_type == item_value_type::TEXT; }
constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); }
constexpr bool is_number_int() const noexcept { return m_data.m_type == item_value_type::INT; }
constexpr bool is_number_float() const noexcept { return m_data.m_type == item_value_type::FLOAT; }
constexpr bool is_boolean() const noexcept { return m_data.m_type == item_value_type::BOOLEAN; }
/// Test if contained value is of type @ref cif::item_value_type::INAPPLICABLE
[[nodiscard]] constexpr bool is_inapplicable() const noexcept { return m_data.m_type == item_value_type::INAPPLICABLE; }
constexpr item_value_type type() const { return m_data.m_type; }
/// Test if contained value is of type @ref cif::item_value_type::MISSING
[[nodiscard]] constexpr bool is_missing() const noexcept { return m_data.m_type == item_value_type::MISSING; }
explicit operator bool() const noexcept
{
bool result;
switch (m_data.m_type)
{
case item_value_type::BOOLEAN: result = m_data.m_value.m_boolean; break;
case item_value_type::INT: result = m_data.m_value.m_integer != 0; break;
case item_value_type::FLOAT: result = m_data.m_value.m_float != 0; break;
case item_value_type::TEXT: result = m_data.m_len != 0; break;
case item_value_type::MISSING:
case item_value_type::EMPTY: result = false; break;
}
return result;
}
/// Test if contained value is null, i.e. of type @ref cif::item_value_type::INAPPLICABLE or @ref cif::item_value_type::MISSING
[[nodiscard]] constexpr bool is_null() const noexcept { return is_inapplicable() or is_missing(); }
bool empty() const noexcept
/// Test if contained value is a string, i.e. of type @ref cif::item_value_type::TEXT
[[nodiscard]] constexpr bool is_string() const noexcept { return m_data.m_type == item_value_type::TEXT; }
/// Test if contained value is of type @ref cif::item_value_type::INT
[[nodiscard]] constexpr bool is_number_int() const noexcept { return m_data.m_type == item_value_type::INT; }
/// Test if contained value is of type @ref cif::item_value_type::FLOAT
[[nodiscard]] constexpr bool is_number_float() const noexcept { return m_data.m_type == item_value_type::FLOAT; }
/// Test if contained value is a number, i.e. of type @ref cif::item_value_type::INT or @ref cif::item_value_type::FLOAT
[[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); }
/// Return the type of the contained value
[[nodiscard]] constexpr item_value_type type() const { return m_data.m_type; }
/// Return true if the contained value is considered to be 'empty'
[[nodiscard]] bool empty() const noexcept
{
switch (m_data.m_type)
{
case item_value_type::MISSING:
case item_value_type::EMPTY:
using enum item_value_type;
case INAPPLICABLE:
case MISSING:
return true;
case item_value_type::TEXT:
case TEXT:
return m_data.sv().empty();
default:
@@ -267,54 +306,33 @@ class item_value
// --------------------------------------------------------------------
/// Return the string representation of the contained value
template <StringType T>
inline std::string get() const
[[nodiscard]] inline std::string get() const
{
switch (m_data.m_type)
{
case item_value_type::EMPTY:
case item_value_type::MISSING:
return "";
case item_value_type::TEXT:
return std::string{ m_data.sv() };
case cif::item_value_type::BOOLEAN:
return m_data.m_value.m_boolean ? "y" : "n";
default:
{
char b[32];
const auto &[ptr, ec] =
m_data.m_type == item_value_type::INT ? std::to_chars(b, b + sizeof(b), m_data.m_value.m_integer)
: m_data.m_len ? std::to_chars(b, b + sizeof(b), m_data.m_value.m_float, std::chars_format::fixed, m_data.m_len)
: std::to_chars(b, b + sizeof(b), m_data.m_value.m_float, std::chars_format::general);
if (ec != std::errc{})
throw std::system_error(std::make_error_code(ec));
return std::string{ b, ptr };
}
}
return str();
}
/// Return the integer representation of the contained value.
/// Will throw if the value cannot be cast to an integer
template <IntegralType T>
std::remove_cvref_t<T> get() const
[[nodiscard]] std::remove_cvref_t<T> get() const
{
static_assert(not std::is_same_v<std::remove_cvref_t<T>, bool>, "bool is no longer supported");
switch (m_data.m_type)
{
case cif::item_value_type::BOOLEAN:
return m_data.m_value.m_boolean;
case item_value_type::INT:
using enum item_value_type;
case INT:
return m_data.m_value.m_integer;
case item_value_type::FLOAT:
case FLOAT:
return m_data.m_value.m_float;
case item_value_type::TEXT:
case TEXT:
{
auto sv = m_data.sv();
int64_t v;
auto &&[ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.length(), v);
auto &&[ptr, ec] = from_chars(sv.data(), sv.data() + sv.length(), v);
if (ec != std::errc{})
throw std::system_error(std::make_error_code(ec));
if (ptr != sv.data() + sv.length())
@@ -327,22 +345,24 @@ class item_value
}
}
/// Return the floating point representation of the contained value.
/// Will throw if the value cannot be cast to the requested type
template <FloatType T>
std::remove_cvref_t<T> get() const
[[nodiscard]] std::remove_cvref_t<T> get() const
{
switch (m_data.m_type)
{
case cif::item_value_type::BOOLEAN:
return m_data.m_value.m_boolean;
case item_value_type::INT:
using enum item_value_type;
case INT:
return m_data.m_value.m_integer;
case item_value_type::FLOAT:
case FLOAT:
return m_data.m_value.m_float;
case item_value_type::TEXT:
case TEXT:
{
auto sv = m_data.sv();
double v;
auto &&[ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.length(), v);
auto &&[ptr, ec] = from_chars(sv.data(), sv.data() + sv.length(), v);
if (ec != std::errc{})
throw std::system_error(std::make_error_code(ec));
if (ptr != sv.data() + sv.length())
@@ -354,44 +374,44 @@ class item_value
}
}
template <BooleanType T>
std::remove_cvref_t<T> get() const
{
switch (m_data.m_type)
{
case cif::item_value_type::BOOLEAN:
return m_data.m_value.m_boolean;
case item_value_type::INT:
return m_data.m_value.m_integer != 0;
case item_value_type::FLOAT:
return m_data.m_value.m_float != 0.;
case item_value_type::TEXT:
return iequals(m_data.sv(), "y") or iequals(m_data.sv(), "yes") or iequals(m_data.sv(), "true");
default:
return not empty();
}
}
/// Return the std::optional<> wrapped value.
/// The return value will not have a value if the contained
/// value is missing or inapplicable.
/// Will throw if the value cannot be cast to the requested type
template <typename T>
requires is_optional_v<T>
auto get() const
[[nodiscard]] auto get() const
{
using value_type = T::value_type;
switch (m_data.m_type)
{
case item_value_type::MISSING:
case item_value_type::EMPTY:
using enum item_value_type;
case INAPPLICABLE:
case MISSING:
return T{};
default:
value_type v = get<value_type>();
{
auto v = get<typename T::value_type>();
return T{ v };
}
}
}
/// Return the string representation of the contained value.
[[nodiscard]] std::string str() const;
/// Return a std::string_view to the contained value.
/// This will of course throw when the type is not @ref cif::item_value_type::TEXT
[[nodiscard]] const std::string_view sv() const
{
assert(m_data.m_type == cif::item_value_type::TEXT);
return m_data.sv();
}
// --------------------------------------------------------------------
/// Swap two item_values
friend void swap(item_value &a, item_value &b) noexcept
{
std::swap(a.m_data.m_type, b.m_data.m_type);
@@ -400,50 +420,68 @@ class item_value
}
// --------------------------------------------------------------------
// std::partial_ordering operator<=>(const item_value &rhs) const
// {
// if (m_data.m_type == rhs.m_data.m_type)
// {
// switch (m_data.m_type)
// {
// case item_value_type::BOOLEAN: return m_data.m_value.m_boolean <=> rhs.m_data.m_value.m_boolean;
// case item_value_type::INT: return m_data.m_value.m_integer <=> rhs.m_data.m_value.m_integer;
// case item_value_type::FLOAT: return m_data.m_value.m_float <=> rhs.m_data.m_value.m_float;
// case item_value_type::TEXT: return m_data.sv() <=> rhs.m_data.sv();
// case item_value_type::MISSING:
// case item_value_type::EMPTY: return std::strong_ordering::equivalent;
// }
// }
// else
// return m_data.m_type <=> rhs.m_data.m_type;
// }
/// Three way comparison operator
auto operator<=>(const item_value &rhs) const noexcept
{
std::partial_ordering result = std::partial_ordering::unordered;
if (m_data.m_type == rhs.m_data.m_type)
{
switch (m_data.m_type)
{
using enum item_value_type;
case INT: result = m_data.m_value.m_integer <=> rhs.m_data.m_value.m_integer;
case FLOAT: result = m_data.m_value.m_float <=> rhs.m_data.m_value.m_float;
case TEXT: result = m_data.sv() <=> rhs.m_data.sv();
default: result = std::partial_ordering::equivalent;
}
}
else
result = m_data.m_type <=> rhs.m_data.m_type;
return result;
}
/// Three way comparison operator is not always found TODO: find out why
bool operator==(const item_value &rhs) const
{
if (m_data.m_type == rhs.m_data.m_type)
{
switch (m_data.m_type)
{
case item_value_type::BOOLEAN: return m_data.m_value.m_boolean == rhs.m_data.m_value.m_boolean;
case item_value_type::INT: return m_data.m_value.m_integer == rhs.m_data.m_value.m_integer;
case item_value_type::FLOAT: return m_data.m_value.m_float == rhs.m_data.m_value.m_float;
case item_value_type::TEXT: return m_data.sv() == rhs.m_data.sv();
case item_value_type::MISSING:
case item_value_type::EMPTY: return true;
using enum item_value_type;
case INT: return m_data.m_value.m_integer == rhs.m_data.m_value.m_integer;
case FLOAT: return m_data.m_value.m_float == rhs.m_data.m_value.m_float;
case TEXT: return m_data.sv() == rhs.m_data.sv();
case INAPPLICABLE:
case MISSING: return true;
}
}
return false;
}
int compare(const item_value &b, bool ignore_case = false) const noexcept;
/// Compare the value of this item_value with b, optionally ignoring case
[[nodiscard]] int compare(const item_value &b, bool ignore_case = false) const noexcept;
/// For debugging, print out a value
friend std::ostream &operator<<(std::ostream &os, const item_value &v);
/// \brief Cast the value to an integer, will throw if not possible
void cast_to_int();
/// \brief Cast the value to a float, will throw if not possible
void cast_to_float();
/// \brief Cast the value to a string, may throw (when value is null)
void cast_to_string();
/// @cond
private:
union value
{
bool m_boolean;
int64_t m_integer{};
double m_float;
char m_local_str[8];
@@ -451,11 +489,6 @@ class item_value
value() = default;
value(bool v)
: m_boolean(v)
{
}
value(int64_t v)
: m_integer(v)
{
@@ -475,7 +508,7 @@ class item_value
m_str[s.length()] = 0;
}
else
memcpy(m_local_str, s.data(), s.length() + 1);
std::memcpy(m_local_str, s.data(), s.length() + 1);
}
value(item_value_type t)
@@ -492,7 +525,7 @@ class item_value
struct data
{
item_value_type m_type = item_value_type::EMPTY;
item_value_type m_type = item_value_type::MISSING;
uint32_t m_len{};
value m_value{};
@@ -519,20 +552,25 @@ class item_value
m_value.destroy(m_type, m_len);
}
std::string_view sv() const noexcept
[[nodiscard]] std::string_view sv() const noexcept
{
assert(m_type == item_value_type::TEXT);
return m_type == item_value_type::TEXT ? std::string_view(m_len >= sizeof(m_value.m_local_str) ? m_value.m_str : m_value.m_local_str, m_len) : std::string_view{};
}
const char *c_str() const noexcept
[[nodiscard]] const char *c_str() const noexcept
{
assert(m_type == item_value_type::TEXT);
return m_type == item_value_type::TEXT ? (m_len >= sizeof(m_value.m_local_str) ? m_value.m_str : m_value.m_local_str) : nullptr;
}
} m_data{};
/// @endcond
};
static_assert(sizeof(item_value) == 16, "item_value should be 16 bytes");
class item
{
public:
@@ -543,10 +581,11 @@ class item
/// content the character '.', i.e. an inapplicable value.
item(std::string name)
: m_name(std::move(name))
, m_value(item_value_type::EMPTY)
, m_value(item_value_type::MISSING)
{
}
/// Constructor using @a name and @a value
item(std::string name, item_value value)
: m_name(std::move(name))
, m_value(std::move(value))
@@ -554,11 +593,7 @@ class item
}
/** @cond */
item(const item &rhs)
: m_name(rhs.m_name)
, m_value(rhs.m_value)
{
}
item(const item &rhs) = default;
item(item &&rhs)
{
@@ -572,27 +607,28 @@ class item
}
/** @endcond */
/// Swap two items
friend void swap(item &a, item &b) noexcept
{
std::swap(a.m_name, b.m_name);
std::swap(a.m_value, b.m_value);
}
const std::string &name() const { return m_name; } ///< Return the name of the item
const item_value &value() const & { return m_value; } ///< Return the value of the item
item_value &value() & { return m_value; } ///< Return the value of the item
[[nodiscard]] const std::string &name() const { return m_name; } ///< Return the name of the item
[[nodiscard]] const item_value &value() const & { return m_value; } ///< Return the value of the item
item_value &value() & { return m_value; } ///< Return the value of the item
/// \brief replace the content of the stored value with \a v
void value(item_value v) { m_value = std::move(v); }
/// \brief empty means either null or unknown
bool empty() const { return m_value.empty(); }
[[nodiscard]] bool empty() const { return m_value.empty(); }
/// \brief returns true if the item contains '.'
bool is_null() const { return m_value.is_null(); }
/// \brief returns true if the item contains '.' or '?'
[[nodiscard]] bool is_null() const { return m_value.is_null(); }
/// \brief returns true if the item contains '?'
bool is_unknown() const { return m_value.is_empty(); }
[[nodiscard]] bool is_unknown() const { return m_value.is_missing(); }
// /// \brief the length of the value string
// std::size_t length() const { return m_value.length(); }
@@ -615,16 +651,243 @@ class item
};
// --------------------------------------------------------------------
// Item Handle is an intermediate object, used to access values in a row
/// \brief This is item_handle, it is used to access the data stored in
/// item_value's in rows
class item_handle
struct item_handle
{
public:
item_handle(item_handle &rhs);
item_handle() = delete;
/**
* @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
*/
item_handle &operator=(item_value value)
{
set(std::move(value), true);
return *this;
}
/// Return the value of the item
[[nodiscard]] item_value &value();
/// Return the const value of the item
[[nodiscard]] const item_value &value() const;
/// Return if value in item is of type INAPPLICABLE
[[nodiscard]] bool is_inapplicable() const noexcept
{
return not empty() and value().type() == item_value_type::INAPPLICABLE;
}
/// Return if value in item is of type MISSING
[[nodiscard]] bool is_missing() const noexcept
{
return empty() or value().type() == item_value_type::MISSING;
}
/// Return if value in item is NULL (MISSING or INAPPLICABLE)
[[nodiscard]] bool is_null() const noexcept
{
return empty() or is_inapplicable() or is_missing();
}
/// Return if value in item is of type TEXT
[[nodiscard]] bool is_string() const noexcept
{
return not empty() and value().type() == item_value_type::TEXT;
}
/// Return if value in item is an integer
[[nodiscard]] bool is_number_int() const noexcept
{
return not empty() and value().type() == item_value_type::INT;
}
/// Return if value in item is a double
[[nodiscard]] bool is_number_float() const noexcept
{
return not empty() and value().type() == item_value_type::FLOAT;
}
/// Return if value in item is a number
[[nodiscard]] bool is_number() const noexcept
{
return not empty() and (is_number_int() or is_number_float());
}
/// Return type of the value
[[nodiscard]] auto type() const
{
return empty() ? item_value_type::MISSING : value().type();
}
/// Return the value casted to the type @tparam T
template <typename T>
[[nodiscard]] auto get() const
{
if (empty())
return T{};
else
return value().template get<T>();
}
/// Return the value casted to the type @tparam T, same as get
template <typename T>
[[deprecated("Use get<T> instead")]] [[nodiscard]] auto as() const
{
if (empty())
return T{};
else
return value().template get<T>();
}
/// Return the value as a std::string
[[nodiscard]] auto str() const
{
return value().str();
}
/// Return a std::string_view to the character data in the value, throws if the type is not TEXT
[[nodiscard]] auto sv() const
{
return value().sv();
}
/** Swap contents of @a a and @a b */
friend void swap(item_handle a, item_handle b) noexcept;
/** Return the contents of this item as type @tparam T or, if not
* set, use @a dv as the default value.
*/
template <typename T>
[[nodiscard]] auto value_or(const T &dv) const
{
return empty() ? dv : this->get<T>();
}
/**
* @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
*
* @param value The value to compare with
* @param icase Flag indicating if we should compare character case sensitive
* @return -1, 0 or 1
*/
[[nodiscard]] int compare(const item_value &value, bool icase = true) const noexcept
{
return this->value().compare(value, icase);
}
/**
* @brief Compare the contents of this item with value of item @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
*
* @param value The value to compare with
* @param icase Flag indicating if we should compare character case sensitive
* @return -1, 0 or 1
*/
[[nodiscard]] int compare(const item_handle &value, bool icase = true) const noexcept
{
if (empty() and value.empty())
return 0;
else if (empty())
return -1;
else if (value.empty())
return 1;
else
return compare(value.value(), icase);
}
/**
* @brief Compare the value contained with the value @a value and
* return true if both are equal.
*/
[[nodiscard]] bool operator==(const item_value &value) const noexcept
{
// TODO: icase or not icase?
return this->value().compare(value) == 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 <typename T>
[[nodiscard]] bool operator!=(const T &value) const noexcept
{
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
*/
[[nodiscard]] bool empty() const;
/** Return a std::string_view for the contents */
[[nodiscard]] std::string_view text_() const;
/**
* @brief Construct a new item handle object
*
* @param cat Reference to category containing row
* @param row Reference to the row
* @param item_ix Item index
*/
item_handle(category &cat, row &row, uint16_t item_ix)
: m_category(cat)
, m_row(row)
, m_item_ix(item_ix)
{
}
/// Constructor
item_handle(const category &cat, const row &r, uint16_t item_ix)
: m_category(const_cast<category &>(cat))
, m_row(const_cast<row &>(r))
, m_item_ix(item_ix)
, m_is_const(true)
{
}
item_handle(const item_handle &) = delete;
item_handle &operator=(const item_handle &) = delete;
/// Print out the item, for debugging
friend std::ostream &operator<<(std::ostream &os, const item_handle &h)
{
if (h.empty())
os << "NULL";
else
os << h.value();
return os;
}
private:
category &m_category;
row &m_row;
uint16_t m_item_ix;
bool m_is_const = false;
friend class parser;
void set(item_value value, bool updateLinked);
};
} // namespace cif
namespace std
@@ -650,6 +913,6 @@ struct tuple_element<1, ::cif::item>
using type = decltype(std::declval<::cif::item>().value());
};
/** @endcond */
/// @endcond
} // namespace std
} // namespace std

View File

@@ -30,6 +30,9 @@
#include "cif++/row.hpp"
#include <array>
#include <cstdint>
#include <numeric>
#include <type_traits>
/**
* @file iterator.hpp
@@ -45,6 +48,8 @@
namespace cif
{
class category;
// --------------------------------------------------------------------
/**
@@ -56,13 +61,13 @@ namespace cif
* @tparam Category The category for this iterator
* @tparam Ts The types this iterator can be dereferenced to
*/
template <typename Category, typename... Ts>
class iterator_impl
template <bool Const, typename... Ts>
class iterator_impl_base
{
public:
/** @cond */
template <typename, typename...>
friend class iterator_impl;
template <bool, typename...>
friend class iterator_impl_base;
friend class category;
/** @endcond */
@@ -71,48 +76,47 @@ class iterator_impl
static constexpr std::size_t N = sizeof...(Ts);
/** @cond */
using category_type = std::remove_cv_t<Category>;
using row_type = std::conditional_t<std::is_const_v<Category>, const row, row>;
using tuple_type = std::tuple<Ts...>;
using row_handle_type = std::conditional_t<Const, const_row_handle, row_handle>;
using iterator_category = std::forward_iterator_tag;
using value_type = tuple_type;
using value_type = std::conditional_t<Const, const tuple_type, tuple_type>;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
iterator_impl() = default;
iterator_impl_base() = default;
iterator_impl(const iterator_impl &rhs) = default;
iterator_impl(iterator_impl &&rhs) = default;
iterator_impl_base(const iterator_impl_base &rhs) = default;
iterator_impl_base(iterator_impl_base &&rhs) = default;
template <typename C2, typename... T2s>
iterator_impl(const iterator_impl<C2, T2s...> &rhs)
: m_current(const_cast<row_handle&>(rhs.m_current))
template <bool C, typename... T2s>
iterator_impl_base(const iterator_impl_base<C, T2s...> &rhs)
: m_current(rhs.m_current)
, m_value(rhs.m_value)
, m_item_ix(rhs.m_item_ix)
{
}
template <typename IRowType>
iterator_impl(iterator_impl<IRowType, Ts...> &rhs)
: m_current(const_cast<row_handle&>(rhs.m_current))
template <bool C>
iterator_impl_base(iterator_impl_base<C, Ts...> &rhs)
: m_current(rhs.m_current)
, m_value(rhs.m_value)
, m_item_ix(rhs.m_item_ix)
{
m_value = get(std::make_index_sequence<N>());
}
template <typename IRowType>
iterator_impl(const iterator_impl<IRowType> &rhs, const std::array<uint16_t, N> &cix)
: m_current(const_cast<row_handle&>(rhs.m_current))
template <bool C>
iterator_impl_base(const iterator_impl_base<C> &rhs, const std::array<uint16_t, N> &cix)
: m_current(rhs.m_current)
, m_item_ix(cix)
{
m_value = get(std::make_index_sequence<N>());
}
iterator_impl &operator=(iterator_impl i)
iterator_impl_base &operator=(iterator_impl_base i)
{
std::swap(m_current, i.m_current);
std::swap(m_item_ix, i.m_item_ix);
@@ -120,29 +124,39 @@ class iterator_impl
return *this;
}
virtual ~iterator_impl() = default;
virtual ~iterator_impl_base() = default;
reference operator*()
auto operator*()
{
return m_value;
}
pointer operator->()
auto operator*() const
{
return m_value;
}
auto operator->()
{
return &m_value;
}
operator const row_handle() const
auto operator->() const
{
return &m_value;
}
operator const_row_handle() const
{
return m_current;
}
operator row_handle()
operator row_handle_type()
{
return m_current;
}
iterator_impl &operator++()
iterator_impl_base &operator++()
{
if (m_current)
m_current.m_row = m_current.m_row->m_next;
@@ -152,24 +166,24 @@ class iterator_impl
return *this;
}
iterator_impl operator++(int)
iterator_impl_base operator++(int)
{
iterator_impl result(*this);
iterator_impl_base 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; }
bool operator==(const iterator_impl_base &rhs) const { return m_current == rhs.m_current; }
bool operator!=(const iterator_impl_base &rhs) const { return m_current != rhs.m_current; }
template <typename IRowType, typename... ITs>
bool operator==(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator==(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current == rhs.m_current;
}
template <typename IRowType, typename... ITs>
bool operator!=(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator!=(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current != rhs.m_current;
}
@@ -178,13 +192,13 @@ class iterator_impl
private:
template <std::size_t... Is>
tuple_type get(std::index_sequence<Is...>) const
[[nodiscard]] tuple_type get(std::index_sequence<Is...>) const
{
return m_current ? tuple_type{ m_current[m_item_ix[Is]].template get<Ts>()... } : tuple_type{};
}
row_handle m_current;
value_type m_value;
row_handle_type m_current;
tuple_type m_value;
std::array<uint16_t, N> m_item_ix;
};
@@ -194,76 +208,94 @@ class iterator_impl
*
* @tparam Category The category for this iterator
*/
template <typename Category>
class iterator_impl<Category>
template <bool Const>
class iterator_impl_base<Const>
{
public:
/** @cond */
template <typename, typename...>
friend class iterator_impl;
template <bool, typename...>
friend class iterator_impl_base;
friend class category;
using category_type = std::remove_cv_t<Category>;
using row_type = std::conditional_t<std::is_const_v<Category>, const row, row>;
using category_type = std::conditional_t<Const, const category, category>;
using row_type = std::conditional_t<Const, const row, row>;
using row_handle_type = std::conditional_t<Const, const_row_handle, row_handle>;
using iterator_category = std::forward_iterator_tag;
using value_type = row_handle;
using value_type = std::conditional_t<Const, const_row_handle, row_handle>;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
iterator_impl() = default;
iterator_impl_base() = default;
iterator_impl(const iterator_impl &rhs) = default;
iterator_impl(iterator_impl &&rhs) = default;
iterator_impl_base(const iterator_impl_base &rhs) = default;
iterator_impl_base(iterator_impl_base &&rhs) = default;
template <typename C2>
iterator_impl(const iterator_impl<C2> &rhs)
: m_current(const_cast<row_handle &>(rhs.m_current))
template <bool C>
iterator_impl_base(const iterator_impl_base<C> &rhs)
: m_current(rhs.m_current)
{
}
iterator_impl(Category &cat, row *current)
: m_current(cat, *current)
iterator_impl_base(const category_type &cat, const row_type *current)
: m_current(const_cast<category &>(cat), const_cast<row_type &>(*current))
{
}
template <typename IRowType>
iterator_impl(const iterator_impl<IRowType> &rhs, const std::array<uint16_t, 0> &)
: m_current(const_cast<row_handle &>(rhs.m_current))
template <bool C>
iterator_impl_base(const iterator_impl_base<C> &rhs, const std::array<uint16_t, 0> &)
: m_current(rhs.m_current)
{
}
iterator_impl &operator=(iterator_impl i)
iterator_impl_base &operator=(iterator_impl_base i)
{
std::swap(m_current, i.m_current);
return *this;
}
virtual ~iterator_impl() = default;
virtual ~iterator_impl_base() = default;
reference operator*()
auto operator*()
{
return m_current;
}
pointer operator->()
auto operator*() const
{
return m_current;
}
auto operator->()
{
return &m_current;
}
operator const row_handle() const
auto operator->() const
{
return &m_current;
}
operator const_row_handle() const
{
return m_current;
}
operator row_handle()
operator row_handle_type()
{
return m_current;
}
iterator_impl &operator++()
[[nodiscard]] int64_t row_id() const
{
return reinterpret_cast<int64_t>(m_current.m_row);
}
iterator_impl_base &operator++()
{
if (m_current)
m_current.m_row = m_current.m_row->m_next;
@@ -271,24 +303,24 @@ class iterator_impl<Category>
return *this;
}
iterator_impl operator++(int)
iterator_impl_base operator++(int)
{
iterator_impl result(*this);
iterator_impl_base 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; }
bool operator==(const iterator_impl_base &rhs) const { return m_current == rhs.m_current; }
bool operator!=(const iterator_impl_base &rhs) const { return m_current != rhs.m_current; }
template <typename IRowType, typename... ITs>
bool operator==(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator==(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current == rhs.m_current;
}
template <typename IRowType, typename... ITs>
bool operator!=(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator!=(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current != rhs.m_current;
}
@@ -296,7 +328,7 @@ class iterator_impl<Category>
/** @endcond */
private:
row_handle m_current;
row_handle_type m_current;
};
/**
@@ -307,18 +339,18 @@ class iterator_impl<Category>
* @tparam T The type this iterator can be dereferenced to
*/
template <typename Category, typename T>
class iterator_impl<Category, T>
template <bool Const, typename T>
class iterator_impl_base<Const, T>
{
public:
/** @cond */
template <typename, typename...>
friend class iterator_impl;
template <bool, typename...>
friend class iterator_impl_base;
friend class category;
using category_type = std::remove_cv_t<Category>;
using row_type = std::conditional_t<std::is_const_v<Category>, const row, row>;
using category_type = std::conditional_t<Const, const category, category>;
using row_handle_type = std::conditional_t<Const, const_row_handle, row_handle>;
using iterator_category = std::forward_iterator_tag;
using value_type = T;
@@ -326,37 +358,37 @@ class iterator_impl<Category, T>
using pointer = value_type *;
using reference = value_type &;
iterator_impl() = default;
iterator_impl_base() = default;
iterator_impl(const iterator_impl &rhs) = default;
iterator_impl(iterator_impl &&rhs) = default;
iterator_impl_base(const iterator_impl_base &rhs) = default;
iterator_impl_base(iterator_impl_base &&rhs) = default;
template <typename C2, typename T2>
iterator_impl(const iterator_impl<C2, T2> &rhs)
template <bool C, typename T2>
iterator_impl_base(const iterator_impl_base<C, T2> &rhs)
: m_current(rhs.m_current)
, m_value(rhs.m_value)
, m_item_ix(rhs.m_item_ix)
{
}
template <typename IRowType>
iterator_impl(iterator_impl<IRowType, T> &rhs)
: m_current(const_cast<row_handle&>(rhs.m_current))
template <bool C>
iterator_impl_base(iterator_impl_base<C, T> &rhs)
: m_current(rhs.m_current)
, m_value(rhs.m_value)
, m_item_ix(rhs.m_item_ix)
{
m_value = get();
}
template <typename IRowType>
iterator_impl(const iterator_impl<IRowType> &rhs, const std::array<uint16_t, 1> &cix)
: m_current(const_cast<row_handle&>(rhs.m_current))
template <bool C>
iterator_impl_base(const iterator_impl_base<C> &rhs, const std::array<uint16_t, 1> &cix)
: m_current(rhs.m_current)
, m_item_ix(cix[0])
{
m_value = get();
}
iterator_impl &operator=(iterator_impl i)
iterator_impl_base &operator=(iterator_impl_base i)
{
std::swap(m_current, i.m_current);
std::swap(m_item_ix, i.m_item_ix);
@@ -364,29 +396,39 @@ class iterator_impl<Category, T>
return *this;
}
virtual ~iterator_impl() = default;
virtual ~iterator_impl_base() = default;
reference operator*()
auto operator*()
{
return m_value;
}
pointer operator->()
auto operator*() const
{
return m_value;
}
auto operator->()
{
return &m_value;
}
operator const row_handle() const
auto operator->() const
{
return &m_value;
}
operator const_row_handle() const
{
return m_current;
}
operator row_handle()
operator row_handle_type()
{
return m_current;
}
iterator_impl &operator++()
iterator_impl_base &operator++()
{
if (m_current)
m_current.m_row = m_current.m_row->m_next;
@@ -396,24 +438,24 @@ class iterator_impl<Category, T>
return *this;
}
iterator_impl operator++(int)
iterator_impl_base operator++(int)
{
iterator_impl result(*this);
iterator_impl_base 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; }
bool operator==(const iterator_impl_base &rhs) const { return m_current == rhs.m_current; }
bool operator!=(const iterator_impl_base &rhs) const { return m_current != rhs.m_current; }
template <typename IRowType, typename... ITs>
bool operator==(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator==(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current == rhs.m_current;
}
template <typename IRowType, typename... ITs>
bool operator!=(const iterator_impl<IRowType, ITs...> &rhs) const
template <bool C, typename... ITs>
bool operator!=(const iterator_impl_base<C, ITs...> &rhs) const
{
return m_current != rhs.m_current;
}
@@ -421,16 +463,26 @@ class iterator_impl<Category, T>
/** @endcond */
private:
value_type get() const
[[nodiscard]] value_type get() const
{
return m_current ? m_current[m_item_ix].template get<value_type>() : value_type{};
}
row_handle m_current;
row_handle_type m_current;
value_type m_value;
uint16_t m_item_ix;
};
// --------------------------------------------------------------------
/// A non-const version of iterator_impl
template<typename ... Ts>
using iterator_impl = iterator_impl_base<false, Ts...>;
/// A const version of iterator_impl
template<typename ... Ts>
using const_iterator_impl = iterator_impl_base<true, Ts...>;
// --------------------------------------------------------------------
// iterator proxy
@@ -446,43 +498,42 @@ class iterator_impl<Category, T>
* @tparam Ts The types the iterators return. See class: iterator
*/
template <typename Category, typename... Ts>
class iterator_proxy
template <bool Const, typename... Ts>
class iterator_proxy_base
{
public:
/** @cond */
static constexpr const std::size_t N = sizeof...(Ts);
using category_type = Category;
using row_type = std::conditional_t<std::is_const_v<category_type>, const row, row>;
using category_type = std::conditional_t<Const, const category, category>;
using iterator = iterator_impl<category_type, Ts...>;
using row_iterator = iterator_impl<category_type>;
using iterator = iterator_impl_base<Const, Ts...>;
using row_iterator = iterator_impl_base<Const>;
iterator_proxy(category_type &cat, row_iterator pos, char const *const items[N]);
iterator_proxy(category_type &cat, row_iterator pos, std::initializer_list<char const *> items);
iterator_proxy_base(category_type &cat, row_iterator pos, char const *const items[N]);
iterator_proxy_base(category_type &cat, row_iterator pos, std::initializer_list<char const *> items); // NOLINT(modernize-pass-by-value)
iterator_proxy(iterator_proxy &&p);
iterator_proxy &operator=(iterator_proxy &&p);
iterator_proxy_base(iterator_proxy_base &&p);
iterator_proxy_base &operator=(iterator_proxy_base &&p);
iterator_proxy(const iterator_proxy &) = delete;
iterator_proxy &operator=(const iterator_proxy &) = delete;
iterator_proxy_base(const iterator_proxy_base &) = delete;
iterator_proxy_base &operator=(const iterator_proxy_base &) = 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
[[nodiscard]] iterator begin() const { return iterator(m_begin, m_item_ix); } ///< Return the iterator pointing to the first row
[[nodiscard]] 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
[[nodiscard]] 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
[[nodiscard]] 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
[[nodiscard]] category_type &get_category() const { return *m_category; } ///< Return the category the iterator belong to
/** swap */
void swap(iterator_proxy &rhs)
void swap(iterator_proxy_base &rhs)
{
std::swap(m_category, rhs.m_category);
std::swap(m_begin, rhs.m_begin);
@@ -490,12 +541,27 @@ class iterator_proxy
std::swap(m_item_ix, rhs.m_item_ix);
}
protected:
/// @cond
iterator_proxy_base(category_type &cat);
/// @endcond
private:
category_type *m_category;
row_iterator m_begin, m_end;
std::array<uint16_t, N> m_item_ix;
};
// --------------------------------------------------------------------
/// A non-const version of iterator_proxy_base
template <typename... Ts>
using iterator_proxy = iterator_proxy_base<false, Ts...>;
/// A const version of iterator_proxy_base
template <typename... Ts>
using const_iterator_proxy = iterator_proxy_base<true, Ts...>;
// --------------------------------------------------------------------
// conditional iterator proxy
@@ -505,44 +571,54 @@ class iterator_proxy
* 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 category_type The category the iterators belong to
* @tparam Ts The types to which the iterators can be dereferenced
*/
template <typename CategoryType, typename... Ts>
class conditional_iterator_proxy
template <bool Const, typename... Ts>
class conditional_iterator_proxy_base
{
public:
/** @cond */
static constexpr const std::size_t N = sizeof...(Ts);
using category_type = std::remove_cv_t<CategoryType>;
using base_iterator = iterator_impl<CategoryType, Ts...>;
using category_type = std::conditional_t<Const, const category, category>;
using base_iterator = iterator_impl_base<Const, Ts...>;
using value_type = typename base_iterator::value_type;
using row_type = typename base_iterator::row_type;
using row_iterator = iterator_impl<CategoryType>;
using row_iterator = iterator_impl_base<Const>;
class conditional_iterator_impl
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = conditional_iterator_proxy::value_type;
using value_type = conditional_iterator_proxy_base::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<uint16_t, N> &cix);
conditional_iterator_impl() = default;
conditional_iterator_impl(category_type &cat, row_iterator pos, const condition &cond, const std::array<uint16_t, N> &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*()
auto operator*()
{
return *m_begin;
}
pointer operator->()
auto operator*() const
{
return *m_begin;
}
auto operator->()
{
m_current = *m_begin;
return &m_current;
}
auto operator->() const
{
m_current = *m_begin;
return &m_current;
@@ -575,16 +651,16 @@ class conditional_iterator_proxy
bool operator==(const row_iterator &rhs) const { return m_begin == rhs; }
bool operator!=(const row_iterator &rhs) const { return m_begin != rhs; }
template <typename IRowType, typename... ITs>
bool operator==(const iterator_impl<IRowType, ITs...> &rhs) const { return m_begin == rhs; }
template <bool C, typename... ITs>
bool operator==(const iterator_impl_base<C, ITs...> &rhs) const { return m_begin == rhs; }
template <typename IRowType, typename... ITs>
bool operator!=(const iterator_impl<IRowType, ITs...> &rhs) const { return m_begin != rhs; }
template <bool C, typename... ITs>
bool operator!=(const iterator_impl_base<C, ITs...> &rhs) const { return m_begin != rhs; }
private:
CategoryType *m_cat;
category_type *m_cat = nullptr;
base_iterator m_begin, m_end;
value_type m_current;
std::remove_cv_t<value_type> m_current;
const condition *m_condition;
};
@@ -592,33 +668,42 @@ class conditional_iterator_proxy
using reference = typename iterator::reference;
template <typename... Ns>
conditional_iterator_proxy(CategoryType &cat, row_iterator pos, condition &&cond, Ns... names);
conditional_iterator_proxy_base(category_type &cat, row_iterator pos, condition &&cond, Ns... names); // NOLINT(modernize-pass-by-value)
conditional_iterator_proxy(conditional_iterator_proxy &&p);
conditional_iterator_proxy &operator=(conditional_iterator_proxy &&p);
conditional_iterator_proxy_base(conditional_iterator_proxy_base &&p)
{
swap(*this, p);
}
conditional_iterator_proxy(const conditional_iterator_proxy &) = delete;
conditional_iterator_proxy &operator=(const conditional_iterator_proxy &) = delete;
conditional_iterator_proxy_base &operator=(conditional_iterator_proxy_base &&p)
{
swap(*this, p);
return *this;
}
conditional_iterator_proxy_base(const conditional_iterator_proxy_base &) = delete;
conditional_iterator_proxy_base &operator=(const conditional_iterator_proxy_base &) = delete;
/** @endcond */
iterator begin() const; ///< Return the iterator pointing to the first row
iterator end() const; ///< Return the iterator pointing past the last row
[[nodiscard]] iterator begin() const; ///< Return the iterator pointing to the first row
[[nodiscard]] iterator end() const; ///< Return the iterator pointing past the last row
bool empty() const; ///< Return true if the range is empty
[[nodiscard]] 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
[[nodiscard]] 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
auto 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
[[nodiscard]] category_type &get_category() const { return *m_cat; } ///< Category the iterators belong to
/** swap */
void swap(conditional_iterator_proxy &rhs);
template <bool C2, typename ... T2s>
friend void swap(conditional_iterator_proxy_base<C2, T2s...> &lhs, conditional_iterator_proxy_base<C2, T2s...> &rhs);
private:
CategoryType *m_cat;
category_type *m_cat;
condition m_condition;
row_iterator mCBegin, mCEnd;
std::array<uint16_t, N> mCix;
@@ -626,9 +711,19 @@ class conditional_iterator_proxy
// --------------------------------------------------------------------
/// A non-const version of conditional_iterator_proxy_base
template <typename... Ts>
using conditional_iterator_proxy = conditional_iterator_proxy_base<false, Ts...>;
/// A const version of conditional_iterator_proxy_base
template <typename... Ts>
using const_conditional_iterator_proxy = conditional_iterator_proxy_base<true, Ts...>;
// --------------------------------------------------------------------
/** @cond */
template <typename Category, typename... Ts>
iterator_proxy<Category, Ts...>::iterator_proxy(Category &cat, row_iterator pos, char const *const items[N])
template <bool Const, typename... Ts>
iterator_proxy_base<Const, Ts...>::iterator_proxy_base(category_type &cat, row_iterator pos, char const *const items[N])
: m_category(&cat)
, m_begin(pos)
, m_end(cat.end())
@@ -637,8 +732,8 @@ iterator_proxy<Category, Ts...>::iterator_proxy(Category &cat, row_iterator pos,
m_item_ix[i] = m_category->get_item_ix(items[i]);
}
template <typename Category, typename... Ts>
iterator_proxy<Category, Ts...>::iterator_proxy(Category &cat, row_iterator pos, std::initializer_list<char const *> items)
template <bool Const, typename... Ts>
iterator_proxy_base<Const, Ts...>::iterator_proxy_base(category_type &cat, row_iterator pos, std::initializer_list<char const *> items)
: m_category(&cat)
, m_begin(pos)
, m_end(cat.end())
@@ -650,11 +745,20 @@ iterator_proxy<Category, Ts...>::iterator_proxy(Category &cat, row_iterator pos,
m_item_ix[i++] = m_category->get_item_ix(item);
}
template <bool Const, typename... Ts>
iterator_proxy_base<Const, Ts...>::iterator_proxy_base(category_type &cat)
: m_category(&cat)
, m_begin(cat.begin())
, m_end(cat.end())
{
std::iota(m_item_ix.begin(), m_item_ix.end(), 0);
}
// --------------------------------------------------------------------
template <typename Category, typename... Ts>
conditional_iterator_proxy<Category, Ts...>::conditional_iterator_impl::conditional_iterator_impl(
Category &cat, row_iterator pos, const condition &cond, const std::array<uint16_t, N> &cix)
template <bool Const, typename... Ts>
conditional_iterator_proxy_base<Const, Ts...>::conditional_iterator_impl::conditional_iterator_impl(
category_type &cat, row_iterator pos, const condition &cond, const std::array<uint16_t, N> &cix)
: m_cat(&cat)
, m_begin(pos, cix)
, m_end(cat.end(), cix)
@@ -662,23 +766,13 @@ conditional_iterator_proxy<Category, Ts...>::conditional_iterator_impl::conditio
{
if (m_condition == nullptr or m_condition->empty())
m_begin = m_end;
else
m_current = *m_begin;
}
template <typename Category, typename... Ts>
conditional_iterator_proxy<Category, Ts...>::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 <typename Category, typename... Ts>
template <bool Const, typename... Ts>
template <typename... Ns>
conditional_iterator_proxy<Category, Ts...>::conditional_iterator_proxy(Category &cat, row_iterator pos, condition &&cond, Ns... names)
conditional_iterator_proxy_base<Const, Ts...>::conditional_iterator_proxy_base(category_type &cat, row_iterator pos, condition &&cond, Ns... names)
: m_cat(&cat)
, m_condition(std::move(cond))
, mCBegin(pos)
@@ -686,10 +780,8 @@ conditional_iterator_proxy<Category, Ts...>::conditional_iterator_proxy(Category
{
static_assert(sizeof...(Ts) == sizeof...(Ns), "Number of item names should be equal to number of requested value types");
if (m_condition)
if (m_condition and m_condition.prepare(cat))
{
m_condition.prepare(cat);
while (mCBegin != mCEnd and not m_condition(*mCBegin))
++mCBegin;
}
@@ -700,41 +792,39 @@ conditional_iterator_proxy<Category, Ts...>::conditional_iterator_proxy(Category
((mCix[i++] = m_cat->get_item_ix(names)), ...);
}
template <typename Category, typename... Ts>
conditional_iterator_proxy<Category, Ts...> &conditional_iterator_proxy<Category, Ts...>::operator=(conditional_iterator_proxy &&p)
template <bool Const, typename... Ts>
auto conditional_iterator_proxy_base<Const, Ts...>::begin() const -> iterator
{
swap(p);
return *this;
return iterator{ *m_cat, mCBegin, m_condition, mCix };
}
template <typename Category, typename... Ts>
typename conditional_iterator_proxy<Category, Ts...>::iterator conditional_iterator_proxy<Category, Ts...>::begin() const
template <bool Const, typename... Ts>
auto conditional_iterator_proxy_base<Const, Ts...>::end() const -> iterator
{
return iterator(*m_cat, mCBegin, m_condition, mCix);
return iterator{ *m_cat, mCEnd, m_condition, mCix };
}
template <typename Category, typename... Ts>
typename conditional_iterator_proxy<Category, Ts...>::iterator conditional_iterator_proxy<Category, Ts...>::end() const
{
return iterator(*m_cat, mCEnd, m_condition, mCix);
}
template <typename Category, typename... Ts>
bool conditional_iterator_proxy<Category, Ts...>::empty() const
template <bool Const, typename... Ts>
bool conditional_iterator_proxy_base<Const, Ts...>::empty() const
{
return mCBegin == mCEnd;
}
template <typename Category, typename... Ts>
void conditional_iterator_proxy<Category, Ts...>::swap(conditional_iterator_proxy &rhs)
template <bool Const, typename... Ts>
void swap(conditional_iterator_proxy_base<Const, Ts...> &lhs, conditional_iterator_proxy_base<Const, Ts...> &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);
std::swap(lhs.m_cat, rhs.m_cat);
std::swap(lhs.m_condition, rhs.m_condition);
std::swap(lhs.mCBegin, rhs.mCBegin);
std::swap(lhs.mCEnd, rhs.mCEnd);
std::swap(lhs.mCix, rhs.mCix);
}
// --------------------------------------------------------------------
// template <bool Const, typename... Ts>
/** @endcond */
} // namespace cif
} // namespace cif

View File

@@ -26,722 +26,4 @@
#pragma once
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <ostream>
#include <tuple>
#include <type_traits>
#include <vector>
/**
* @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 <typename M>
class matrix_expression
{
public:
constexpr std::size_t dim_m() const { return static_cast<const M &>(*this).dim_m(); } ///< Return the size (dimension) in direction m
constexpr std::size_t dim_n() const { return static_cast<const M &>(*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<M &>(*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<const M &>(*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;
}
template <typename M2>
constexpr bool operator==(const matrix_expression<M2> &m) const
{
bool same = false;
if (dim_m() == m.dim_m() and dim_n() == m.dim_n())
{
same = true;
for (std::size_t i = 0; same and i < m.dim_m(); ++i)
{
for (std::size_t j = 0; same and j < m.dim_n(); ++j)
same = operator()(i, j) == m(i, j);
}
}
return same;
}
};
// --------------------------------------------------------------------
/**
* @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 <typename F = float>
class matrix : public matrix_expression<matrix<F>>
{
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 <typename M2>
matrix(const matrix_expression<M2> &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<value_type> 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 <typename F, std::size_t M, std::size_t N>
class matrix_fixed : public matrix_expression<matrix_fixed<F, M, N>>
{
public:
/** The value type */
using value_type = F;
/** The storage size */
static constexpr std::size_t kSize = M * N;
/** Copy constructor */
template <typename M2>
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<kSize>{});
}
/** @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<std::size_t... Ixs>
matrix_fixed& fill(const F (&a)[kSize], std::index_sequence<Ixs...>)
{
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<value_type, M * N> m_data;
};
/** typedef of a fixed matrix of size 3x3 */
template <typename F>
using matrix3x3 = matrix_fixed<F, 3, 3>;
/** typedef of a fixed matrix of size 4x4 */
template <typename F>
using matrix4x4 = matrix_fixed<F, 4, 4>;
// --------------------------------------------------------------------
/**
* @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 <typename F = float>
class symmetric_matrix : public matrix_expression<symmetric_matrix<F>>
{
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<value_type> 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 <typename F, std::size_t M>
class symmetric_matrix_fixed : public matrix_expression<symmetric_matrix_fixed<F, M>>
{
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<value_type, (M * (M + 1)) / 2> m_data;
};
/** typedef of a fixed symmetric matrix of size 3x3 */
template <typename F>
using symmetric_matrix3x3 = symmetric_matrix_fixed<F, 3>;
/** typedef of a fixed symmetric matrix of size 4x4 */
template <typename F>
using symmetric_matrix4x4 = symmetric_matrix_fixed<F, 4>;
// --------------------------------------------------------------------
/**
* @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 <typename F = float>
class identity_matrix : public matrix_expression<identity_matrix<F>>
{
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<value_type>(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 <typename M1, typename M2>
class matrix_subtraction : public matrix_expression<matrix_subtraction<M1, M2>>
{
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 <typename M1, typename M2>
auto operator-(const matrix_expression<M1> &m1, const matrix_expression<M2> &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 <typename M1, typename M2>
class matrix_matrix_multiplication : public matrix_expression<matrix_matrix_multiplication<M1, M2>>
{
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 <typename M, typename T>
class matrix_scalar_multiplication : public matrix_expression<matrix_scalar_multiplication<M, T>>
{
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 <typename M1, typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
auto operator*(const matrix_expression<M1> &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 <typename M1, typename M2, std::enable_if_t<not std::is_floating_point_v<M2>, int> = 0>
auto operator*(const matrix_expression<M1> &m1, const matrix_expression<M2> &m2)
{
return matrix_matrix_multiplication(m1, m2);
}
// --------------------------------------------------------------------
template <typename M2>
class sub_matrix : public matrix_expression<sub_matrix<M2>>
{
public:
sub_matrix(const M2 &m, int i, int j)
: m_m(m)
, m_i(i)
, m_j(j)
{
}
constexpr std::size_t dim_m() const { return m_m.dim_m() - 1; } ///< Return dimension m
constexpr std::size_t dim_n() const { return m_m.dim_n() - 1; } ///< 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 >= m_i ? i + 1 : i,
j >= m_j ? j + 1 : j);
}
private:
const M2 &m_m;
std::size_t m_i, m_j;
};
// --------------------------------------------------------------------
/** Generic routine to calculate the determinant of a matrix
*
* @note This is currently only implemented for fixed matrices of size 3x3
*/
template <typename M>
auto determinant(const M &m);
/** Implementation of the determinant function for fixed size matrices of size 3x3 */
template <typename F = float>
auto determinant(const matrix3x3<F> &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))));
}
/** Implementation of the determinant function for fixed size matrices of size 4x4 */
template <typename F = float>
F determinant(const matrix4x4<F> &m)
{
return m(0, 0) * determinant(matrix3x3<F>(sub_matrix<decltype(m)>(m, 0, 0))) -
m(0, 1) * determinant(matrix3x3<F>(sub_matrix<decltype(m)>(m, 0, 1))) +
m(0, 2) * determinant(matrix3x3<F>(sub_matrix<decltype(m)>(m, 0, 2))) -
m(0, 3) * determinant(matrix3x3<F>(sub_matrix<decltype(m)>(m, 0, 3)));
}
// --------------------------------------------------------------------
/** Generic routine to calculate the inverse of a matrix
*
* @note This is currently only implemented for fixed matrices of size 3x3
*/
template <typename M>
M inverse(const M &m);
/** Implementation of the inverse function for fixed size matrices of size 3x3 */
template <typename F = float>
matrix3x3<F> inverse(const matrix3x3<F> &m)
{
F det = determinant(m);
matrix3x3<F> 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 <typename M>
class matrix_cofactors : public matrix_expression<matrix_cofactors<M>>
{
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
#warning "Using this file is deprecated"

View File

@@ -32,10 +32,10 @@
#include "cif++/row.hpp"
#include <memory>
#include <numeric>
#if __cpp_lib_format
# include <format>
# include <utility>
#endif
/** @file model.hpp
@@ -56,6 +56,11 @@
*
*/
namespace cif
{
class file;
}
namespace cif::mm
{
@@ -87,14 +92,14 @@ class atom
/** @cond */
struct atom_impl : public std::enable_shared_from_this<atom_impl>
{
atom_impl(const datablock &db, std::string_view id)
atom_impl(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");
std::tie(m_location.x, m_location.y, m_location.z) = r.get<float, float, float>("Cartn_x", "Cartn_y", "Cartn_z");
}
// constructor for a symmetry copy of an atom
@@ -107,30 +112,27 @@ class atom
atom_impl(const atom_impl &i) = default;
int compare(const atom_impl &b) const;
[[nodiscard]] int compare(const atom_impl &b) const;
// bool getAnisoU(float anisou[6]) const;
int get_charge() const;
[[nodiscard]] 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);
[[nodiscard]] const item_value &get_property(std::string_view name) const;
void set_property(const std::string_view name, item_value value);
row_handle row()
{
return m_cat[{ { "id", m_id } }];
return m_cat[{ { .name = "id", .value = m_id } }];
}
const row_handle row() const
[[nodiscard]] const_row_handle row() const
{
return m_cat[{ { "id", m_id } }];
return m_cat[{ { .name = "id", .value = m_id } }];
}
row_handle row_aniso()
@@ -138,21 +140,21 @@ class atom
row_handle result{};
auto cat = m_db.get("atom_site_anisotrop");
if (cat)
result = cat->operator[]({ { "id", m_id } });
result = cat->operator[]({ { .name = "id", .value = m_id } });
return result;
}
const row_handle row_aniso() const
[[nodiscard]] const_row_handle row_aniso() const
{
row_handle result{};
auto cat = m_db.get("atom_site_anisotrop");
if (cat)
result = cat->operator[]({ { "id", m_id } });
result = cat->operator[]({ { .name = "id", .value = m_id } });
return result;
}
const datablock &m_db;
const category &m_cat;
datablock &m_db;
category &m_cat;
std::string m_id;
point m_location;
std::string m_symop = "1_555";
@@ -163,7 +165,7 @@ class atom
/**
* @brief Construct a new, empty atom object
*/
atom() {}
atom() = default;
/**
* @brief Construct a new atom object using @a impl as impl
@@ -171,25 +173,40 @@ class atom
* @param impl The implementation objectt
*/
atom(std::shared_ptr<atom_impl> impl)
: m_impl(impl)
: m_impl(std::move(impl))
{
}
/**
* @brief Copy construct a new atom object
*/
atom(const atom &rhs)
atom(const atom &rhs) // NOLINT(modernize-use-equals-default)
: m_impl(rhs.m_impl)
{
}
/**
* @brief Move construct a new atom object
*/
atom(atom &&rhs)
{
std::swap(m_impl, rhs.m_impl);
}
/// \brief Copy assignement operator
atom &operator=(atom rhs)
{
std::swap(m_impl, rhs.m_impl);
return *this;
}
/**
* @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(datablock &db, const_row_handle row)
: atom(std::make_shared<atom_impl>(db, row["id"].get<std::string>()))
{
}
@@ -207,46 +224,46 @@ class atom
}
/// \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;
explicit operator bool() const { return m_impl.operator bool(); }
/// \brief Return the item named @a name in the _atom_site category for this atom
std::string get_property(std::string_view name) const
[[nodiscard]] const item_value &get_property_value(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
/// \brief Return the item named @a name in the _atom_site category for this atom as string
[[nodiscard]] auto 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_int(name);
return get_property_value(name).get<std::string>();
}
/// \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
/// \brief Return the item named @a name in the _atom_site category for this atom as float
[[nodiscard]] auto 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);
return get_property_value(name).get<float>();
}
/// \brief Return the item named @a name in the _atom_site category for this atom as string
[[nodiscard]] auto get_property_int(std::string_view name) const
{
return get_property_value(name).get<int>();
}
/// \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)
void set_property(const std::string_view name, item_value value)
{
if (not m_impl)
throw std::logic_error("Error trying to modify an uninitialized atom");
m_impl->set_property(name, value);
m_impl->set_property(name, std::move(value));
}
/// \brief Set value for the item named @a name in the _atom_site category to @a value
template <typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
template <typename T>
void set_property(const std::string_view name, const T &value)
requires(std::is_arithmetic_v<T>)
{
set_property(name, std::to_string(value));
}
@@ -256,13 +273,13 @@ class atom
* @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; }
[[nodiscard]] 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(); }
[[nodiscard]] 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; }
[[nodiscard]] 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)
@@ -281,16 +298,16 @@ class atom
/// \brief Rotate the position of this atom by \a q
void rotate(quaternion q)
{
auto loc = get_location();
loc.rotate(q);
set_location(loc);
set_location(q * get_location());
}
/// \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);
loc -= p;
loc = q * loc;
loc += p;
set_location(loc);
}
@@ -299,7 +316,7 @@ class atom
{
auto loc = get_location();
loc += t;
loc.rotate(q);
loc = q * loc;
set_location(loc);
}
@@ -308,62 +325,62 @@ class atom
{
auto loc = get_location();
loc += t1;
loc.rotate(q);
loc = q * loc;
loc += t2;
set_location(loc);
}
/// for direct access to underlying data, be careful!
const row_handle get_row() const { return impl().row(); }
[[nodiscard]] 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(); }
[[nodiscard]] 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"; }
[[nodiscard]] bool is_symmetry_copy() const { return impl().m_symop != "1_555"; }
/// Return the symmetry operator used
std::string symmetry() const { return impl().m_symop; }
[[nodiscard]] std::string symmetry() const { return impl().m_symop; }
/// Return true if this atom is part of a water molecule
bool is_water() const
[[nodiscard]] 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(); }
[[nodiscard]] int get_charge() const { return impl().get_charge(); }
/// Return the occupancy
float get_occupancy() const { return get_property_float("occupancy"); }
[[nodiscard]] 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
[[nodiscard]] std::string get_label_asym_id() const { return get_property("label_asym_id"); } ///< Return the label_asym_id property
[[nodiscard]] int get_label_seq_id() const { return get_property_int("label_seq_id"); } ///< Return the label_seq_id property
[[nodiscard]] std::string get_label_atom_id() const { return get_property("label_atom_id"); } ///< Return the label_atom_id property
[[nodiscard]] std::string get_label_alt_id() const { return get_property("label_alt_id"); } ///< Return the label_alt_id property
[[nodiscard]] std::string get_label_comp_id() const { return get_property("label_comp_id"); } ///< Return the label_comp_id property
[[nodiscard]] 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("pdbx_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
[[nodiscard]] std::string get_auth_asym_id() const { return get_property("auth_asym_id"); } ///< Return the auth_asym_id property
[[nodiscard]] std::string get_auth_seq_id() const { return get_property("auth_seq_id"); } ///< Return the auth_seq_id property
[[nodiscard]] std::string get_auth_atom_id() const { return get_property("auth_atom_id"); } ///< Return the auth_atom_id property
[[nodiscard]] std::string get_auth_alt_id() const { return get_property("pdbx_auth_alt_id"); } ///< Return the auth_alt_id property
[[nodiscard]] std::string get_auth_comp_id() const { return get_property("auth_comp_id"); } ///< Return the auth_comp_id property
[[nodiscard]] 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
[[nodiscard]] bool is_alternate() const
{
if (auto alt_id = get_label_alt_id(); alt_id.empty() or alt_id == ".")
if (auto alt_id = get_property_value("label_alt_id"); alt_id.empty())
return false;
return true;
}
/// Convenience method to return a string that might be ID in PDB space
std::string pdb_id() const
[[nodiscard]] std::string pdb_id() const
{
return get_label_comp_id() + '_' + get_auth_asym_id() + '_' + get_auth_seq_id() + get_pdb_ins_code();
}
@@ -387,7 +404,7 @@ class atom
}
/// Is this atom a backbone atom
bool is_back_bone() const
[[nodiscard]] bool is_back_bone() const
{
auto atomID = get_label_atom_id();
return atomID == "N" or atomID == "O" or atomID == "C" or atomID == "CA";
@@ -400,7 +417,7 @@ class atom
}
/// Compare this atom with @a b
int compare(const atom &b) const { return impl().compare(*b.m_impl); }
[[nodiscard]] 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
@@ -414,7 +431,7 @@ class atom
private:
friend class structure;
const atom_impl &impl() const
[[nodiscard]] const atom_impl &impl() const
{
if (not m_impl)
throw std::runtime_error("Uninitialized atom, not found?");
@@ -436,16 +453,6 @@ 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());
}
// --------------------------------------------------------------------
/**
@@ -478,17 +485,15 @@ class residue
/**
* @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)
residue(structure &structure, std::string compoundID, std::string asymID, int seqID,
std::string authAsymID, std::string authSeqID, std::string pdbInsCode)
: m_structure(&structure)
, m_compound_id(compoundID)
, m_asym_id(asymID)
, m_compound_id(std::move(compoundID))
, m_asym_id(std::move(asymID))
, m_seq_id(seqID)
, m_pdb_strand_id(authAsymID)
, m_pdb_seq_num(authSeqID)
, m_pdb_ins_code(pdbInsCode)
, m_pdb_strand_id(std::move(authAsymID))
, m_pdb_seq_num(std::move(authSeqID))
, m_pdb_ins_code(std::move(pdbInsCode))
{
}
@@ -496,33 +501,51 @@ class residue
residue(structure &structure, const std::vector<atom> &atoms);
/** @cond */
residue(const residue &rhs) = delete;
residue &operator=(const residue &rhs) = delete;
residue(const residue &rhs) = default;
residue(residue &&rhs)
{
swap(*this, rhs);
}
residue(residue &&rhs) = default;
residue &operator=(residue &&rhs) = default;
residue &operator=(residue rhs)
{
swap(*this, rhs);
return *this;
}
friend void swap(residue &a, residue &b) noexcept
{
if (&a != &b)
{
std::swap(a.m_structure, b.m_structure);
std::swap(a.m_asym_id, b.m_asym_id);
std::swap(a.m_seq_id, b.m_seq_id);
std::swap(a.m_pdb_ins_code, b.m_pdb_ins_code);
std::swap(a.m_atoms, b.m_atoms);
}
}
virtual ~residue() = default;
/** @endcond */
/** Return the entity_id of this residue */
std::string get_entity_id() const;
[[nodiscard]] std::string get_entity_id() const;
/** Return the entity type of this residue */
EntityType entity_type() const;
[[nodiscard]] 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
[[nodiscard]] const std::string &get_asym_id() const { return m_asym_id; } ///< Return the asym_id
[[nodiscard]] int get_seq_id() const { return m_seq_id; } ///< Return the seq_id
const std::string get_pdb_strand_id() const { return m_pdb_strand_id; } ///< Return the pdb_strand_id
const std::string get_pdb_seq_num() const { return m_pdb_seq_num; } ///< Return the pdb_seq_num
std::string get_pdb_ins_code() const { return m_pdb_ins_code; } ///< Return the pdb_ins_code
[[nodiscard]] const std::string get_pdb_strand_id() const { return m_pdb_strand_id; } ///< Return the pdb_strand_id
[[nodiscard]] const std::string get_pdb_seq_num() const { return m_pdb_seq_num; } ///< Return the pdb_seq_num
[[nodiscard]] 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
[[nodiscard]] 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; }
[[nodiscard]] structure *get_structure() const { return m_structure; }
/** Return a list of the atoms for this residue */
std::vector<atom> &atoms()
@@ -531,7 +554,7 @@ class residue
}
/** Return a const list of the atoms for this residue */
const std::vector<atom> &atoms() const
[[nodiscard]] const std::vector<atom> &atoms() const
{
return m_atoms;
}
@@ -540,40 +563,40 @@ class 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<atom> unique_atoms() const;
[[nodiscard]] std::vector<atom> unique_atoms() const;
/// \brief Return the atom with atom_id @a atomID
atom get_atom_by_atom_id(const std::string &atomID) const;
[[nodiscard]] atom get_atom_by_atom_id(const std::string &atomID) const;
/// \brief Return the atom with atom_id @a atomID and alternate_id @a altID
atom get_atom_by_atom_id(const std::string &atomID, const std::string &altID) const;
[[nodiscard]] atom get_atom_by_atom_id(const std::string &atomID, const std::string &altID) 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<atom> get_atoms_by_id(const std::string &atomID) const;
[[nodiscard]] std::vector<atom> get_atoms_by_id(const std::string &atomID) const;
/// \brief Is this residue a single entity?
bool is_entity() const;
[[nodiscard]] bool is_entity() const;
/// \brief Is this residue a water molecule?
bool is_water() const { return m_compound_id == "HOH"; }
[[nodiscard]] bool is_water() const { return m_compound_id == "HOH"; }
/// \brief Return true if this residue has alternate atoms
bool has_alternate_atoms() const;
[[nodiscard]] 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;
[[nodiscard]] 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<std::string> get_alternate_ids() const;
[[nodiscard]] std::set<std::string> get_alternate_ids() const;
/// \brief Return the list of unique atom ID's
std::set<std::string> get_atom_ids() const;
[[nodiscard]] std::set<std::string> get_atom_ids() const;
/// \brief Return a tuple containing the center location and the radius for the atoms of this residue
std::tuple<point, float> center_and_radius() const;
[[nodiscard]] std::tuple<point, float> 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);
@@ -594,7 +617,7 @@ class residue
protected:
/** @cond */
residue() {}
residue() = default;
structure *m_structure = nullptr;
std::string m_compound_id, m_asym_id;
@@ -614,58 +637,73 @@ class residue
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
/// \brief Copy constructor
monomer(const monomer &rhs) = default;
const monomer &prev() const; // Return previous monomer in polymer
const monomer &next() const; // Return next monomer in polymer
/// \brief Move constructor
monomer(monomer &&rhs)
{
swap(*this, rhs);
}
/// Assignment for both move and copy (modern move semantics)
monomer &operator=(monomer rhs)
{
swap(*this, rhs);
return *this;
}
/// swap two monomers
friend void swap(monomer &a, monomer &b) noexcept
{
assert(a.m_polymer == b.m_polymer);
std::swap(a.m_index, b.m_index);
swap(static_cast<residue &>(a), static_cast<residue &>(b));
}
[[nodiscard]] bool is_first_in_chain() const; ///< Return if this residue is the first residue in the chain
[[nodiscard]] bool is_last_in_chain() const; ///< Return if this residue is the last residue in the chain
[[nodiscard]] const monomer &prev() const; ///< Return previous monomer in polymer
[[nodiscard]] const monomer &next() const; ///< Return next monomer in polymer
// 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)
[[nodiscard]] bool has_alpha() const; ///< Return if a alpha value can be calculated (depends on location in chain)
[[nodiscard]] 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
[[nodiscard]] float phi() const; ///< Return the phi value for this residue
[[nodiscard]] float psi() const; ///< Return the psi value for this residue
[[nodiscard]] float alpha() const; ///< Return the alpha value for this residue
[[nodiscard]] float kappa() const; ///< Return the kappa value for this residue
[[nodiscard]] float tco() const; ///< Return the tco value for this residue
[[nodiscard]] 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
[[nodiscard]] std::size_t nr_of_chis() const; ///< Return how many torsion angles can be calculated
[[nodiscard]] 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
[[nodiscard]] 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;
[[nodiscard]] bool is_complete() const;
/// \brief Returns true if any of the backbone atoms has an alternate
bool has_alternate_backbone_atoms() const;
[[nodiscard]] 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
[[nodiscard]] atom CAlpha() const { return get_atom_by_atom_id("CA"); } ///< Return the CAlpha atom
[[nodiscard]] atom C() const { return get_atom_by_atom_id("C"); } ///< Return the C atom
[[nodiscard]] atom N() const { return get_atom_by_atom_id("N"); } ///< Return the N atom
[[nodiscard]] atom O() const { return get_atom_by_atom_id("O"); } ///< Return the O atom
[[nodiscard]] 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
[[nodiscard]] bool is_bonded_to(const monomer &rhs) const
{
return this != &rhs and are_bonded(*this, rhs);
}
@@ -687,7 +725,7 @@ class monomer : public residue
static float omega(const monomer &a, const monomer &b);
/// \brief Return the chiral volume, only for LEU and VAL
float chiral_volume() const;
[[nodiscard]] float chiral_volume() const;
/// \brief Compare this monomer with \a rhs
bool operator==(const monomer &rhs) const
@@ -699,7 +737,7 @@ class monomer : public residue
private:
const polymer *m_polymer;
std::size_t m_index;
std::size_t m_index{};
};
// --------------------------------------------------------------------
@@ -712,16 +750,16 @@ class polymer : public std::vector<monomer>
{
public:
/// \brief Constructor
polymer(structure &s, const std::string &entityID, const std::string &asymID, const std::string &auth_asym_id);
polymer(structure &s, std::string entityID, std::string asymID, std::string auth_asym_id);
polymer(const polymer &) = delete;
polymer &operator=(const polymer &) = delete;
structure *get_structure() const { return m_structure; } ///< Return the structure
[[nodiscard]] 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_pdb_strand_id() const { return m_pdb_strand_id; } ///< Return the PDB chain ID, actually
std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id
[[nodiscard]] std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id
[[nodiscard]] std::string get_pdb_strand_id() const { return m_pdb_strand_id; } ///< Return the PDB chain ID, actually
[[nodiscard]] std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id
private:
structure *m_structure;
@@ -748,8 +786,26 @@ class sugar : public residue
const std::string &asymID, int authSeqID);
/** @cond */
sugar(sugar &&rhs);
sugar &operator=(sugar &&rhs);
sugar(const sugar &rhs) = default;
sugar(sugar &&rhs)
{
swap(*this, rhs);
}
sugar &operator=(sugar rhs)
{
swap(*this, rhs);
return *this;
}
friend void swap(sugar &a, sugar &b) noexcept
{
assert(a.m_branch == b.m_branch);
std::swap(a.m_link, b.m_link);
swap(static_cast<residue &>(a), static_cast<residue &>(b));
}
/** @endcond */
/**
@@ -762,26 +818,26 @@ class sugar : public residue
*
* @return The sugar number
*/
int num() const
[[nodiscard]] int num() const
{
int result;
auto r = std::from_chars(m_pdb_seq_num.data(), m_pdb_seq_num.data() + m_pdb_seq_num.length(), result);
if ((bool)r.ec)
if (r.ec != std::errc{})
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;
[[nodiscard]] std::string name() const;
/// \brief Return the atom the C1 is linked to
atom get_link() const { return m_link; }
[[nodiscard]] 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
[[nodiscard]] std::size_t get_link_nr() const
{
std::size_t result = 0;
if (m_link)
@@ -809,7 +865,7 @@ class branch : public std::vector<sugar>
{
public:
/// \brief constructor
branch(structure &structure, const std::string &asym_id, const std::string &entity_id);
branch(structure &structure, std::string asym_id, std::string entity_id);
branch(const branch &) = delete;
branch &operator=(const branch &) = delete;
@@ -823,22 +879,22 @@ class branch : public std::vector<sugar>
void link_atoms();
/// \brief Return the name of the branch
std::string name() const;
[[nodiscard]] std::string name() const;
/// \brief Return the weight of the branch based on the formulae of the sugars
float weight() const;
[[nodiscard]] 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
[[nodiscard]] std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id
[[nodiscard]] 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
[[nodiscard]] structure &get_structure() { return *m_structure; } ///< Return the structure
[[nodiscard]] 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);
[[nodiscard]] 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
[[nodiscard]] const sugar &get_sugar_by_num(int nr) const
{
return const_cast<branch *>(this)->get_sugar_by_num(nr);
}
@@ -858,7 +914,7 @@ class branch : public std::vector<sugar>
private:
friend sugar;
std::string name(const sugar &s) const;
[[nodiscard]] std::string name(const sugar &s) const;
structure *m_structure;
std::string m_asym_id, m_entity_id;
@@ -886,6 +942,7 @@ enum class occupancy_policy
UNOCCUPIED = 3
};
/// When creating a structure, you can specify what to include using this options class.
struct structure_open_options
{
bool skip_hydrogen = false; ///< Do not include hydrogen atoms in the structure object
@@ -925,85 +982,85 @@ class structure
~structure() = default;
/// \brief Return the model number
std::size_t get_model_nr() const { return m_model_nr; }
[[nodiscard]] 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<atom> &atoms() const { return m_atoms; }
[[nodiscard]] const std::vector<atom> &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
[[nodiscard]] EntityType get_entity_type_for_entity_id(const std::string entityID) const; ///< Return the entity type for the entity with id @a entity_id
[[nodiscard]] 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<polymer> &polymers() const { return m_polymers; } ///< Return the list of polymers
std::list<polymer> &polymers() { return m_polymers; } ///< Return the list of polymers
[[nodiscard]] const std::list<polymer> &polymers() const { return m_polymers; } ///< Return the list of polymers
[[nodiscard]] std::list<polymer> &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
[[nodiscard]] polymer &get_polymer_by_asym_id(const std::string &asymID); ///< Return the polymer having asym ID @a asymID
[[nodiscard]] const polymer &get_polymer_by_asym_id(const std::string &asymID) const ///< Return the polymer having asym ID @a asymID
{
return const_cast<structure *>(this)->get_polymer_by_asym_id(asymID);
}
const std::list<branch> &branches() const { return m_branches; } ///< Return the list of all branches
std::list<branch> &branches() { return m_branches; } ///< Return the list of all branches
[[nodiscard]] const std::list<branch> &branches() const { return m_branches; } ///< Return the list of all branches
[[nodiscard]] std::list<branch> &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
[[nodiscard]] branch &get_branch_by_asym_id(const std::string &asymID); ///< Return the branch having asym ID @a asymID
[[nodiscard]] const branch &get_branch_by_asym_id(const std::string &asymID) const; ///< Return the branch having asym ID @a asymID
const std::vector<residue> &non_polymers() const { return m_non_polymers; } ///< Return the list of non-polymers, actually the list of ligands
[[nodiscard]] const std::vector<residue> &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
[[nodiscard]] bool has_atom_id(const std::string &id) const; ///< Return true if an atom with ID @a id exists in this structure
[[nodiscard]] 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,
[[nodiscard]] 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;
[[nodiscard]] 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;
[[nodiscard]] 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<atom> &atoms);
/// \brief Get a non-poly residue for an asym with id \a asymID
residue &get_residue(const std::string &asymID)
[[nodiscard]] 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
[[nodiscard]] 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);
[[nodiscard]] 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
[[nodiscard]] const residue &get_residue(const std::string &asymID, int seqID, const std::string &authSeqID) const
{
return const_cast<structure *>(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);
[[nodiscard]] 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
[[nodiscard]] const residue &get_residue(const std::string &asymID, const std::string &compID, int seqID, const std::string &authSeqID) const
{
return const_cast<structure *>(this)->get_residue(asymID, compID, seqID, authSeqID);
}
/// \brief Get a the residue for atom \a atom
residue &get_residue(const atom &atom)
[[nodiscard]] 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
[[nodiscard]] 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());
}
@@ -1129,13 +1186,13 @@ class structure
void cleanup_empty_categories();
/// \brief Direct access to underlying data
category &get_category(std::string_view name) const
[[nodiscard]] category &get_category(std::string_view name) const
{
return m_db[name];
}
/// \brief Direct access to underlying data
datablock &get_datablock() const
[[nodiscard]] datablock &get_datablock() const
{
return m_db;
}
@@ -1144,14 +1201,13 @@ class structure
void validate_atoms() const;
/// \brief emplace a newly created atom using @a args
template <typename... Args>
atom &emplace_atom(Args &...args)
atom &emplace_atom(datablock &db, const_row_handle rh)
{
return emplace_atom(atom{ std::forward<Args>(args)... });
return emplace_atom(atom{ db, rh });
}
/// \brief emplace the moved atom @a atom
atom &emplace_atom(atom &&atom);
atom &emplace_atom(atom atom);
/// \brief Reorder atom_site atoms based on 'natural' ordering
void reorder_atoms();

View File

@@ -26,19 +26,34 @@
#pragma once
#include "cif++/category.hpp"
#include "cif++/datablock.hpp"
#include "cif++/item.hpp"
#include "cif++/row.hpp"
#include "cif++/text.hpp"
#include "cif++/utilities.hpp"
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
/**
* @file parser.hpp
*
*
* This file contains the declaration of an mmCIF parser
*/
namespace cif
{
class category;
class datablock;
class file;
class validator;
// --------------------------------------------------------------------
@@ -57,14 +72,14 @@ class parse_error : public std::runtime_error
// --------------------------------------------------------------------
/**
* @brief The sac_parser is a similar to SAX parsers (Simple API for XML,
* @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.
*/
@@ -92,10 +107,10 @@ class sac_parser
/// 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
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
@@ -143,12 +158,102 @@ class sac_parser
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
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
@@ -177,23 +282,26 @@ class sac_parser
{
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";
using enum CIFToken;
case CIFToken::VALUE_INAPPLICABLE: return "Inapplicable value";
case CIFToken::VALUE_UNKNOWN: return "'Unknown' value (=null)";
case CIFToken::VALUE_NUMERIC_INTEGER: return "Integer value";
case CIFToken::VALUE_NUMERIC_FLOAT: return "Float value";
case CIFToken::VALUE_CHARSTRING: return "Charstring value";
case CIFToken::VALUE_TEXTFIELD: return "Textfield value";
case UNKNOWN: return "Unknown";
case END_OF_FILE: return "Eof";
case DATA: return "DATA";
case LOOP: return "LOOP";
case GLOBAL: return "GLOBAL";
case SAVE_: return "SAVE";
case SAVE_NAME: return "SAVE+name";
case STOP: return "STOP";
case ITEM_NAME:
return "Tag";
// case VALUE: return "Value";
case VALUE_INAPPLICABLE: return "Inapplicable value";
case VALUE_UNKNOWN: return "'Unknown' value (=null)";
case VALUE_NUMERIC_INTEGER: return "Integer value";
case VALUE_NUMERIC_FLOAT: return "Float value";
case VALUE_CHARSTRING: return "Charstring value";
case VALUE_TEXTFIELD: return "Textfield value";
default: return "Invalid token parameter";
}
@@ -213,7 +321,6 @@ class sac_parser
/** @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
@@ -228,10 +335,10 @@ class sac_parser
/**
* @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
@@ -241,12 +348,11 @@ class sac_parser
/**
* @brief Parse the file
*
*
*/
void parse_file();
protected:
/** @cond */
sac_parser(std::istream &is, bool init = true);
@@ -259,7 +365,7 @@ class sac_parser
void error(const std::string &msg)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "Error parsing mmCIF: " << msg << '\n';
throw parse_error(m_line_nr, msg);
@@ -267,7 +373,7 @@ class sac_parser
void warning(const std::string &msg)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "parser warning at line " << m_line_nr << ": " << msg << '\n';
}
@@ -279,7 +385,6 @@ class sac_parser
virtual void produce_item(std::string_view category, std::string_view item, item_value value) = 0;
protected:
enum class State
{
Start,
@@ -297,6 +402,11 @@ class sac_parser
Reserved,
Value,
TextItemBS,
TextItemBS2,
TextItemBSNL,
Numeric_Zero,
Numeric_Integer,
Numeric_Float,
Numeric_Exponent1,
@@ -308,6 +418,7 @@ class sac_parser
// Parser state
uint32_t m_line_nr;
bool m_bol;
bool m_backslash_strings = false;
CIFToken m_lookahead;
// token buffer
@@ -315,6 +426,7 @@ class sac_parser
std::string_view m_token_value;
int64_t m_token_value_int;
double m_token_value_float;
int m_float_precision;
/** @endcond */
};
@@ -323,7 +435,7 @@ class sac_parser
/**
* @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
*/

View File

@@ -64,7 +64,7 @@ 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);
void read_pdb_file(std::istream &pdbFile, cif::file &cifFile);
// mmCIF to PDB
@@ -149,6 +149,23 @@ void fixup_pdbx(file &pdbx_file, const validator &v);
bool reconstruct_pdbx(file &pdbx_file, const validator &v);
/** \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 pdbx_file The input file
* \result Returns true if the file was valid and consistent
*/
bool is_valid_pdbx_file(const file &pdbx_file);
/** \brief This is an extension to cif::validator, use the logic in common
* PDBx files to see if the file is internally consistent.
*
@@ -165,8 +182,7 @@ bool reconstruct_pdbx(file &pdbx_file, const validator &v);
* \result Returns true if the file was valid and consistent
*/
bool is_valid_pdbx_file(const file &pdbx_file,
const validator &v = validator_factory::instance().get("mmcif_pdbx.dic"));
bool is_valid_pdbx_file(const file &pdbx_file, const validator &v);
/** \brief This is an extension to cif::validator, use the logic in common
* PDBx files to see if the file is internally consistent.
@@ -180,7 +196,8 @@ bool is_valid_pdbx_file(const file &pdbx_file,
* 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 pdbx_file The input file
* \param ec In case of error, this will contain what went wrong
* \result Returns true if the file was valid and consistent
*/
@@ -195,7 +212,7 @@ bool is_valid_pdbx_file(const file &pdbx_file, std::error_code &ec);
*
* Use the common \ref cif::VERBOSE flag to turn on diagnostic messages.
*
* \param file The input file
* \param pdbx_file The input file
* \param v The validator to use
* \param ec The error_code in case something was wrong
* \result Returns true if the file was valid and consistent

View File

@@ -1,33 +0,0 @@
/*-
* 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"

View File

@@ -1,32 +0,0 @@
/*-
* 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"

View File

@@ -26,692 +26,62 @@
#pragma once
#include <array>
#include <cmath>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <format>
#include <functional>
#include <glm/ext/quaternion_common.hpp>
#include <glm/ext/quaternion_geometric.hpp>
#include <glm/ext/quaternion_trigonometric.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/trigonometric.hpp>
#include <limits>
#include <numbers>
#include <optional>
#include <valarray>
#include <tuple>
#include <vector>
#if __has_include(<clipper/core/coords.h>)
#define HAVE_LIBCLIPPER 1
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wignored-qualifiers"
#include <clipper/core/coords.h>
#pragma GCC diagnostic pop
# define HAVE_LIBCLIPPER 1
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wignored-qualifiers"
# include <clipper/core/clipper_types.h>
# include <clipper/core/coords.h>
# pragma GCC diagnostic pop
#endif
/** \file point.hpp
*
* This file contains the definition for *cif::point* as well as
* This file contains the definition for *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);"
*/
// Using glm now
template <typename T>
class quaternion_type
{
public:
/// \brief the value type of the elements, usually this is float
using value_type = T;
using quaternion_type = glm::qua<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<value_type> const &z0, std::complex<value_type> const &z1 = std::complex<value_type>())
: 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 <typename X>
constexpr explicit quaternion_type(quaternion_type<X> const &rhs)
: a(static_cast<value_type>(rhs.a))
, b(static_cast<value_type>(rhs.b))
, c(static_cast<value_type>(rhs.c))
, d(static_cast<value_type>(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 <typename X>
constexpr quaternion_type &operator=(quaternion_type<X> const &rhs)
{
a = static_cast<value_type>(rhs.a);
b = static_cast<value_type>(rhs.b);
c = static_cast<value_type>(rhs.c);
d = static_cast<value_type>(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<value_type>(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<value_type> const &rhs)
{
a = rhs.real();
b = rhs.imag();
c = d = static_cast<value_type>(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<value_type> 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 <class X>
constexpr quaternion_type &operator+=(quaternion_type<X> 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<value_type> 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 <class X>
constexpr quaternion_type &operator-=(quaternion_type<X> 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<value_type> 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 <typename X>
constexpr quaternion_type &operator*=(quaternion_type<X> const &rhs)
{
value_type ar = static_cast<value_type>(rhs.a);
value_type br = static_cast<value_type>(rhs.b);
value_type cr = static_cast<value_type>(rhs.c);
value_type dr = static_cast<value_type>(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<value_type> 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 <typename X>
constexpr quaternion_type &operator/=(quaternion_type<X> const &rhs)
{
value_type ar = static_cast<value_type>(rhs.a);
value_type br = static_cast<value_type>(rhs.b);
value_type cr = static_cast<value_type>(rhs.c);
value_type dr = static_cast<value_type>(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<value_type> 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<value_type>(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 explicit operator bool() const
{
return a != 0 or b != 0 or c != 0 or d != 0;
}
/// \brief for debugging e.g.
friend std::ostream &operator<<(std::ostream &os, const quaternion_type &rhs)
{
os << std::format("{{ a: {}, b: {}, c: {}, d: {} }}", rhs.a, rhs.b, rhs.c, rhs.d);
return os;
}
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**<sup>3</sup>). 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 <typename T>
inline quaternion_type<T> 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<float>;
template <typename T>
using point_type = glm::vec<3, T>;
using point = point_type<float>;
// --------------------------------------------------------------------
/**
* @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 <typename F>
struct point_type
constexpr point rotate(point pt, quaternion q)
{
/// \brief the value type of the x, y and z members
using value_type = F;
return q * pt;
}
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 <typename PF>
constexpr point_type(const point_type<PF> &pt)
: m_x(static_cast<F>(pt.m_x))
, m_y(static_cast<F>(pt.m_y))
, m_z(static_cast<F>(pt.m_z))
{
}
/// \brief constructor taking a tuple of three values
constexpr point_type(const std::tuple<value_type, value_type, value_type> &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 <typename PF>
constexpr point_type &operator=(const point_type<PF> &rhs)
{
m_x = static_cast<F>(rhs.m_x);
m_y = static_cast<F>(rhs.m_y);
m_z = static_cast<F>(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 <typename F2>
friend constexpr auto operator+(const point_type &lhs, const point_type<F2> &rhs)
{
return point_type<std::common_type_t<value_type, F2>>(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 <typename F2>
friend constexpr auto operator-(const point_type &lhs, const point_type<F2> &rhs)
{
return point_type<std::common_type_t<value_type, F2>>(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 <typename F2>
friend constexpr auto operator*(const point_type &pt, F2 f)
{
return point_type<std::common_type_t<value_type, F2>>(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 <typename F2>
friend constexpr auto operator*(F2 f, const point_type &pt)
{
return point_type<std::common_type_t<value_type, F2>>(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 <typename F2>
friend constexpr auto operator/(const point_type &pt, F2 f)
{
return point_type<std::common_type_t<value_type, F2>>(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<value_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 value_type &, const value_type &, const value_type &>() 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<value_type &, value_type &, value_type &>()
{
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<float>;
constexpr point rotate(point pt, quaternion q, point a)
{
return q * (pt - a) + a;
}
// --------------------------------------------------------------------
// several standard 3d operations
@@ -720,43 +90,16 @@ using point = point_type<float>;
template <typename F1, typename F2>
constexpr auto distance_squared(const point_type<F1> &a, const point_type<F2> &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 <typename F1, typename F2>
constexpr auto distance(const point_type<F1> &a, const point_type<F2> &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 <typename F1, typename F2>
inline constexpr auto dot_product(const point_type<F1> &a, const point_type<F2> &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 <typename F1, typename F2>
inline constexpr auto cross_product(const point_type<F1> &a, const point_type<F2> &b)
{
return point_type<std::common_type_t<F1, F2>>(
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);
return (a.x - b.x) * (a.x - b.x) +
(a.y - b.y) * (a.y - b.y) +
(a.z - b.z) * (a.z - b.z);
}
/// \brief return the squared norm of point @a p
template <typename F>
constexpr F norm_squared(const point_type<F> &p)
{
return p.m_x * p.m_x + p.m_y * p.m_y + p.m_z * p.m_z;
return p.x * p.x + p.y * p.y + p.z * p.z;
}
/// \brief return the norm of point @a p
@@ -768,27 +111,27 @@ constexpr point_type<F> norm(const point_type<F> &p)
/// \brief return the point where two lines intersect, or an empty value if they don't intersect at all
template <typename F>
std::optional<cif::point> line_line_intersection(const point_type<F> &p1,
std::optional<point> line_line_intersection(const point_type<F> &p1,
const point_type<F> &p2, const point_type<F> &p3, const point_type<F> &p4)
{
auto p13 = p1 - p3;
auto p43 = p4 - p3;
if (std::abs(p43.m_x) < std::numeric_limits<F>::epsilon() and std::abs(p43.m_y) < std::numeric_limits<F>::epsilon() and std::abs(p43.m_z) < std::numeric_limits<F>::epsilon())
return {};
if (std::abs(p43.x) < std::numeric_limits<F>::epsilon() and std::abs(p43.y) < std::numeric_limits<F>::epsilon() and std::abs(p43.z) < std::numeric_limits<F>::epsilon())
return std::nullopt;
auto p21 = p2 - p1;
if (std::abs(p21.m_x) < std::numeric_limits<F>::epsilon() and std::abs(p21.m_y) < std::numeric_limits<F>::epsilon() and std::abs(p21.m_z) < std::numeric_limits<F>::epsilon())
return {};
if (std::abs(p21.x) < std::numeric_limits<F>::epsilon() and std::abs(p21.y) < std::numeric_limits<F>::epsilon() and std::abs(p21.z) < std::numeric_limits<F>::epsilon())
return std::nullopt;
auto d1343 = cif::dot_product(p43, p13);
auto d4321 = cif::dot_product(p43, p21);
auto d1321 = cif::dot_product(p13, p21);
auto d4343 = cif::dot_product(p43, p43);
auto d2121 = cif::dot_product(p21, p21);
auto d1343 = dot(p43, p13);
auto d4321 = dot(p43, p21);
auto d1321 = dot(p13, p21);
auto d4343 = dot(p43, p43);
auto d2121 = dot(p21, p21);
auto denom = d2121 * d4343 - d4321 * d4321;
if (std::abs(denom) < std::numeric_limits<F>::epsilon())
return {};
return std::nullopt;
auto numer = d1343 * d4321 - d1321 * d4343;
@@ -798,7 +141,7 @@ std::optional<cif::point> line_line_intersection(const point_type<F> &p1,
auto pa = p1 + mua * p21;
auto pb = p3 + mub * p43;
return { (pa + pb) / 2 };
return { (pa + pb) / 2.0f };
}
/// \brief return the angle in degrees between the vectors from point @a p2 to @a p1 and @a p2 to @a p3
@@ -808,7 +151,7 @@ constexpr auto angle(const point_type<F> &p1, const point_type<F> &p2, const poi
point_type<F> v1 = p1 - p2;
point_type<F> v2 = p3 - p2;
return std::acos(dot_product(v1, v2) / (v1.length() * v2.length())) * 180 / kPI;
return std::acos(glm::dot(v1, v2) / (glm::length(v1) * glm::length(v2))) * 180 / std::numbers::pi_v<F>;
}
/// \brief return the dihedral angle in degrees for the four points @a p1, @a p2, @a p3 and @a p4
@@ -822,20 +165,20 @@ constexpr auto dihedral_angle(const point_type<F> &p1, const point_type<F> &p2,
point_type<F> z = p2 - p3; // vector from p3 to p2
point_type<F> p = cross_product(z, v12);
point_type<F> x = cross_product(z, v43);
point_type<F> y = cross_product(z, x);
point_type<F> p = glm::cross(z, v12);
point_type<F> x = glm::cross(z, v43);
point_type<F> y = glm::cross(z, x);
auto u = dot_product(x, x);
auto v = dot_product(y, y);
auto u = glm::dot(x, x);
auto v = glm::dot(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);
u = glm::dot(p, x) / std::sqrt(u);
v = glm::dot(p, y) / std::sqrt(v);
if (u != 0 or v != 0)
result = std::atan2(v, u) * static_cast<F>(180 / kPI);
result = std::atan2(v, u) * static_cast<F>(180 / std::numbers::pi_v<F>);
}
return result;
@@ -848,9 +191,9 @@ constexpr auto cosinus_angle(const point_type<F> &p1, const point_type<F> &p2, c
point_type<F> v12 = p1 - p2;
point_type<F> v34 = p3 - p4;
auto x = dot_product(v12, v12) * dot_product(v34, v34);
auto x = glm::dot(v12, v12) * glm::dot(v34, v34);
return x > 0 ? dot_product(v12, v34) / std::sqrt(x) : 0;
return x > 0 ? glm::dot(v12, v34) / std::sqrt(x) : 0;
}
/// \brief return the distance from point @a p to the line from @a l1 to @a l2
@@ -860,28 +203,26 @@ constexpr auto distance_point_to_line(const point_type<F> &l1, const point_type<
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();
auto cross = glm::cross(p_to_l1, p_to_l2);
return glm::length(cross) / glm::length(line);
}
/// \brief return the smallest sphere around the points in @a pts
std::tuple<point, float> smallest_sphere_around_points(std::vector<point> pts);
// --------------------------------------------------------------------
/**
* @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);
constexpr quaternion construct_from_angle_axis(float angle, point axis)
{
return glm::angleAxis(glm::radians(angle), glm::normalize(axis));
}
/// \brief Return a tuple of an angle and an axis for quaternion @a q
std::tuple<double, point> quaternion_to_angle_axis(quaternion q);
constexpr std::tuple<float, point> quaternion_to_angle_axis(quaternion q)
{
return { glm::degrees(glm::angle(q)), glm::axis(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
@@ -907,74 +248,19 @@ quaternion align_points(const std::vector<point> &a, const std::vector<point> &b
/// \brief The RMSd for the points in \a a and \a b
double RMSd(const std::vector<point> &a, const std::vector<point> &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 <int N>
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<point, P>;
/// \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
template <>
struct std::formatter<glm::vec3> : std::formatter<std::string_view> // NOLINT
{
template <class FmtContext>
auto format(glm::vec3 pt, FmtContext &ctx) const
{
std::string temp;
std::format_to(std::back_inserter(temp), "( {}, {}, {} )", pt.x, pt.y, pt.z);
return std::formatter<std::string_view>::format(temp, ctx);
}
};

View File

@@ -29,88 +29,464 @@
#include "cif++/item.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
/**
* @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 a reference to a cif::item_value 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<std::string>();
*
* std::string name = rh["label_atom_id"].get<std::string>();
*
* // by index:
* uint16_t ix = atom_site.get_item_ix("label_atom_id");
* assert(rh[ix].as<std::string() == name);
* assert(rh[ix].get<std::string() == name);
* @endcode
*
*
* There some template magic here to allow easy extracting of data
* from rows. This can be done using cif::tie e.g.:
*
*
* @code {.cpp}
* std::string name;
* float x, y, z;
*
*
* cif::tie(name, x, y, z) = rh.get("label_atom_id", "cartn_x", "cartn_y", "cartn_z");
* @endcode
*
*
* However, a more modern way uses structured binding:
*
*
* @code {.cpp}
* const auto &[name, x, y, z] = rh.get<std::string,float,float,float>("label_atom_id", "cartn_x", "cartn_y", "cartn_z");
* @endcode
*
*
*
*
*
*
*/
namespace cif
{
class category;
namespace cql
{
struct connection_impl;
}
namespace detail
{
template <typename... C>
struct get_row_result;
}
// some helper classes to help create tuple result types
// --------------------------------------------------------------------
/// \brief the row class, this one is not directly accessible from the outside
class row : public std::vector<item_value>
{
public:
row() = default;
private:
/**
* @brief Return the item_value pointer for item at index @a ix
*/
item_value *get(uint16_t ix)
{
if (ix >= size())
resize(ix + 1);
return &data()[ix];
}
/**
* @brief Return the const item_value pointer for item at index @a ix
*/
[[nodiscard]] const item_value *get(uint16_t ix) const
{
return ix < size() ? &data()[ix] : nullptr;
}
void set(uint16_t ix, item_value v)
{
if (ix >= size())
resize(ix + 1);
operator[](ix) = std::move(v);
}
friend class category;
friend class category_index;
template <bool, typename...>
friend class iterator_impl_base;
row *m_next = nullptr;
};
// --------------------------------------------------------------------
/// \brief row_handle is the way to access data stored in rows
class row_handle
{
public:
/** @cond */
template <bool>
friend struct item_handle_base;
friend class category;
friend class category_index;
friend class row_initializer;
friend class const_row_handle;
template <bool, typename...>
friend class iterator_impl_base;
row_handle() = default;
virtual ~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(category &cat, row &r)
: m_category(&cat)
, m_row(&r)
{
}
/// \brief return the category this row belongs to
[[nodiscard]] category &get_category() const
{
return *m_category;
}
/// \brief return the row ID
[[nodiscard]] int64_t row_id() const
{
return reinterpret_cast<int64_t>(m_row);
}
/// \brief Return true if the row is empty or uninitialised
[[nodiscard]] 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 the count of the items
[[nodiscard]] size_t size() const { return m_row->size(); }
/// \brief return a cif::item_handle to the item in item @a item_ix
item_handle operator[](uint16_t item_ix)
{
return { *m_category, *m_row, item_ix };
}
/// \brief return a cif::item_handle to the item in item @a item_ix
const item_handle operator[](uint16_t item_ix) const
{
return { *m_category, *m_row, item_ix };
}
/// \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 { *m_category, *m_row, add_item(item_name) };
}
/// \brief return a cif::item_handle to the item in the item named @a item_name
const item_handle operator[](std::string_view item_name) const
{
return { *m_category, *m_row, get_item_ix(item_name) };
}
/// \brief assign each of the items named in @a values to their respective value
void assign(const std::vector<item> &values, bool updateLinked = true)
{
for (auto &value : values)
assign(value, updateLinked);
}
/** \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, item_value value, bool updateLinked, bool validate = true)
{
assign(add_item(name), std::move(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, item_value value, bool updateLinked, bool validate = true);
/// \brief Return an object that can be used in combination with cif::tie
/// to assign the values for the items @a items
template <typename... C>
[[nodiscard]] auto get(C... items) const
{
return detail::get_row_result<C...>(*this, { get_item_ix(items)... });
}
/// \brief Return a tuple of values of types @a Ts for the items @a items
template <typename... Ts, typename... C>
std::tuple<Ts...> get(C... items) const
requires(sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1)
{
return detail::get_row_result<Ts...>(*this, { get_item_ix(items)... });
}
/// \brief Get the value of item @a item cast to type @a T
template <typename T>
[[nodiscard]] T get(std::string_view item) const
{
return operator[](get_item_ix(item)).template get<T>();
}
/// \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; }
protected:
/// Return the index number for the item named @a name
[[nodiscard]] uint16_t get_item_ix(std::string_view name) const;
/// Return the name for the item with index number @a ix
[[nodiscard]] std::string_view get_item_name(uint16_t ix) const;
friend cql::connection_impl;
/// Return the actual row
[[nodiscard]] auto get_row() const
{
return m_row;
}
category *m_category = nullptr; ///< The category
row *m_row = nullptr; ///< The row
private:
/// @cond
uint16_t add_item(std::string_view name);
void assign(const item &i, bool updateLinked)
{
assign(i.name(), i.value(), updateLinked);
}
/// @endcond
};
/// A const version of row_handle.
class const_row_handle
{
public:
/** @cond */
template <bool>
friend struct item_handle_base;
friend class category;
friend class category_index;
friend class row_initializer;
template <bool, typename...>
friend class iterator_impl_base;
const_row_handle() = default;
virtual ~const_row_handle() = default;
const_row_handle(const const_row_handle &) = default;
const_row_handle(const_row_handle &&) = default;
const_row_handle &operator=(const const_row_handle &) = default;
const_row_handle &operator=(const_row_handle &&) = default;
const_row_handle(row_handle rh)
: m_category(rh.m_category)
, m_row(rh.m_row)
{
}
/** @endcond */
/// \brief constructor taking a category @a cat and a row @a r
const_row_handle(const category &cat, const row &r)
: m_category(&cat)
, m_row(&r)
{
}
/// \brief return the category this row belongs to
[[nodiscard]] const category &get_category() const
{
return *m_category;
}
/// \brief return the row ID
[[nodiscard]] int64_t row_id() const
{
return reinterpret_cast<int64_t>(m_row);
}
/// \brief Return true if the row is empty or uninitialised
[[nodiscard]] 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 the count of the items
[[nodiscard]] size_t size() const { return m_row->size(); }
/// \brief return a cif::item_handle to the item in item @a item_ix
const item_handle operator[](uint16_t item_ix) const
{
return { *m_category, *m_row, item_ix };
}
/// \brief return a cif::item_handle to the item in the item named @a item_name
const item_handle operator[](std::string_view item_name) const
{
return operator[](get_item_ix(item_name));
}
/// \brief Return an object that can be used in combination with cif::tie
/// to assign the values for the items @a items
template <typename... C>
[[nodiscard]] auto get(C... items) const
{
return detail::get_row_result<C...>(*this, { get_item_ix(items)... });
}
/// \brief Return a tuple of values of types @a Ts for the items @a items
template <typename... Ts, typename... C>
std::tuple<Ts...> get(C... items) const
requires(sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1)
{
return detail::get_row_result<Ts...>(*this, { get_item_ix(items)... });
}
/// \brief Get the value of item @a item cast to type @a T
template <typename T>
[[nodiscard]] T get(std::string_view item) const
{
return operator[](get_item_ix(item)).template get<T>();
}
/// \brief compare two rows
// bool operator==(const const_row_handle &rhs) const { return m_category == rhs.m_category and m_row == rhs.m_row; }
friend bool operator==(const_row_handle a, const_row_handle b)
{
return a.m_category == b.m_category and a.m_row == b.m_row;
}
/// \brief compare two rows
bool operator!=(const const_row_handle &rhs) const { return m_category != rhs.m_category or m_row != rhs.m_row; }
protected:
/// Return the index number for the item named @a name
[[nodiscard]] uint16_t get_item_ix(std::string_view name) const;
/// Return the name for the item with index number @a ix
[[nodiscard]] std::string_view get_item_name(uint16_t ix) const;
friend cql::connection_impl;
/// Return the actual row
[[nodiscard]] auto get_row() const
{
return m_row;
}
const category *m_category = nullptr; ///< The category
const row *m_row = nullptr; ///< The row
};
namespace detail
{
/// @cond
/// some helper classes to help create tuple result types
template <typename... C>
struct get_row_result
{
static constexpr std::size_t N = sizeof...(C);
get_row_result(const row_handle &r, std::array<uint16_t, N> &&items)
: m_row(r)
get_row_result(const_row_handle r, std::array<uint16_t, N> &&items)
: m_row(std::move(r))
, m_items(std::move(items))
{
}
const item_value &operator[](uint16_t ix) const
const item_handle operator[](uint16_t ix) const
{
return m_row[m_items[ix]];
}
template <typename... Ts, std::enable_if_t<N == sizeof...(Ts), int> = 0>
template <typename... Ts>
operator std::tuple<Ts...>() const
requires(N == sizeof...(Ts))
{
return get<Ts...>(std::index_sequence_for<Ts...>{});
}
template <typename... Ts, std::size_t... Is>
std::tuple<Ts...> get(std::index_sequence<Is...>) const
[[nodiscard]] std::tuple<Ts...> get(std::index_sequence<Is...>) const
{
return std::tuple<Ts...>{ m_row[m_items[Is]].template get<Ts>()... };
}
const row_handle &m_row;
const_row_handle m_row;
std::array<uint16_t, N> m_items;
};
@@ -131,7 +507,7 @@ namespace detail
// of the row should be equal to the number of items in the tuple
// you are trying to tie.
using RType = std::tuple<typename std::remove_reference<Ts>::type...>;
using RType = std::tuple<std::remove_reference_t<Ts>...>;
m_value = static_cast<RType>(rr);
}
@@ -139,9 +515,11 @@ namespace detail
std::tuple<Ts...> m_value;
};
/// @endcond
} // namespace detail
/// \brief similar to std::tie, assign values to each element in @a v from the
/// \brief similar to std::tie, assign values to each element in @a v from the
/// result of a get on a row_handle.
template <typename... Ts>
auto tie(Ts &...v)
@@ -149,219 +527,11 @@ auto tie(Ts &...v)
return detail::tie_wrap<Ts &...>(std::forward<Ts &>(v)...);
}
// --------------------------------------------------------------------
/// \brief the row class, this one is not directly accessible from the outside
class row : public std::vector<item_value>
{
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 <typename, typename...>
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 class category;
friend class category_index;
friend class row_initializer;
template <typename, typename...> 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<category *>(&cat))
, m_row(const_cast<row *>(&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 the count of the items
[[nodiscard]] size_t size() const { return m_row->size(); }
/// \brief return a reference to a cif::item_value to the item in item @a item_ix
item_value &operator[](uint16_t item_ix);
/// \brief return a const reference to a cif::item_value to the item in item @a item_ix
const item_value &operator[](uint16_t item_ix) const;
/// \brief return a reference to a cif::item_value to the item in the item named @a item_name
item_value &operator[](std::string_view item_name);
/// \brief return a const reference to a cif::item_value to the item in the item named @a item_name
const item_value &operator[](std::string_view item_name) const;
/// \brief Return an object that can be used in combination with cif::tie
/// to assign the values for the items @a items
template <typename... C>
auto get(C... items) const
{
return detail::get_row_result<C...>(*this, { get_item_ix(items)... });
}
/// \brief Return a tuple of values of types @a Ts for the items @a items
template <typename... Ts, typename... C, std::enable_if_t<sizeof...(Ts) == sizeof...(C) and sizeof...(C) != 1, int> = 0>
std::tuple<Ts...> get(C... items) const
{
return detail::get_row_result<Ts...>(*this, { get_item_ix(items)... });
}
/// \brief Get the value of item @a item cast to type @a T
template <typename T>
T get(const char *item) const
{
return operator[](get_item_ix(item)).template get<T>();
}
/// \brief Get the value of item @a item cast to type @a T
template <typename T>
T get(std::string_view item) const
{
return operator[](get_item_ix(item)).template get<T>();
}
/// \brief assign each of the items named in @a values to their respective value
void assign(const std::vector<item> &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, item_value value, bool updateLinked, bool validate = true)
{
assign(add_item(name), std::move(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, item_value 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.
@@ -387,15 +557,21 @@ class row_initializer : public std::vector<item>
}
/// \brief constructor taking a range of items
template <typename ItemIter, std::enable_if_t<std::is_same_v<typename ItemIter::value_type, item>, int> = 0>
template <typename ItemIter>
row_initializer(ItemIter b, ItemIter e)
requires(std::is_constructible_v<item, typename ItemIter::value_type>)
: std::vector<item>(b, e)
{
}
/// \brief constructor taking the values of an existing row
row_initializer(row_handle rh);
row_initializer(row_handle rh)
: cif::row_initializer(const_row_handle{ rh })
{
}
/// Constructor
row_initializer(const_row_handle rh);
/// \brief set the value for item name @a name to @a value
void set_value(std::string name, item_value value);
@@ -414,6 +590,12 @@ class row_initializer : public std::vector<item>
{
set_value_if_empty(i.name(), i.value());
}
/// \brief enable emplace_back for more complex items (floats with precission)
auto emplace_back(std::string name, item_value value)
{
return std::vector<item>::emplace_back(item(std::forward<std::string>(name), std::forward<item_value>(value)));
}
};
} // namespace cif
} // namespace cif

View File

@@ -27,15 +27,15 @@
#pragma once
#include "cif++/exports.hpp"
#include "cif++/matrix.hpp"
#include "cif++/point.hpp"
#include <array>
#include <cstdint>
#include <glm/ext/matrix_float3x3.hpp>
#include <string>
#if defined(__cpp_impl_three_way_comparison)
#include <compare>
# include <utility>
#endif
/** \file cif++/symmetry.hpp
@@ -49,18 +49,6 @@ namespace cif
// --------------------------------------------------------------------
/// \brief Apply matrix transformation @a m on point @a pt and return the result
inline point operator*(const matrix3x3<float> &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
{
@@ -95,7 +83,7 @@ extern CIFPP_EXPORT const std::size_t kNrOfSpaceGroups;
struct symop_data
{
/// \brief constructor
constexpr symop_data(const std::array<int, 15> &data)
constexpr symop_data(const std::array<int, 15> &data) noexcept
: m_packed((data[0] bitand 0x03ULL) << 34 bitor
(data[1] bitand 0x03ULL) << 32 bitor
(data[2] bitand 0x03ULL) << 30 bitor
@@ -127,20 +115,20 @@ struct symop_data
}
/// \brief return an int representing the value stored in the two bits at offset @a offset
inline constexpr int unpack3(int offset) const
[[nodiscard]] inline constexpr int unpack3(int offset) const
{
int result = (m_packed >> offset) bitand 0x03;
int result = static_cast<int>((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
[[nodiscard]] inline constexpr int unpack7(int offset) const
{
return (m_packed >> offset) bitand 0x07;
return static_cast<int>((m_packed >> offset) bitand 0x07);
}
/// \brief return an array of 15 ints representing the values stored
constexpr std::array<int, 15> data() const
[[nodiscard]] constexpr std::array<int, 15> data() const
{
return {
unpack3(34),
@@ -182,16 +170,16 @@ struct symop_data
struct symop_datablock
{
/// \brief constructor
constexpr symop_datablock(int spacegroup, int rotational_number, const std::array<int, 15> &rt_data)
constexpr symop_datablock(int spacegroup, int rotational_number, const std::array<int, 15> &rt_data) noexcept
: 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
[[nodiscard]] int spacegroup() const { return m_v >> 48; } ///< Return the spacegroup
[[nodiscard]] symop_data symop() const { return { m_v }; } ///< Return the symmetry operation
[[nodiscard]] uint8_t rotational_number() const { return (m_v >> 40) bitand 0xff; } ///< Return the rotational_number
private:
uint64_t m_v;
@@ -249,7 +237,7 @@ struct sym_op
/** @endcond */
/// \brief return true if this sym_op is the identity operator
constexpr bool is_identity() const
[[nodiscard]] constexpr bool is_identity() const
{
return m_nr == 1 and m_ta == 5 and m_tb == 5 and m_tc == 5;
}
@@ -261,7 +249,7 @@ struct sym_op
}
/// \brief return the content encoded in a string
std::string string() const;
[[nodiscard]] std::string string() const;
#if defined(__cpp_impl_three_way_comparison)
/// \brief a default spaceship operator
@@ -326,7 +314,7 @@ class transformation
transformation(const symop_data &data);
/// \brief constructor taking a rotation matrix @a r and a translation vector @a t
transformation(const matrix3x3<float> &r, const cif::point &t);
transformation(const glm::mat3 &r, const cif::point &t);
/** @cond */
transformation(const transformation &) = default;
@@ -338,8 +326,8 @@ class transformation
/// \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);
if (m_q != quaternion{})
pt = m_q * pt;
else
pt = m_rotation * pt;
@@ -367,9 +355,9 @@ class transformation
void try_create_quaternion();
matrix3x3<float> m_rotation;
quaternion m_q;
point m_translation;
glm::mat3 m_rotation{};
quaternion m_q{};
point m_translation{};
};
// --------------------------------------------------------------------
@@ -389,24 +377,24 @@ class cell
/// \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
[[nodiscard]] float get_a() const { return m_a; } ///< return dimension a
[[nodiscard]] float get_b() const { return m_b; } ///< return dimension b
[[nodiscard]] 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
[[nodiscard]] float get_alpha() const { return m_alpha; } ///< return angle alpha
[[nodiscard]] float get_beta() const { return m_beta; } ///< return angle beta
[[nodiscard]] float get_gamma() const { return m_gamma; } ///< return angle gamma
float get_volume() const; ///< return the calculated volume for this cell
[[nodiscard]] float get_volume() const; ///< return the calculated volume for this cell
matrix3x3<float> get_orthogonal_matrix() const { return m_orthogonal; } ///< return the matrix to use to transform coordinates from fractional to orthogonal
matrix3x3<float> get_fractional_matrix() const { return m_fractional; } ///< return the matrix to use to transform coordinates from orthogonal to fractional
[[nodiscard]] glm::mat3 get_orthogonal_matrix() const { return m_orthogonal; } ///< return the matrix to use to transform coordinates from fractional to orthogonal
[[nodiscard]] glm::mat3 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<float> m_orthogonal, m_fractional;
glm::mat3 m_orthogonal, m_fractional;
};
// --------------------------------------------------------------------
@@ -448,8 +436,8 @@ class spacegroup : public std::vector<transformation>
/// \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
[[nodiscard]] int get_nr() const { return m_nr; } ///< Return the nr
[[nodiscard]] 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
@@ -460,7 +448,7 @@ class spacegroup : public std::vector<transformation>
/** \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;
[[nodiscard]] point inverse(const point &pt, const cell &c, sym_op symop) const;
private:
int m_nr;
@@ -486,9 +474,9 @@ class crystal
}
/// \brief constructor using cell @a c and spacegroup @a sg
crystal(const cell &c, const spacegroup &sg)
crystal(const cell &c, spacegroup sg)
: m_cell(c)
, m_spacegroup(sg)
, m_spacegroup(std::move(sg))
{
}
@@ -499,24 +487,24 @@ class crystal
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
[[nodiscard]] const cell &get_cell() const { return m_cell; } ///< Return the cell
[[nodiscard]] 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
[[nodiscard]] 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
[[nodiscard]] 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<float, point, sym_op> closest_symmetry_copy(point a, point b) const;
[[nodiscard]] std::tuple<float, point, sym_op> closest_symmetry_copy(point a, point b) const;
private:
cell m_cell;

View File

@@ -29,22 +29,27 @@
#include "cif++/exports.hpp"
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <iterator>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#if __has_include(<experimental/type_traits>)
#include <experimental/type_traits>
# include <experimental/type_traits>
namespace std_experimental = std::experimental;
#else
// A quick hack to work around the missing is_detected in MSVC
/// @cond
namespace std_experimental
{
@@ -69,6 +74,7 @@ using is_detected = typename detail::detector<void, Op, Args...>::value_t;
template <template <class...> class Op, class... Args>
const auto is_detected_v = is_detected<Op, Args...>::value;
/// @endcond
} // namespace std_experimental
#endif
@@ -88,16 +94,16 @@ namespace cif
// our own case conversion routines.
/// \brief return whether string @a is equal to string @a b ignoring changes in character case
bool iequals(std::string_view a, std::string_view b);
bool iequals(std::string_view a, std::string_view b) noexcept;
/// \brief compare string @a is to string @a b ignoring changes in character case
int icompare(std::string_view a, std::string_view b);
int icompare(std::string_view a, std::string_view b) noexcept;
/// \brief return whether string @a is equal to string @a b ignoring changes in character case
bool iequals(const char *a, const char *b);
bool iequals(const char *a, const char *b) noexcept;
/// \brief compare string @a is to string @a b ignoring changes in character case
int icompare(const char *a, const char *b);
int icompare(const char *a, const char *b) noexcept;
/// \brief convert the string @a s to lower case in situ
void to_lower(std::string &s);
@@ -328,7 +334,6 @@ inline char tolower(int ch)
[[deprecated("use split_item_name instead")]]
std::tuple<std::string, std::string> split_tag_name(std::string_view item_name);
/** \brief return a tuple consisting of the category and item name for @a item_name
*
* The category name is stripped of its leading underscore character.
@@ -356,6 +361,9 @@ std::vector<std::string> word_wrap(const std::string &text, std::size_t width);
// --------------------------------------------------------------------
/// @cond
// Code to select a version of from_chars that is implemented...
template <typename T>
using from_chars_function = decltype(std::from_chars(std::declval<const char *>(), std::declval<const char *>(), std::declval<T &>()));
@@ -386,4 +394,6 @@ constexpr auto from_chars(const char *s, const char *e, T &v)
return charconv<T>::from_chars(s, e, v);
}
} // namespace cif
/// @endcond
} // namespace cif

View File

@@ -28,33 +28,44 @@
#include "cif++/exports.hpp"
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#ifndef STDOUT_FILENO
/// @brief For systems that lack this value
#define STDOUT_FILENO 1
# define STDOUT_FILENO 1
#endif
#ifndef STDERR_FILENO
/// @brief For systems that lack this value
#define STDERR_FILENO 2
# define STDERR_FILENO 2
#endif
#if _WIN32
#include <io.h>
#define isatty _isatty
# include <io.h>
# define isatty _isatty
#else
#include <unistd.h>
# include <unistd.h>
#endif
#if _MSC_VER
#pragma warning(disable : 4996) // unsafe function or variable (strcpy e.g.)
#pragma warning(disable : 4068) // unknown pragma
#pragma warning(disable : 4100) // unreferenced formal parameter
#pragma warning(disable : 4101) // unreferenced local variable
#pragma warning(disable : 4702) // unreachable code (too bad, this one. Happens in for loops)
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1
# pragma warning(disable : 4996) // unsafe function or variable (strcpy e.g.)
# pragma warning(disable : 4068) // unknown pragma
# pragma warning(disable : 4100) // unreferenced formal parameter
# pragma warning(disable : 4101) // unreferenced local variable
# pragma warning(disable : 4702) // unreachable code (too bad, this one. Happens in for loops)
// Truncation warnings: yes, perhaps, but I think they are okay
# pragma warning(disable : 4244)
# pragma warning(disable : 4267)
# pragma warning(disable : 4305)
# define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1
#endif
/** \file utilities.hpp
@@ -76,10 +87,10 @@ namespace cif
extern CIFPP_EXPORT int VERBOSE;
/// return the git 'build' number
std::string get_version_nr();
[[nodiscard]] std::string get_version_nr();
/// return the width of the current output terminal, or 80 if it cannot be determined
uint32_t get_terminal_width();
[[nodiscard]] uint32_t get_terminal_width();
// --------------------------------------------------------------------
@@ -88,7 +99,7 @@ namespace colour
/// @brief The defined colours
enum colour_type
{
black = 0,
black,
red,
green,
yellow,
@@ -96,7 +107,8 @@ namespace colour
magenta,
cyan,
white,
none = 9
_unused,
none
};
/// @brief The defined styles
@@ -127,6 +139,7 @@ namespace colour
{
}
coloured_string_t(coloured_string_t &) = delete;
coloured_string_t &operator=(coloured_string_t &) = delete;
/**
@@ -136,14 +149,7 @@ namespace colour
friend std::basic_ostream<char_type, traits_type> &operator<<(
std::basic_ostream<char_type, traits_type> &os, const coloured_string_t &cs)
{
bool use_colour = false;
if (os.rdbuf() == std::cout.rdbuf() and isatty(STDOUT_FILENO))
use_colour = true;
else if (os.rdbuf() == std::cerr.rdbuf() and isatty(STDERR_FILENO))
use_colour = true;
if (use_colour)
if ((os.rdbuf() == std::cout.rdbuf() and isatty(STDOUT_FILENO)) or (os.rdbuf() == std::cerr.rdbuf() and isatty(STDERR_FILENO)))
{
os << "\033[" << cs.m_fore_colour << ';' << cs.m_style << ';' << cs.m_back_colour << 'm'
<< cs.m_str
@@ -167,7 +173,7 @@ namespace colour
/**
* @brief Manipulator for coloured strings.
*
*
* When writing out text to the terminal it is often useful to have
* some of the text colourised. But only if the output is really a
* terminal since colouring text is done using escape sequences
@@ -204,51 +210,51 @@ inline auto coloured(T str,
/**
* @brief A simple progress bar class for terminal based output
*
*
* Using a progress bar is very convenient for the end user when
* you have long running code. It gives feed back on how fast an
* operation is performed and may give an indication how long it
* will take before it is finished.
*
*
* Using this cif::progress_bar implementation is straightforward:
*
*
* @code {.cpp}
* using namespace std::chrono_literals;
*
*
* cif::progress_bar pb(10, "counting to ten");
*
*
* for (int i = 1; i <= 10; ++i)
* {
* pb.consumed(1);
* std::this_thread::sleep_for(1s);
* }
*
*
* @endcode
*
*
* When the progress_bar is created, it first checks
* to see if stdout is to a real TTY and if the VERBOSE
* flag is not less than zero (quiet mode). If this passes
* a thread is started that waits for updates.
*
*
* The first two seconds, nothing is written to the screen
* so if the work is finished within those two seconds
* the screen stays clean.
*
*
* After this time, a progress bar is printed that may look
* like this:
*
*
* @code
* step 3 ========================-------------------------------- 40% ⢁
* @endcode
*
*
* The first characters contain the initial action name or
* the message text if it was used afterwards.
*
*
* The thermometer is made up with '=' and '-' characters.
*
*
* A percentage is also shown and at the end there is a spinner
* that gives feedback that the program is really still working.
*
*
* The progress bar is removed if the max has been reached
* or if the progress bar is destructed. If any output has
* been generated, the initial action is printed out along
@@ -258,13 +264,16 @@ inline auto coloured(T str,
class progress_bar
{
public:
progress_bar(const progress_bar &) = delete;
progress_bar &operator=(const progress_bar &) = delete;
/**
* @brief Construct a new progress bar object
*
*
* Progress ranges from 0 (zero) to @a inMax
*
*
* The action in @a inAction is used for display
*
*
* @param inMax The maximum value
* @param inAction The description of what is
* going on
@@ -274,7 +283,7 @@ class progress_bar
/**
* @brief Destroy the progress bar object
*
*
*/
~progress_bar();
@@ -302,10 +311,7 @@ class progress_bar
void flush();
private:
progress_bar(const progress_bar &) = delete;
progress_bar &operator=(const progress_bar &) = delete;
struct progress_bar_impl *m_impl;
struct progress_bar_impl *m_impl = nullptr;
};
// --------------------------------------------------------------------
@@ -313,14 +319,14 @@ class progress_bar
/**
* @brief Load a resource from disk or the compiled in resources
*
*
* @verbatim embed:rst
.. note::
See the :doc:`documentation on resources </resources>` for more information.
@endverbatim
*
*
* @param name The named resource to load
* @return std::unique_ptr<std::istream> A pointer to the std::istream or empty if not found
*/
@@ -329,14 +335,14 @@ std::unique_ptr<std::istream> load_resource(std::filesystem::path name);
/**
* @brief Add a file specified by @a dataFile as the data for resource @a name
*
*
* @verbatim embed:rst
.. note::
See the :doc:`documentation on resources </resources>` for more information.
@endverbatim
*
*
* @param name The name of the resource to specify
* @param dataFile Path to a file containing the data
*/
@@ -345,7 +351,7 @@ void add_file_resource(const std::string &name, std::filesystem::path dataFile);
/**
* @brief List all the file resources added with cif::add_file_resource.
*
*
* @param os The std::ostream to write the directories to
*/
@@ -354,7 +360,7 @@ void list_file_resources(std::ostream &os);
/**
* @brief Add a directory to the list of search directories. This list is
* searched in a last-in-first-out order.
*
*
* @verbatim embed:rst
.. note::
@@ -367,7 +373,7 @@ void add_data_directory(std::filesystem::path dataDir);
/**
* @brief List all the data directories, for error reporting on missing resources.
*
*
* @param os The std::ostream to write the directories to
*/

View File

@@ -27,15 +27,22 @@
#pragma once
#include "cif++/category.hpp"
#include "cif++/item.hpp"
#include "cif++/text.hpp"
#include <cassert>
#include <filesystem>
#include <iosfwd>
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
/**
* @file validate.hpp
@@ -65,6 +72,8 @@ enum class validation_error
{
value_does_not_match_rx = 1, /**< The value of an item does not conform to the regular expression specified for it */
value_is_not_in_enumeration_list, /**< The value of an item is not in the list of values allowed */
value_is_not_a_number, /**< The value is not a number */
value_is_not_a_char_string, /**< The value is not a character string */
not_a_known_primitive_type, /**< The type is not a known primitive type */
undefined_category, /**< Category has no definition in the dictionary */
unknown_item, /**< The item is not defined to be part of the category */
@@ -90,7 +99,7 @@ class validation_category_impl : public std::error_category
* @return const char*
*/
const char *name() const noexcept override
[[nodiscard]] const char *name() const noexcept override
{
return "cif::validation";
}
@@ -102,35 +111,41 @@ class validation_category_impl : public std::error_category
* @return std::string
*/
std::string message(int ev) const override
[[nodiscard]] std::string message(int ev) const override
{
switch (static_cast<validation_error>(ev))
{
case validation_error::value_does_not_match_rx:
using enum validation_error;
case value_does_not_match_rx:
return "Value in item does not match regular expression";
case validation_error::value_is_not_in_enumeration_list:
case value_is_not_in_enumeration_list:
return "Value is not in the enumerated list of valid values";
case validation_error::not_a_known_primitive_type:
case value_is_not_a_number:
return "Value is not a number";
case value_is_not_a_char_string:
return "Value is not a character string";
case not_a_known_primitive_type:
return "The type is not a known primitive type";
case validation_error::undefined_category:
case undefined_category:
return "Category has no definition in the dictionary";
case validation_error::unknown_item:
case unknown_item:
return "Item is not defined to be part of the category";
case validation_error::incorrect_item_validator:
case incorrect_item_validator:
return "Incorrectly specified validator for item";
case validation_error::missing_mandatory_items:
case missing_mandatory_items:
return "Missing mandatory items";
case validation_error::missing_key_items:
case missing_key_items:
return "An index could not be constructed due to missing key items";
case validation_error::item_not_allowed_in_category:
case item_not_allowed_in_category:
return "Requested item not allowed in category according to dictionary";
case validation_error::empty_file:
case empty_file:
return "The file contains no datablocks";
case validation_error::empty_datablock:
case empty_datablock:
return "The datablock contains no categories";
case validation_error::empty_category:
case empty_category:
return "The category is empty";
case validation_error::not_valid_pdbx:
case not_valid_pdbx:
return "The file is not a valid PDBx file";
default:
@@ -144,7 +159,7 @@ class validation_category_impl : public std::error_category
*
*/
bool equivalent(const std::error_code & /*code*/, int /*condition*/) const noexcept override
[[nodiscard]] bool equivalent(const std::error_code & /*code*/, int /*condition*/) const noexcept override
{
return false;
}
@@ -161,21 +176,27 @@ inline std::error_category &validation_category()
return instance;
}
/// Return a std::error_code for a validation error
inline std::error_code make_error_code(validation_error e)
{
return std::error_code(static_cast<int>(e), validation_category());
return { static_cast<int>(e), validation_category() };
}
/// Return a std::error_condition for a validation error
inline std::error_condition make_error_condition(validation_error e)
{
return std::error_condition(static_cast<int>(e), validation_category());
return { static_cast<int>(e), validation_category() };
}
// --------------------------------------------------------------------
/// Exception class for validation errors
class validation_exception : public std::runtime_error
{
public:
// Constructors
/// @cond
validation_exception(validation_error err)
: validation_exception(make_error_code(err))
{
@@ -196,6 +217,7 @@ class validation_exception : public std::runtime_error
validation_exception(std::error_code ec, std::string_view category);
validation_exception(std::error_code ec, std::string_view category, std::string_view item);
/// @endcond
};
// --------------------------------------------------------------------
@@ -238,7 +260,7 @@ struct type_validator
type_validator(std::string_view name, DDL_PrimitiveType type, std::string_view rx);
/// @brief Copy constructor
type_validator(const type_validator &tv);
type_validator(const type_validator &tv) = default;
/// @brief Move constructor
type_validator(type_validator &&rhs)
@@ -254,8 +276,9 @@ struct type_validator
}
/// @brief Destructor
~type_validator();
~type_validator() = default;
/// Swap two type validators
friend void swap(type_validator &a, type_validator &b)
{
std::swap(a.m_name, b.m_name);
@@ -273,7 +296,7 @@ struct type_validator
/// primitive type of this type. A value of zero indicates the
/// values are equal. Less than zero means @a a sorts before @a b
/// and a value larger than zero likewise means the opposite
int compare(std::string_view a, std::string_view b) const;
[[nodiscard]] int compare(const item_value &a, const item_value &b) const;
};
/** @brief Item alias, items can be renamed over time
@@ -281,14 +304,18 @@ struct type_validator
struct item_alias
{
item_alias(const std::string &alias_name, const std::string &dictionary, const std::string &version)
: m_name(alias_name)
, m_dict(dictionary)
, m_vers(version)
/// constructor
item_alias(std::string alias_name, std::string dictionary, std::string version)
: m_name(std::move(alias_name))
, m_dict(std::move(dictionary))
, m_vers(std::move(version))
{
}
/// default copy constructor
item_alias(const item_alias &) = default;
/// default copy assignment
item_alias &operator=(const item_alias &) = default;
std::string m_name; ///< The alias_name
@@ -327,12 +354,14 @@ struct item_validator
return iequals(m_item_name, rhs.m_item_name);
}
/// @brief Validate the value in @a value for this item
/// Will throw a std::system_error exception if it fails
void operator()(const item_value &value) const;
/// @brief Validate value @a value, throws if invalid
void validate_value(const item_value &value) const;
/// @brief A more gentle version of value validation
/// @brief Validate value @a value and return potential error in @a ec
bool validate_value(const item_value &value, std::error_code &ec) const noexcept;
/// @brief Validate value @a value and return potential error in @a ec
bool validate_value(std::string_view value, std::error_code &ec) const noexcept;
};
/**
@@ -343,11 +372,11 @@ struct item_validator
*/
struct category_validator
{
std::string m_name; ///< The name of the category
std::vector<std::string> m_keys; ///< The list of items that make up the key
cif::iset m_groups; ///< The category groups this category belongs to
cif::iset m_mandatory_items; ///< The mandatory items for this category
std::set<item_validator> m_item_validators; ///< The item validators for the items in this category
std::string m_name; ///< The name of the category
std::vector<std::string> m_keys; ///< The list of items that make up the key
cif::iset m_groups; ///< The category groups this category belongs to
cif::iset m_mandatory_items; ///< The mandatory items for this category
std::vector<item_validator> m_item_validators; ///< The item validators for the items in this category
/// @brief return true if this category sorts before @a rhs
bool operator<(const category_validator &rhs) const
@@ -359,10 +388,10 @@ struct category_validator
void add_item_validator(item_validator &&v);
/// @brief Return the item_validator for item @a item_name, may return nullptr
const item_validator *get_validator_for_item(std::string_view item_name) const;
[[nodiscard]] const item_validator *get_validator_for_item(std::string_view item_name) const;
/// @brief Return the item_validator for an item that has as alias name @a item_name, may return nullptr
const item_validator *get_validator_for_aliased_item(std::string_view item_name) const;
[[nodiscard]] const item_validator *get_validator_for_aliased_item(std::string_view item_name) const;
};
/**
@@ -397,8 +426,6 @@ class validator
public:
/**
* @brief Construct a new validator object
*
* @param name The name of the underlying dictionary
*/
validator()
: m_audit_conform("audit_conform")
@@ -408,7 +435,6 @@ class validator
/**
* @brief Construct a new validator object
*
* @param name The name of the underlying dictionary
* @param is The data to parse
*/
validator(std::istream &is)
@@ -420,7 +446,8 @@ class validator
/// @brief destructor
~validator() = default;
validator(const validator &rhs);
/// default copy constructor
validator(const validator &rhs) = default;
/// @brief move constructor
validator(validator &&rhs)
@@ -428,12 +455,14 @@ class validator
swap(*this, rhs);
}
/// default copy assignment
validator &operator=(validator rhs)
{
swap(*this, rhs);
return *this;
}
/// swap the two validators
friend void swap(validator &a, validator &b) noexcept;
friend class dictionary_parser;
@@ -447,22 +476,22 @@ class validator
void add_type_validator(type_validator &&v);
/// @brief Return the type validator for @a type_code, may return nullptr
const type_validator *get_validator_for_type(std::string_view type_code) const;
[[nodiscard]] const type_validator *get_validator_for_type(std::string_view type_code) const;
/// @brief Add category_validator @a v to the list of category validators
void add_category_validator(category_validator &&v);
/// @brief Return the category validator for @a category, may return nullptr
const category_validator *get_validator_for_category(std::string_view category) const;
[[nodiscard]] const category_validator *get_validator_for_category(std::string_view category) const;
/// @brief Add link_validator @a v to the list of link validators
void add_link_validator(link_validator &&v);
/// @brief Return the list of link validators for which the parent is @a category
std::vector<const link_validator *> get_links_for_parent(std::string_view category) const;
[[nodiscard]] std::vector<const link_validator *> get_links_for_parent(std::string_view category) const;
/// @brief Return the list of link validators for which the child is @a category
std::vector<const link_validator *> get_links_for_child(std::string_view category) const;
[[nodiscard]] std::vector<const link_validator *> get_links_for_child(std::string_view category) const;
/// @brief Bottleneck function to report an error in validation
void report_error(validation_error err, bool fatal = true) const
@@ -489,14 +518,14 @@ class validator
void fill_audit_conform(category &audit_conform) const;
/// @brief Return true if this validator matches @a audit_conform
bool matches_audit_conform(const category &audit_conform) const;
[[nodiscard]] bool matches_audit_conform(const category &audit_conform) const;
/// @brief Add info
void append_audit_conform(const std::string &name, const std::optional<std::string> &version);
private:
// name is fully qualified here:
item_validator *get_validator_for_item(std::string_view name) const;
[[nodiscard]] item_validator *get_validator_for_item(std::string_view name) const;
category m_audit_conform;
@@ -520,10 +549,18 @@ class validator_factory
static validator_factory &instance();
/// @brief Return validator with info recorded in @a audit_conform
const validator &get(const category &audit_conform);
const validator *get(const category &audit_conform);
/// @brief Return the single-file validator with name @a dictionary_name
const validator &get(std::string_view dictionary_name);
/// and the dictionary name may be a set of dictionaries separated by comma
const validator *get(std::string_view dictionary_name);
/// @brief Return validator with info recorded in @a audit_conform
const validator &operator[](const category &audit_conform);
/// @brief Return the single-file validator with name @a dictionary_name
/// and the dictionary name may be a set of dictionaries separated by comma
const validator &operator[](std::string_view dictionary_name);
/// @brief Return true if the version @a found is equal or higher than @a expected for dictionary @a name
static bool check_version(std::string_view name, std::string_view expected, std::string_view found);
@@ -535,6 +572,21 @@ class validator_factory
return m_validators.emplace_back(std::move(v));
}
// #if __cplusplus >= 202302L
// /// @brief Return validator with info recorded in @a audit_conform
// static validator &operator[](const category &audit_conform)
// {
// return instance()[audit_conform];
// }
// /// @brief Return the single-file validator with name @a dictionary_name
// /// and the dictionary name may be a set of dictionaries separated by comma
// static validator &operator[](std::string_view dict)
// {
// return instance()[dict];
// }
// #endif
private:
validator_factory() = default;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -24,43 +24,32 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/compound.hpp" // for compound_atom, compound_bond, compoun...
#include "cif++/compound.hpp"
#include "cif++/cif++.hpp"
#include "cif++/atom_type.hpp" // for atom_type_traits
#include "cif++/category.hpp" // for category
#include "cif++/datablock.hpp" // for datablock
#include "cif++/file.hpp" // for file
#include "cif++/item.hpp" // for item
#include "cif++/iterator.hpp" // for iterator_proxy
#include "cif++/parser.hpp" // for parser
#include "cif++/point.hpp" // for distance, point
#include "cif++/row.hpp" // for tie, row_initializer, tie_wrap
#include "cif++/text.hpp" // for iequals, replace_all, iset
#include "cif++/utilities.hpp" // for load_resource, VERBOSE, colour_type
#include <algorithm> // for find_if
#include <cstddef> // for size_t
#include <exception> // for exception, throw_with_nested
#include <filesystem> // for path, exists
#include <fstream> // for char_traits, basic_ostream, operator<<
#include <iomanip> // for operator<<, quoted
#include <iostream> // for clog, cout, cerr
#include <limits> // for numeric_limits
#include <list> // for _List_iterator
#include <map> // for allocator, map, _Rb_tree_iterator
#include <memory> // for shared_ptr, unique_ptr, __shared_ptr_...
#include <optional> // for optional
#include <shared_mutex> // for shared_lock, shared_timed_mutex
#include <stdexcept> // for runtime_error, invalid_argument, out_...
#include <string> // for basic_string, string, operator==, ope...
#include <string_view> // for string_view, basic_string_view
#include <utility> // for pair, exchange, move
#include <vector> // for vector
#include <cstddef>
#include <exception>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <ranges>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace fs = std::filesystem;
namespace cif
{
using std::shared_ptr;
// --------------------------------------------------------------------
@@ -68,36 +57,32 @@ std::string to_string(bond_type bondType)
{
switch (bondType)
{
case bond_type::sing: return "sing";
case bond_type::doub: return "doub";
case bond_type::trip: return "trip";
case bond_type::quad: return "quad";
case bond_type::arom: return "arom";
case bond_type::poly: return "poly";
case bond_type::delo: return "delo";
case bond_type::pi: return "pi";
using enum bond_type;
case sing: return "sing";
case doub: return "doub";
case trip: return "trip";
case quad: return "quad";
case arom: return "arom";
case poly: return "poly";
case delo: return "delo";
case pi: return "pi";
}
throw std::invalid_argument("Invalid bondType");
}
bond_type parse_bond_type_from_string(const std::string &bondType)
{
if (cif::iequals(bondType, "sing"))
return bond_type::sing;
if (cif::iequals(bondType, "doub"))
return bond_type::doub;
if (cif::iequals(bondType, "trip"))
return bond_type::trip;
if (cif::iequals(bondType, "quad"))
return bond_type::quad;
if (cif::iequals(bondType, "arom"))
return bond_type::arom;
if (cif::iequals(bondType, "poly"))
return bond_type::poly;
if (cif::iequals(bondType, "delo"))
return bond_type::delo;
if (cif::iequals(bondType, "pi"))
return bond_type::pi;
using enum bond_type;
if (cif::iequals(bondType, "sing")) return sing;
if (cif::iequals(bondType, "doub")) return doub;
if (cif::iequals(bondType, "trip")) return trip;
if (cif::iequals(bondType, "quad")) return quad;
if (cif::iequals(bondType, "arom")) return arom;
if (cif::iequals(bondType, "poly")) return poly;
if (cif::iequals(bondType, "delo")) return delo;
if (cif::iequals(bondType, "pi")) return pi;
throw std::invalid_argument("Invalid bondType: " + bondType);
}
@@ -105,21 +90,21 @@ std::string to_string(stereo_config_type stereoConfig)
{
switch (stereoConfig)
{
case stereo_config_type::N: return "N";
case stereo_config_type::R: return "R";
case stereo_config_type::S: return "S";
using enum stereo_config_type;
case N: return "N";
case R: return "R";
case S: return "S";
}
throw std::invalid_argument("Invalid stereoConfig");
}
stereo_config_type parse_stereo_config_from_string(const std::string &stereoConfig)
{
if (cif::iequals(stereoConfig, "N"))
return stereo_config_type::N;
if (cif::iequals(stereoConfig, "R"))
return stereo_config_type::R;
if (cif::iequals(stereoConfig, "S"))
return stereo_config_type::S;
using enum stereo_config_type;
if (cif::iequals(stereoConfig, "N")) return N;
if (cif::iequals(stereoConfig, "R")) return R;
if (cif::iequals(stereoConfig, "S")) return S;
throw std::invalid_argument("Invalid stereoConfig: " + stereoConfig);
}
@@ -176,9 +161,16 @@ compound::compound(cif::datablock &db)
{
compound_atom atom;
std::string type_symbol, stereo_config;
cif::tie(atom.id, type_symbol, atom.charge, atom.aromatic, atom.leaving_atom, stereo_config, atom.x, atom.y, atom.z) =
std::string aromaticFlag, leavingAtomFlag;
cif::tie(atom.id, type_symbol, atom.charge, aromaticFlag, leavingAtomFlag, stereo_config, atom.x, atom.y, atom.z) =
row.get("atom_id", "type_symbol", "charge", "pdbx_aromatic_flag", "pdbx_leaving_atom_flag", "pdbx_stereo_config",
"model_Cartn_x", "model_Cartn_y", "model_Cartn_z");
atom.aromatic = iequals(aromaticFlag, "Y");
atom.leaving_atom = iequals(leavingAtomFlag, "Y");
atom.type_symbol = atom_type_traits(type_symbol).type();
if (stereo_config.empty())
atom.stereo_config = stereo_config_type::N;
@@ -191,8 +183,13 @@ compound::compound(cif::datablock &db)
for (auto row : chemCompBond)
{
compound_bond bond;
std::string valueOrder;
cif::tie(bond.atom_id[0], bond.atom_id[1], valueOrder, bond.aromatic, bond.stereo_config) = row.get("atom_id_1", "atom_id_2", "value_order", "pdbx_aromatic_flag", "pdbx_stereo_config");
std::string valueOrder, aromaticFlag, stereoConfigFlag;
cif::tie(bond.atom_id[0], bond.atom_id[1], valueOrder, aromaticFlag, stereoConfigFlag) = row.get("atom_id_1", "atom_id_2", "value_order", "pdbx_aromatic_flag", "pdbx_stereo_config");
bond.aromatic = iequals(aromaticFlag, "Y");
bond.stereo_config = iequals(stereoConfigFlag, "Y");
if (valueOrder.empty())
bond.type = bond_type::sing;
else
@@ -221,7 +218,7 @@ compound_atom compound::get_atom_by_atom_id(const std::string &atom_id) const
bool compound::atoms_bonded(const std::string &atomId_1, const std::string &atomId_2) const
{
auto i = find_if(m_bonds.begin(), m_bonds.end(),
auto i = std::ranges::find_if(m_bonds,
[&](const compound_bond &b)
{
return (b.atom_id[0] == atomId_1 and b.atom_id[1] == atomId_2) or (b.atom_id[0] == atomId_2 and b.atom_id[1] == atomId_1);
@@ -232,7 +229,7 @@ bool compound::atoms_bonded(const std::string &atomId_1, const std::string &atom
float compound::bond_length(const std::string &atomId_1, const std::string &atomId_2) const
{
auto i = find_if(m_bonds.begin(), m_bonds.end(),
auto i = std::ranges::find_if(m_bonds,
[&](const compound_bond &b)
{
return (b.atom_id[0] == atomId_1 and b.atom_id[1] == atomId_2) or (b.atom_id[0] == atomId_2 and b.atom_id[1] == atomId_1);
@@ -266,7 +263,7 @@ bool compound::is_base() const
// --------------------------------------------------------------------
// known amino acids and bases
const std::map<std::string, char> compound_factory::kAAMap{
const std::map<std::string, char> compound_factory::kAAMap{ // NOLINT(bugprone-throwing-static-initialization,cert-err58-cpp)
{ "ALA", 'A' },
{ "ARG", 'R' },
{ "ASN", 'N' },
@@ -291,7 +288,7 @@ const std::map<std::string, char> compound_factory::kAAMap{
{ "ASX", 'B' }
};
const std::map<std::string, char> compound_factory::kBaseMap{
const std::map<std::string, char> compound_factory::kBaseMap{ // NOLINT(bugprone-throwing-static-initialization,cert-err58-cpp)
{ "A", 'A' },
{ "C", 'C' },
{ "G", 'G' },
@@ -309,21 +306,21 @@ const std::map<std::string, char> compound_factory::kBaseMap{
class compound_factory_impl : public std::enable_shared_from_this<compound_factory_impl>
{
public:
compound_factory_impl();
compound_factory_impl() = default;
compound_factory_impl(const fs::path &file, std::shared_ptr<compound_factory_impl> next);
virtual ~compound_factory_impl()
virtual ~compound_factory_impl() // NOLINT(modernize-use-equals-default)
{
for (auto c : m_compounds)
delete c;
}
virtual bool exists_self(const std::string &id) const
[[nodiscard]] virtual bool exists_self(const std::string &id) const
{
if (m_missing.contains(id))
return false;
if (std::find_if(m_compounds.begin(), m_compounds.end(), [id](compound *c)
if (std::ranges::find_if(m_compounds, [id](compound *c)
{ return c->id() == id; }) != m_compounds.end())
return true;
@@ -384,12 +381,8 @@ class compound_factory_impl : public std::enable_shared_from_this<compound_facto
std::shared_ptr<compound_factory_impl> m_next;
};
compound_factory_impl::compound_factory_impl()
{
}
compound_factory_impl::compound_factory_impl(std::shared_ptr<compound_factory_impl> next)
: m_next(next)
: m_next(std::move(next))
{
}
@@ -406,7 +399,7 @@ compound *compound_factory_impl::create(const std::string &id)
if (m_missing.contains(id))
return nullptr;
if (auto i = find_if(m_compounds.begin(), m_compounds.end(), [id](compound *c)
if (auto i = std::ranges::find_if(m_compounds, [id](compound *c)
{ return c->id() == id; });
i != m_compounds.end())
return *i;
@@ -425,13 +418,13 @@ compound *compound_factory_impl::create(const std::string &id)
}
}
else
ccd.reset(new std::ifstream(m_file));
ccd = std::make_unique<std::ifstream>(m_file);
cif::file file;
if (m_index.empty())
{
if (cif::VERBOSE > 1)
if (VERBOSE > 1)
{
std::cout << "Creating component index "
<< "...";
@@ -441,7 +434,7 @@ compound *compound_factory_impl::create(const std::string &id)
cif::parser parser(*ccd, file);
m_index = parser.index_datablocks();
if (cif::VERBOSE > 1)
if (VERBOSE > 1)
std::cout << " done\n";
// reload the resource, perhaps this should be improved...
@@ -452,10 +445,10 @@ compound *compound_factory_impl::create(const std::string &id)
throw std::runtime_error("Could not locate the CCD components.cif file, please make sure the software is installed properly and/or use the update-libcifpp-data to fetch the data.");
}
else
ccd.reset(new std::ifstream(m_file));
ccd = std::make_unique<std::ifstream>(m_file);
}
if (cif::VERBOSE > 1)
if (VERBOSE > 1)
{
std::cout << "Loading component " << id << "...";
std::cout.flush();
@@ -464,7 +457,7 @@ compound *compound_factory_impl::create(const std::string &id)
cif::parser parser(*ccd, file);
parser.parse_single_datablock(id, m_index);
if (cif::VERBOSE > 1)
if (VERBOSE > 1)
std::cout << " done\n";
if (not file.empty())
@@ -490,9 +483,9 @@ compound *compound_factory_impl::create(const std::string &id)
class local_compound_factory_impl : public compound_factory_impl
{
public:
local_compound_factory_impl(const cif::file &file, std::shared_ptr<compound_factory_impl> next)
local_compound_factory_impl(cif::file file, shared_ptr<compound_factory_impl> next)
: compound_factory_impl(next)
, m_local_file(file)
, m_local_file(std::move(file))
{
}
@@ -509,7 +502,7 @@ compound *local_compound_factory_impl::create(const std::string &id)
if (m_missing.contains(id))
return nullptr;
if (auto i = find_if(m_compounds.begin(), m_compounds.end(), [id](compound *c)
if (auto i = std::ranges::find_if(m_compounds, [id](compound *c)
{ return c->id() == id; });
i != m_compounds.end())
return *i;
@@ -573,16 +566,16 @@ compound *local_compound_factory_impl::construct_compound(const datablock &rdb,
{ "atom_id", atom_id },
{ "type_symbol", type_symbol },
{ "charge", charge },
{ "model_Cartn_x", x.has_value() ? x : xi, 3 },
{ "model_Cartn_y", y.has_value() ? y : yi, 3 },
{ "model_Cartn_z", z.has_value() ? z : zi, 3 },
{ "model_Cartn_x", { x.has_value() ? x : xi , 3 } },
{ "model_Cartn_y", { y.has_value() ? y : yi , 3 } },
{ "model_Cartn_z", { z.has_value() ? z : zi , 3 } },
{ "pdbx_ordinal", ord++ } });
formal_charge += charge;
}
for (std::size_t ord = 1; const auto &[atom_id_1, atom_id_2, type, aromatic] :
rdb["chem_comp_bond"].rows<std::string, std::string, std::string, bool>("atom_id_1", "atom_id_2", "type", "aromatic"))
rdb["chem_comp_bond"].rows<std::string, std::string, std::string, std::string>("atom_id_1", "atom_id_2", "type", "aromatic"))
{
std::string value_order("SING");
@@ -629,7 +622,7 @@ compound *local_compound_factory_impl::construct_compound(const datablock &rdb,
{ "type", type },
{ "formula", formula },
{ "pdbx_formal_charge", formal_charge },
{ "formula_weight", formula_weight },
{ "formula_weight", { formula_weight, 3 } },
{ "three_letter_code", three_letter_code } });
std::shared_lock lock(mMutex);
@@ -656,14 +649,10 @@ compound_factory::compound_factory()
auto ccd = cif::load_resource("components.cif");
if (ccd)
m_impl = std::make_shared<compound_factory_impl>();
else if (cif::VERBOSE > 0)
else if (VERBOSE > 0)
std::cerr << "CCD components.cif resource was not found\n";
}
compound_factory::~compound_factory()
{
}
compound_factory &compound_factory::instance()
{
if (s_use_thread_local_instance)
@@ -695,7 +684,7 @@ void compound_factory::set_default_dictionary(const fs::path &inDictFile)
try
{
m_impl.reset(new compound_factory_impl(inDictFile, m_impl));
m_impl = std::make_shared<compound_factory_impl>(inDictFile, m_impl);
}
catch (const std::exception &)
{
@@ -710,7 +699,7 @@ void compound_factory::push_dictionary(const fs::path &inDictFile)
try
{
m_impl.reset(new compound_factory_impl(inDictFile, m_impl));
m_impl = std::make_shared<compound_factory_impl>(inDictFile, m_impl);
}
catch (const std::exception &)
{
@@ -722,7 +711,7 @@ void compound_factory::push_dictionary(const cif::file &inDictFile)
{
try
{
m_impl.reset(new local_compound_factory_impl(inDictFile, m_impl));
m_impl = std::make_shared<local_compound_factory_impl>(inDictFile, m_impl);
}
catch (const std::exception &)
{

View File

@@ -24,9 +24,17 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/condition.hpp"
#include "cif++/category.hpp"
#include "cif++/validate.hpp"
#include "cif++/cif++.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <typeinfo>
#include <vector>
namespace cif
{
@@ -36,9 +44,13 @@ iset get_category_items(const category &cat)
return cat.key_items();
}
uint16_t get_item_ix(const category &cat, std::string_view col)
std::optional<uint16_t> get_item_ix(const category &cat, std::string_view col)
{
return cat.get_item_ix(col);
auto ix = cat.get_item_ix(col);
std::optional<uint16_t> result;
if (ix < cat.get_item_count())
result = ix;
return result;
}
bool is_item_type_uchar(const category &cat, std::string_view col)
@@ -75,7 +87,7 @@ namespace detail
// return this;
// }
//
// bool test(row_handle r) const override
// bool test(const_row_handle r) const override
// {
// return m_single_hit == r;
// }
@@ -110,17 +122,24 @@ namespace detail
condition_impl *key_equals_condition_impl::prepare(const category &c)
{
m_item_ix = c.get_item_ix(m_item_name);
m_icase = is_item_type_uchar(c, m_item_name);
condition_impl *result = nullptr;
if (c.get_cat_validator() != nullptr and
c.key_item_indices().contains(m_item_ix) and
c.key_item_indices().size() == 1)
if (auto ix = get_item_ix(c, m_item_name); ix.has_value())
{
m_single_hit = c[{ { m_item_name, m_value } }];
m_item_ix = *ix;
m_icase = is_item_type_uchar(c, m_item_name);
if (c.get_cat_validator() != nullptr and
c.key_item_indices().contains(m_item_ix) and
c.key_item_indices().size() == 1)
{
m_single_hit = c[{ { m_item_name, m_value } }];
}
result = this;
}
return this;
return result;
}
bool found_in_range(condition_impl *c, std::vector<and_condition_impl *>::iterator b, std::vector<and_condition_impl *>::iterator e)
@@ -131,7 +150,7 @@ namespace detail
{
auto &cs = (*s)->m_sub;
if (find_if(cs.begin(), cs.end(), [c](const condition_impl *i)
if (std::ranges::find_if(cs, [c](const condition_impl *i)
{ return i->equals(c); }) == cs.end())
{
result = false;
@@ -162,7 +181,7 @@ namespace detail
and_result = new and_condition_impl();
and_result->m_sub.push_back(c);
fc.erase(fc.begin() + fc_i);
fc.erase(fc.begin() + static_cast<std::string::difference_type>(fc_i));
for (auto sub : subs)
{
@@ -177,7 +196,7 @@ namespace detail
continue;
}
ssub.erase(ssub.begin() + ssub_i);
ssub.erase(ssub.begin() + static_cast<std::string::difference_type>(ssub_i));
delete sc;
break;
}
@@ -196,7 +215,10 @@ namespace detail
condition_impl *and_condition_impl::prepare(const category &c)
{
for (auto &sub : m_sub)
sub = sub->prepare(c);
{
if (sub->prepare(c) == nullptr)
return nullptr;
}
if (auto cv = c.get_cat_validator(); cv != nullptr)
{
@@ -236,14 +258,14 @@ namespace detail
m_single = c[lookup];
for (auto s : subs)
m_sub.erase(std::remove(m_sub.begin(), m_sub.end(), s), m_sub.end());
std::erase(m_sub, s);
}
}
return this;
}
bool and_condition_impl::test(row_handle r) const
bool and_condition_impl::test(const_row_handle r) const
{
bool result = true;
@@ -270,25 +292,30 @@ namespace detail
for (auto &sub : m_sub)
{
sub = sub->prepare(c);
if (sub->prepare(c) == nullptr)
{
delete sub;
sub = nullptr;
continue;
}
if (typeid(*sub) == typeid(and_condition_impl))
and_conditions.push_back(static_cast<and_condition_impl *>(sub));
}
if (and_conditions.size() == m_sub.size())
std::erase(m_sub, nullptr);
if (not m_sub.empty() and and_conditions.size() == m_sub.size())
return and_condition_impl::combine_equal(and_conditions, this);
return this;
return m_sub.empty() ? nullptr : this;
}
} // namespace detail
void condition::prepare(const category &c)
bool condition::prepare(const category &c)
{
if (m_impl)
m_impl = m_impl->prepare(c);
m_prepared = true;
return m_impl and m_impl->prepare(c) != nullptr;
}
} // namespace cif

1183
src/cql.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -24,11 +24,19 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/datablock.hpp"
#include "cif++/validate.hpp"
#include "cif++/cif++.hpp"
#include <algorithm>
#include <cassert>
#include <exception>
#include <iostream>
#include <list>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
namespace cif
{
@@ -48,7 +56,7 @@ void datablock::load_dictionary()
{
try
{
set_validator(&validator_factory::instance().get(*audit_conform));
set_validator(validator_factory::instance().get(*audit_conform));
}
catch (const std::exception &ex)
{
@@ -57,6 +65,18 @@ void datablock::load_dictionary()
}
}
void datablock::load_dictionary(std::string_view dict)
{
try
{
set_validator(validator_factory::instance().get(dict));
}
catch (const std::exception &ex)
{
std::clog << ex.what() << '\n';
}
}
void datablock::set_validator(const validator *v)
{
m_validator = v;
@@ -108,17 +128,16 @@ bool datablock::strip()
bool result = true;
// remove all categories that have no validator
erase(std::remove_if(begin(), end(), [](category &c)
{
std::erase_if(*this, [](category &c)
{
bool result = false;
if (c.get_cat_validator() == nullptr)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Dropping category " << c.name() << '\n';
result = true;
}
return result; }),
end());
return result; });
// then strip the remaining categories
for (auto &cat : *this)
@@ -147,7 +166,7 @@ bool datablock::strip()
category &datablock::operator[](std::string_view name)
{
auto i = std::find_if(begin(), end(), [name](const category &c)
auto i = std::ranges::find_if(*this, [name](const category &c)
{ return iequals(c.name(), name); });
if (i != end())
@@ -164,14 +183,14 @@ category &datablock::operator[](std::string_view name)
const category &datablock::operator[](std::string_view name) const
{
static const category s_empty;
auto i = std::find_if(begin(), end(), [name](const category &c)
auto i = std::ranges::find_if(*this, [name](const category &c)
{ return iequals(c.name(), name); });
return i == end() ? s_empty : *i;
}
category *datablock::get(std::string_view name)
{
auto i = std::find_if(begin(), end(), [name](const category &c)
auto i = std::ranges::find_if(*this, [name](const category &c)
{ return iequals(c.name(), name); });
return i == end() ? nullptr : &*i;
}
@@ -217,7 +236,7 @@ std::vector<std::string> datablock::get_item_order() const
std::vector<std::string> result;
// for entry and audit_conform on top
auto ci = find_if(begin(), end(), [](const category &cat)
auto ci = std::ranges::find_if(*this, [](const category &cat)
{ return cat.name() == "entry"; });
if (ci != end())
{
@@ -225,7 +244,7 @@ std::vector<std::string> datablock::get_item_order() const
result.insert(result.end(), cto.begin(), cto.end());
}
ci = find_if(begin(), end(), [](const category &cat)
ci = std::ranges::find_if(*this, [](const category &cat)
{ return cat.name() == "audit_conform"; });
if (ci != end())
{
@@ -273,7 +292,7 @@ namespace
for (auto link : validator.get_links_for_child(cat))
{
auto ei = std::find_if(cat_order.begin(), cat_order.end(), [parent = link->m_parent_category](elem_t &a)
auto ei = std::ranges::find_if(cat_order, [parent = link->m_parent_category](elem_t &a)
{ return std::get<0>(a) == parent; });
if (ei == cat_order.end())
@@ -310,7 +329,7 @@ void datablock::write(std::ostream &os) const
for (auto i = cat_order.begin(); i != cat_order.end(); ++i)
calculate_cat_order(cat_order, i, *m_validator);
std::sort(cat_order.begin(), cat_order.end(), [](const elem_t &a, const elem_t &b)
std::ranges::sort(cat_order, [](const elem_t &a, const elem_t &b)
{
const auto &[cat_a, count_a, on_stack_a] = a;
const auto &[cat_b, count_b, on_stack_b] = b;
@@ -362,7 +381,7 @@ void datablock::write(std::ostream &os, const std::vector<std::string> &item_nam
{
std::string cat_name, item_name;
std::tie(cat_name, item_name) = split_item_name(o);
if (find_if(cat_order.rbegin(), cat_order.rend(), [cat_name](const std::string &s) -> bool
if (std::ranges::find_if(std::ranges::reverse_view(cat_order), [cat_name](const std::string &s) -> bool
{ return iequals(cat_name, s); }) == cat_order.rend())
cat_order.push_back(cat_name);
}
@@ -389,7 +408,7 @@ void datablock::write(std::ostream &os, const std::vector<std::string> &item_nam
// for any Category we missed in the catOrder
for (auto &cat : *this)
{
if (find_if(cat_order.begin(), cat_order.end(), [&](const std::string &s) -> bool
if (std::ranges::find_if(cat_order, [&](const std::string &s) -> bool
{ return iequals(cat.name(), s); }) != cat_order.end())
continue;
@@ -413,14 +432,14 @@ bool datablock::operator==(const datablock &rhs) const
if (not cat.empty())
catA.push_back(cat.name());
}
std::sort(catA.begin(), catA.end());
std::ranges::sort(catA);
for (auto &cat : dbB)
{
if (not cat.empty())
catB.push_back(cat.name());
}
std::sort(catB.begin(), catB.end());
std::ranges::sort(catB);
// loop over categories twice, to group output
// First iteration is to list missing categories.

View File

@@ -24,13 +24,23 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/condition.hpp"
#include "cif++/dictionary_parser.hpp"
#include "cif++/file.hpp"
#include "cif++/parser.hpp"
#include "cif++/cif++.hpp"
#include <cstddef>
#include <exception>
#include <iomanip>
#include <format>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <ranges>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
namespace cif
{
@@ -63,7 +73,7 @@ class dictionary_parser : public parser
default:
{
dict.reset(new datablock(m_token_value)); // dummy datablock, for constructing the validator only
dict = std::make_unique<datablock>(m_token_value); // dummy datablock, for constructing the validator only
m_datablock = dict.get();
match(CIFToken::DATA);
@@ -104,7 +114,7 @@ class dictionary_parser : public parser
// store meta information
if (auto dictionary = m_datablock->get("dictionary"); dictionary != nullptr and not dictionary->empty())
{
const auto &[name, version] = dictionary->front().get<std::string,std::optional<std::string>>("title", "version");
const auto &[name, version] = dictionary->front().get<std::string, std::optional<std::string>>("title", "version");
m_validator.append_audit_conform(name, version);
}
@@ -119,7 +129,7 @@ class dictionary_parser : public parser
if (not m_collected_item_types)
m_collected_item_types = collect_item_types();
std::string saveFrameName { m_token_value };
std::string saveFrameName{ m_token_value };
if (saveFrameName.empty())
error("Invalid save frame, should contain more than just 'save_' here");
@@ -127,7 +137,7 @@ class dictionary_parser : public parser
bool isCategorySaveFrame = m_token_value[0] != '_';
datablock dict(m_token_value);
datablock::iterator cat = dict.end();
auto cat = dict.end();
match(CIFToken::SAVE_NAME);
while (m_lookahead == CIFToken::LOOP or m_lookahead == CIFToken::ITEM_NAME)
@@ -160,8 +170,34 @@ class dictionary_parser : public parser
for (auto item_name : item_names)
{
row[item_name] = m_token_value;
match(m_lookahead);
switch (m_lookahead)
{
using enum CIFToken;
case VALUE_INAPPLICABLE:
row[item_name] = nullptr;
match(VALUE_INAPPLICABLE);
break;
case VALUE_UNKNOWN:
row[item_name] = item_value{ std::optional<std::string>{} };
match(VALUE_UNKNOWN);
break;
case VALUE_NUMERIC_INTEGER:
row[item_name] = m_token_value_int;
match(VALUE_NUMERIC_INTEGER);
break;
case VALUE_NUMERIC_FLOAT:
row[item_name] = m_token_value_float;
match(VALUE_NUMERIC_FLOAT);
break;
case VALUE_CHARSTRING:
case VALUE_TEXTFIELD:
row[item_name] = m_token_value;
match(m_lookahead);
break;
default:
match(VALUE_CHARSTRING);
}
}
}
@@ -179,9 +215,35 @@ class dictionary_parser : public parser
if (cat->empty())
cat->emplace({});
cat->back()[item_name] = m_token_value;
match(m_lookahead >= CIFToken::VALUE_INAPPLICABLE ? m_lookahead : CIFToken::VALUE_CHARSTRING);
switch (m_lookahead)
{
using enum CIFToken;
case VALUE_INAPPLICABLE:
cat->back()[item_name] = nullptr;
match(VALUE_INAPPLICABLE);
break;
case VALUE_UNKNOWN:
cat->back()[item_name] = item_value{ std::optional<std::string>{} };
match(VALUE_UNKNOWN);
break;
case VALUE_NUMERIC_INTEGER:
cat->back()[item_name] = m_token_value_int;
match(VALUE_NUMERIC_INTEGER);
break;
case VALUE_NUMERIC_FLOAT:
cat->back()[item_name] = m_token_value_float;
match(VALUE_NUMERIC_FLOAT);
break;
case VALUE_CHARSTRING:
case VALUE_TEXTFIELD:
cat->back()[item_name] = m_token_value;
match(m_lookahead);
break;
default:
match(VALUE_CHARSTRING);
}
}
}
@@ -189,7 +251,7 @@ class dictionary_parser : public parser
if (isCategorySaveFrame)
{
std::string category = dict["category"].front().get<std::string>("id");
auto category = dict["category"].front().get<std::string>("id");
std::vector<std::string> keys;
for (auto k : dict["category_key"])
@@ -204,17 +266,22 @@ class dictionary_parser : public parser
else
{
// if the type code is missing, this must be a pointer, just skip it
std::string typeCode = dict["item_type"].front().get<std::string>("code");
std::optional<std::string> typeCode;
if (not dict["item_type"].empty())
typeCode = dict["item_type"].front().get<std::optional<std::string>>("code");
const type_validator *tv = nullptr;
if (not(typeCode.empty() or typeCode == "?"))
tv = m_validator.get_validator_for_type(typeCode);
if (typeCode.has_value())
tv = m_validator.get_validator_for_type(*typeCode);
iset ess;
for (auto e : dict["item_enumeration"])
ess.insert(e["value"].get<std::string>());
std::string defaultValue = dict["item_default"].front().get<std::string>("value");
std::string defaultValue;
if (auto &cat = dict["item_default"]; not cat.empty())
defaultValue = cat.front().get<std::string>("value");
// bool defaultIsNull = false;
// if (defaultValue.empty())
// {
@@ -228,7 +295,7 @@ class dictionary_parser : public parser
std::vector<item_alias> aliases;
for (const auto &[alias_name, dictionary, version] :
dict["item_aliases"].rows<std::string,std::string,std::string>("alias_name", "dictionary", "version"))
dict["item_aliases"].rows<std::string, std::string, std::string>("alias_name", "dictionary", "version"))
{
aliases.emplace_back(alias_name, dictionary, version);
}
@@ -236,8 +303,7 @@ class dictionary_parser : public parser
// collect the dict from our dataBlock and construct validators
for (auto i : dict["item"])
{
std::string item, category, mandatory;
cif::tie(item, category, mandatory) = i.get("name", "category_id", "mandatory_code");
auto &&[item, category, mandatory] = i.get<std::string, std::string, std::string>("name", "category_id", "mandatory_code");
std::string cat_name, item_name;
std::tie(cat_name, item_name) = split_item_name(item);
@@ -252,9 +318,9 @@ class dictionary_parser : public parser
auto &ivs = mItemValidators[category];
auto vi = find(ivs.begin(), ivs.end(), item_validator{ item_name });
auto vi = std::ranges::find(ivs, item_validator{ item_name });
if (vi == ivs.end())
ivs.push_back(item_validator{ item_name, iequals(mandatory, "yes"), tv, ess, defaultValue, cat_name, std::move(aliases) });
ivs.push_back(item_validator{ item_name, iequals(mandatory, "yes"), tv, ess, defaultValue, cat_name, aliases });
else
{
// need to update the itemValidator?
@@ -293,9 +359,7 @@ class dictionary_parser : public parser
// collect the dict from our dataBlock and construct validators
for (auto i : dict["item_linked"])
{
mLinkedItems.emplace(i.get<std::string,std::string>("child_name", "parent_name"));
}
mLinkedItems.emplace(i.get<std::string, std::string>("child_name", "parent_name"));
}
}
@@ -340,9 +404,7 @@ class dictionary_parser : public parser
for (auto gl : linkedGroupList)
{
std::string child, parent;
int link_group_id;
cif::tie(child, parent, link_group_id) = gl.get("child_name", "parent_name", "link_group_id");
auto &&[child, parent, link_group_id] = gl.get<std::string, std::string, int>("child_name", "parent_name", "link_group_id");
auto civ = m_validator.get_validator_for_item(child);
if (civ == nullptr)
@@ -356,7 +418,7 @@ class dictionary_parser : public parser
if (not linkIndex.count(key))
{
linkIndex[key] = linkKeys.size();
linkKeys.push_back({});
linkKeys.emplace_back();
}
std::size_t ix = linkIndex.at(key);
@@ -384,7 +446,7 @@ class dictionary_parser : public parser
if (not linkIndex.count(key))
{
linkIndex[key] = linkKeys.size();
linkKeys.push_back({});
linkKeys.emplace_back();
}
std::size_t ix = linkIndex.at(key);
@@ -409,6 +471,35 @@ class dictionary_parser : public parser
break;
}
// A last validation, link ends should both point to the same time
auto childCatValidator = m_validator.get_validator_for_category(link.m_child_category);
auto parentCatValidator = m_validator.get_validator_for_category(link.m_parent_category);
if (childCatValidator == nullptr)
throw std::runtime_error(std::format("Invalid dictionary, undefined category {} in link {}", link.m_child_category, link.m_link_group_id));
if (parentCatValidator == nullptr)
throw std::runtime_error(std::format("Invalid dictionary, undefined category {} in link {}", link.m_parent_category, link.m_link_group_id));
for (size_t ix = 0; ix < link.m_child_keys.size(); ++ix)
{
auto childItemValidator = childCatValidator->get_validator_for_item(link.m_child_keys[ix]);
auto parentItemValidator = parentCatValidator->get_validator_for_item(link.m_parent_keys[ix]);
if (childItemValidator == nullptr)
throw std::runtime_error(std::format("Invalid dictionary, in link group {} the item {} is not know in category {}",
link.m_link_group_id, link.m_child_keys[ix], link.m_child_category));
if (parentItemValidator == nullptr)
throw std::runtime_error(std::format("Invalid dictionary, in link group {} the item {} is not know in category {}",
link.m_link_group_id, link.m_parent_keys[ix], link.m_parent_category));
if (childItemValidator->m_type == nullptr)
const_cast<item_validator *>(childItemValidator)->m_type = parentItemValidator->m_type;
else if (childItemValidator->m_type != parentItemValidator->m_type)
throw std::runtime_error(std::format("Invalid dictionary, in link group {} the items _{}.{}/_{}.{} do not have the same type",
link.m_link_group_id, link.m_parent_category, link.m_parent_keys[ix], link.m_child_category, link.m_child_keys[ix]));
}
m_validator.add_link_validator(std::move(link));
}
@@ -418,7 +509,7 @@ class dictionary_parser : public parser
{
for (auto &iv : cv.m_item_validators)
{
if (iv.m_type == nullptr and cif::VERBOSE >= 0)
if (iv.m_type == nullptr and VERBOSE >= 0)
std::cerr << "Missing item_type for " << iv.m_item_name << '\n';
}
}

View File

@@ -24,9 +24,16 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/file.hpp"
#include "cif++/condition.hpp"
#include "cif++/gzio.hpp"
#include "cif++/cif++.hpp"
#include <cassert>
#include <exception>
#include <filesystem>
#include <istream>
#include <list>
#include <stdexcept>
#include <string_view>
#include <tuple>
namespace cif
{
@@ -76,13 +83,13 @@ bool file::validate_links() const
bool file::contains(std::string_view name) const
{
return std::find_if(begin(), end(), [name](const datablock &db)
return std::ranges::find_if(*this, [name](const datablock &db)
{ return iequals(db.name(), name); }) != end();
}
datablock &file::operator[](std::string_view name)
{
auto i = std::find_if(begin(), end(), [name](const datablock &c)
auto i = std::ranges::find_if(*this, [name](const datablock &c)
{ return iequals(c.name(), name); });
if (i != end())
@@ -95,7 +102,7 @@ datablock &file::operator[](std::string_view name)
const datablock &file::operator[](std::string_view name) const
{
static const datablock s_empty;
auto i = std::find_if(begin(), end(), [name](const datablock &c)
auto i = std::ranges::find_if(*this, [name](const datablock &c)
{ return iequals(c.name(), name); });
return i == end() ? s_empty : *i;
}
@@ -139,30 +146,6 @@ void file::load(const std::filesystem::path &p)
}
}
void file::load(const std::filesystem::path &p, const validator &v)
{
gzio::ifstream in(p);
if (not in.is_open())
throw std::runtime_error("Could not open file '" + p.string() + '\'');
try
{
load(in, v);
}
catch (const std::exception &)
{
throw_with_nested(std::runtime_error("Error reading file '" + p.string() + '\''));
}
}
void file::load(std::istream &is, const validator &v)
{
parser p(is, *this);
p.parse_file();
for (auto &db : *this)
db.set_validator(&v);
}
void file::load(std::istream &is)
{
parser p(is, *this);

View File

@@ -27,14 +27,55 @@
#include "cif++/item.hpp"
#include "cif++/row.hpp"
#include "cif++/text.hpp"
#include <algorithm>
#include <cassert>
#include <charconv>
#include <cmath>
#include <compare>
#include <ios>
#include <cstdint>
#include <ostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
namespace cif
{
bool item_handle::empty() const
{
return m_item_ix >= m_row.size() or m_row[m_item_ix].empty();
}
item_value &item_handle::value()
{
assert(m_item_ix < m_row.size());
return m_row.operator[](m_item_ix);
}
const item_value &item_handle::value() const
{
assert(m_item_ix < m_row.size());
return m_row.operator[](m_item_ix);
}
void swap(item_handle a, item_handle b) noexcept
{
item_value v(std::move(a.value()));
a.value() = std::move(b.value());
b.value() = std::move(v);
}
void item_handle::set(item_value value, bool updateLinked)
{
row_handle rh{ m_category, m_row };
rh.assign(m_item_ix, std::move(value), updateLinked);
}
int item_value::compare(const item_value &b, bool ignore_case) const noexcept
{
int d = static_cast<int>(m_data.m_type) - static_cast<int>(b.m_data.m_type);
@@ -43,90 +84,210 @@ int item_value::compare(const item_value &b, bool ignore_case) const noexcept
{
switch (m_data.m_type)
{
case cif::item_value_type::BOOLEAN:
d = static_cast<int>(m_data.m_value.m_boolean) - static_cast<int>(b.m_data.m_value.m_boolean);
break;
case cif::item_value_type::INT:
using enum item_value_type;
case INT:
d = m_data.m_value.m_integer - b.m_data.m_value.m_integer;
break;
case cif::item_value_type::FLOAT:
{
auto dp = (m_data.m_value.m_float <=> b.m_data.m_value.m_float);
if (dp == std::partial_ordering::less)
d = -1;
else if (dp == std::partial_ordering::greater)
d = 1;
case FLOAT:
// stupid comparison based on chopped textual representation
if (m_data.m_len > 0 or b.m_data.m_len > 0)
{
double fa = m_data.m_value.m_float;
double fb = b.m_data.m_value.m_float;
auto delta = std::abs(fa - fb);
if (delta == 0 or std::isnan(delta))
d = 0;
else if (m_data.m_len and b.m_data.m_len)
{
auto epsilon = std::pow(10.0f, -1.0f * std::min(m_data.m_len, b.m_data.m_len));
if (delta > epsilon)
d = fa < fb ? -1 : 1;
else
d = 0;
}
else
{
auto dp = (m_data.m_value.m_float <=> b.m_data.m_value.m_float);
if (dp == std::partial_ordering::less)
d = -1;
else if (dp == std::partial_ordering::greater)
d = 1;
}
}
else
{
auto dp = (m_data.m_value.m_float <=> b.m_data.m_value.m_float);
if (dp == std::partial_ordering::less)
d = -1;
else if (dp == std::partial_ordering::greater)
d = 1;
}
break;
}
case cif::item_value_type::TEXT:
case TEXT:
d = m_data.sv().compare(b.m_data.sv());
break;
default:;
}
}
else if (is_number() and b.is_number())
{
std::partial_ordering dp = std::partial_ordering::equivalent;
if (is_number_float())
dp = m_data.m_value.m_float <=> b.m_data.m_value.m_integer;
else /* if (is_number_int()) */
dp = m_data.m_value.m_integer <=> b.m_data.m_value.m_float;
if (dp == std::partial_ordering::less)
d = -1;
else if (dp == std::partial_ordering::greater)
d = 1;
else
d = 0;
}
else if (is_number_int() and b.is_string())
d = str().compare(b.m_data.sv());
else if (is_string() and b.is_number_int())
d = m_data.sv().compare(b.str());
return d;
}
// const item_handle item_handle::s_null_item;
// row_handle s_null_row_handle;
std::string item_value::str() const
{
switch (m_data.m_type)
{
using enum item_value_type;
// item_handle::item_handle()
// : m_item_ix(std::numeric_limits<uint16_t>::max())
// , m_row_handle(s_null_row_handle)
// {
// }
case MISSING:
return "?";
// std::string_view item_handle::text() const
// {
// if (not m_row_handle.empty())
// {
// auto iv = m_row_handle.m_row->get(m_item_ix);
// if (iv != nullptr)
// return iv->text();
// }
case INAPPLICABLE:
return ".";
// return {};
// }
case TEXT:
return std::string{ m_data.sv() };
// void item_handle::assign_value(std::string_view value)
case INT:
{
char s[32];
std::to_chars_result r = std::to_chars(s, s + sizeof(s), m_data.m_value.m_integer);
return r.ec == std::errc{} ? std::string{ s, r.ptr } : "*****";
}
case FLOAT:
{
char s[32];
std::to_chars_result r;
if (m_data.m_len)
{
r = std::to_chars(s, s + sizeof(s), m_data.m_value.m_float, std::chars_format::fixed, m_data.m_len);
if (r.ec != std::errc{})
r = std::to_chars(s, s + sizeof(s), m_data.m_value.m_float);
}
else
r = std::to_chars(s, s + sizeof(s), m_data.m_value.m_float);
return r.ec == std::errc{} ? std::string{ s, r.ptr } : "*****";
}
}
std::unreachable();
}
// void const_item_handle::assign_value(const item_value &value)
// {
// assert(not m_row_handle.empty());
// m_row_handle.assign(m_item_ix, value, true);
// }
// void item_handle::swap(item_handle &b)
// {
// assert(m_item_ix == b.m_item_ix);
// // assert(&m_row_handle.m_category == &b.m_row_handle.m_category);
// m_row_handle.swap(m_item_ix, b.m_row_handle);
// }
std::ostream &operator<<(std::ostream &os, const item_value &v)
{
switch (v.type())
{
case cif::item_value_type::BOOLEAN:
os << std::boolalpha << v.m_data.m_value.m_boolean;
break;
case cif::item_value_type::INT:
os << v.m_data.m_value.m_integer;
break;
case cif::item_value_type::FLOAT:
os << v.m_data.m_value.m_float;
break;
case cif::item_value_type::TEXT:
os << v.m_data.sv();
break;
case cif::item_value_type::MISSING:
os << '?';
break;
case cif::item_value_type::EMPTY:
os << '.';
break;
using enum item_value_type;
case INT: os << v.m_data.m_value.m_integer; break;
case FLOAT: os << v.m_data.m_value.m_float; break;
case TEXT: os << v.m_data.sv(); break;
case MISSING: os << '?'; break;
case INAPPLICABLE: os << '.'; break;
default: os.setstate(std::ios::failbit);
}
return os;
}
void item_value::cast_to_int()
{
switch (type())
{
using enum item_value_type;
case INT:
break;
case FLOAT:
*this = std::rint(m_data.m_value.m_float);
break;
case TEXT:
{
auto s = sv();
int64_t v;
auto [ptr, ec] = cif::from_chars(s.data(), s.data() + s.size(), v);
if (ec != std::errc{})
throw std::system_error(std::make_error_code(ec), "attempt to cast value to integer failed");
if (ptr != s.data() + s.size())
throw std::runtime_error("attempt to cast value to integer failed, trailing data");
*this = v;
break;
}
default:
break;
}
}
void item_value::cast_to_float()
{
switch (type())
{
using enum item_value_type;
case INT:
*this = static_cast<double>(m_data.m_value.m_integer);
break;
case FLOAT:
break;
case TEXT:
{
auto s = sv();
double v;
auto [ptr, ec] = cif::from_chars(s.data(), s.data() + s.size(), v);
if (ec != std::errc{})
throw std::system_error(std::make_error_code(ec), "attempt to cast value to integer failed");
if (ptr != s.data() + s.size())
throw std::runtime_error("attempt to cast value to integer failed, trailing data");
*this = v;
break;
}
default:
break;
}
}
void item_value::cast_to_string()
{
*this = str();
}
} // namespace cif

View File

@@ -24,19 +24,35 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/model.hpp"
#include "cif++.hpp"
#include "cif++/point.hpp"
#include "cif++/cif++.hpp"
#include "cif++/item.hpp"
#include <filesystem>
#include <fstream>
#include <algorithm>
#include <cassert>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <exception>
#include <format>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <ranges>
#include <set>
#include <stack>
#include <stdexcept>
namespace fs = std::filesystem;
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
namespace cif::mm
{
@@ -51,95 +67,50 @@ void atom::atom_impl::moveTo(const point &p)
auto r = row();
r.assign("Cartn_x", cif::format("{:.3f}", p.m_x), false, false);
r.assign("Cartn_y", cif::format("{:.3f}", p.m_y), false, false);
r.assign("Cartn_z", cif::format("{:.3f}", p.m_z), false, false);
r.assign("Cartn_x", { p.x, 3 }, false, false);
r.assign("Cartn_y", { p.y, 3 }, false, false);
r.assign("Cartn_z", { p.z, 3 }, false, false);
m_location = p;
}
// const compound *compound() const;
std::string atom::atom_impl::get_property(std::string_view name) const
const item_value &atom::atom_impl::get_property(std::string_view name) const
{
return row()[name].get<std::string>();
if (auto rh = row(); rh)
return rh[name].value();
throw std::runtime_error(std::format("Missing property {} for atom", name));
}
int atom::atom_impl::get_property_int(std::string_view name) const
{
int result = 0;
if (not row()[name].empty())
{
auto s = get_property(name);
std::from_chars_result r = std::from_chars(s.data(), s.data() + s.length(), result);
if ((bool)r.ec and VERBOSE > 0)
std::cerr << "Error converting " << s << " to number for property " << name << '\n';
}
return result;
}
float atom::atom_impl::get_property_float(std::string_view name) const
{
float result = 0;
if (not row()[name].empty())
{
auto s = get_property(name);
std::from_chars_result r = cif::from_chars(s.data(), s.data() + s.length(), result);
if ((bool)r.ec and VERBOSE > 0)
std::cerr << "Error converting " << s << " to number for property " << name << '\n';
}
return result;
}
void atom::atom_impl::set_property(const std::string_view name, const std::string &value)
void atom::atom_impl::set_property(const std::string_view name, item_value value)
{
auto r = row();
if (not r)
throw std::runtime_error("Trying to modify a row that does not exist");
r.assign(name, value, true, true);
r.assign(name, std::move(value), true, true);
}
int atom::atom_impl::compare(const atom_impl &b) const
{
int d = get_property("label_asym_id").compare(b.get_property("label_asym_id"));
if (d == 0)
d = get_property_int("label_seq_id") - b.get_property_int("label_seq_id");
d = get_property("label_seq_id").compare(b.get_property("label_seq_id"));
if (d == 0)
d = get_property_int("auth_seq_id") - b.get_property_int("auth_seq_id");
d = get_property("auth_seq_id").compare(b.get_property("auth_seq_id"));
if (d == 0)
d = get_property("label_atom_id").compare(b.get_property("label_atom_id"));
return d;
}
// bool atom::atom_impl::getAnisoU(float anisou[6]) const
// {
// bool result = false;
// auto cat = m_db.get("atom_site_anisotrop");
// if (cat)
// {
// for (auto r : cat->find(key("id") == m_id))
// {
// tie(anisou[0], anisou[1], anisou[2], anisou[3], anisou[4], anisou[5]) =
// r.get("U[1][1]", "U[1][2]", "U[1][3]", "U[2][2]", "U[2][3]", "U[3][3]");
// result = true;
// break;
// }
// }
// return result;
// }
int atom::atom_impl::get_charge() const
{
auto formalCharge = row()["pdbx_formal_charge"].get<std::optional<int>>();
if (not formalCharge.has_value())
{
auto c = cif::compound_factory::instance().create(get_property("label_comp_id"));
auto c = cif::compound_factory::instance().create(get_property("label_comp_id").get<std::string>());
if (c != nullptr and c->atoms().size() == 1)
formalCharge = c->atoms().front().charge;
@@ -228,15 +199,16 @@ atom residue::create_new_atom(atom_type inType, const std::string &inAtomID, poi
{ "label_entity_id", get_entity_id() },
{ "label_atom_id", inAtomID },
{ "label_asym_id", m_asym_id },
{ "label_alt_id", "." },
{ "label_alt_id", cif::item_value_type::INAPPLICABLE },
{ "label_comp_id", m_compound_id },
{ "label_seq_id", m_seq_id },
{ "pdbx_PDB_ins_code", get_pdb_ins_code() },
{ "auth_asym_id", m_pdb_strand_id },
{ "auth_atom_id", inAtomID },
{ "auth_comp_id", m_compound_id },
{ "auth_seq_id", m_pdb_seq_num },
{ "occupancy", 1.0f, 2 },
{ "B_iso_or_equiv", 20.0f },
{ "occupancy", { 1.0f, 2 } },
{ "B_iso_or_equiv", { 20.0f, 3 } },
{ "pdbx_PDB_model_num", m_structure->get_model_nr() },
});
@@ -357,13 +329,13 @@ std::tuple<point, float> residue::center_and_radius() const
bool residue::has_alternate_atoms() const
{
return std::find_if(m_atoms.begin(), m_atoms.end(), [](const atom &atom)
return std::ranges::find_if(m_atoms, [](const atom &atom)
{ return atom.is_alternate(); }) != m_atoms.end();
}
bool residue::has_alternate_atoms_for(const std::string &atomID) const
{
return std::find_if(m_atoms.begin(), m_atoms.end(), [atomID](const atom &atom)
return std::ranges::find_if(m_atoms, [atomID](const atom &atom)
{ return atom.get_label_atom_id() == atomID and atom.is_alternate(); }) != m_atoms.end();
}
@@ -407,24 +379,6 @@ monomer::monomer(const polymer &polymer, std::size_t index, int seqID, const std
{
}
monomer::monomer(monomer &&rhs)
: residue(std::move(rhs))
, m_polymer(rhs.m_polymer)
, m_index(rhs.m_index)
{
rhs.m_polymer = nullptr;
}
monomer &monomer::operator=(monomer &&rhs)
{
residue::operator=(std::move(rhs));
m_polymer = rhs.m_polymer;
rhs.m_polymer = nullptr;
m_index = rhs.m_index;
return *this;
}
bool monomer::is_first_in_chain() const
{
return m_index == 0;
@@ -538,7 +492,7 @@ float monomer::kappa() const
{
double ckap = cosinus_angle(CAlpha().get_location(), prevPrev.CAlpha().get_location(), nextNext.CAlpha().get_location(), CAlpha().get_location());
double skap = std::sqrt(1 - ckap * ckap);
result = static_cast<float>(std::atan2(skap, ckap) * 180 / kPI);
result = static_cast<float>(std::atan2(skap, ckap) * 180 / std::numbers::pi_v<float>);
}
}
}
@@ -594,7 +548,7 @@ float monomer::omega() const
return result;
}
const std::map<std::string, std::vector<std::string>> kChiAtomsMap = {
const std::map<std::string, std::vector<std::string>> kChiAtomsMap = { // NOLINT(bugprone-throwing-static-initialization,cert-err58-cpp)
{ "ASP", { "CG", "OD1" } },
{ "ASN", { "CG", "OD1" } },
{ "ARG", { "CG", "CD", "NE", "CZ" } },
@@ -735,8 +689,8 @@ float monomer::chiral_volume() const
auto atom2 = get_atom_by_atom_id("CD1");
auto atom3 = get_atom_by_atom_id("CD2");
result = dot_product(atom1.get_location() - centre.get_location(),
cross_product(atom2.get_location() - centre.get_location(), atom3.get_location() - centre.get_location()));
result = dot(atom1.get_location() - centre.get_location(),
cross(atom2.get_location() - centre.get_location(), atom3.get_location() - centre.get_location()));
}
else if (m_compound_id == "VAL")
{
@@ -745,8 +699,8 @@ float monomer::chiral_volume() const
auto atom2 = get_atom_by_atom_id("CG1");
auto atom3 = get_atom_by_atom_id("CG2");
result = dot_product(atom1.get_location() - centre.get_location(),
cross_product(atom2.get_location() - centre.get_location(), atom3.get_location() - centre.get_location()));
result = dot(atom1.get_location() - centre.get_location(),
cross(atom2.get_location() - centre.get_location(), atom3.get_location() - centre.get_location()));
}
return result;
@@ -773,8 +727,10 @@ bool monomer::are_bonded(const monomer &a, const monomer &b, float errorMargin)
result = std::abs(distanceCACA - maxCACADistance) < errorMargin;
}
catch (...)
catch (const std::exception &ex)
{
if (VERBOSE > 2)
std::cerr << "missing atoms in monomer::are_bonded: " << ex.what() << '\n';
}
return result;
@@ -816,11 +772,11 @@ atom monomer::create_new_atom(atom_type inType, const std::string &inAtomID, poi
// --------------------------------------------------------------------
// polymer
polymer::polymer(structure &s, const std::string &entityID, const std::string &asym_id, const std::string &auth_asym_id)
polymer::polymer(structure &s, std::string entityID, std::string asym_id, std::string auth_asym_id)
: m_structure(const_cast<structure *>(&s))
, m_entity_id(entityID)
, m_asym_id(asym_id)
, m_pdb_strand_id(auth_asym_id)
, m_entity_id(std::move(entityID))
, m_asym_id(std::move(asym_id))
, m_pdb_strand_id(std::move(auth_asym_id))
{
using namespace cif::literals;
@@ -829,7 +785,7 @@ polymer::polymer(structure &s, const std::string &entityID, const std::string &a
auto &poly_seq_scheme = s.get_datablock()["pdbx_poly_seq_scheme"];
reserve(poly_seq_scheme.size());
for (auto r : poly_seq_scheme.find("asym_id"_key == asym_id))
for (auto r : poly_seq_scheme.find("asym_id"_key == m_asym_id))
{
int seqID;
std::string compoundID, pdbSeqNum, pdbInsCode;
@@ -853,7 +809,7 @@ polymer::polymer(structure &s, const std::string &entityID, const std::string &a
// std::string polymer::chainID() const
// {
// return mPolySeq.front()["pdb_strand_id"].as<std::string>();
// return mPolySeq.front()["pdb_strand_id"].get<std::string>();
// }
// monomer &polymer::getBySeqID(int seqID)
@@ -909,23 +865,6 @@ sugar::sugar(branch &branch, const std::string &compoundID,
{
}
sugar::sugar(sugar &&rhs)
: residue(std::forward<residue>(rhs))
, m_branch(rhs.m_branch)
{
}
sugar &sugar::operator=(sugar &&rhs)
{
if (this != &rhs)
{
residue::operator=(std::forward<residue>(rhs));
m_branch = rhs.m_branch;
}
return *this;
}
// bool sugar::hasLinkedSugarAtLeavingO(int leavingO) const
// {
// return false;
@@ -981,27 +920,28 @@ cif::mm::atom sugar::add_atom(row_initializer atom_info)
atom_info.set_value({ "label_entity_id", m_branch->get_entity_id() });
atom_info.set_value({ "label_asym_id", m_branch->get_asym_id() });
atom_info.set_value({ "label_comp_id", m_compound_id });
atom_info.set_value({ "label_seq_id", "." });
atom_info.set_value({ "label_alt_id", "." });
atom_info.set_value({ "label_seq_id", nullptr });
atom_info.set_value({ "label_alt_id", nullptr });
atom_info.set_value({ "auth_asym_id", m_branch->get_asym_id() });
atom_info.set_value({ "auth_comp_id", m_compound_id });
atom_info.set_value({ "auth_seq_id", m_pdb_seq_num });
atom_info.set_value({ "occupancy", 1.0, 2 });
atom_info.set_value({ "B_iso_or_equiv", 30.0, 2 });
atom_info.set_value({ "occupancy", { 1.0, 2 } });
atom_info.set_value({ "B_iso_or_equiv", { 30.0, 2 } });
atom_info.set_value({ "pdbx_PDB_model_num", 1 });
auto row = atom_site.emplace(std::move(atom_info));
auto result = m_structure->emplace_atom(db, row);
const_row_handle rh = *row;
auto result = m_structure->emplace_atom(db, rh);
residue::add_atom(result);
return result;
}
branch::branch(structure &structure, const std::string &asym_id, const std::string &entity_id)
branch::branch(structure &structure, std::string asym_id, std::string entity_id)
: m_structure(&structure)
, m_asym_id(asym_id)
, m_entity_id(entity_id)
, m_asym_id(std::move(asym_id))
, m_entity_id(std::move(entity_id))
{
using namespace literals;
@@ -1010,12 +950,12 @@ branch::branch(structure &structure, const std::string &asym_id, const std::stri
auto &branch_scheme = db["pdbx_branch_scheme"];
auto &branch_link = db["pdbx_entity_branch_link"];
for (const auto &asym_entity_id : struct_asym.find<std::string>("id"_key == asym_id, "entity_id"))
for (const auto &asym_entity_id : struct_asym.find<std::string>("id"_key == m_asym_id, "entity_id"))
{
for (const auto &[comp_id, num] : branch_scheme.find<std::string, int>(
"asym_id"_key == asym_id, "mon_id", "pdb_seq_num"))
"asym_id"_key == m_asym_id, "mon_id", "pdb_seq_num"))
{
emplace_back(*this, comp_id, asym_id, num);
emplace_back(*this, comp_id, m_asym_id, num);
}
for (const auto &[num1, num2, atom1, atom2] : branch_link.find<std::size_t, std::size_t, std::string, std::string>(
@@ -1061,7 +1001,7 @@ void branch::link_atoms()
sugar &branch::get_sugar_by_num(int nr)
{
auto i = find_if(begin(), end(), [nr](const sugar &s)
auto i = std::ranges::find_if(*this, [nr](const sugar &s)
{ return s.num() == nr; });
if (i == end())
throw std::out_of_range("Sugar with num " + std::to_string(nr) + " not found in branch " + m_asym_id);
@@ -1089,7 +1029,7 @@ sugar &branch::construct_sugar(const std::string &compound_id)
chemComp.emplace({ { "id", compound_id },
{ "name", compound->name() },
{ "formula", compound->formula() },
{ "formula_weight", compound->formula_weight() },
{ "formula_weight", { compound->formula_weight(), 3 } },
{ "type", compound->type() } });
}
@@ -1126,17 +1066,18 @@ sugar &branch::construct_sugar(const std::string &compound_id, const std::string
auto &pdbx_entity_branch_link = db["pdbx_entity_branch_link"];
auto linkID = pdbx_entity_branch_link.get_unique_id("");
db["pdbx_entity_branch_link"].emplace({ { "link_id", linkID },
{ "entity_id", get_entity_id() },
{ "entity_branch_list_num_1", result.num() },
{ "comp_id_1", compound_id },
{ "atom_id_1", atom_id },
{ "leaving_atom_id_1", "O1" }, /// TODO: Need to fix this!
{ "entity_branch_list_num_2", linked.num() },
{ "comp_id_2", linked.get_compound_id() },
{ "atom_id_2", linked_atom_id },
{ "leaving_atom_id_2", "." },
{ "value_order", "sing" } });
db["pdbx_entity_branch_link"].emplace( //
{ { "link_id", std::stoi(linkID) },
{ "entity_id", get_entity_id() },
{ "entity_branch_list_num_1", result.num() },
{ "comp_id_1", compound_id },
{ "atom_id_1", atom_id },
{ "leaving_atom_id_1", "O1" }, /// TODO: Need to fix this!
{ "entity_branch_list_num_2", linked.num() },
{ "comp_id_2", linked.get_compound_id() },
{ "atom_id_2", linked_atom_id },
{ "leaving_atom_id_2", nullptr },
{ "value_order", "sing" } });
return result;
}
@@ -1193,10 +1134,10 @@ structure::structure(datablock &db, std::size_t modelNr, structure_open_options
load_atoms_for_model(options);
// Check to see if we should actually load another model?
if (m_atoms.empty() and m_model_nr == 1)
if (m_atoms.empty() and m_model_nr == 1 and not atom_site.empty())
{
std::optional<std::size_t> model_nr;
cif::tie(model_nr) = atom_site.front().get("pdbx_PDB_model_num");
auto model_nr =
atom_site.front().get<std::optional<std::size_t>>("pdbx_PDB_model_num");
if (model_nr and *model_nr != m_model_nr)
{
if (VERBOSE > 0)
@@ -1261,7 +1202,7 @@ void structure::load_atoms_for_model(structure_open_options options)
for (auto id : atom_site.find<std::string>(std::move(c), "id"))
{
auto a = std::make_shared<atom::atom_impl>(m_db, id);
if (a->get_property_float("occupancy") > 0)
if (a->get_property("occupancy").get<float>() > 0)
continue;
emplace_atom(a);
}
@@ -1359,31 +1300,31 @@ void structure::load_data()
// place atoms in residues
using key_type = std::tuple<std::string, int, std::string>;
using key_type = std::tuple<std::string, int, std::string, std::string>;
std::map<key_type, residue *> resMap;
for (auto &poly : m_polymers)
{
for (auto &res : poly)
resMap[{ res.get_asym_id(), res.get_seq_id(), res.get_pdb_seq_num() }] = &res;
resMap[{ res.get_asym_id(), res.get_seq_id(), res.get_pdb_seq_num(), res.get_compound_id() }] = &res;
}
for (auto &res : m_non_polymers)
resMap[{ res.get_asym_id(), res.get_seq_id(), res.get_pdb_seq_num() }] = &res;
resMap[{ res.get_asym_id(), res.get_seq_id(), res.get_pdb_seq_num(), res.get_compound_id() }] = &res;
std::set<std::string> sugars;
for (auto &branch : m_branches)
{
for (auto &sugar : branch)
{
resMap[{ sugar.get_asym_id(), sugar.get_seq_id(), sugar.get_pdb_seq_num() }] = &sugar;
resMap[{ sugar.get_asym_id(), sugar.get_seq_id(), sugar.get_pdb_seq_num(), sugar.get_compound_id() }] = &sugar;
sugars.insert(sugar.get_compound_id());
}
}
for (auto &atom : m_atoms)
{
key_type k(atom.get_label_asym_id(), atom.get_label_seq_id(), atom.get_auth_seq_id());
key_type k(atom.get_label_asym_id(), atom.get_label_seq_id(), atom.get_auth_seq_id(), atom.get_label_comp_id());
auto ri = resMap.find(k);
if (ri == resMap.end())
@@ -1408,9 +1349,8 @@ void structure::load_data()
}
// what the ...
m_branches.erase(std::remove_if(m_branches.begin(), m_branches.end(), [](const branch &b)
{ return b.empty(); }),
m_branches.end());
std::erase_if(m_branches, [](const branch &b)
{ return b.empty(); });
for (auto &branch : m_branches)
branch.link_atoms();
@@ -1711,7 +1651,7 @@ std::string structure::insert_compound(const std::string &compoundID, bool is_en
chemComp.emplace({ { "id", compoundID },
{ "name", compound->name() },
{ "formula", compound->formula() },
{ "formula_weight", compound->formula_weight() },
{ "formula_weight", { compound->formula_weight(), 3 } },
{ "type", compound->type() } });
}
@@ -1731,7 +1671,7 @@ std::string structure::insert_compound(const std::string &compoundID, bool is_en
entity.emplace({ { "id", entity_id },
{ "type", "non-polymer" },
{ "pdbx_description", compound->name() },
{ "formula_weight", compound->formula_weight() } });
{ "formula_weight", { compound->formula_weight(), 3 } } });
pdbxEntityNonpoly.emplace({ { "entity_id", entity_id },
{ "name", compound->name() },
@@ -1744,7 +1684,7 @@ std::string structure::insert_compound(const std::string &compoundID, bool is_en
// --------------------------------------------------------------------
atom &structure::emplace_atom(atom &&atom)
atom &structure::emplace_atom(atom atom)
{
int L = 0, R = static_cast<int>(m_atom_index.size() - 1);
while (L <= R)
@@ -1786,22 +1726,12 @@ void structure::remove_atom(atom &a, bool removeFromResidue)
auto &atomSite = m_db["atom_site"];
if (a.is_water())
{
auto ra = atomSite.find1("id"_key == a.id());
if (ra)
{
auto &nps = m_db["pdbx_nonpoly_scheme"];
for (auto rnp : atomSite.get_children(ra, nps))
nps.erase(rnp);
}
}
else if (removeFromResidue)
if (removeFromResidue)
{
try
{
auto &res = get_residue(a);
res.m_atoms.erase(std::remove(res.m_atoms.begin(), res.m_atoms.end(), a), res.m_atoms.end());
std::erase(res.m_atoms, a);
}
catch (const std::exception &ex)
{
@@ -1812,6 +1742,13 @@ void structure::remove_atom(atom &a, bool removeFromResidue)
for (auto ri : atomSite.find("id"_key == a.id()))
{
if (a.is_water())
{
auto &nps = m_db["pdbx_nonpoly_scheme"];
for (auto rnp : atomSite.get_children(ri, nps))
nps.erase(rnp);
}
// also remove struct_conn records for this atom
auto &structConn = m_db["struct_conn"];
@@ -1891,7 +1828,7 @@ void structure::swap_atoms(atom a1, atom a2)
auto r2 = atomSites.find1(key("id") == a2.id());
for (std::string fld : std::initializer_list<std::string>{ "label_atom_id", "auth_atom_id", "type_symbol" })
swap(r1[fld], r2[fld]);
swap(r1[fld].value(), r2[fld].value());
}
catch (const std::exception &ex)
{
@@ -1932,7 +1869,7 @@ void structure::change_residue(residue &res, const std::string &newCompound,
entity.emplace({ { "id", entityID },
{ "type", "non-polymer" },
{ "pdbx_description", compound->name() },
{ "formula_weight", compound->formula_weight() } });
{ "formula_weight", { compound->formula_weight(), 3 } } });
auto &pdbxEntityNonpoly = m_db["pdbx_entity_nonpoly"];
pdbxEntityNonpoly.emplace({ { "entity_id", entityID },
@@ -1956,7 +1893,7 @@ void structure::change_residue(residue &res, const std::string &newCompound,
chemComp.emplace({ { "id", newCompound },
{ "name", compound->name() },
{ "formula", compound->formula() },
{ "formula_weight", compound->formula_weight() },
{ "formula_weight", { compound->formula_weight(), 3 } },
{ "type", compound->type() } });
}
@@ -1973,7 +1910,7 @@ void structure::change_residue(residue &res, const std::string &newCompound,
for (const auto &[a1, a2] : remappedAtoms)
{
auto i = find_if(atoms.begin(), atoms.end(), [id = a1](const atom &a)
auto i = std::ranges::find_if(atoms, [id = a1](const atom &a)
{ return a.get_label_atom_id() == id; });
if (i == atoms.end())
{
@@ -1987,7 +1924,7 @@ void structure::change_residue(residue &res, const std::string &newCompound,
if (r.size() != 1)
continue;
if (a2.empty() or a2 == ".")
if (a2.empty())
{
i->set_property("label_comp_id", newCompound);
remove_atom(*i);
@@ -2070,19 +2007,19 @@ void structure::remove_residue(residue &res)
"seq_id"_key == res.get_seq_id());
for (auto &poly : m_polymers)
poly.erase(std::remove(poly.begin(), poly.end(), m), poly.end());
std::erase(poly, m);
break;
}
case EntityType::NonPolymer:
m_db["pdbx_nonpoly_scheme"].erase("asym_id"_key == res.get_asym_id());
m_db["struct_asym"].erase("id"_key == res.get_asym_id());
m_non_polymers.erase(std::remove(m_non_polymers.begin(), m_non_polymers.end(), res), m_non_polymers.end());
std::erase(m_non_polymers, res);
break;
case EntityType::Water:
m_db["pdbx_nonpoly_scheme"].erase("asym_id"_key == res.get_asym_id());
m_non_polymers.erase(std::remove(m_non_polymers.begin(), m_non_polymers.end(), res), m_non_polymers.end());
std::erase(m_non_polymers, res);
break;
case EntityType::Branched:
@@ -2110,7 +2047,7 @@ void structure::remove_sugar(sugar &s)
std::string asym_id = s.get_asym_id();
branch &branch = get_branch_by_asym_id(asym_id);
auto si = std::find(branch.begin(), branch.end(), s);
auto si = std::ranges::find(branch, s);
if (si == branch.end())
throw std::runtime_error("sugar not part of branch");
std::size_t six = si - branch.begin();
@@ -2122,6 +2059,7 @@ void structure::remove_sugar(sugar &s)
std::set<std::size_t> dix;
std::stack<std::size_t> test;
test.push(s.num());
std::vector<atom> da;
while (not test.empty())
{
@@ -2140,12 +2078,14 @@ void structure::remove_sugar(sugar &s)
}
for (auto atom : branch[tix - 1].atoms())
remove_atom(atom, false);
da.emplace_back(atom);
}
branch.erase(remove_if(branch.begin(), branch.end(), [dix](const sugar &s)
{ return dix.count(s.num()); }),
branch.end());
std::erase_if(branch, [dix](const sugar &s)
{ return dix.count(s.num()); });
for (auto atom : da)
remove_atom(atom, false);
auto entity_id = create_entity_for_branch(branch);
@@ -2171,7 +2111,7 @@ void structure::remove_sugar(sugar &s)
{ "mon_id", sugar.get_compound_id() },
{ "pdb_asym_id", asym_id },
{ "pdb_seq_num", sugar.num() },
{ "pdb_seq_num", std::to_string(sugar.num()) },
{ "pdb_mon_id", sugar.get_compound_id() },
// TODO: need fix, collect from nag_atoms?
@@ -2199,7 +2139,7 @@ void structure::remove_branch(branch &branch)
m_db["struct_asym"].erase("id"_key == branch.get_asym_id());
m_db["struct_conn"].erase("ptnr1_label_asym_id"_key == branch.get_asym_id() or "ptnr2_label_asym_id"_key == branch.get_asym_id());
m_branches.erase(remove(m_branches.begin(), m_branches.end(), branch), m_branches.end());
std::erase(m_branches, branch);
}
std::string structure::create_non_poly_entity(const std::string &comp_id)
@@ -2220,7 +2160,7 @@ std::string structure::create_non_poly(const std::string &entity_id, const std::
{ "entity_id", entity_id },
{ "details", "?" } });
std::string comp_id = m_db["pdbx_entity_nonpoly"].find1<std::string>("entity_id"_key == entity_id, "comp_id");
auto comp_id = m_db["pdbx_entity_nonpoly"].find1<std::string>("entity_id"_key == entity_id, "comp_id");
auto &atom_site = m_db["atom_site"];
@@ -2232,24 +2172,24 @@ std::string structure::create_non_poly(const std::string &entity_id, const std::
auto row = atom_site.emplace({ { "group_PDB", atom.get_property("group_PDB") },
{ "id", atom_id },
{ "type_symbol", atom.get_property("type_symbol") },
{ "label_atom_id", atom.get_property("label_atom_id") },
{ "label_alt_id", atom.get_property("label_alt_id") },
{ "type_symbol", atom.get_property_value("type_symbol") },
{ "label_atom_id", atom.get_property_value("label_atom_id") },
{ "label_alt_id", atom.get_property_value("label_alt_id") },
{ "label_comp_id", comp_id },
{ "label_asym_id", asym_id },
{ "label_entity_id", entity_id },
{ "label_seq_id", "." },
{ "label_seq_id", nullptr },
{ "pdbx_PDB_ins_code", "" },
{ "Cartn_x", atom.get_property("Cartn_x") },
{ "Cartn_y", atom.get_property("Cartn_y") },
{ "Cartn_z", atom.get_property("Cartn_z") },
{ "occupancy", atom.get_property("occupancy") },
{ "B_iso_or_equiv", atom.get_property("B_iso_or_equiv") },
{ "pdbx_formal_charge", atom.get_property("pdbx_formal_charge") },
{ "auth_seq_id", 1 },
{ "Cartn_x", atom.get_property_value("Cartn_x") },
{ "Cartn_y", atom.get_property_value("Cartn_y") },
{ "Cartn_z", atom.get_property_value("Cartn_z") },
{ "occupancy", atom.get_property_value("occupancy") },
{ "B_iso_or_equiv", atom.get_property_value("B_iso_or_equiv") },
{ "pdbx_formal_charge", atom.get_property_value("pdbx_formal_charge") },
{ "auth_seq_id", "1" },
{ "auth_comp_id", comp_id },
{ "auth_asym_id", asym_id },
{ "auth_atom_id", atom.get_property("label_atom_id") },
{ "auth_atom_id", atom.get_property_value("label_atom_id") },
{ "pdbx_PDB_model_num", 1 } });
auto &newAtom = emplace_atom(std::make_shared<atom::atom_impl>(m_db, atom_id));
@@ -2262,13 +2202,13 @@ std::string structure::create_non_poly(const std::string &entity_id, const std::
{ "asym_id", asym_id },
{ "entity_id", entity_id },
{ "mon_id", comp_id },
{ "ndb_seq_num", ndb_nr },
{ "ndb_seq_num", std::to_string(ndb_nr) },
{ "pdb_seq_num", res.get_pdb_seq_num() },
{ "auth_seq_num", res.get_pdb_seq_num() },
{ "pdb_mon_id", comp_id },
{ "auth_mon_id", comp_id },
{ "pdb_strand_id", asym_id },
{ "pdb_ins_code", "." },
{ "pdb_ins_code", nullptr },
});
return asym_id;
@@ -2287,7 +2227,7 @@ std::string structure::create_non_poly(const std::string &entity_id, std::vector
{ "entity_id", entity_id },
{ "details", "?" } });
std::string comp_id = m_db["pdbx_entity_nonpoly"].find1<std::string>("entity_id"_key == entity_id, "comp_id");
auto comp_id = m_db["pdbx_entity_nonpoly"].find1<std::string>("entity_id"_key == entity_id, "comp_id");
auto &atom_site = m_db["atom_site"];
@@ -2304,12 +2244,12 @@ std::string structure::create_non_poly(const std::string &entity_id, std::vector
atom.set_value_if_empty({ "group_PDB", "HETATM" });
atom.set_value_if_empty({ "label_comp_id", comp_id });
atom.set_value_if_empty({ "label_seq_id", "." });
atom.set_value_if_empty({ "label_seq_id", nullptr });
atom.set_value_if_empty({ "auth_comp_id", comp_id });
atom.set_value_if_empty({ "auth_seq_id", 1 });
atom.set_value_if_empty({ "auth_seq_id", "1" });
atom.set_value_if_empty({ "pdbx_PDB_model_num", 1 });
atom.set_value_if_empty({ "label_alt_id", "" });
atom.set_value_if_empty({ "occupancy", 1.0, 2 });
atom.set_value_if_empty({ "occupancy", { 1.0, 2 } });
auto row = atom_site.emplace(atom.begin(), atom.end());
@@ -2323,13 +2263,13 @@ std::string structure::create_non_poly(const std::string &entity_id, std::vector
{ "asym_id", asym_id },
{ "entity_id", entity_id },
{ "mon_id", comp_id },
{ "ndb_seq_num", ndb_nr },
{ "ndb_seq_num", std::to_string(ndb_nr) },
{ "pdb_seq_num", res.get_pdb_seq_num() },
{ "auth_seq_num", res.get_pdb_seq_num() },
{ "pdb_mon_id", comp_id },
{ "auth_mon_id", comp_id },
{ "pdb_strand_id", asym_id },
{ "pdb_ins_code", "." },
{ "pdb_ins_code", nullptr },
});
return asym_id;
@@ -2340,7 +2280,7 @@ std::string structure::create_non_poly(const std::string &compound_id, bool skip
auto compound = cif::compound_factory::instance().create(compound_id);
if (compound == nullptr)
throw std::runtime_error(std::format("{} is not a known compound", compound_id));
std::vector<cif::row_initializer> atoms;
for (auto a : compound->atoms())
{
@@ -2348,17 +2288,15 @@ std::string structure::create_non_poly(const std::string &compound_id, bool skip
if (skip_hydrogen and cif::atom_type_traits(a.type_symbol).symbol() == "H")
continue;
auto ax = a.get_location().get_x();
auto ay = a.get_location().get_y();
auto az = a.get_location().get_z();
auto aloc = a.get_location();
atoms.emplace_back(cif::row_initializer{
{ "type_symbol", cif::atom_type_traits(a.type_symbol).symbol() },
{ "label_atom_id", a.id },
{ "auth_atom_id", a.id },
{ "Cartn_x", ax },
{ "Cartn_y", ay },
{ "Cartn_z", az },
{ "Cartn_x", aloc.x },
{ "Cartn_y", aloc.y },
{ "Cartn_z", aloc.z },
{ "B_iso_or_equiv", 30.00 } });
}
@@ -2369,15 +2307,18 @@ void structure::create_water(row_initializer atom)
{
using namespace literals;
auto entity_id = insert_compound("HOH", true);
std::string entity_id;
if (auto id = m_db["entity"].find_first<std::optional<std::string>>("type"_key == "water", "id"))
entity_id = *id;
else
entity_id = insert_compound("HOH", true);
auto &struct_asym = m_db["struct_asym"];
std::string asym_id;
try
{
asym_id = struct_asym.find1<std::string>("entity_id"_key == entity_id, "id");
}
catch (const std::exception &)
if (auto known_asym_id = struct_asym.find1<std::optional<std::string>>("entity_id"_key == entity_id, "id"); known_asym_id.has_value())
asym_id = *known_asym_id;
else
{
asym_id = struct_asym.get_unique_id();
@@ -2389,7 +2330,8 @@ void structure::create_water(row_initializer atom)
}
auto &atom_site = m_db["atom_site"];
auto auth_seq_id = atom_site.find_max<int>("auth_seq_id", "label_entity_id"_key == entity_id) + 1;
int auth_seq_id = atom_site.find_max<int>("auth_seq_id", "label_entity_id"_key == entity_id) + 1;
if (auth_seq_id < 0)
auth_seq_id = 1;
@@ -2397,17 +2339,18 @@ void structure::create_water(row_initializer atom)
atom.set_value("id", atom_id);
atom.set_value("label_asym_id", asym_id);
atom.set_value("auth_asym_id", asym_id);
atom.set_value("label_entity_id", entity_id);
atom.set_value_if_empty("auth_asym_id", asym_id);
atom.set_value("auth_seq_id", std::to_string(auth_seq_id));
atom.set_value_if_empty({ "group_PDB", "HETATM" });
atom.set_value_if_empty({ "label_comp_id", "HOH" });
atom.set_value_if_empty({ "label_seq_id", "." });
atom.set_value_if_empty({ "label_seq_id", nullptr });
atom.set_value_if_empty({ "auth_comp_id", "HOH" });
atom.set_value_if_empty({ "pdbx_PDB_model_num", 1 });
atom.set_value_if_empty({ "label_alt_id", "" });
atom.set_value_if_empty({ "occupancy", 1.0, 2 });
atom.set_value_if_empty({ "occupancy", { 1.0, 2 } });
auto row = atom_site.emplace(atom.begin(), atom.end());
@@ -2419,13 +2362,13 @@ void structure::create_water(row_initializer atom)
{ "asym_id", asym_id },
{ "entity_id", entity_id },
{ "mon_id", "HOH" },
{ "ndb_seq_num", ndb_nr },
{ "pdb_seq_num", auth_seq_id },
{ "auth_seq_num", auth_seq_id },
{ "ndb_seq_num", std::to_string(ndb_nr) },
{ "pdb_seq_num", std::to_string(auth_seq_id) },
{ "auth_seq_num", std::to_string(auth_seq_id) },
{ "pdb_mon_id", "HOH" },
{ "auth_mon_id", "HOH" },
{ "pdb_strand_id", asym_id },
{ "pdb_ins_code", "." },
{ "pdb_ins_code", nullptr },
});
}
@@ -2478,7 +2421,7 @@ std::string structure::create_link(atom a1, atom a2, const std::string &link_typ
{ "ptnr2_auth_seq_id", a2.get_auth_seq_id() },
{ "ptnr2_symmetry", a2.symmetry() },
{ "pdbx_dist_value", distance(a1.get_location(), a2.get_location()), 3 },
{ "pdbx_dist_value", { distance(a1.get_location(), a2.get_location()), 3 } },
{ "pdbx_role", role } });
return link_id;
@@ -2538,7 +2481,7 @@ branch &structure::create_branch()
// // atom.set_value_if_empty({"group_PDB", "HETATM"});
// // atom.set_value_if_empty({"label_comp_id", "NAG"});
// // atom.set_value_if_empty({"label_seq_id", "."});
// // atom.set_value_if_empty({"label_seq_id", nullptr});
// // atom.set_value_if_empty({"auth_comp_id", "NAG"});
// // atom.set_value_if_empty({"pdbx_PDB_model_num", 1});
// // atom.set_value_if_empty({"label_alt_id", ""});
@@ -2687,7 +2630,7 @@ std::string structure::create_entity_for_branch(branch &branch)
auto &entity = m_db["entity"];
std::string entityID = entity.find_first<std::string>("type"_key == "branched" and "pdbx_description"_key == entityName, "id");
auto entityID = entity.find_first<std::string>("type"_key == "branched" and "pdbx_description"_key == entityName, "id");
if (entityID.empty())
{
@@ -2700,7 +2643,7 @@ std::string structure::create_entity_for_branch(branch &branch)
{ "type", "branched" },
{ "src_method", "man" },
{ "pdbx_description", entityName },
{ "formula_weight", branch.weight() } });
{ "formula_weight", { branch.weight(), 3 } } });
auto &pdbx_entity_branch_list = m_db["pdbx_entity_branch_list"];
for (auto &sugar : branch)
@@ -2722,13 +2665,14 @@ std::string structure::create_entity_for_branch(branch &branch)
auto &s2 = branch.at(stoi(l2.get_auth_seq_id()) - 1);
auto l1 = s2.get_atom_by_atom_id("C1");
pdbx_entity_branch_link.emplace({ { "link_id", pdbx_entity_branch_link.get_unique_id("") },
pdbx_entity_branch_link.emplace({ //
{ "link_id", std::stoi(pdbx_entity_branch_link.get_unique_id("")) },
{ "entity_id", entityID },
{ "entity_branch_list_num_1", s1.get_pdb_seq_num() },
{ "entity_branch_list_num_1", std::stoi(s1.get_pdb_seq_num()) },
{ "comp_id_1", s1.get_compound_id() },
{ "atom_id_1", l1.get_label_atom_id() },
{ "leaving_atom_id_1", "O1" },
{ "entity_branch_list_num_2", s2.get_pdb_seq_num() },
{ "entity_branch_list_num_2", std::stoi(s2.get_pdb_seq_num()) },
{ "comp_id_2", s2.get_compound_id() },
{ "atom_id_2", l2.get_label_atom_id() },
{ "leaving_atom_id_2", "H" + l2.get_label_atom_id() },
@@ -2754,7 +2698,7 @@ void structure::cleanup_empty_categories()
for (auto chemComp : chem_comp)
{
std::string compID = chemComp["id"].get<std::string>();
auto compID = chemComp["id"].get<std::string>();
if (atomSite.contains("label_comp_id"_key == compID or "auth_comp_id"_key == compID) or
pdbxPolySeqScheme.contains("mon_id"_key == compID or "auth_mon_id"_key == compID or "pdb_mon_id"_key == compID) or
entityPolySeq.contains("mon_id"_key == compID))
@@ -2775,7 +2719,7 @@ void structure::cleanup_empty_categories()
for (auto entity : entities)
{
std::string entityID = entity["id"].get<std::string>();
auto entityID = entity["id"].get<std::string>();
if (atomSite.contains("label_entity_id"_key == entityID))
continue;
@@ -2786,7 +2730,7 @@ void structure::cleanup_empty_categories()
for (auto entity : obsoleteEntities)
{
std::string entityID = entity["id"].as<std::string>();
auto entityID = entity["id"].get<std::string>();
if (validator)
{
for (auto linked : validator->get_links_for_parent("entity"))
@@ -2873,19 +2817,12 @@ void structure::validate_atoms() const
std::vector<atom> atoms = m_atoms;
auto removeAtomFromList = [&atoms](const atom &a)
{
auto i = std::find(atoms.begin(), atoms.end(), a);
assert(i != atoms.end());
atoms.erase(i);
};
for (auto &poly : m_polymers)
{
for (auto &monomer : poly)
{
for (auto &atom : monomer.atoms())
removeAtomFromList(atom);
std::erase(atoms, atom);
}
}
@@ -2894,14 +2831,14 @@ void structure::validate_atoms() const
for (auto &sugar : branch)
{
for (auto &atom : sugar.atoms())
removeAtomFromList(atom);
std::erase(atoms, atom);
}
}
for (auto &res : m_non_polymers)
{
for (auto &atom : res.atoms())
removeAtomFromList(atom);
std::erase(atoms, atom);
}
assert(atoms.empty());
@@ -2920,7 +2857,7 @@ static int compare_numbers(std::string_view a, std::string_view b)
ra = from_chars(a.data(), a.data() + a.length(), da);
rb = from_chars(b.data(), b.data() + b.length(), db);
if (not(bool) ra.ec and not(bool) rb.ec)
if (ra.ec == std::errc{} and rb.ec == std::errc{})
{
auto d = da - db;
if (std::abs(d) > std::numeric_limits<double>::epsilon())
@@ -2931,7 +2868,7 @@ static int compare_numbers(std::string_view a, std::string_view b)
result = -1;
}
}
else if ((bool)ra.ec)
else if (ra.ec != std::errc{})
result = 1;
else
result = -1;

View File

@@ -24,19 +24,19 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/parser.hpp"
#include "cif++/file.hpp"
#include "cif++/forward_decl.hpp"
#include "cif++/item.hpp"
#include "cif++/utilities.hpp"
#include "cif++/cif++.hpp"
#include <cassert>
#include <charconv>
#include <cctype>
#include <cstdint>
#include <iostream>
#include <map>
#include <stack>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
namespace cif
{
@@ -46,7 +46,7 @@ namespace cif
class reserved_words_automaton
{
public:
reserved_words_automaton() {}
reserved_words_automaton() = default;
enum move_result
{
@@ -60,12 +60,12 @@ class reserved_words_automaton
stop
};
constexpr bool finished() const
[[nodiscard]] constexpr bool finished() const
{
return m_state <= 0;
}
constexpr bool matched() const
[[nodiscard]] constexpr bool matched() const
{
return m_state < 0;
}
@@ -141,8 +141,8 @@ class reserved_words_automaton
static constexpr struct node
{
int16_t ch;
int8_t next_match;
int8_t next_nomatch;
int next_match;
int next_nomatch;
} s_dag[] = {
{ 0 },
{ 'D', 5, 2 },
@@ -282,6 +282,7 @@ sac_parser::CIFToken sac_parser::get_next_token()
m_token_value = {};
bool negative = false;
m_float_precision = 0;
reserved_words_automaton dag;
@@ -291,50 +292,59 @@ sac_parser::CIFToken sac_parser::get_next_token()
switch (state)
{
case State::Start:
using enum State;
case Start:
if (ch == kEOF)
result = CIFToken::END_OF_FILE;
else if (ch == '\n')
{
m_bol = true;
state = State::White;
state = White;
}
else if (ch == ' ' or ch == '\t')
state = State::White;
state = White;
else if (ch == '#')
state = State::Comment;
state = Comment;
else if (ch == '_')
state = State::ItemName;
state = ItemName;
else if (ch == ';' and m_bol)
state = State::TextItem;
{
if (m_backslash_strings)
state = TextItemBS;
else
state = TextItem;
}
else if (ch == '?')
state = State::QuestionMark;
state = QuestionMark;
else if (ch == '\'' or ch == '"')
{
quoteChar = ch;
state = State::QuotedString;
state = QuotedString;
}
else if (dag.move(ch) == reserved_words_automaton::undefined)
state = State::Reserved;
state = Reserved;
else if (ch == '+' or ch == '-')
{
negative = true;
state = State::Numeric_Integer;
state = Numeric_Integer;
}
else if (ch >= '0' and ch <= '9')
state = State::Numeric_Integer;
else if (ch == '0')
state = Numeric_Zero;
else if (ch >= '1' and ch <= '9')
state = Numeric_Integer;
else if (ch == '.')
state = State::Numeric_Float;
state = Numeric_Float;
else
state = State::Value;
state = Value;
break;
case State::White:
case White:
if (ch == kEOF)
result = CIFToken::END_OF_FILE;
else if (not is_space(ch))
{
state = State::Start;
state = Start;
retract();
m_token_buffer.clear();
}
@@ -342,11 +352,13 @@ sac_parser::CIFToken sac_parser::get_next_token()
m_bol = (ch == '\n');
break;
case State::Comment:
case Comment:
if (ch == '\n')
{
state = State::Start;
state = Start;
m_bol = true;
if (m_token_buffer.size() == 3 and m_token_buffer == std::vector{ '#', '\\', '\n' })
m_backslash_strings = true;
m_token_buffer.clear();
}
else if (ch == kEOF)
@@ -355,28 +367,67 @@ sac_parser::CIFToken sac_parser::get_next_token()
error("invalid character in comment");
break;
case State::QuestionMark:
case QuestionMark:
if (not is_non_blank(ch))
{
retract();
result = CIFToken::VALUE_UNKNOWN;
}
else
state = State::Value;
state = Value;
break;
case State::TextItem:
case TextItemBS:
if (ch == '\\')
{
state = TextItemBS2;
break;
}
[[fallthrough]];
case TextItem:
if (ch == '\n')
state = State::TextItemNL;
state = TextItemNL;
else if (ch == kEOF)
error("unterminated textfield");
else if (not is_any_print(ch) and cif::VERBOSE > 2)
warning("invalid character in text field '" + std::string({ static_cast<char>(ch) }) + "' (" + std::to_string((int)ch) + ")");
else if (not is_any_print(ch) and VERBOSE > 2)
warning("invalid character in text field '" + std::string({ static_cast<char>(ch) }) + "' (" + std::to_string(ch) + ")");
break;
case State::TextItemNL:
case TextItemBS2:
if (ch == '\n')
{
if (m_token_buffer[m_token_buffer.size() - 2] == '\\')
{
m_token_buffer.pop_back();
m_token_buffer.pop_back();
}
state = TextItemBSNL;
}
else if (ch == kEOF)
error("unterminated textfield");
else if (not is_any_print(ch) and VERBOSE > 2)
warning("invalid character in text field '" + std::string({ static_cast<char>(ch) }) + "' (" + std::to_string(ch) + ")");
break;
case TextItemBSNL:
if (is_text_lead(ch) or ch == ' ' or ch == '\t')
state = State::TextItem;
state = TextItemBS;
else if (ch == ';')
{
assert(m_token_buffer.size() >= 2);
m_token_value = std::string_view(m_token_buffer.data() + 1, m_token_buffer.size() - 3);
result = CIFToken::VALUE_CHARSTRING;
}
else if (ch == kEOF)
error("unterminated textfield");
else if (ch != '\n')
error("invalid character in text field");
break;
case TextItemNL:
if (is_text_lead(ch) or ch == ' ' or ch == '\t')
state = TextItem;
else if (ch == ';')
{
assert(m_token_buffer.size() >= 2);
@@ -389,16 +440,16 @@ sac_parser::CIFToken sac_parser::get_next_token()
error("invalid character in text field");
break;
case State::QuotedString:
case QuotedString:
if (ch == kEOF)
error("unterminated quoted string");
else if (ch == quoteChar)
state = State::QuotedStringQuote;
else if (not is_any_print(ch) and cif::VERBOSE > 2)
warning("invalid character in quoted string: '" + std::string({ static_cast<char>(ch) }) + "' (" + std::to_string((int)ch) + ")");
state = QuotedStringQuote;
else if (not is_any_print(ch) and VERBOSE > 2)
warning("invalid character in quoted string: '" + std::string({ static_cast<char>(ch) }) + "' (" + std::to_string(ch) + ")");
break;
case State::QuotedStringQuote:
case QuotedStringQuote:
if (is_white(ch))
{
retract();
@@ -411,14 +462,14 @@ sac_parser::CIFToken sac_parser::get_next_token()
else if (ch == quoteChar)
;
else if (is_any_print(ch))
state = State::QuotedString;
state = QuotedString;
else if (ch == kEOF)
error("unterminated quoted string");
else
error("invalid character in quoted string");
break;
case State::ItemName:
case ItemName:
if (not is_non_blank(ch))
{
retract();
@@ -427,7 +478,7 @@ sac_parser::CIFToken sac_parser::get_next_token()
}
break;
case State::Reserved:
case Reserved:
switch (dag.move(ch))
{
case reserved_words_automaton::undefined:
@@ -441,7 +492,7 @@ sac_parser::CIFToken sac_parser::get_next_token()
m_token_value = std::string_view(m_token_buffer.data(), m_token_buffer.size());
}
else
state = State::Value;
state = Value;
break;
case reserved_words_automaton::data:
@@ -478,21 +529,39 @@ sac_parser::CIFToken sac_parser::get_next_token()
}
break;
case State::Numeric_Integer:
if (ch == '.')
state = State::Numeric_Float;
else if (ch == 'e' or ch == 'E')
state = State::Numeric_Exponent1;
else if (not is_non_blank(ch))
case Numeric_Zero:
if (not is_non_blank(ch))
{
retract();
result = CIFToken::VALUE_NUMERIC_INTEGER;
}
else if (ch < '0' or ch > '9')
state = State::Value;
else if (ch == '.')
state = Numeric_Float;
else
state = Value;
break;
case State::Numeric_Float:
case Numeric_Integer:
if (ch == '.')
state = Numeric_Float;
else if (ch == 'e' or ch == 'E')
state = Numeric_Exponent1;
else if (not is_non_blank(ch))
{
retract();
if (m_token_buffer.size() == 1 and negative)
{
result = CIFToken::VALUE_CHARSTRING; // A single hyphen...
m_token_value = std::string_view{ m_token_buffer.data(), m_token_buffer.data() + 1 };
}
else
result = CIFToken::VALUE_NUMERIC_INTEGER;
}
else if (ch < '0' or ch > '9')
state = Value;
break;
case Numeric_Float:
if (not is_non_blank(ch))
{
retract();
@@ -502,23 +571,30 @@ sac_parser::CIFToken sac_parser::get_next_token()
result = CIFToken::VALUE_NUMERIC_FLOAT;
}
else if (ch == 'e' or ch == 'E')
state = State::Numeric_Exponent1;
state = Numeric_Exponent1;
else if (ch < '0' or ch > '9')
state = State::Value;
state = Value;
else
++m_float_precision;
break;
case State::Numeric_Exponent1:
if (ch == '+' or ch == '-' or (ch >= '0' and ch <= '9'))
state = State::Numeric_Exponent2;
case Numeric_Exponent1:
if (not is_non_blank(ch))
{
retract();
result = CIFToken::VALUE_CHARSTRING;
m_token_value = std::string_view(m_token_buffer.data(), m_token_buffer.size());
}
else if (ch == '+' or ch == '-' or (ch >= '0' and ch <= '9'))
state = Numeric_Exponent2;
else
{
if (VERBOSE > 0)
std::cerr << "parsing " << std::string_view{ m_token_buffer.data(), m_token_buffer.size() } << " Invalid floating point value, expected digit or sign character\n";
state = State::Value;
// warning(std::format("parsing {}: Invalid floating point value, expected digit or sign character", std::string_view{ m_token_buffer.data(), m_token_buffer.size() }));
state = Value;
}
break;
case State::Numeric_Exponent2:
case Numeric_Exponent2:
if (not is_non_blank(ch))
{
retract();
@@ -527,12 +603,12 @@ sac_parser::CIFToken sac_parser::get_next_token()
else if (ch < '0' or ch > '9')
{
if (VERBOSE > 0)
std::cerr << "parsing " << std::string_view{ m_token_buffer.data(), m_token_buffer.size() } << " Invalid floating point value, expected exponent digit\n";
state = State::Value;
// warning(std::format("parsing {}: Invalid floating point value, expected digit or sign character", std::string_view{ m_token_buffer.data(), m_token_buffer.size() }));
state = Value;
}
break;
case State::Value:
case Value:
if (not is_non_blank(ch))
{
retract();
@@ -559,13 +635,13 @@ sac_parser::CIFToken sac_parser::get_next_token()
if (result == CIFToken::VALUE_NUMERIC_INTEGER)
{
auto [ptr, ec] = std::from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_int);
auto [ptr, ec] = from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_int);
if (ec != std::errc{})
error("Invalid integer value: " + std::make_error_code(ec).message());
}
else if (result == CIFToken::VALUE_NUMERIC_FLOAT)
{
auto [ptr, ec] = std::from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_float);
auto [ptr, ec] = from_chars(m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size(), m_token_value_float);
if (ec != std::errc{})
error("Invalid integer value: " + std::make_error_code(ec).message());
}
@@ -622,6 +698,7 @@ bool sac_parser::parse_single_datablock(const std::string &datablock)
if (bol)
state = qstring;
break;
default:;
}
break;
@@ -715,6 +792,7 @@ sac_parser::datablock_index sac_parser::index_datablocks()
if (bol)
state = qstring;
break;
default:;
}
break;
@@ -752,7 +830,7 @@ sac_parser::datablock_index sac_parser::index_datablocks()
case data_name:
if (is_non_blank(ch))
datablock.insert(datablock.end(), (char)std::toupper(ch));
datablock.insert(datablock.end(), static_cast<char>(std::toupper(ch)));
else if (is_space(ch))
{
if (not datablock.empty())
@@ -871,11 +949,11 @@ void sac_parser::parse_datablock()
switch (m_lookahead)
{
case CIFToken::VALUE_INAPPLICABLE:
produce_item(cat, item_name, nullptr);
produce_item(cat, item_name, item_value_type::INAPPLICABLE);
match(m_lookahead);
break;
case CIFToken::VALUE_UNKNOWN:
produce_item(cat, item_name, std::optional<std::string>{});
produce_item(cat, item_name, item_value_type::MISSING);
match(m_lookahead);
break;
case CIFToken::VALUE_NUMERIC_INTEGER:
@@ -883,7 +961,7 @@ void sac_parser::parse_datablock()
match(m_lookahead);
break;
case CIFToken::VALUE_NUMERIC_FLOAT:
produce_item(cat, item_name, m_token_value_float);
produce_item(cat, item_name, { m_token_value_float, m_float_precision });
match(m_lookahead);
break;
case CIFToken::VALUE_CHARSTRING:
@@ -918,11 +996,11 @@ void sac_parser::parse_datablock()
switch (m_lookahead)
{
case CIFToken::VALUE_INAPPLICABLE:
produce_item(cat, itemName, nullptr);
produce_item(cat, itemName, item_value_type::INAPPLICABLE);
match(CIFToken::VALUE_INAPPLICABLE);
break;
case CIFToken::VALUE_UNKNOWN:
produce_item(cat, itemName, item_value{ std::optional<std::string>{} });
produce_item(cat, itemName, item_value_type::MISSING);
match(CIFToken::VALUE_UNKNOWN);
break;
case CIFToken::VALUE_NUMERIC_INTEGER:
@@ -930,7 +1008,7 @@ void sac_parser::parse_datablock()
match(CIFToken::VALUE_NUMERIC_INTEGER);
break;
case CIFToken::VALUE_NUMERIC_FLOAT:
produce_item(cat, itemName, m_token_value_float);
produce_item(cat, itemName, { m_token_value_float, m_float_precision });
match(CIFToken::VALUE_NUMERIC_FLOAT);
break;
case CIFToken::VALUE_CHARSTRING:
@@ -980,7 +1058,9 @@ void parser::produce_category(std::string_view name)
if (VERBOSE >= 4)
std::cerr << "producing category " << name << '\n';
const auto &[cat, ignore] = m_datablock->emplace(name);
const auto &[cat, is_new] = m_datablock->emplace(name);
if (is_new and m_validator)
cat->set_validator(m_validator, *m_datablock);
m_category = &*cat;
}
@@ -992,9 +1072,8 @@ void parser::produce_row()
if (m_category == nullptr)
error("inconsistent categories in loop_");
m_category->emplace({});
m_row = m_category->back();
// m_row.lineNr(m_line_nr);
auto i = m_category->emplace({});
m_row = *i;
}
void parser::produce_item(std::string_view category, std::string_view item, item_value value)
@@ -1005,7 +1084,20 @@ void parser::produce_item(std::string_view category, std::string_view item, item
if (m_category == nullptr or not iequals(category, m_category->name()))
error("inconsistent categories in loop_");
m_row[item] = std::move(value);
if (value.is_number())
{
auto cv = m_category->get_cat_validator();
if (cv != nullptr)
{
if (auto iv = cv->get_validator_for_item(item))
{
if (auto tv = iv->m_type; tv and tv->m_primitive_type != DDL_PrimitiveType::Numb)
value = std::string_view{ m_token_buffer.data(), m_token_buffer.data() + m_token_buffer.size() };
}
}
}
m_row[item].set(value, false);
}
} // namespace cif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -26,10 +26,19 @@
#include "pdb2cif_remark_3.hpp"
#include <cif++.hpp>
#include "cif++/cif++.hpp"
#include <map>
#include <set>
#include <algorithm>
#include <cstddef>
#include <cctype>
#include <exception>
#include <iostream>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
// NOLINTBEGIN(bugprone-empty-catch)
namespace cif::pdb
{
@@ -153,7 +162,7 @@ const TemplateLine kBusterTNT_Template[] = {
class BUSTER_TNT_Remark3Parser : public Remark3Parser
{
public:
BUSTER_TNT_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
BUSTER_TNT_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db,
kBusterTNT_Template, sizeof(kBusterTNT_Template) / sizeof(TemplateLine),
std::regex(R"((BUSTER(?:-TNT)?)(?: (\d+(?:\..+)?))?)"))
@@ -237,18 +246,28 @@ const TemplateLine kCNS_Template[] = {
/* 72 */ { R"(METHOD USED\s*:\s*(.+))", 1, "refine", { "solvent_model_details" } },
/* 73 */ { R"(KSOL\s*:\s*(.+))", 1, "refine", { "solvent_model_param_ksol" } },
/* 74 */ { R"(BSOL\s*:\s*(.+))", 1, "refine", { "solvent_model_param_bsol" } },
/* 75 */ { R"(NCS MODEL\s*:\s*(.+))", 1, /* "refine_ls_restr_ncs", { "ncs_model_details" } */ },
/* 75 */ { R"(NCS MODEL\s*:\s*(.+))",
1,
/* "refine_ls_restr_ncs", { "ncs_model_details" } */ },
/* 76 */ { R"(NCS RESTRAINTS\. RMS SIGMA/WEIGHT)", 1 },
/* 77 */ { R"(GROUP (\d+) POSITIONAL \(A\)\s*:\s*(.+))", 1, /* "refine_ls_restr_ncs", { "dom_id", "rms_dev_position", "weight_position" } */ },
/* 78 */ { R"(GROUP (\d+) B-FACTOR \(A\*\*2\)\s*:\s*(.+))", 1, /* "refine_ls_restr_ncs", { "dom_id", "rms_dev_B_iso", "weight_B_iso" } */ },
/* 79 */ { R"(PARAMETER FILE (\d+) :\s+(.+))", 1, /* "pdbx_xplor_file", { "serial_no", "param_file" } */ },
/* 80 */ { R"(TOPOLOGY FILE (\d+) :\s+(.+))", 1, /* "pdbx_xplor_file", { "serial_no", "topol_file" } */ },
/* 77 */ { R"(GROUP (\d+) POSITIONAL \(A\)\s*:\s*(.+))",
1,
/* "refine_ls_restr_ncs", { "dom_id", "rms_dev_position", "weight_position" } */ },
/* 78 */ { R"(GROUP (\d+) B-FACTOR \(A\*\*2\)\s*:\s*(.+))",
1,
/* "refine_ls_restr_ncs", { "dom_id", "rms_dev_B_iso", "weight_B_iso" } */ },
/* 79 */ { R"(PARAMETER FILE (\d+) :\s+(.+))",
1,
/* "pdbx_xplor_file", { "serial_no", "param_file" } */ },
/* 80 */ { R"(TOPOLOGY FILE (\d+) :\s+(.+))",
1,
/* "pdbx_xplor_file", { "serial_no", "topol_file" } */ },
};
class CNS_Remark3Parser : public Remark3Parser
{
public:
CNS_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
CNS_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kCNS_Template,
sizeof(kCNS_Template) / sizeof(TemplateLine), std::regex(R"((CN[SX])(?: (\d+(?:\.\d+)?))?)"))
{
@@ -332,13 +351,13 @@ const TemplateLine kPHENIX_Template[] = {
class PHENIX_Remark3Parser : public Remark3Parser
{
public:
PHENIX_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
PHENIX_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kPHENIX_Template, sizeof(kPHENIX_Template) / sizeof(TemplateLine),
std::regex(R"((PHENIX)(?: \(PHENIX\.REFINE:) (\d+(?:\.[^)]+)?)\)?)"))
{
}
virtual void fixup();
void fixup() override;
};
void PHENIX_Remark3Parser::fixup()
@@ -347,7 +366,7 @@ void PHENIX_Remark3Parser::fixup()
{
try
{
float val = r["percent_reflns_obs"].as<float>();
auto val = r["percent_reflns_obs"].get<float>();
int perc = static_cast<int>(val * 100);
r["percent_reflns_obs"] = perc;
}
@@ -420,13 +439,13 @@ const TemplateLine kNUCLSQ_Template[] = {
class NUCLSQ_Remark3Parser : public Remark3Parser
{
public:
NUCLSQ_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
NUCLSQ_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kNUCLSQ_Template, sizeof(kNUCLSQ_Template) / sizeof(TemplateLine),
std::regex(R"((NUCLSQ)(?: (\d+(?:\.\d+)?))?)"))
{
}
virtual void fixup()
void fixup() override
{
for (auto r : mDb["refine_hist"])
{
@@ -513,13 +532,13 @@ const TemplateLine kPROLSQ_Template[] = {
class PROLSQ_Remark3Parser : public Remark3Parser
{
public:
PROLSQ_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
PROLSQ_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kPROLSQ_Template, sizeof(kPROLSQ_Template) / sizeof(TemplateLine),
std::regex(R"((PROLSQ)(?: (\d+(?:\.\d+)?))?)"))
{
}
virtual void fixup()
void fixup() override
{
for (auto r : mDb["refine_hist"])
{
@@ -556,7 +575,9 @@ const TemplateLine kREFMAC_Template[] = {
/* 17 */ { R"(NUCLEIC ACID ATOMS\s*:\s*(.+))", 1, "refine_hist", { "pdbx_number_atoms_nucleic_acid" } },
/* 18 */ { R"(HETEROGEN ATOMS\s*:\s*(.+))", 1, "refine_hist", { "pdbx_number_atoms_ligand" } },
/* 19 */ { R"(SOLVENT ATOMS\s*:\s*(.+))", 1, "refine_hist", { "number_atoms_solvent" } },
/* 20 */ { R"(ALL ATOMS\s*:\s*(.+))", 1, /* "refine_hist", "pdbx_number_atoms_protein" */ },
/* 20 */ { R"(ALL ATOMS\s*:\s*(.+))",
1,
/* "refine_hist", "pdbx_number_atoms_protein" */ },
/* 21 */ { R"(B VALUES\..*)", 1 },
/* 22 */ { R"(B VALUE TYPE\s*:\s*(.+))", 1, "refine", { "pdbx_TLS_residual_ADP_flag" } },
/* 23 */ { R"(FROM WILSON PLOT \(A\*\*2\)\s*:\s*(.+))", 1, "reflns", { "B_iso_Wilson_estimate" } },
@@ -601,14 +622,14 @@ const TemplateLine kREFMAC_Template[] = {
class REFMAC_Remark3Parser : public Remark3Parser
{
public:
REFMAC_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
REFMAC_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kREFMAC_Template, sizeof(kREFMAC_Template) / sizeof(TemplateLine),
std::regex(".+"))
{
}
virtual std::string program() { return "REFMAC"; }
virtual std::string version() { return ""; }
std::string program() override { return "REFMAC"; }
std::string version() override { return ""; }
};
const TemplateLine kREFMAC5_Template[] = {
@@ -641,7 +662,9 @@ const TemplateLine kREFMAC5_Template[] = {
/* 26 */ { R"(NUCLEIC ACID ATOMS\s*:\s*(.+))", 1, "refine_hist", { "pdbx_number_atoms_nucleic_acid" } },
/* 27 */ { R"(HETEROGEN ATOMS\s*:\s*(.+))", 1, "refine_hist", { "pdbx_number_atoms_ligand" } },
/* 28 */ { R"(SOLVENT ATOMS\s*:\s*(.+))", 1, "refine_hist", { "number_atoms_solvent" } },
/* 29 */ { R"(ALL ATOMS\s*:\s*(.+))", 1, /* "refine_hist", { "pdbx_number_atoms_protein" } */ },
/* 29 */ { R"(ALL ATOMS\s*:\s*(.+))",
1,
/* "refine_hist", { "pdbx_number_atoms_protein" } */ },
/* 30 */ { R"(B VALUES\..*)", 1 },
/* 31 */ { R"(B VALUE TYPE\s*:\s*(.+))", 1, "refine", { "pdbx_TLS_residual_ADP_flag" } },
/* 32 */ { R"(FROM WILSON PLOT \(A\*\*2\)\s*:\s*(.+))", 1, "reflns", { "B_iso_Wilson_estimate" } },
@@ -705,8 +728,12 @@ const TemplateLine kREFMAC5_Template[] = {
// Simply ignore NCS, you can ask Robbie why
/* 90 */ { R"(NCS RESTRAINTS STATISTICS)", 1 },
/* 91 */ { R"(NUMBER OF DIFFERENT NCS GROUPS\s*:\s*(.+))", 1 },
/* 92 */ { R"(NCS GROUP NUMBER\s*:\s*(\d+))", 1, /*"struct_ncs_dom", { "pdbx_ens_id" }*/ },
/* 93 */ { R"(CHAIN NAMES\s*:\s*(.+))", 1, /*"struct_ncs_dom", { "details" }*/ },
/* 92 */ { R"(NCS GROUP NUMBER\s*:\s*(\d+))",
1,
/*"struct_ncs_dom", { "pdbx_ens_id" }*/ },
/* 93 */ { R"(CHAIN NAMES\s*:\s*(.+))",
1,
/*"struct_ncs_dom", { "details" }*/ },
/* 94 */ { R"(NUMBER OF COMPONENTS NCS GROUP\s*:\s*(\d+))", 1 },
/* 95 */ { R"(COMPONENT C SSSEQI TO C SSSEQI CODE)", 1 },
//// This sucks.... The following line is fixed format
@@ -719,7 +746,9 @@ const TemplateLine kREFMAC5_Template[] = {
/* 102 */ { R"(TIGHT THERMAL\s+\d+\s+(.)\s+\(A\*\*2\):\s+(\d+)\s*;\s*(\d+(?:\.\d*)?)\s*;\s*(\d+(?:\.\d*)?))", 0 }, // , "refine_ls_restr_ncs", {"pdbx_auth_asym_id", "pdbx_number", "rms_dev_position", "weight_position"}, { "pdbx_type", "tight thermal", }, 1 },
/* 103 */ { R"(MEDIUM THERMAL\s+\d+\s+(.)\s+\(A\*\*2\):\s+(\d+)\s*;\s*(\d+(?:\.\d*)?)\s*;\s*(\d+(?:\.\d*)?))", 0 }, // , "refine_ls_restr_ncs", {"pdbx_auth_asym_id", "pdbx_number", "rms_dev_position", "weight_position"}, { "pdbx_type", "medium thermal", }, 1 },
/* 104 */ { R"(LOOSE THERMAL\s+\d+\s+(.)\s+\(A\*\*2\):\s+(\d+)\s*;\s*(\d+(?:\.\d*)?)\s*;\s*(\d+(?:\.\d*)?))", 0 }, // , "refine_ls_restr_ncs", {"pdbx_auth_asym_id", "pdbx_number", "rms_dev_position", "weight_position"}, { "pdbx_type", "loose thermal", }, 10 },
/* 105 */ { R"(NCS GROUP NUMBER\s*:\s*(\d+))", 93 - 105, /*"struct_ncs_dom", { "pdbx_ens_id" }*/ },
/* 105 */ { R"(NCS GROUP NUMBER\s*:\s*(\d+))",
93 - 105,
/*"struct_ncs_dom", { "pdbx_ens_id" }*/ },
/* 106 */ { R"(TWIN DETAILS)", 1 },
/* 107 */ { R"(NUMBER OF TWIN DOMAINS\s*:\s*(\d*))", 1 },
/* 108 */ { R"(TWIN DOMAIN\s*:\s*(.+))", 1, "pdbx_reflns_twin", { "domain_id" }, nullptr, true },
@@ -755,7 +784,7 @@ const TemplateLine kREFMAC5_Template[] = {
class REFMAC5_Remark3Parser : public Remark3Parser
{
public:
REFMAC5_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
REFMAC5_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kREFMAC5_Template, sizeof(kREFMAC5_Template) / sizeof(TemplateLine),
std::regex(R"((REFMAC)(?: (\d+(?:\..+)?))?)"))
{
@@ -815,7 +844,7 @@ const TemplateLine kSHELXL_Template[] = {
class SHELXL_Remark3Parser : public Remark3Parser
{
public:
SHELXL_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
SHELXL_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kSHELXL_Template, sizeof(kSHELXL_Template) / sizeof(TemplateLine),
std::regex(R"((SHELXL)(?:-(\d+(?:\..+)?)))"))
{
@@ -872,7 +901,7 @@ const TemplateLine kTNT_Template[] = {
class TNT_Remark3Parser : public Remark3Parser
{
public:
TNT_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
TNT_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kTNT_Template, sizeof(kTNT_Template) / sizeof(TemplateLine),
std::regex(R"((TNT)(?: V. (\d+.+)?)?)"))
{
@@ -941,18 +970,28 @@ const TemplateLine kXPLOR_Template[] = {
/* 58 */ { R"(MAIN-CHAIN ANGLE \(A\*\*2\) :\s+(.+?);\s+(.+))", 1, "refine_ls_restr", { "dev_ideal", "dev_ideal_target" }, "x_mcangle_it", false },
/* 59 */ { R"(SIDE-CHAIN BOND \(A\*\*2\) :\s+(.+?);\s+(.+))", 1, "refine_ls_restr", { "dev_ideal", "dev_ideal_target" }, "x_scbond_it", false },
/* 60 */ { R"(SIDE-CHAIN ANGLE \(A\*\*2\) :\s+(.+?);\s+(.+))", 1, "refine_ls_restr", { "dev_ideal", "dev_ideal_target" }, "x_scangle_it", false },
/* 61 */ { R"(NCS MODEL :\s+(.+))", 1, /* "refine_ls_restr_ncs", { "ncs_model_details" } */ },
/* 61 */ { R"(NCS MODEL :\s+(.+))",
1,
/* "refine_ls_restr_ncs", { "ncs_model_details" } */ },
/* 62 */ { R"(NCS RESTRAINTS\. RMS SIGMA/WEIGHT)", 1 },
/* 63 */ { R"(GROUP (\d+) POSITIONAL \(A\) :\s+(.+?);\s+(.+))", 1, /* "refine_ls_restr_ncs", { ":dom_id", "rms_dev_position", "weight_position" } */ },
/* 64 */ { R"(GROUP (\d+) B-FACTOR \(A\*\*2\) :\s+(.+?);\s+(.+))", 63 - 64, /* "refine_ls_restr_ncs", { ":dom_id", "rms_dev_B_iso", "weight_B_iso" } */ },
/* 65 */ { R"(PARAMETER FILE (\d+) :\s+(.+))", 0, /* "pdbx_xplor_file", { "serial_no", "param_file" } */ },
/* 66 */ { R"(TOPOLOGY FILE (\d+) :\s+(.+))", 0, /* "pdbx_xplor_file", { "serial_no", "topol_file" } */ },
/* 63 */ { R"(GROUP (\d+) POSITIONAL \(A\) :\s+(.+?);\s+(.+))",
1,
/* "refine_ls_restr_ncs", { ":dom_id", "rms_dev_position", "weight_position" } */ },
/* 64 */ { R"(GROUP (\d+) B-FACTOR \(A\*\*2\) :\s+(.+?);\s+(.+))",
63 - 64,
/* "refine_ls_restr_ncs", { ":dom_id", "rms_dev_B_iso", "weight_B_iso" } */ },
/* 65 */ { R"(PARAMETER FILE (\d+) :\s+(.+))",
0,
/* "pdbx_xplor_file", { "serial_no", "param_file" } */ },
/* 66 */ { R"(TOPOLOGY FILE (\d+) :\s+(.+))",
0,
/* "pdbx_xplor_file", { "serial_no", "topol_file" } */ },
};
class XPLOR_Remark3Parser : public Remark3Parser
{
public:
XPLOR_Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db)
XPLOR_Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db)
: Remark3Parser(name, expMethod, r, db, kXPLOR_Template, sizeof(kXPLOR_Template) / sizeof(TemplateLine),
std::regex(R"((X-PLOR)(?: (\d+(?:\.\d+)?))?)"))
{
@@ -961,15 +1000,15 @@ class XPLOR_Remark3Parser : public Remark3Parser
// --------------------------------------------------------------------
Remark3Parser::Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db,
Remark3Parser::Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db,
const TemplateLine templatelines[], uint32_t templateLineCount, std::regex programversion)
: mName(name)
, mExpMethod(expMethod)
: mName(std::move(name))
, mExpMethod(std::move(expMethod))
, mRec(r)
, mDb(db.name())
, mTemplate(templatelines)
, mTemplateCount(templateLineCount)
, mProgramVersion(programversion)
, mProgramVersion(std::move(programversion))
{
mDb.set_validator(db.get_validator());
}
@@ -1042,13 +1081,13 @@ std::string Remark3Parser::nextLine()
break;
}
if (cif::VERBOSE >= 2)
if (VERBOSE >= 2)
std::cerr << "RM3: " << mLine << '\n';
return mLine;
}
bool Remark3Parser::match(const char *expr, int nextState)
bool Remark3Parser::match(const char *expr, uint32_t nextState)
{
std::regex rx(expr);
@@ -1056,7 +1095,7 @@ bool Remark3Parser::match(const char *expr, int nextState)
if (result)
mState = nextState;
else if (cif::VERBOSE >= 3)
else if (VERBOSE >= 3)
{
using namespace colour;
@@ -1085,7 +1124,7 @@ float Remark3Parser::parse()
if (mState == 0 and match(R"(AUTHORS\s*:.+)", 0))
continue;
auto state = mState;
uint32_t state;
for (state = mState; state < mTemplateCount; ++state)
{
const TemplateLine &tmpl = mTemplate[state];
@@ -1120,7 +1159,7 @@ float Remark3Parser::parse()
continue;
}
if (cif::VERBOSE >= 2)
if (VERBOSE >= 2)
{
using namespace colour;
@@ -1136,7 +1175,7 @@ float Remark3Parser::parse()
mDb["refine"].front()["details"] = remarks;
}
float score = float(lineCount - dropped) / lineCount;
float score = static_cast<float>(lineCount - dropped) / static_cast<float>(lineCount);
return score;
}
@@ -1176,7 +1215,7 @@ void Remark3Parser::storeCapture(const char *category, std::initializer_list<con
if (iequals(value, "NULL") or iequals(value, "NONE") or iequals(value, "Inf") or iequals(value, "+Inf") or iequals(value, std::string(value.length(), '*')))
continue;
if (cif::VERBOSE >= 3)
if (VERBOSE >= 3)
std::cerr << "storing: '" << value << "' in _" << category << '.' << item << '\n';
auto &cat = mDb[category];
@@ -1185,7 +1224,7 @@ void Remark3Parser::storeCapture(const char *category, std::initializer_list<con
if (iequals(category, "refine"))
cat.emplace({ { "pdbx_refine_id", mExpMethod },
{ "entry_id", mDb.name() },
//#warning("this diffrn-id is probably not correct?")
// #warning("this diffrn-id is probably not correct?")
{ "pdbx_diffrn_id", 1 } });
else if (iequals(category, "refine_analyze") or iequals(category, "pdbx_refine"))
cat.emplace({
@@ -1195,17 +1234,17 @@ void Remark3Parser::storeCapture(const char *category, std::initializer_list<con
});
else if (iequals(category, "refine_hist"))
{
std::string dResHigh, dResLow;
std::optional<float> dResHigh, dResLow;
for (auto r : mDb["refine"])
{
cif::tie(dResHigh, dResLow) = r.get("ls_d_res_high", "ls_d_res_low");
cif::tie(dResHigh, dResLow) = r.get<float, float>("ls_d_res_high", "ls_d_res_low");
break;
}
cat.emplace({ { "pdbx_refine_id", mExpMethod },
{ "cycle_id", "LAST" },
{ "d_res_high", dResHigh.empty() ? "." : dResHigh },
{ "d_res_low", dResLow.empty() ? "." : dResLow } });
{ "d_res_high", dResHigh },
{ "d_res_low", dResLow } });
}
else if (iequals(category, "refine_ls_shell"))
{
@@ -1217,11 +1256,10 @@ void Remark3Parser::storeCapture(const char *category, std::initializer_list<con
{
std::string tlsID;
if (not mDb["pdbx_refine_tls"].empty())
tlsID = mDb["pdbx_refine_tls"].back()["id"].as<std::string>();
tlsID = mDb["pdbx_refine_tls"].back()["id"].get<std::string>();
std::string tlsGroupID = cat.get_unique_id("");
cat.emplace({
{ "pdbx_refine_id", mExpMethod },
cat.emplace({ { "pdbx_refine_id", mExpMethod },
{ "id", tlsGroupID },
{ "refine_tls_id", tlsID } });
}
@@ -1276,10 +1314,8 @@ void Remark3Parser::storeRefineLsRestr(const char *type, std::initializer_list<c
if (r.empty())
{
r = mDb["refine_ls_restr"].emplace({
{"pdbx_refine_id", mExpMethod},
{"type", type}
});
r = mDb["refine_ls_restr"].emplace({ { "pdbx_refine_id", mExpMethod },
{ "type", type } });
}
r[item] = value;
@@ -1337,7 +1373,7 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
if (line != "REFINEMENT.")
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "Unexpected data in REMARK 3\n";
return false;
}
@@ -1349,7 +1385,7 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
if (not std::regex_match(line, m, rxp))
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "Expected valid PROGRAM line in REMARK 3\n";
return false;
}
@@ -1358,8 +1394,8 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
struct programScore
{
programScore(const std::string &program, Remark3Parser *parser, float score)
: program(program)
programScore(std::string program, Remark3Parser *parser, float score)
: program(std::move(program))
, parser(parser)
, score(score)
{
@@ -1388,20 +1424,18 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
}
catch (const std::exception &e)
{
if (cif::VERBOSE >= 0)
if (VERBOSE >= 0)
std::cerr << "Error parsing REMARK 3 with " << parser->program() << '\n'
<< e.what() << '\n';
score = 0;
}
if (cif::VERBOSE >= 2)
if (VERBOSE >= 2)
std::cerr << "Score for " << parser->program() << ": " << score << '\n';
if (score > 0)
{
std::string program = parser->program();
std::string version = parser->version();
scores.emplace_back(program, parser.release(), score);
}
};
@@ -1430,16 +1464,16 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
tryParser(new TNT_Remark3Parser(program, expMethod, r, db));
else if (cif::starts_with(program, "X-PLOR"))
tryParser(new XPLOR_Remark3Parser(program, expMethod, r, db));
else if (cif::VERBOSE > 0)
else if (VERBOSE > 0)
std::cerr << "Skipping unknown program (" << program << ") in REMARK 3\n";
}
sort(scores.begin(), scores.end());
std::sort(scores.begin(), scores.end()); // NOLINT(modernize-use-ranges)
bool guessProgram = scores.empty() or scores.front().score < 0.9f;
if (guessProgram)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "Unknown or untrusted program in REMARK 3, trying all parsers to see if there is a match\n";
tryParser(new BUSTER_TNT_Remark3Parser("BUSTER-TNT", expMethod, r, db));
@@ -1460,11 +1494,11 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
{
result = true;
sort(scores.begin(), scores.end());
sort(scores.begin(), scores.end()); // NOLINT(modernize-use-ranges)
auto &best = scores.front();
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::cerr << "Choosing " << best.parser->program() << " version '" << best.parser->version() << "' as refinement program. Score = " << best.score << '\n';
auto &software = db["software"];
@@ -1478,7 +1512,7 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
best.parser->fixup();
auto &validator = cif::validator_factory::instance().get("mmcif_pdbx.dic");
auto &validator = cif::validator_factory::instance()["mmcif_pdbx.dic"];
for (auto &cat1 : best.parser->mDb)
{
@@ -1487,37 +1521,104 @@ bool Remark3Parser::parse(const std::string &expMethod, PDBRecord *r, cif::datab
auto &cat2 = db[cat1.name()];
// copy only the values in the first row for the following categories
if (cat1.name() == "reflns" or cat1.name() == "refine")
{
if (cat2.empty())
cat2.emplace(cat1.front());
else
{
auto r1 = cat1.front();
auto r2 = cat2.front();
row_handle r1 = cat1.front();
row_handle r2;
auto cv = cat1.get_cat_validator();
if (cv == nullptr)
cv = validator.get_validator_for_category(cat1.name());
if (cv == nullptr)
continue;
for (auto &iv : cv->m_item_validators)
r2[iv.m_item_name] = r1[iv.m_item_name].text();
}
}
if (cat2.empty() or (cat1.name() == "reflns" or cat1.name() == "refine"))
r2 = cat2.emplace({});
else
r2 = cat2.front();
auto cv = cat1.get_cat_validator();
if (cv == nullptr)
cv = validator.get_validator_for_category(cat1.name());
if (cv == nullptr)
continue;
for (auto &iv : cv->m_item_validators)
{
for (auto rs : cat1)
cat2.emplace(rs);
if (r1[iv.m_item_name].empty())
continue;
// if (iv.m_type and iv.m_type->m_primitive_type == DDL_PrimitiveType::Numb)
// {
// try
// {
// r2[iv.m_item_name] = r1[iv.m_item_name].get<int64_t>();
// continue;
// }
// catch (...)
// {
// }
// try
// {
// r2[iv.m_item_name] = r1[iv.m_item_name].get<double>();
// continue;
// }
// catch (...)
// {
// }
// }
r2[iv.m_item_name] = r1[iv.m_item_name].value();
}
// // copy only the values in the first row for the following categories
// if (cat1.name() == "reflns" or cat1.name() == "refine")
// {
// if (cat2.empty())
// cat2.emplace(cat1.front());
// else
// {
// auto r1 = cat1.front();
// auto r2 = cat2.front();
// auto cv = cat1.get_cat_validator();
// if (cv == nullptr)
// cv = validator.get_validator_for_category(cat1.name());
// if (cv == nullptr)
// continue;
// for (auto &iv : cv->m_item_validators)
// {
// if (r1[iv.m_item_name].empty())
// continue;
// if (iv.m_type and iv.m_type->m_primitive_type == DDL_PrimitiveType::Numb)
// {
// try
// {
// r2[iv.m_item_name] = r1[iv.m_item_name].get<int64_t>();
// continue;
// }
// catch (...) {}
// try
// {
// r2[iv.m_item_name] = r1[iv.m_item_name].get<double>();
// continue;
// }
// catch (...) {}
// }
// r2[iv.m_item_name] = r1[iv.m_item_name].value();
// }
// }
// }
// else
// {
// for (auto rs : cat1)
// cat2.emplace(rs);
// }
}
}
return result;
}
} // namespace pdbx
} // namespace cif::pdb
// NOLINTEND(bugprone-empty-catch)

View File

@@ -26,8 +26,14 @@
#pragma once
#include "cif++/datablock.hpp"
#include "pdb_record.hpp"
#include <cstdint>
#include <initializer_list>
#include <regex>
#include <string>
// --------------------------------------------------------------------
namespace cif::pdb
@@ -38,7 +44,7 @@ struct TemplateLine;
class Remark3Parser
{
public:
virtual ~Remark3Parser() {}
virtual ~Remark3Parser() = default;
static bool parse(const std::string &expMethod, PDBRecord *r, cif::datablock &db);
@@ -46,13 +52,13 @@ class Remark3Parser
virtual std::string version();
protected:
Remark3Parser(const std::string &name, const std::string &expMethod, PDBRecord *r, cif::datablock &db,
Remark3Parser(std::string name, std::string expMethod, PDBRecord *r, cif::datablock &db,
const TemplateLine templatelines[], uint32_t templateLineCount, std::regex programVersion);
virtual float parse();
std::string nextLine();
bool match(const char *expr, int nextState);
bool match(const char *expr, uint32_t nextState);
void storeCapture(const char *category, std::initializer_list<const char *> items, bool createNew = false);
void storeRefineLsRestr(const char *type, std::initializer_list<const char *> values);
void updateRefineLsRestr(const char *type, std::initializer_list<const char *> values);
@@ -72,4 +78,4 @@ class Remark3Parser
std::regex mProgramVersion;
};
} // namespace pdbx
} // namespace cif::pdb

View File

@@ -26,7 +26,10 @@
#pragma once
#include "cif++/file.hpp"
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
/// \file pdb_record.hpp
@@ -37,14 +40,14 @@ namespace cif::pdb
struct PDBRecord
{
PDBRecord *mNext;
PDBRecord *mNext = nullptr;
uint32_t mLineNr;
char mName[11];
std::size_t mVlen;
char mValue[1];
PDBRecord(uint32_t lineNr, const std::string &name, const std::string &value);
~PDBRecord();
~PDBRecord() = default;
void *operator new(std::size_t);
void *operator new(std::size_t size, std::size_t vLen);
@@ -57,7 +60,7 @@ struct PDBRecord
char vC(std::size_t column);
std::string vS(std::size_t columnFirst, std::size_t columnLast = std::numeric_limits<std::size_t>::max());
int vI(int columnFirst, int columnLast);
std::string vF(std::size_t columnFirst, std::size_t columnLast);
std::optional<float> vF(std::size_t columnFirst, std::size_t columnLast);
};
} // namespace pdbx
} // namespace cif::pdb

View File

@@ -24,12 +24,27 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++.hpp"
#include "cif++/compound.hpp"
#include "cif++/row.hpp"
#include "cif++/cif++.hpp"
#include <algorithm>
#include <cstddef>
#include <exception>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <optional>
#include <ranges>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
// --------------------------------------------------------------------
@@ -143,12 +158,12 @@ void checkEntities(datablock &db)
auto compound = cf.create(comp_id);
if (compound)
formula_weight += compound->formula_weight();
// else if (cif::VERBOSE > 0)
// else if (VERBOSE > 0)
// std::clog << "missing information for compound " + comp_id << '\n';
++n;
}
formula_weight -= (n - 1) * 18.015f;
formula_weight -= (static_cast<float>(n) - 1) * 18.015f;
}
else if (type == "water")
formula_weight = 18.015f;
@@ -161,12 +176,12 @@ void checkEntities(datablock &db)
auto compound = cf.create(comp_id);
if (compound)
formula_weight += compound->formula_weight();
// else if (cif::VERBOSE > 0)
// else if (VERBOSE > 0)
// std::clog << "missing information for compound " + comp_id << '\n';
++n;
}
formula_weight -= (n - 1) * 18.015f;
formula_weight -= (static_cast<float>(n) - 1) * 18.015f;
}
else if (type == "non-polymer")
{
@@ -184,7 +199,7 @@ void checkEntities(datablock &db)
}
if (formula_weight > 0)
entity.assign({ { "formula_weight", formula_weight, 3 } });
entity.assign({ { "formula_weight", { formula_weight, 3 } } });
}
}
@@ -253,7 +268,7 @@ void createEntityIDs(datablock &db)
// continue;
if (asym_id != lastAsymID or (not is_monomer and lastSeqID != seq_id))
entities.push_back({});
entities.emplace_back();
entities.back().emplace_back(rh);
@@ -348,10 +363,10 @@ void fillLabelAsymID(category &atom_site)
auto i = mapAuthAsymIDAndEntityToLabelAsymID.find(key);
if (i == mapAuthAsymIDAndEntityToLabelAsymID.end())
mapAuthAsymIDAndEntityToLabelAsymID.emplace(make_pair(key, label_asym_id));
mapAuthAsymIDAndEntityToLabelAsymID.emplace(key, label_asym_id);
else if (i->second != label_asym_id)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Inconsistent assignment of label_asym_id for the tuple entity_id: " << *label_entity_id << " and auth_asym_id: " << auth_asym_id << '\n';
mapAuthAsymIDAndEntityToLabelAsymID.clear();
@@ -424,7 +439,7 @@ void fixNegativeSeqID(category &atom_site)
key("auth_seq_id") == auth_seq_id and
key("label_seq_id") == label_seq_id))
{
row.assign("label_seq_id", std::to_string(seq_id), false, false);
row.assign("label_seq_id", seq_id, false, false);
}
++seq_id;
@@ -438,7 +453,7 @@ void fixNegativeSeqID(category &atom_site)
key("auth_seq_id") == auth_seq_id and
key("label_seq_id") == label_seq_id))
{
row.assign("label_seq_id", ".", false, false);
row.assign("label_seq_id", cif::item_value_type::INAPPLICABLE, false, false);
}
}
}
@@ -451,22 +466,22 @@ void checkChemCompRecords(datablock &db)
for (auto chem_comp_entry : chem_comp)
{
auto compound = cf.create(chem_comp_entry["id"].text());
auto compound = cf.create(chem_comp_entry["id"].str());
if (not compound)
std::cerr << "Unknown compound: " << chem_comp_entry["id"].text() << '\n';
std::cerr << "Unknown compound: " << chem_comp_entry["id"].str() << '\n';
else
{
std::vector<item> items;
if (not chem_comp_entry["type"])
items.emplace_back(item{ "type", compound->type() });
if (not chem_comp_entry["name"])
items.emplace_back(item{ "name", compound->name() });
if (not chem_comp_entry["formula"])
items.emplace_back(item{ "formula", compound->formula() });
if (not chem_comp_entry["formula_weight"])
items.emplace_back(item{ "formula_weight", compound->formula_weight() });
if (chem_comp_entry["type"].empty())
items.emplace_back("type", compound->type());
if (chem_comp_entry["name"].empty())
items.emplace_back("name", compound->name());
if (chem_comp_entry["formula"].empty())
items.emplace_back("formula", compound->formula());
if (chem_comp_entry["formula_weight"].empty())
items.emplace_back("formula_weight", item_value{ compound->formula_weight(), 3 });
if (not items.empty())
chem_comp_entry.assign(items);
@@ -501,7 +516,7 @@ void checkAtomRecords(datablock &db)
std::optional<int> last_label_seq_id, last_auth_seq_id;
std::set<std::string> entityIDs;
for (auto &[entity_id, label_comp_id, label_seq_id, auth_comp_id, auth_seq_id] :
for (const auto &[entity_id, label_comp_id, label_seq_id, auth_comp_id, auth_seq_id] :
atom_site.rows<std::string, std::string, std::optional<int>, std::string, std::optional<int>>(
"label_entity_id", "label_comp_id", "label_seq_id", "auth_comp_id", "auth_seq_id"))
{
@@ -541,7 +556,7 @@ void checkAtomRecords(datablock &db)
if (row["type_symbol"].empty())
throw std::runtime_error("Missing type symbol in atom_site record");
std::string symbol{ row["type_symbol"].text() };
std::string symbol{ row["type_symbol"].str() };
if (atom_type.count("symbol"_key == symbol) == 0)
atom_type.emplace({ { "symbol", symbol } });
@@ -554,28 +569,27 @@ void checkAtomRecords(datablock &db)
if (not has_seq_id(k))
throw std::runtime_error("atom_site records does not have a label_atom_id nor an auth_atom_id, cannot continue");
std::string asym_id = get_asym_id(k);
std::string comp_id = get_comp_id(k);
if (missingCompounds.contains(comp_id))
continue;
bool is_polymer = polymer_entities.contains(row["label_entity_id"].as<std::string>());
bool is_polymer = polymer_entities.contains(row["label_entity_id"].get<std::string>());
auto compound = cf.create(comp_id);
if (not compound)
{
missingCompounds.insert(comp_id);
// if (cif::VERBOSE > 0)
// if (VERBOSE > 0)
// std::cerr << "Missing compound information for " << comp_id << "\n";
continue;
}
auto chem_comp_entry = chem_comp.find_first("id"_key == comp_id);
std::optional<bool> non_std;
std::optional<std::string> non_std;
if (cf.is_monomer(comp_id))
non_std = cf.is_std_monomer(comp_id);
non_std = cf.is_std_monomer(comp_id) ? "n" : "y";
if (not chem_comp_entry)
{
@@ -585,22 +599,22 @@ void checkAtomRecords(datablock &db)
{ "mon_nstd_flag", non_std },
{ "name", compound->name() },
{ "formula", compound->formula() },
{ "formula_weight", compound->formula_weight() } });
{ "formula_weight", { compound->formula_weight(), 3 } } });
}
else
{
std::vector<item> items;
if (not chem_comp_entry["type"])
items.emplace_back(item{ "type", compound->type() });
if (not chem_comp_entry["mon_nstd_flag"] and non_std.has_value())
items.emplace_back(item{ "mon_nstd_flag", non_std });
if (not chem_comp_entry["name"])
items.emplace_back(item{ "name", compound->name() });
if (not chem_comp_entry["formula"])
items.emplace_back(item{ "formula", compound->formula() });
if (not chem_comp_entry["formula_weight"])
items.emplace_back(item{ "formula_weight", compound->formula_weight() });
if (chem_comp_entry["type"].empty())
items.emplace_back("type", compound->type());
if (chem_comp_entry["mon_nstd_flag"].empty() and non_std.has_value())
items.emplace_back("mon_nstd_flag", non_std);
if (chem_comp_entry["name"].empty())
items.emplace_back("name", compound->name());
if (chem_comp_entry["formula"].empty())
items.emplace_back("formula", compound->formula());
if (chem_comp_entry["formula_weight"].empty())
items.emplace_back("formula_weight", item_value{ compound->formula_weight(), 3 });
if (not items.empty())
chem_comp_entry.assign(items);
@@ -612,22 +626,22 @@ void checkAtomRecords(datablock &db)
int seq_id = get_seq_id(k);
if (is_polymer and row["label_seq_id"].empty() and cf.is_monomer(comp_id))
row["label_seq_id"] = std::to_string(seq_id);
row["label_seq_id"] = seq_id;
if (row["label_asym_id"].empty())
row["label_asym_id"] = row["auth_asym_id"].text();
row["label_asym_id"] = row["auth_asym_id"].value();
else if (row["auth_asym_id"].empty())
row["auth_asym_id"] = row["label_asym_id"].text();
row["auth_asym_id"] = row["label_asym_id"].value();
if (row["label_comp_id"].empty())
row["label_comp_id"] = row["auth_comp_id"].text();
row["label_comp_id"] = row["auth_comp_id"].value();
else if (row["auth_comp_id"].empty())
row["auth_comp_id"] = row["label_comp_id"].text();
row["auth_comp_id"] = row["label_comp_id"].value();
if (row["label_atom_id"].empty())
row["label_atom_id"] = row["auth_atom_id"].text();
row["label_atom_id"] = row["auth_atom_id"].value();
else if (row["auth_atom_id"].empty())
row["auth_atom_id"] = row["label_atom_id"].text();
row["auth_atom_id"] = row["label_atom_id"].value();
// Rewrite the coordinates and other items that look better in a fixed format
// Be careful not to nuke invalidly formatted data here
@@ -643,16 +657,17 @@ void checkAtomRecords(datablock &db)
float v;
auto s = row.get<std::string>(item_name);
if (auto [ptr, ec] = cif::from_chars(s.data(), s.data() + s.length(), v); (bool)ec)
if (auto [ptr, ec] = cif::from_chars(s.data(), s.data() + s.length(), v); ec != std::errc{})
continue;
if (s.length() < prec + 1UL or s[s.length() - prec - 1] != '.')
{
char b[12];
/* if (s.length() < prec + 1UL or s[s.length() - prec - 1] != '.')
{
char b[12];
if (auto [ptr, ec] = std::to_chars(b, b + sizeof(b), v, std::chars_format::fixed, prec); ec == std::errc{})
row.assign(item_name, { b, static_cast<std::string::size_type>(ptr - b) }, false, false);
}
if (auto [ptr, ec] = std::to_chars(b, b + sizeof(b), v, std::chars_format::fixed, prec); ec == std::errc{})
row.assign(item_name, { b, static_cast<std::string::size_type>(ptr - b) }, false, false);
}
*/
}
}
@@ -668,12 +683,12 @@ void checkAtomRecords(datablock &db)
// auto r = atom_site.find_first(key(item_name) != null);
// if (not r)
// {
// if (cif::VERBOSE > 0)
// if (VERBOSE > 0)
// std::clog << "Dropping unknown item " << item_name << '\n';
// atom_site.remove_item(item_name);
// }
// else if (cif::VERBOSE > 0)
// else if (VERBOSE > 0)
// std::clog << "Keeping unknown item " << std::quoted(item_name) << " in atom_site since it is not empty\n";
// }
// }
@@ -712,29 +727,29 @@ void checkAtomAnisotropRecords(datablock &db)
// this happens sometimes (Phenix):
if (row["type_symbol"].empty())
row["type_symbol"] = parent["type_symbol"].text();
else if (row["type_symbol"].text() != parent["type_symbol"].text())
row["type_symbol"] = parent["type_symbol"].value();
else if (row["type_symbol"].value() != parent["type_symbol"].value())
{
if (cif::VERBOSE and std::exchange(warnReplaceTypeSymbol, false))
if (VERBOSE and std::exchange(warnReplaceTypeSymbol, false))
std::clog << "Replacing type_symbol in atom_site_anisotrop record(s)\n";
row["type_symbol"] = parent["type_symbol"].text();
row["type_symbol"] = parent["type_symbol"].value();
}
if (row["pdbx_auth_alt_id"].empty() and not parent["pdbx_auth_alt_id"].empty())
row["pdbx_auth_alt_id"] = parent["pdbx_auth_alt_id"].text();
row["pdbx_auth_alt_id"] = parent["pdbx_auth_alt_id"].value();
if (row["pdbx_label_seq_id"].empty() and not parent["label_seq_id"].empty())
row["pdbx_label_seq_id"] = parent["label_seq_id"].text();
row["pdbx_label_seq_id"] = parent["label_seq_id"].value();
if (row["pdbx_label_asym_id"].empty() and not parent["label_asym_id"].empty())
row["pdbx_label_asym_id"] = parent["label_asym_id"].text();
row["pdbx_label_asym_id"] = parent["label_asym_id"].value();
if (row["pdbx_label_atom_id"].empty() and not parent["label_atom_id"].empty())
row["pdbx_label_atom_id"] = parent["label_atom_id"].text();
row["pdbx_label_atom_id"] = parent["label_atom_id"].value();
if (row["pdbx_label_comp_id"].empty() and not parent["label_comp_id"].empty())
row["pdbx_label_comp_id"] = parent["label_comp_id"].text();
row["pdbx_label_comp_id"] = parent["label_comp_id"].value();
}
if (not to_be_deleted.empty())
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Dropped " << to_be_deleted.size() << " anisotrop records since they did not have exactly one parent\n";
for (auto row : to_be_deleted)
@@ -744,51 +759,31 @@ void checkAtomAnisotropRecords(datablock &db)
void checkStructAsym(datablock &db)
{
auto &atom_site = db["atom_site"];
// auto &atom_site = db["atom_site"];
auto &struct_asym = db["struct_asym"];
if (struct_asym.empty())
cql::connection conn(db);
cql::transaction tx(conn);
for (const auto [asym_id, entity_id] : tx.stream<std::optional<std::string>, std::string>(
"select distinct label_asym_id, label_entity_id from atom_site"))
{
for (const auto &[label_asym_id, entity_id] : atom_site.rows<std::string, std::string>("label_asym_id", "label_entity_id"))
if (not asym_id)
throw std::runtime_error("File contains atom_site records without a label_asym_id");
auto sa = struct_asym.find_first(key("id") == asym_id);
if (sa)
{
if (label_asym_id.empty())
throw std::runtime_error("File contains atom_site records without a label_asym_id");
if (struct_asym.count(key("id") == label_asym_id) == 0)
{
struct_asym.emplace({
// clang-format off
{ "id", label_asym_id },
{ "entity_id", entity_id }
//clang-format on
});
}
if (sa["entity_id"].empty())
sa.assign("entity_id", entity_id, false, true);
else if (sa.get<std::string>("entity_id") != entity_id)
throw std::runtime_error("Inconsistent entity ID's in struct_asym");
}
}
else
{
for (const auto &[label_asym_id, entity_id] :
atom_site.rows<std::string, std::string>("label_asym_id", "label_entity_id"))
else
{
if (label_asym_id.empty())
throw std::runtime_error("File contains atom_site records without a label_asym_id");
auto sa = struct_asym.find_first(key("id") == label_asym_id);
if (sa)
{
if (sa["entity_id"].empty())
sa.assign("entity_id", entity_id, false, true);
else if (sa.get<std::string>("entity_id") != entity_id)
throw std::runtime_error("Inconsistent entity ID's in struct_asym");
}
else
{
struct_asym.emplace({
// clang-format off
{ "id", label_asym_id },
{ "entity_id", entity_id }
//clang-format on
});
}
struct_asym.emplace({ //
{ "id", asym_id },
{ "entity_id", entity_id } });
}
}
}
@@ -893,7 +888,7 @@ void createEntity(datablock &db)
{ "id", entity_id },
{ "type", type },
{ "pdbx_description", desc },
{ "formula_weight", weight },
{ "formula_weight", { weight, 3 } },
{ "pdbx_number_of_molecules", count } });
}
}
@@ -913,8 +908,8 @@ void createEntityPoly(datablock &db)
std::string type;
int last_seq_id = -1;
std::map<std::string, std::string> seq, seq_can;
bool non_std_monomer = false;
bool non_std_linkage = false;
std::string non_std_monomer = "n";
std::string non_std_linkage = "n";
std::vector<std::string> pdb_strand_ids;
for (const auto &[comp_id, seq_id, auth_asym_id] : atom_site.find<std::string, int, std::string>(
@@ -958,8 +953,8 @@ void createEntityPoly(datablock &db)
letter_can = c->one_letter_code();
letter = '(' + comp_id + ')';
non_std_linkage = true;
non_std_monomer = true;
non_std_linkage = "y";
non_std_monomer = "y";
}
else if (iequals(c->type(), "L-PEPTIDE LINKING") or iequals(c->type(), "PEPTIDE LINKING"))
{
@@ -968,7 +963,7 @@ void createEntityPoly(datablock &db)
letter_can = c->one_letter_code();
letter = '(' + comp_id + ')';
non_std_monomer = true;
non_std_monomer = "y";
}
else
{
@@ -977,7 +972,7 @@ void createEntityPoly(datablock &db)
letter_can = c->one_letter_code();
letter = '(' + comp_id + ')';
non_std_monomer = true;
non_std_monomer = "y";
}
if (type.empty())
@@ -989,7 +984,7 @@ void createEntityPoly(datablock &db)
seq[auth_asym_id] += letter;
seq_can[auth_asym_id] += letter_can ? letter_can : 'X';
if (find(pdb_strand_ids.begin(), pdb_strand_ids.end(), auth_asym_id) == pdb_strand_ids.end())
if (std::ranges::find(pdb_strand_ids, auth_asym_id) == pdb_strand_ids.end())
pdb_strand_ids.emplace_back(auth_asym_id);
}
@@ -1059,23 +1054,23 @@ void createEntityPolySeq(datablock &db)
{
int last_seq_id = -1;
std::string last_comp_id;
std::string asym_id = struct_asym.find_first<std::string>("entity_id"_key == entity_id, "id");
auto asym_id = struct_asym.find_first<std::string>("entity_id"_key == entity_id, "id");
for (const auto &[comp_id, seq_id] : atom_site.find<std::string, int>("label_entity_id"_key == entity_id and "label_asym_id"_key == asym_id, "label_comp_id", "label_seq_id"))
{
bool hetero = false;
std::string hetero = "n";
if (seq_id == last_seq_id)
{
if (last_comp_id != comp_id)
hetero = true;
hetero = "y";
else
continue;
}
if (hetero)
if (hetero == "y")
{
entity_poly_seq.back().assign({ { "hetero", true } });
entity_poly_seq.back().assign({ { "hetero", hetero } });
}
entity_poly_seq.emplace({ //
@@ -1102,7 +1097,7 @@ void createPdbxPolySeqScheme(datablock &db)
if (auto cat = db.get("entity_poly_seq"); cat == nullptr or cat->empty())
createEntityPolySeq(db);
using namespace literals;
// Check first if this is needed
auto &atom_site = db["atom_site"];
auto &entity_poly = db["entity_poly"];
@@ -1110,6 +1105,14 @@ void createPdbxPolySeqScheme(datablock &db)
auto &struct_asym = db["struct_asym"];
auto &pdbx_poly_seq_scheme = db["pdbx_poly_seq_scheme"];
cql::connection conn(db);
cql::transaction tx(conn);
using namespace literals;
// Recreate it
pdbx_poly_seq_scheme.clear();
// Find the mapping between asym_id and pdb_strand_id first
std::map<std::string, std::string> asym_id_to_pdb_strand_map;
@@ -1122,23 +1125,55 @@ void createPdbxPolySeqScheme(datablock &db)
}
}
for (auto &entity_id : entity_poly.rows<std::string>("entity_id"))
for (auto col : { "label_asym_id", "label_entity_id", "label_seq_id", "label_comp_id", "auth_seq_id", "auth_comp_id", "pdbx_PDB_ins_code" })
atom_site.add_item(col);
// entity_id, mon_id, num, asym_id
using key = std::tuple<std::string, std::string, int, std::string>;
// auth_seq_num, auth_mon_id, ins_code
using value = std::tuple<std::string, std::string, std::optional<std::string>>;
std::map<key, value> data;
for (const auto [entity_id, mon_id, num, asym_id, auth_seq_num, auth_mon_id, ins_code] :
tx.stream<std::string, std::string, int, std::string, std::string, std::string, std::optional<std::string>>(
R"(
select distinct label_entity_id, label_comp_id, label_seq_id, label_asym_id, auth_seq_id, auth_comp_id, pdbx_PDB_ins_code from atom_site
where label_entity_id in (select id from entity where type = 'polymer')
)"))
{
assert(not data.contains(key{ entity_id, mon_id, num, asym_id }));
data.emplace(key{ entity_id, mon_id, num, asym_id }, value{ auth_seq_num, auth_mon_id, ins_code });
}
std::string last_asym_id;
int last_seq_id = -1;
for (auto entity_id : entity_poly.rows<std::string>("entity_id"))
{
for (auto asym_id : struct_asym.find<std::string>("entity_id"_key == entity_id, "id"))
{
for (const auto &[comp_id, num, hetero] : entity_poly_seq.find<std::string, int, bool>("entity_id"_key == entity_id, "mon_id", "num", "hetero"))
for (const auto [mon_id, seq_id] : entity_poly_seq.find<std::string, int>("entity_id"_key == entity_id, "mon_id", "num"))
{
const auto &[auth_seq_num, auth_mon_id, ins_code] =
atom_site.find_first<std::string, std::string, std::optional<std::string>>(
"label_asym_id"_key == asym_id and "label_seq_id"_key == num,
"auth_seq_id", "auth_comp_id", "pdbx_PDB_ins_code");
std::optional<std::string> auth_seq_num, auth_mon_id, ins_code;
if (auto i = data.find(key{ entity_id, mon_id, seq_id, asym_id }); i != data.end())
{
std::tie(auth_seq_num, auth_mon_id, ins_code) = i->second;
}
std::string hetero = (asym_id == last_asym_id and seq_id == last_seq_id) ? "y" : "n";
if (hetero == "y")
pdbx_poly_seq_scheme.back().assign("hetero", "y", false);
pdbx_poly_seq_scheme.emplace({ //
{ "asym_id", asym_id },
{ "entity_id", entity_id },
{ "seq_id", num },
{ "mon_id", comp_id },
{ "ndb_seq_num", num },
{ "seq_id", seq_id },
{ "mon_id", mon_id },
{ "ndb_seq_num", seq_id },
{ "pdb_seq_num", auth_seq_num },
{ "auth_seq_num", auth_seq_num },
{ "pdb_mon_id", auth_mon_id },
@@ -1146,7 +1181,31 @@ void createPdbxPolySeqScheme(datablock &db)
{ "pdb_strand_id", asym_id_to_pdb_strand_map[asym_id] },
{ "pdb_ins_code", ins_code },
{ "hetero", hetero } });
last_asym_id = asym_id;
last_seq_id = seq_id;
}
// std::string hetero = (asym_id == last_asym_id and seq_id == last_seq_id) ? "y" : "n";
// if (hetero == "y")
// pdbx_poly_seq_scheme.back().assign("hetero", "y", false);
// pdbx_poly_seq_scheme.emplace({ //
// { "asym_id", asym_id },
// { "entity_id", entity_id },
// { "seq_id", seq_id },
// { "mon_id", comp_id },
// { "ndb_seq_num", seq_id },
// { "pdb_seq_num", auth_seq_id },
// { "auth_seq_num", auth_seq_id },
// { "pdb_mon_id", auth_comp_id },
// { "auth_mon_id", auth_comp_id },
// { "pdb_strand_id", asym_id_to_pdb_strand_map[asym_id] },
// { "pdb_ins_code", pdb_ins_code },
// { "hetero", hetero } });
// last_asym_id = asym_id;
// last_seq_id = seq_id;
}
}
}
@@ -1168,14 +1227,14 @@ void comparePolySeqSchemes(datablock &db)
for (auto asym_id : ndb_poly_seq_scheme.rows<std::string>("id"))
{
auto i = std::lower_bound(asym_ids_ndb.begin(), asym_ids_ndb.end(), asym_id);
auto i = std::ranges::lower_bound(asym_ids_ndb, asym_id);
if (i == asym_ids_ndb.end() or *i != asym_id)
asym_ids_ndb.insert(i, asym_id);
}
for (auto asym_id : pdbx_poly_seq_scheme.rows<std::string>("asym_id"))
{
auto i = std::lower_bound(asym_ids_pdbx.begin(), asym_ids_pdbx.end(), asym_id);
auto i = std::ranges::lower_bound(asym_ids_pdbx, asym_id);
if (i == asym_ids_pdbx.end() or *i != asym_id)
asym_ids_pdbx.insert(i, asym_id);
}
@@ -1183,7 +1242,7 @@ void comparePolySeqSchemes(datablock &db)
// If we have different Asym ID's assume the ndb is invalid.
if (asym_ids_ndb != asym_ids_pdbx)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "The asym ID's of ndb_poly_seq_scheme and pdbx_poly_seq_scheme are not equal, dropping ndb_poly_seq_scheme\n";
ndb_poly_seq_scheme.clear();
}
@@ -1197,11 +1256,11 @@ void comparePolySeqSchemes(datablock &db)
auto pdbx_range = pdbx_poly_seq_scheme.find(key("asym_id") == asym_id);
for (auto ndb_i = ndb_range.begin(), pdbx_i = pdbx_range.begin();
ndb_i != ndb_range.end() or pdbx_i != pdbx_range.end(); ++ndb_i, ++pdbx_i)
ndb_i != ndb_range.end() or pdbx_i != pdbx_range.end(); ++ndb_i, ++pdbx_i)
{
if (ndb_i == ndb_range.end() or pdbx_i == pdbx_range.end())
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "The sequences in ndb_poly_seq_scheme and pdbx_poly_seq_scheme are unequal in size for asym ID " << asym_id << '\n';
valid = false;
break;
@@ -1212,7 +1271,7 @@ void comparePolySeqSchemes(datablock &db)
if (ndb_mon_id != pdbx_mon_id)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "The sequences in ndb_poly_seq_scheme and pdbx_poly_seq_scheme contain different mon ID's for asym ID " << asym_id << '\n';
valid = false;
break;
@@ -1221,7 +1280,7 @@ void comparePolySeqSchemes(datablock &db)
if (not valid)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Dropping asym ID " << asym_id << " from ndb_poly_seq_scheme\n";
ndb_poly_seq_scheme.erase(key("id") == asym_id);
}
@@ -1253,19 +1312,19 @@ void createPdbxEntityNonpoly(datablock &db)
pdbx_entity_nonpoly.emplace({ //
{ "entity_id", entity_id },
{ "name", "water" },
{ "comp_id", comp_id }
});
{ "comp_id", comp_id } });
else
{
auto c = cif::compound_factory::instance().create(comp_id);
std::string name = c ? c->name() : ".";
std::optional<std::string> name;
if (c)
name = c->name();
pdbx_entity_nonpoly.emplace({ //
{ "entity_id", entity_id },
{ "name", name },
{ "comp_id", comp_id }
});
{ "comp_id", comp_id } });
}
}
}
@@ -1281,7 +1340,7 @@ void createPdbxNonpolyScheme(datablock &db)
auto &pdbx_nonpoly_scheme = db["pdbx_nonpoly_scheme"];
auto &atom_site = db["atom_site"];
for (const auto &[entity_id, comp_id] : pdbx_entity_nonpoly.rows<std::string,std::string>("entity_id", "comp_id"))
for (const auto &[entity_id, comp_id] : pdbx_entity_nonpoly.rows<std::string, std::string>("entity_id", "comp_id"))
{
for (int ndb_nr = 1; auto row : atom_site.find("label_entity_id"_key == entity_id and "label_comp_id"_key == comp_id))
{
@@ -1292,16 +1351,16 @@ void createPdbxNonpolyScheme(datablock &db)
int num = row.get<int>("auth_seq_id");
pdbx_nonpoly_scheme.emplace({//
pdbx_nonpoly_scheme.emplace({ //
{ "asym_id", row.get<std::string>("label_asym_id") },
{ "entity_id", entity_id },
{ "mon_id", comp_id },
{ "ndb_seq_num", ndb_nr++ },
{ "pdb_seq_num", num },
{ "auth_seq_num", num },
{ "ndb_seq_num", std::to_string(ndb_nr++) },
{ "pdb_seq_num", std::to_string(num) },
{ "auth_seq_num", std::to_string(num) },
{ "pdb_mon_id", row.get<std::string>("auth_comp_id") },
{ "auth_mon_id", row.get<std::string>("auth_comp_id") },
{ "auth_mon_id", row.get<std::string>("auth_comp_id") },
{ "pdb_strand_id", row.get<std::string>("auth_asym_id") },
{ "pdb_ins_code", row.get<std::string>("pdbx_PDB_ins_code") }
@@ -1353,7 +1412,7 @@ void createPdbxBranchScheme(datablock &db)
{ "num", num },
{ "pdb_asym_id", asym_id },
{ "pdb_mon_id", comp_id },
{ "pdb_seq_num", num }
{ "pdb_seq_num", std::to_string(num) }
// clang-format on
});
}
@@ -1383,7 +1442,7 @@ void reconstruct_index_for_category(const validator &validator, category &cat, d
{
if (state == State::MissingKeys)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Repairing failed for category " << cat.name() << ", missing keys remain: " << ex.what() << '\n';
throw;
@@ -1393,7 +1452,7 @@ void reconstruct_index_for_category(const validator &validator, category &cat, d
auto key = ex.get_key();
if (cif::VERBOSE > 1)
if (VERBOSE > 1)
std::clog << "Need to add key " << key << " to category " << cat.name() << '\n';
for (auto row : cat)
@@ -1410,7 +1469,7 @@ void reconstruct_index_for_category(const validator &validator, category &cat, d
{
if (state == State::DuplicateKeys)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Repairing failed for category " << cat.name() << ", duplicate keys remain: " << ex.what() << '\n';
throw;
@@ -1418,7 +1477,7 @@ void reconstruct_index_for_category(const validator &validator, category &cat, d
state = State::DuplicateKeys;
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Attempt to fix " << cat.name() << " failed: " << ex.what() << '\n';
// replace items that do not define a relation to a parent
@@ -1429,7 +1488,7 @@ void reconstruct_index_for_category(const validator &validator, category &cat, d
bool replaceable = true;
for (auto lv : validator.get_links_for_child(cat.name()))
{
if (find(lv->m_child_keys.begin(), lv->m_child_keys.end(), key) != lv->m_child_keys.end())
if (std::ranges::find(lv->m_child_keys, key) != lv->m_child_keys.end())
{
replaceable = false;
break;
@@ -1464,9 +1523,9 @@ bool reconstruct_pdbx(file &file)
auto &db = file.front();
if (auto ac = db.get("audit_conform"); ac != nullptr)
return reconstruct_pdbx(file, validator_factory::instance().get(*ac));
return reconstruct_pdbx(file, validator_factory::instance()[*ac]);
else
return reconstruct_pdbx(file, validator_factory::instance().get("mmcif_pdbx.dic"));
return reconstruct_pdbx(file, validator_factory::instance()["mmcif_pdbx.dic"]);
}
bool reconstruct_pdbx(file &file, const validator &validator)
@@ -1507,7 +1566,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
checkChemCompRecords(db);
// If the data is really horrible, it might not contain entities
if (db["atom_site"].find_first(key("label_entity_id") == null))
if (db["atom_site"].contains(key("label_entity_id") == null))
createEntityIDs(db);
// Now see if atom records make sense at all
@@ -1536,7 +1595,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
if (not iv)
continue;
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Renaming " << item_name << " to " << iv->m_item_name << " in category " << cat.name() << '\n';
cat.rename_item(item_name, iv->m_item_name);
}
@@ -1567,7 +1626,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
// So, this cat should have a link to the entry
auto pk = find(link->m_parent_keys.begin(), link->m_parent_keys.end(), "id");
auto pk = std::ranges::find(link->m_parent_keys, "id");
if (pk == link->m_parent_keys.end())
continue;
@@ -1585,12 +1644,12 @@ bool reconstruct_pdbx(file &file, const validator &validator)
{
if (not cat.has_item(item))
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << "Adding mandatory item " << item << " to category " << cat.name() << '\n';
cat.add_item(item);
cat.update_value(all(), item, "?");
cat.update_value(all(), item, cif::item_value_type::MISSING);
}
}
@@ -1610,14 +1669,30 @@ bool reconstruct_pdbx(file &file, const validator &validator)
for (auto row : cat)
{
std::error_code ec;
std::string_view value = row[ix].text();
if (not iv->validate_value(value, ec))
if (ix >= row.size() or row[ix].empty())
continue;
if (not iv->validate_value(row[ix].value(), ec))
{
if (cif::VERBOSE > 0)
std::clog << "Replacing value (" << std::quoted(value) << ") for item " << item_name << " in category " << cat.name() << " since it does not validate\n";
if (ec == cif::make_error_code(cif::validation_error::value_is_not_a_char_string))
{
row[ix] = item_value{ std::to_string(row[ix].value().get<int>()) };
if (iv->validate_value(row[ix].value(), ec))
continue;
}
row[ix] = "?";
if (ec == cif::make_error_code(cif::validation_error::value_is_not_a_number))
{
row[ix] = item_value{ std::stoi(row[ix].value().get<std::string>()) };
if (iv->validate_value(row[ix].value(), ec))
continue;
}
if (VERBOSE > 0)
std::clog << "Replacing value (" << std::quoted(row[ix].str()) << ") for item " << item_name << " in category " << cat.name() << " since it does not validate: " << ec.message() << "\n";
row[ix] = item_value{ cif::item_value_type::INAPPLICABLE };
}
}
}
@@ -1626,7 +1701,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
}
catch (const std::exception &ex)
{
if (cif::VERBOSE > 0)
if (VERBOSE > 0)
std::clog << ex.what() << '\n';
std::clog << "Will drop category " << cat.name() << " since it cannot be repaired\n";
@@ -1637,7 +1712,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
for (auto cat_name : invalidCategories)
{
auto i = find_if(db.begin(), db.end(), [cat_name](const category &cat)
auto i = std::ranges::find_if(db, [cat_name](const category &cat)
{ return cat.name() == cat_name; });
if (i != db.end())
db.erase(i);
@@ -1657,8 +1732,7 @@ bool reconstruct_pdbx(file &file, const validator &validator)
if (auto cat = db.get("entity"); cat == nullptr or cat->empty())
createEntity(db);
if (auto cat = db.get("pdbx_poly_seq_scheme"); cat == nullptr or cat->empty())
createPdbxPolySeqScheme(db);
createPdbxPolySeqScheme(db);
if (auto cat = db.get("ndb_poly_seq_scheme"); cat == nullptr or cat->empty())
comparePolySeqSchemes(db);
@@ -1689,9 +1763,9 @@ void fixup_pdbx(file &file)
auto &db = file.front();
if (auto ac = db.get("audit_conform"); ac != nullptr)
fixup_pdbx(file, validator_factory::instance().get(*ac));
fixup_pdbx(file, validator_factory::instance()[*ac]);
else
fixup_pdbx(file, validator_factory::instance().get("mmcif_pdbx.dic"));
fixup_pdbx(file, validator_factory::instance()["mmcif_pdbx.dic"]);
}
void fixup_pdbx(file &file, const validator &validator)
@@ -1711,7 +1785,7 @@ void fixup_pdbx(file &file, const validator &validator)
// Be silent about missing compound info in fixup
auto &cf = compound_factory::instance();
bool save_report = cf.get_report_missing();
cf.set_report_missing(cif::VERBOSE > 1);
cf.set_report_missing(VERBOSE > 1);
std::string entry_id;

View File

@@ -24,12 +24,27 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++.hpp"
#include "cif++/cif++.hpp"
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <exception>
#include <iostream>
#include <map>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
namespace cif::pdb
{
condition get_parents_condition(const validator &validator, row_handle rh, const category &parentCat)
condition get_parents_condition(const validator &validator, const_row_handle rh, const category &parentCat)
{
condition result;
@@ -38,9 +53,8 @@ condition get_parents_condition(const validator &validator, row_handle rh, const
auto parentName = parentCat.name();
auto links = validator.get_links_for_child(childName);
links.erase(remove_if(links.begin(), links.end(), [n = parentName](auto &l)
{ return l->m_parent_category != n; }),
links.end());
std::erase_if(links, [n = parentName](auto &l)
{ return l->m_parent_category != n; });
if (not links.empty())
{
@@ -50,12 +64,10 @@ condition get_parents_condition(const validator &validator, row_handle rh, const
for (std::size_t ix = 0; ix < link->m_child_keys.size(); ++ix)
{
auto childValue = rh[link->m_child_keys[ix]];
if (childValue.empty())
if (rh[link->m_child_keys[ix]].empty())
continue;
cond = std::move(cond) and key(link->m_parent_keys[ix]) == childValue.text();
cond = std::move(cond) and key(link->m_parent_keys[ix]) == rh[link->m_child_keys[ix]].value();
}
result = std::move(result) or std::move(cond);
@@ -65,11 +77,18 @@ condition get_parents_condition(const validator &validator, row_handle rh, const
return result;
}
bool is_valid_pdbx_file(const file &file)
{
std::error_code ec;
bool result = is_valid_pdbx_file(file, validator_factory::instance()["mmcif_pdbx.dic"], ec);
return result and ec == std::errc{};
}
bool is_valid_pdbx_file(const file &file, const validator &v)
{
std::error_code ec;
bool result = is_valid_pdbx_file(file, v, ec);
return result and not(bool) ec;
return result and ec == std::errc{};
}
bool is_valid_pdbx_file(const file &file, std::error_code &ec)
@@ -79,9 +98,9 @@ bool is_valid_pdbx_file(const file &file, std::error_code &ec)
if (file.empty())
ec = make_error_code(validation_error::empty_file);
else if (auto ac = file.front().get("audit_conform"); ac != nullptr)
result = is_valid_pdbx_file(file, validator_factory::instance().get(*ac), ec);
result = is_valid_pdbx_file(file, validator_factory::instance()[*ac], ec);
else
result = is_valid_pdbx_file(file, validator_factory::instance().get("mmcif_pdbx.dic"), ec);
result = is_valid_pdbx_file(file, validator_factory::instance()["mmcif_pdbx.dic"], ec);
return result;
}
@@ -110,7 +129,6 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
auto &pdbx_poly_seq_scheme = db["pdbx_poly_seq_scheme"];
std::string last_asym_id;
int last_seq_id = -1;
for (auto r : atom_site)
{
@@ -139,7 +157,7 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
if (p.size() != 1)
{
if (VERBOSE > 0)
std::clog << "In atom_site record: " << r["id"].text() << '\n';
std::clog << "In atom_site record: " << r["id"].str() << '\n';
throw std::runtime_error("For each monomer in atom_site there should be exactly one pdbx_poly_seq_scheme record");
}
}
@@ -165,11 +183,11 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
if (entity_poly.count("entity_id"_key == entity_id) != 1)
throw std::runtime_error("There should be exactly one entity_poly record per polymer entity");
const auto entity_poly_type = entity_poly.find1<std::string>("entity_id"_key == entity_id, "type");
// const auto entity_poly_type = entity_poly.find1<std::string>("entity_id"_key == entity_id, "type");
std::map<int, std::set<std::string>> mon_per_seq_id;
for (const auto &[num, mon_id, hetero] : entity_poly_seq.find<int, std::string, bool>("entity_id"_key == entity_id, "num", "mon_id", "hetero"))
for (const auto &[num, mon_id, hetero] : entity_poly_seq.find<int, std::string, std::string>("entity_id"_key == entity_id, "num", "mon_id", "hetero"))
{
mon_per_seq_id[num].emplace(mon_id);
@@ -187,7 +205,7 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
}
}
for (const auto &[seq_id, mon_id, hetero] : pdbx_poly_seq_scheme.find<int, std::string, bool>("entity_id"_key == entity_id, "seq_id", "mon_id", "hetero"))
for (const auto &[seq_id, mon_id, hetero] : pdbx_poly_seq_scheme.find<int, std::string, std::string>("entity_id"_key == entity_id, "seq_id", "mon_id", "hetero"))
{
if (entity_poly_seq.count(
"entity_id"_key == entity_id and
@@ -198,7 +216,7 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
throw std::runtime_error("For each pdbx_poly_seq/struct_asym record there should be exactly one entity_poly_seq record");
}
if ((mon_per_seq_id[seq_id].size() > 1) != hetero)
if ((mon_per_seq_id[seq_id].size() > 1) != iequals(hetero, "Y"))
throw std::runtime_error("Mismatch between the hetero flag in the poly seq schemes and the number residues per seq_id");
}
@@ -270,10 +288,10 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
letter = '(' + comp_id + ')';
}
if (iequals(std::string{ si, si + letter.length() }, letter))
if (iequals(std::string{ si, si + static_cast<int>(letter.length()) }, letter))
{
match = true;
si += letter.length();
si += static_cast<int>(letter.length());
break;
}
else
@@ -328,7 +346,7 @@ bool is_valid_pdbx_file(const file &file, const validator &validator, std::error
ec = make_error_code(validation_error::not_valid_pdbx);
}
if (not result and (bool) ec)
if (not result and ec == std::errc{})
ec = make_error_code(validation_error::not_valid_pdbx);
return result;

View File

@@ -24,97 +24,50 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/point.hpp"
#include "cif++/matrix.hpp" // for matrix_subtraction, matrix_cofactors
#include "cif++/cif++.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
#include <random> // for uniform_real_distribution, normal_distri...
#include <optional>
#include <glm/ext/matrix_double3x3.hpp>
#include <glm/ext/matrix_double4x4.hpp>
#include <random>
#include <stdexcept>
#include <tuple>
#include <valarray>
#include <vector>
namespace cif
{
// --------------------------------------------------------------------
template <typename T>
quaternion_type<T> normalize(quaternion_type<T> q)
{
std::valarray<double> t(4);
t[0] = q.get_a();
t[1] = q.get_b();
t[2] = q.get_c();
t[3] = q.get_d();
t *= t;
double length = std::sqrt(t.sum());
if (length > 0.001)
q /= static_cast<quaternion::value_type>(length);
else
q = quaternion(1, 0, 0, 0);
return q;
}
// --------------------------------------------------------------------
quaternion construct_from_angle_axis(float angle, point axis)
{
angle = static_cast<float>((angle * kPI / 180) / 2);
auto s = std::sin(angle);
auto c = std::cos(angle);
axis.normalize();
return normalize(quaternion{
static_cast<float>(c),
static_cast<float>(s * axis.m_x),
static_cast<float>(s * axis.m_y),
static_cast<float>(s * axis.m_z) });
}
std::tuple<double, point> quaternion_to_angle_axis(quaternion q)
{
if (q.get_a() > 1)
q = normalize(q);
// angle:
double angle = 2 * std::acos(q.get_a());
angle = angle * 180 / kPI;
// axis:
float s = std::sqrt(1 - q.get_a() * q.get_a());
if (s < 0.001)
s = 1;
point axis(q.get_b() / s, q.get_c() / s, q.get_d() / s);
return { angle, axis };
}
point center_points(std::vector<point> &Points)
{
point t;
point t{};
for (point &pt : Points)
{
t.m_x += pt.m_x;
t.m_y += pt.m_y;
t.m_z += pt.m_z;
t.x += pt.x;
t.y += pt.y;
t.z += pt.z;
}
t.m_x /= Points.size();
t.m_y /= Points.size();
t.m_z /= Points.size();
t.x /= static_cast<float>(Points.size());
t.y /= static_cast<float>(Points.size());
t.z /= static_cast<float>(Points.size());
for (point &pt : Points)
{
pt.m_x -= t.m_x;
pt.m_y -= t.m_y;
pt.m_z -= t.m_z;
pt.x -= t.x;
pt.y -= t.y;
pt.z -= t.z;
}
return t;
@@ -153,16 +106,16 @@ double RMSd(const std::vector<point> &a, const std::vector<point> &b)
{
std::valarray<double> d(3);
d[0] = b[i].m_x - a[i].m_x;
d[1] = b[i].m_y - a[i].m_y;
d[2] = b[i].m_z - a[i].m_z;
d[0] = b[i].x - a[i].x;
d[1] = b[i].y - a[i].y;
d[2] = b[i].z - a[i].z;
d *= d;
sum += d.sum();
}
return std::sqrt(sum / a.size());
return std::sqrt(sum / static_cast<double>(a.size()));
}
// The next function returns the largest solution for a quartic equation
@@ -204,43 +157,82 @@ double LargestDepressedQuarticSolution(double a, double b, double c)
return t.max();
}
/**
* @brief Implementation of a cofactor calculation as a matrix expression
*
* @tparam M Type of matrix
*/
auto cofactors(glm::dmat4 &m)
{
glm::dmat4 result{};
const std::size_t ixs[4][3] = {
{ 1, 2, 3 },
{ 0, 2, 3 },
{ 0, 1, 3 },
{ 0, 1, 2 }
};
for (size_t i = 0; i < 4; ++i)
{
for (size_t j = 0; j < 4; ++j)
{
const std::size_t *ix = ixs[i];
const std::size_t *iy = ixs[j];
auto e =
m[ix[0]][iy[0]] * m[ix[1]][iy[1]] * m[ix[2]][iy[2]] +
m[ix[0]][iy[1]] * m[ix[1]][iy[2]] * m[ix[2]][iy[0]] +
m[ix[0]][iy[2]] * m[ix[1]][iy[0]] * m[ix[2]][iy[1]] -
m[ix[0]][iy[2]] * m[ix[1]][iy[1]] * m[ix[2]][iy[0]] -
m[ix[0]][iy[1]] * m[ix[1]][iy[0]] * m[ix[2]][iy[2]] -
m[ix[0]][iy[0]] * m[ix[1]][iy[2]] * m[ix[2]][iy[1]];
result[i][j] = (i + j) % 2 == 1 ? -e : e;
}
}
return result;
}
quaternion align_points(const std::vector<point> &pa, const std::vector<point> &pb)
{
// First calculate M, a 3x3 matrix containing the sums of products of the coordinates of A and B
matrix3x3<double> M;
glm::dmat3x3 M{};
for (uint32_t i = 0; i < pa.size(); ++i)
{
const point &a = pa[i];
const point &b = pb[i];
M(0, 0) += a.m_x * b.m_x;
M(0, 1) += a.m_x * b.m_y;
M(0, 2) += a.m_x * b.m_z;
M(1, 0) += a.m_y * b.m_x;
M(1, 1) += a.m_y * b.m_y;
M(1, 2) += a.m_y * b.m_z;
M(2, 0) += a.m_z * b.m_x;
M(2, 1) += a.m_z * b.m_y;
M(2, 2) += a.m_z * b.m_z;
M[0][0] += a.x * b.x;
M[0][1] += a.x * b.y;
M[0][2] += a.x * b.z;
M[1][0] += a.y * b.x;
M[1][1] += a.y * b.y;
M[1][2] += a.y * b.z;
M[2][0] += a.z * b.x;
M[2][1] += a.z * b.y;
M[2][2] += a.z * b.z;
}
// Now calculate N, a symmetric 4x4 matrix
symmetric_matrix4x4<double> N(4);
glm::dmat4x4 N;
N(0, 0) = M(0, 0) + M(1, 1) + M(2, 2);
N(0, 1) = M(1, 2) - M(2, 1);
N(0, 2) = M(2, 0) - M(0, 2);
N(0, 3) = M(0, 1) - M(1, 0);
N[0][0] = M[0][0] + M[1][1] + M[2][2];
N[0][1] = N[1][0] = M[1][2] - M[2][1];
N[0][2] = N[2][0] = M[2][0] - M[0][2];
N[0][3] = N[3][0] = M[0][1] - M[1][0];
N(1, 1) = M(0, 0) - M(1, 1) - M(2, 2);
N(1, 2) = M(0, 1) + M(1, 0);
N(1, 3) = M(0, 2) + M(2, 0);
N[1][1] = M[0][0] - M[1][1] - M[2][2];
N[1][2] = N[2][1] = M[0][1] + M[1][0];
N[1][3] = N[3][1] = M[0][2] + M[2][0];
N(2, 2) = -M(0, 0) + M(1, 1) - M(2, 2);
N(2, 3) = M(1, 2) + M(2, 1);
N[2][2] = -M[0][0] + M[1][1] - M[2][2];
N[2][3] = N[3][2] = M[1][2] + M[2][1];
N(3, 3) = -M(0, 0) - M(1, 1) + M(2, 2);
N[3][3] = -M[0][0] - M[1][1] + M[2][2];
// det(N - λI) = 0
// find the largest λ (λm)
@@ -251,41 +243,41 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> &
// and so this is a so-called depressed quartic
// solve it using Ferrari's algorithm
double C = -2 * (M(0, 0) * M(0, 0) + M(0, 1) * M(0, 1) + M(0, 2) * M(0, 2) +
M(1, 0) * M(1, 0) + M(1, 1) * M(1, 1) + M(1, 2) * M(1, 2) +
M(2, 0) * M(2, 0) + M(2, 1) * M(2, 1) + M(2, 2) * M(2, 2));
double C = -2 * (M[0][0] * M[0][0] + M[0][1] * M[0][1] + M[0][2] * M[0][2] +
M[1][0] * M[1][0] + M[1][1] * M[1][1] + M[1][2] * M[1][2] +
M[2][0] * M[2][0] + M[2][1] * M[2][1] + M[2][2] * M[2][2]);
double D = 8 * (M(0, 0) * M(1, 2) * M(2, 1) +
M(1, 1) * M(2, 0) * M(0, 2) +
M(2, 2) * M(0, 1) * M(1, 0)) -
8 * (M(0, 0) * M(1, 1) * M(2, 2) +
M(1, 2) * M(2, 0) * M(0, 1) +
M(2, 1) * M(1, 0) * M(0, 2));
double D = 8 * (M[0][0] * M[1][2] * M[2][1] +
M[1][1] * M[2][0] * M[0][2] +
M[2][2] * M[0][1] * M[1][0]) -
8 * (M[0][0] * M[1][1] * M[2][2] +
M[1][2] * M[2][0] * M[0][1] +
M[2][1] * M[1][0] * M[0][2]);
// E is the determinant of N:
double E =
(N(0, 0) * N(1, 1) - N(0, 1) * N(0, 1)) * (N(2, 2) * N(3, 3) - N(2, 3) * N(2, 3)) +
(N(0, 1) * N(0, 2) - N(0, 0) * N(2, 1)) * (N(2, 1) * N(3, 3) - N(2, 3) * N(1, 3)) +
(N(0, 0) * N(1, 3) - N(0, 1) * N(0, 3)) * (N(2, 1) * N(2, 3) - N(2, 2) * N(1, 3)) +
(N(0, 1) * N(2, 1) - N(1, 1) * N(0, 2)) * (N(0, 2) * N(3, 3) - N(2, 3) * N(0, 3)) +
(N(1, 1) * N(0, 3) - N(0, 1) * N(1, 3)) * (N(0, 2) * N(2, 3) - N(2, 2) * N(0, 3)) +
(N(0, 2) * N(1, 3) - N(2, 1) * N(0, 3)) * (N(0, 2) * N(1, 3) - N(2, 1) * N(0, 3));
(N[0][0] * N[1][1] - N[0][1] * N[0][1]) * (N[2][2] * N[3][3] - N[2][3] * N[2][3]) +
(N[0][1] * N[0][2] - N[0][0] * N[2][1]) * (N[2][1] * N[3][3] - N[2][3] * N[1][3]) +
(N[0][0] * N[1][3] - N[0][1] * N[0][3]) * (N[2][1] * N[2][3] - N[2][2] * N[1][3]) +
(N[0][1] * N[2][1] - N[1][1] * N[0][2]) * (N[0][2] * N[3][3] - N[2][3] * N[0][3]) +
(N[1][1] * N[0][3] - N[0][1] * N[1][3]) * (N[0][2] * N[2][3] - N[2][2] * N[0][3]) +
(N[0][2] * N[1][3] - N[2][1] * N[0][3]) * (N[0][2] * N[1][3] - N[2][1] * N[0][3]);
// solve quartic
double lambda = LargestDepressedQuarticSolution(C, D, E);
// calculate t = (N - λI)
matrix<double> t(N - identity_matrix(4) * lambda);
auto t = N - glm::dmat4x4(1.0) * lambda;
// calculate a matrix of cofactors for t
auto cf = matrix_cofactors(t);
auto cf = cofactors(t);
int maxR = 0;
double maxCF = std::abs(cf(0, 0));
double maxCF = std::abs(cf[0][0]);
for (int r = 1; r < 4; ++r)
{
auto cfr = std::abs(cf(r, 0));
auto cfr = std::abs(cf[r][0]);
if (maxCF < cfr)
{
maxCF = cfr;
@@ -294,10 +286,10 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> &
}
quaternion q(
static_cast<float>(cf(maxR, 0)),
static_cast<float>(cf(maxR, 1)),
static_cast<float>(cf(maxR, 2)),
static_cast<float>(cf(maxR, 3)));
static_cast<float>(cf[maxR][0]),
static_cast<float>(cf[maxR][1]),
static_cast<float>(cf[maxR][2]),
static_cast<float>(cf[maxR][3]));
q = normalize(q);
return q;
@@ -305,54 +297,29 @@ quaternion align_points(const std::vector<point> &pa, const std::vector<point> &
// --------------------------------------------------------------------
point nudge(point p, float offset)
std::tuple<point, float> smallest_sphere_around_2_points(std::array<point, 2> pts)
{
static const float kPI_f = static_cast<float>(kPI);
static std::random_device rd;
static std::mt19937_64 rng(rd());
std::uniform_real_distribution<float> randomAngle(0, 2 * kPI_f);
std::normal_distribution<float> randomOffset(0, offset);
float theta = randomAngle(rng);
float phi1 = randomAngle(rng) - kPI_f;
float phi2 = randomAngle(rng) - kPI_f;
quaternion q = spherical(1.0f, theta, phi1, phi2);
point r{ 0, 0, 1 };
r.rotate(q);
r *= randomOffset(rng);
return p + r;
return { (pts[0] + pts[1]) / 2.0f, distance(pts[0], pts[1]) / 2.0f };
}
// --------------------------------------------------------------------
std::tuple<point, float> smallest_sphere_around_2_points(std::array<cif::point, 2> pts)
{
return { (pts[0] + pts[1]) / 2, distance(pts[0], pts[1]) / 2 };
}
std::tuple<point, float> smallest_sphere_around_3_points(std::array<cif::point, 3> pts)
std::tuple<point, float> smallest_sphere_around_3_points(std::array<point, 3> pts)
{
// Find two bisectors
auto vz = cross_product(pts[1] - pts[0], pts[2] - pts[0]);
auto vz = cross(pts[1] - pts[0], pts[2] - pts[0]);
auto bs1 = cross_product(vz, pts[1] - pts[0]);
bs1.normalize();
auto bs1 = cross(vz, pts[1] - pts[0]);
bs1 = glm::normalize(bs1);
auto v1 = (pts[1] - pts[0]);
v1.normalize();
v1 = glm::normalize(v1);
auto s1 = pts[0] + (distance(pts[1], pts[0]) / 2) * v1;
auto bs2 = cross_product(vz, pts[2] - pts[0]);
bs2.normalize();
auto bs2 = cross(vz, pts[2] - pts[0]);
bs2 = glm::normalize(bs2);
auto v2 = (pts[2] - pts[0]);
v2.normalize();
v2 = glm::normalize(v2);
auto s2 = pts[0] + (distance(pts[2], pts[0]) / 2) * v2;
@@ -373,7 +340,7 @@ std::tuple<point, float> smallest_sphere_around_3_points(std::array<cif::point,
return smallest_sphere_around_2_points({ pts[1], pts[2] });
}
std::tuple<point, float> smallest_sphere_around_4_points(std::array<cif::point, 4> pts)
std::tuple<point, float> smallest_sphere_around_4_points(std::array<point, 4> pts)
{
auto t0 = -norm_squared(pts[0]);
auto t1 = -norm_squared(pts[1]);
@@ -381,46 +348,46 @@ std::tuple<point, float> smallest_sphere_around_4_points(std::array<cif::point,
auto t3 = -norm_squared(pts[3]);
// clang-format off
matrix4x4<float> Tm({
pts[0].m_x, pts[0].m_y, pts[0].m_z, 1,
pts[1].m_x, pts[1].m_y, pts[1].m_z, 1,
pts[2].m_x, pts[2].m_y, pts[2].m_z, 1,
pts[3].m_x, pts[3].m_y, pts[3].m_z, 1
glm::mat4x4 Tm({
pts[0].x, pts[0].y, pts[0].z, 1,
pts[1].x, pts[1].y, pts[1].z, 1,
pts[2].x, pts[2].y, pts[2].z, 1,
pts[3].x, pts[3].y, pts[3].z, 1
});
auto T = determinant(Tm);
if (T != 0)
{
matrix4x4<float> Dm({
t0, pts[0].m_y, pts[0].m_z, 1,
t1, pts[1].m_y, pts[1].m_z, 1,
t2, pts[2].m_y, pts[2].m_z, 1,
t3, pts[3].m_y, pts[3].m_z, 1
glm::mat4x4 Dm({
t0, pts[0].y, pts[0].z, 1,
t1, pts[1].y, pts[1].z, 1,
t2, pts[2].y, pts[2].z, 1,
t3, pts[3].y, pts[3].z, 1
});
auto D = determinant(Dm) / T;
matrix4x4<float> Em({
pts[0].m_x, t0, pts[0].m_z, 1,
pts[1].m_x, t1, pts[1].m_z, 1,
pts[2].m_x, t2, pts[2].m_z, 1,
pts[3].m_x, t3, pts[3].m_z, 1
glm::mat4x4 Em({
pts[0].x, t0, pts[0].z, 1,
pts[1].x, t1, pts[1].z, 1,
pts[2].x, t2, pts[2].z, 1,
pts[3].x, t3, pts[3].z, 1
});
auto E = determinant(Em) / T;
matrix4x4<float> Fm({
pts[0].m_x, pts[0].m_y, t0, 1,
pts[1].m_x, pts[1].m_y, t1, 1,
pts[2].m_x, pts[2].m_y, t2, 1,
pts[3].m_x, pts[3].m_y, t3, 1
glm::mat4x4 Fm({
pts[0].x, pts[0].y, t0, 1,
pts[1].x, pts[1].y, t1, 1,
pts[2].x, pts[2].y, t2, 1,
pts[3].x, pts[3].y, t3, 1
});
auto F = determinant(Fm) / T;
matrix4x4<float> Gm({
pts[0].m_x, pts[0].m_y, pts[0].m_z, t0,
pts[1].m_x, pts[1].m_y, pts[1].m_z, t1,
pts[2].m_x, pts[2].m_y, pts[2].m_z, t2,
pts[3].m_x, pts[3].m_y, pts[3].m_z, t3
glm::mat4x4 Gm({
pts[0].x, pts[0].y, pts[0].z, t0,
pts[1].x, pts[1].y, pts[1].z, t1,
pts[2].x, pts[2].y, pts[2].z, t2,
pts[3].x, pts[3].y, pts[3].z, t3
});
auto G = determinant(Gm) / T;
@@ -500,19 +467,19 @@ bool point_in_circle(point p, std::vector<point> c)
case 2:
{
auto [center, radius] = smallest_sphere_around_2_points({ c[0], c[1] });
return cif::distance_squared(p, center) <= radius * radius;
return distance_squared(p, center) <= radius * radius;
}
case 3:
{
auto [center, radius] = smallest_sphere_around_3_points({ c[0], c[1], c[2] });
return cif::distance_squared(p, center) <= radius * radius;
return distance_squared(p, center) <= radius * radius;
}
case 4:
{
auto [center, radius] = smallest_sphere_around_4_points({ c[0], c[1], c[2], c[3] });
return cif::distance_squared(p, center) <= radius * radius;
return distance_squared(p, center) <= radius * radius;
}
default:
@@ -541,16 +508,15 @@ std::tuple<point, float> smallest_sphere_around_points(std::vector<point> pts)
size_t i = 0;
while (i < pts.size())
{
if (std::find(cix.begin(), cix.end(), i) != cix.end() or
if (std::ranges::find(cix, i) != cix.end() or
point_in_circle(pts[i], cirle_points()))
{
++i;
}
else
{
cix.erase(std::remove_if(cix.begin(), cix.end(), [i](size_t j)
{ return j < i; }),
cix.end());
std::erase_if(cix, [i](size_t j)
{ return j < i; });
cix.push_back(i);
if (cix.size() < 4)
i = 0;
@@ -571,7 +537,8 @@ std::tuple<point, float> smallest_sphere_around_points(std::vector<point> pts)
return smallest_sphere_around_4_points({ pts[cix[0]], pts[cix[1]], pts[cix[2]], pts[cix[3]] });
default:
assert(false);
throw std::runtime_error("Error finding smallest sphere");
throw std::runtime_error(std::format("Error finding smallest sphere (cix size: {}, pts size: {})",
cix.size(), pts.size()));
}
}

View File

@@ -24,51 +24,38 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/row.hpp"
#include "cif++/cif++.hpp"
#include "cif++/category.hpp"
#include "cif++/item.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
namespace cif
{
// --------------------------------------------------------------------
item_value s_null_item;
// item_value &row_handle::operator[](uint16_t item_ix)
// {
// return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix);
// }
item_value &row_handle::operator[](uint16_t item_ix)
{
return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix);
}
// const item_value &row_handle::operator[](uint16_t item_ix) const
// {
// return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix);
// }
const item_value &row_handle::operator[](uint16_t item_ix) const
{
return empty() or item_ix >= m_row->size() ? s_null_item : m_row->operator[](item_ix);
}
// item_value &row_handle::operator[](std::string_view item_name)
// {
// return operator[](get_item_ix(item_name));
// }
item_value &row_handle::operator[](std::string_view item_name)
{
auto ix = add_item(item_name);
if (ix >= size())
m_row->resize(ix + 1);
return m_row->operator[](ix);
}
const item_value &row_handle::operator[](std::string_view item_name) const
{
return operator[](get_item_ix(item_name));
}
// --------------------------------------------------------------------
void row_handle::assign(uint16_t item, item_value value, bool updateLinked, bool validate)
{
if (not m_category)
throw std::runtime_error("uninitialized row");
m_category->update_value(m_row, item, std::move(value), updateLinked, validate);
}
// const item_value &row_handle::operator[](std::string_view item_name) const
// {
// return operator[](get_item_ix(item_name));
// }
uint16_t row_handle::get_item_ix(std::string_view name) const
{
@@ -86,6 +73,32 @@ std::string_view row_handle::get_item_name(uint16_t ix) const
return m_category->get_item_name(ix);
}
uint16_t const_row_handle::get_item_ix(std::string_view name) const
{
if (not m_category)
throw std::runtime_error("uninitialized row");
return m_category->get_item_ix(name);
}
std::string_view const_row_handle::get_item_name(uint16_t ix) const
{
if (not m_category)
throw std::runtime_error("uninitialized row");
return m_category->get_item_name(ix);
}
// --------------------------------------------------------------------
void row_handle::assign(uint16_t item, item_value value, bool updateLinked, bool validate)
{
if (not m_category)
throw std::runtime_error("uninitialized row");
m_category->update_value(m_row, item, std::move(value), updateLinked, validate);
}
uint16_t row_handle::add_item(std::string_view name)
{
if (not m_category)
@@ -94,31 +107,21 @@ uint16_t row_handle::add_item(std::string_view name)
return m_category->add_item(name);
}
void row_handle::swap(uint16_t item, row_handle &b)
{
if (not m_category)
throw std::runtime_error("uninitialized row");
m_category->swap_item(item, *this, b);
}
// --------------------------------------------------------------------
row_initializer::row_initializer(row_handle rh)
row_initializer::row_initializer(const_row_handle rh)
{
if (not rh.m_category)
throw std::runtime_error("uninitialized row");
assert(rh.m_row);
row *r = rh.get_row();
auto r = rh.get_row();
auto &cat = *rh.m_category;
for (uint16_t ix = 0; ix < r->size(); ++ix)
for (uint16_t ix = 0; std::cmp_less(ix, r->size()); ++ix)
{
auto &i = r->operator[](ix);
if (not i)
continue;
emplace_back(cat.get_item_name(ix), i);
}
}
@@ -144,4 +147,4 @@ void row_initializer::set_value_if_empty(std::string name, item_value value)
emplace_back(std::move(name), std::move(value));
}
} // namespace cif
} // namespace cif

View File

@@ -24,17 +24,25 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/symmetry.hpp"
#include "cif++/datablock.hpp"
#include "cif++/point.hpp"
#include <stdexcept>
#include "cif++/cif++.hpp"
#include "symop_table_data.hpp"
#include <array>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <vector>
#if defined(_MSC_VER)
#pragma warning (disable : 5054) // warning C5054: operator '&': deprecated between enumerations of different types
#pragma warning (disable : 4127) // conditional expression is constant
# pragma warning(disable : 5054) // warning C5054: operator '&': deprecated between enumerations of different types
# pragma warning(disable : 4127) // conditional expression is constant
#endif
#include <Eigen/Eigen>
@@ -67,29 +75,30 @@ cell::cell(const datablock &db)
void cell::init()
{
auto alpha = (m_alpha * kPI) / 180;
auto beta = (m_beta * kPI) / 180;
auto gamma = (m_gamma * kPI) / 180;
auto alpha = (m_alpha * std::numbers::pi_v<float>) / 180;
auto beta = (m_beta * std::numbers::pi_v<float>) / 180;
auto gamma = (m_gamma * std::numbers::pi_v<float>) / 180;
auto alpha_star = std::acos((std::cos(gamma) * std::cos(beta) - std::cos(alpha)) / (std::sin(beta) * std::sin(gamma)));
m_orthogonal = identity_matrix(3);
m_orthogonal = glm::mat3(1.0f);
m_orthogonal(0, 0) = static_cast<float>(m_a);
m_orthogonal(0, 1) = static_cast<float>(m_b * std::cos(gamma));
m_orthogonal(0, 2) = static_cast<float>(m_c * std::cos(beta));
m_orthogonal(1, 1) = static_cast<float>(m_b * std::sin(gamma));
m_orthogonal(1, 2) = static_cast<float>(-m_c * std::sin(beta) * std::cos(alpha_star));
m_orthogonal(2, 2) = static_cast<float>(m_c * std::sin(beta) * std::sin(alpha_star));
// WARNING: glm matrices are column major, by default
m_orthogonal[0][0] = m_a;
m_orthogonal[1][0] = m_b * std::cos(gamma);
m_orthogonal[2][0] = m_c * std::cos(beta);
m_orthogonal[1][1] = m_b * std::sin(gamma);
m_orthogonal[2][1] = m_c * std::sin(beta) * std::cos(alpha_star);
m_orthogonal[2][2] = m_c * std::sin(beta) * std::sin(alpha_star);
m_fractional = inverse(m_orthogonal);
}
float cell::get_volume() const
{
auto alpha = (m_alpha * kPI) / 180;
auto beta = (m_beta * kPI) / 180;
auto gamma = (m_gamma * kPI) / 180;
auto alpha = (m_alpha * std::numbers::pi_v<float>) / 180;
auto beta = (m_beta * std::numbers::pi_v<float>) / 180;
auto gamma = (m_gamma * std::numbers::pi_v<float>) / 180;
auto cos_alpha = std::cos(alpha);
auto cos_beta = std::cos(beta);
@@ -116,7 +125,7 @@ sym_op::sym_op(std::string_view s)
m_tb = r.ptr[2] - '0';
m_tc = r.ptr[3] - '0';
if ((bool)r.ec or rnri > 192 or r.ptr[0] != '_' or m_ta > 9 or m_tb > 9 or m_tc > 9)
if (r.ec != std::errc{} or rnri > 192 or r.ptr[0] != '_' or m_ta > 9 or m_tb > 9 or m_tc > 9)
throw std::invalid_argument("Could not convert string into sym_op");
}
@@ -124,13 +133,13 @@ std::string sym_op::string() const
{
char b[9];
auto r = std::to_chars(b, b + sizeof(b), m_nr);
if ((bool)r.ec or r.ptr > b + 4)
if (r.ec != std::errc{} or r.ptr > b + 4)
throw std::runtime_error("Could not write out symmetry operation to string");
*r.ptr++ = '_';
*r.ptr++ = '0' + m_ta;
*r.ptr++ = '0' + m_tb;
*r.ptr++ = '0' + m_tc;
*r.ptr++ = static_cast<char>('0' + m_ta);
*r.ptr++ = static_cast<char>('0' + m_tb);
*r.ptr++ = static_cast<char>('0' + m_tc);
*r.ptr = 0;
return { b, static_cast<std::size_t>(r.ptr - b) };
@@ -142,24 +151,25 @@ transformation::transformation(const symop_data &data)
{
const auto &d = data.data();
m_rotation(0, 0) = static_cast<float>(d[0]);
m_rotation(0, 1) = static_cast<float>(d[1]);
m_rotation(0, 2) = static_cast<float>(d[2]);
m_rotation(1, 0) = static_cast<float>(d[3]);
m_rotation(1, 1) = static_cast<float>(d[4]);
m_rotation(1, 2) = static_cast<float>(d[5]);
m_rotation(2, 0) = static_cast<float>(d[6]);
m_rotation(2, 1) = static_cast<float>(d[7]);
m_rotation(2, 2) = static_cast<float>(d[8]);
// WARNING: glm::mat is column major
m_rotation[0][0] = static_cast<float>(d[0]);
m_rotation[1][0] = static_cast<float>(d[1]);
m_rotation[2][0] = static_cast<float>(d[2]);
m_rotation[0][1] = static_cast<float>(d[3]);
m_rotation[1][1] = static_cast<float>(d[4]);
m_rotation[2][1] = static_cast<float>(d[5]);
m_rotation[0][2] = static_cast<float>(d[6]);
m_rotation[1][2] = static_cast<float>(d[7]);
m_rotation[2][2] = static_cast<float>(d[8]);
try_create_quaternion();
m_translation.m_x = static_cast<float>(d[9] == 0 ? 0 : 1.0 * d[9] / d[10]);
m_translation.m_y = static_cast<float>(d[11] == 0 ? 0 : 1.0 * d[11] / d[12]);
m_translation.m_z = static_cast<float>(d[13] == 0 ? 0 : 1.0 * d[13] / d[14]);
m_translation.x = static_cast<float>(d[9] == 0 ? 0 : 1.0 * d[9] / d[10]);
m_translation.y = static_cast<float>(d[11] == 0 ? 0 : 1.0 * d[11] / d[12]);
m_translation.z = static_cast<float>(d[13] == 0 ? 0 : 1.0 * d[13] / d[14]);
}
transformation::transformation(const matrix3x3<float> &r, const cif::point &t)
transformation::transformation(const glm::mat3 &r, const cif::point &t)
: m_rotation(r)
, m_translation(t)
{
@@ -170,9 +180,9 @@ void transformation::try_create_quaternion()
{
Eigen::Matrix3f rot;
rot << m_rotation(0, 0), m_rotation(0, 1), m_rotation(0, 2),
m_rotation(1, 0), m_rotation(1, 1), m_rotation(1, 2),
m_rotation(2, 0), m_rotation(2, 1), m_rotation(2, 2);
rot << m_rotation[0][0], m_rotation[0][1], m_rotation[0][2],
m_rotation[1][0], m_rotation[1][1], m_rotation[1][2],
m_rotation[2][0], m_rotation[2][1], m_rotation[2][2];
if (rot * rot.transpose() == Eigen::Matrix3f::Identity() and rot.determinant() == 1)
{
@@ -187,7 +197,7 @@ transformation operator*(const transformation &lhs, const transformation &rhs)
auto t = lhs.m_rotation * rhs.m_translation;
t = t + lhs.m_translation;
return transformation(r, t);
return { r, t };
}
transformation inverse(const transformation &t)
@@ -233,20 +243,20 @@ point offsetToOrigin(const cell &c, const point &p)
{
point d{};
while (p.m_x + d.m_x < -(c.get_a()))
d.m_x += c.get_a();
while (p.m_x + d.m_x > (c.get_a()))
d.m_x -= c.get_a();
while (p.x + d.x < -(c.get_a()))
d.x += c.get_a();
while (p.x + d.x > (c.get_a()))
d.x -= c.get_a();
while (p.m_y + d.m_y < -(c.get_b()))
d.m_y += c.get_b();
while (p.m_y + d.m_y > (c.get_b()))
d.m_y -= c.get_b();
while (p.y + d.y < -(c.get_b()))
d.y += c.get_b();
while (p.y + d.y > (c.get_b()))
d.y -= c.get_b();
while (p.m_z + d.m_z < -(c.get_c()))
d.m_z += c.get_c();
while (p.m_z + d.m_z > (c.get_c()))
d.m_z -= c.get_c();
while (p.z + d.z < -(c.get_c()))
d.z += c.get_c();
while (p.z + d.z > (c.get_c()))
d.z -= c.get_c();
return d;
};
@@ -255,20 +265,20 @@ point offsetToOriginFractional(const point &p)
{
point d{};
while (p.m_x + d.m_x < -0.5f)
d.m_x += 1;
while (p.m_x + d.m_x > 0.5f)
d.m_x -= 1;
while (p.x + d.x < -0.5f)
d.x += 1;
while (p.x + d.x > 0.5f)
d.x -= 1;
while (p.m_y + d.m_y < -0.5f)
d.m_y += 1;
while (p.m_y + d.m_y > 0.5f)
d.m_y -= 1;
while (p.y + d.y < -0.5f)
d.y += 1;
while (p.y + d.y > 0.5f)
d.y -= 1;
while (p.m_z + d.m_z < -0.5f)
d.m_z += 1;
while (p.m_z + d.m_z > 0.5f)
d.m_z -= 1;
while (p.z + d.z < -0.5f)
d.z += 1;
while (p.z + d.z > 0.5f)
d.z -= 1;
return d;
};
@@ -280,16 +290,11 @@ point spacegroup::operator()(const point &pt, const cell &c, sym_op symop) const
transformation t = at(symop.m_nr - 1);
t.m_translation.m_x += symop.m_ta - 5;
t.m_translation.m_y += symop.m_tb - 5;
t.m_translation.m_z += symop.m_tc - 5;
t.m_translation.x += symop.m_ta - 5;
t.m_translation.y += symop.m_tb - 5;
t.m_translation.z += symop.m_tc - 5;
auto fpt = fractional(pt, c);
auto o = offsetToOriginFractional(fpt);
auto spt = t(fpt + o) - o;
return orthogonal(spt, c);
return orthogonal(t(fractional(pt, c)), c);
}
point spacegroup::inverse(const point &pt, const cell &c, sym_op symop) const
@@ -299,17 +304,12 @@ point spacegroup::inverse(const point &pt, const cell &c, sym_op symop) const
transformation t = at(symop.m_nr - 1);
t.m_translation.m_x += symop.m_ta - 5;
t.m_translation.m_y += symop.m_tb - 5;
t.m_translation.m_z += symop.m_tc - 5;
auto fpt = fractional(pt, c);
auto o = offsetToOriginFractional(fpt);
t.m_translation.x += symop.m_ta - 5;
t.m_translation.y += symop.m_tb - 5;
t.m_translation.z += symop.m_tc - 5;
auto it = cif::inverse(t);
auto spt = it(fpt + o) - o;
return orthogonal(spt, c);
return orthogonal(it(fractional(pt, c)), c);
}
// --------------------------------------------------------------------
@@ -345,9 +345,8 @@ int get_space_group_number(std::string_view spacegroup)
// not found, see if we can find a match based on xHM name
if (result == 0)
{
for (std::size_t i = 0; i < kNrOfSpaceGroups; ++i)
for (const auto &sp : kSpaceGroups)
{
auto &sp = kSpaceGroups[i];
if (sp.xHM == spacegroup)
{
result = sp.nr;
@@ -441,7 +440,7 @@ std::tuple<float, point, sym_op> crystal::closest_symmetry_copy(point a, point b
if (m_cell.get_a() == 0 or m_cell.get_b() == 0 or m_cell.get_c() == 0)
throw std::runtime_error("Invalid cell, contains a dimension that is zero");
point result_fsb;
point result_fsb{};
float result_d = std::numeric_limits<float>::max();
sym_op result_s;
@@ -462,39 +461,39 @@ std::tuple<float, point, sym_op> crystal::closest_symmetry_copy(point a, point b
auto fsb = t(fb);
while (fsb.m_x - 0.5f > fa.m_x)
while (fsb.x - 0.5f > fa.x)
{
fsb.m_x -= 1;
fsb.x -= 1;
s.m_ta -= 1;
}
while (fsb.m_x + 0.5f < fa.m_x)
while (fsb.x + 0.5f < fa.x)
{
fsb.m_x += 1;
fsb.x += 1;
s.m_ta += 1;
}
while (fsb.m_y - 0.5f > fa.m_y)
while (fsb.y - 0.5f > fa.y)
{
fsb.m_y -= 1;
fsb.y -= 1;
s.m_tb -= 1;
}
while (fsb.m_y + 0.5f < fa.m_y)
while (fsb.y + 0.5f < fa.y)
{
fsb.m_y += 1;
fsb.y += 1;
s.m_tb += 1;
}
while (fsb.m_z - 0.5f > fa.m_z)
while (fsb.z - 0.5f > fa.z)
{
fsb.m_z -= 1;
fsb.z -= 1;
s.m_tc -= 1;
}
while (fsb.m_z + 0.5f < fa.m_z)
while (fsb.z + 0.5f < fa.z)
{
fsb.m_z += 1;
fsb.z += 1;
s.m_tc += 1;
}

View File

@@ -280,7 +280,7 @@ int main(int argc, char* const argv[])
if (std::isdigit(line[0])) // start of new spacegroup
{
auto r = std::from_chars(line.data(), line.data() + line.length(), sgnr);
if ((bool)r.ec)
if (r.ec != std::errc{})
throw std::runtime_error("Error parsing symop.lib file");
rnr = 1;
continue;

View File

@@ -28,10 +28,11 @@
#include <algorithm>
#include <cassert>
#include <charconv>
#include <cctype>
#include <stdexcept>
#if __has_include("fast_float/fast_float.h")
#include "fast_float/fast_float.h"
#if defined(USE_FAST_FLOAT)
# include "fast_float/fast_float.h"
#endif
namespace cif
@@ -61,31 +62,29 @@ const uint8_t kCharToLowerMap[256] = {
// --------------------------------------------------------------------
bool iequals(std::string_view a, std::string_view b)
bool iequals(std::string_view a, std::string_view b) noexcept
{
bool result = a.length() == b.length();
for (auto ai = a.begin(), bi = b.begin(); result and ai != a.end(); ++ai, ++bi)
result = kCharToLowerMap[uint8_t(*ai)] == kCharToLowerMap[uint8_t(*bi)];
// result = tolower(*ai) == tolower(*bi);
result = kCharToLowerMap[static_cast<uint8_t>(*ai)] == kCharToLowerMap[static_cast<uint8_t>(*bi)];
return result;
}
bool iequals(const char *a, const char *b)
bool iequals(const char *a, const char *b) noexcept
{
bool result = true;
for (; result and *a and *b; ++a, ++b)
result = kCharToLowerMap[uint8_t(*a)] == kCharToLowerMap[uint8_t(*b)];
result = kCharToLowerMap[static_cast<uint8_t>(*a)] == kCharToLowerMap[static_cast<uint8_t>(*b)];
return result and *a == *b;
}
int icompare(std::string_view a, std::string_view b)
int icompare(std::string_view a, std::string_view b) noexcept
{
int d = 0;
auto ai = a.begin(), bi = b.begin();
for (; d == 0 and ai != a.end() and bi != b.end(); ++ai, ++bi)
d = (int)kCharToLowerMap[uint8_t(*ai)] - (int)kCharToLowerMap[uint8_t(*bi)];
d = static_cast<int>(kCharToLowerMap[static_cast<uint8_t>(*ai)]) - static_cast<int>(kCharToLowerMap[static_cast<uint8_t>(*bi)]);
if (d == 0)
{
@@ -98,12 +97,12 @@ int icompare(std::string_view a, std::string_view b)
return d;
}
int icompare(const char *a, const char *b)
int icompare(const char *a, const char *b) noexcept
{
int d = 0;
for (; d == 0 and *a != 0 and *b != 0; ++a, ++b)
d = (int)kCharToLowerMap[uint8_t(*a)] - (int)kCharToLowerMap[uint8_t(*b)];
d = static_cast<int>(kCharToLowerMap[static_cast<uint8_t>(*a)]) - static_cast<int>(kCharToLowerMap[static_cast<uint8_t>(*b)]);
if (d == 0)
{
@@ -199,7 +198,7 @@ void trim_left(std::string &s)
while (in != s.end() and std::isspace(*in))
++in;
if (in == s.end())
s.clear();
else if (in != out)
@@ -219,7 +218,7 @@ void trim(std::string &s)
while (in != end and std::isspace(*in))
++in;
if (in == end)
s.clear();
else if (in != out)
@@ -268,7 +267,7 @@ std::string cif_id_for_number(int number)
number = (number - r) / 26 - 1;
} while (number >= 0);
std::reverse(result.begin(), result.end());
std::ranges::reverse(result);
assert(not result.empty());
@@ -390,7 +389,7 @@ std::string::const_iterator nextLineBreak(std::string::const_iterator text, std:
/* JT */ { DBK, PBK, PBK, IBK, IBK, IBK, PBK, PBK, PBK, DBK, IBK, DBK, DBK, DBK, IBK, IBK, IBK, DBK, DBK, PBK, CIB, PBK, DBK, DBK, DBK, DBK, IBK },
};
uint8_t ch = static_cast<uint8_t>(*text);
auto ch = static_cast<uint8_t>(*text);
LineBreakClass cls;
@@ -494,7 +493,7 @@ std::vector<std::string> wrapLine(const std::string &text, std::size_t width)
j = i;
}
reverse(result.begin(), result.end());
std::ranges::reverse(result);
return result;
}
@@ -506,7 +505,7 @@ std::vector<std::string> word_wrap(const std::string &text, std::size_t width)
{
if (p.empty())
{
result.push_back("");
result.emplace_back("");
continue;
}
@@ -517,7 +516,7 @@ std::vector<std::string> word_wrap(const std::string &text, std::size_t width)
return result;
}
#if __has_include("fast_float/fast_float.h")
#if defined(USE_FAST_FLOAT)
template <typename T>
std::from_chars_result ff_charconv<T, typename std::enable_if_t<std::is_floating_point_v<T>>>::from_chars(const char *a, const char *b, T &v)
@@ -530,19 +529,19 @@ template struct ff_charconv<float>;
template struct ff_charconv<double>;
// template struct ff_charconv<long double>;
#ifdef __STDCPP_FLOAT64_T__
# ifdef __STDCPP_FLOAT64_T__
template struct ff_charconv<std::float64_t>;
#endif
#ifdef __STDCPP_FLOAT32_T__
# endif
# ifdef __STDCPP_FLOAT32_T__
template struct ff_charconv<std::float32_t>;
#endif
#ifdef __STDCPP_FLOAT16_T__
# endif
# ifdef __STDCPP_FLOAT16_T__
template struct ff_charconv<std::float16_t>;
#endif
#ifdef __STDCPP_BFLOAT16_T__
# endif
# ifdef __STDCPP_BFLOAT16_T__
template struct ff_charconv<std::bfloat16_t>;
#endif
# endif
#endif
} // namespace cif
} // namespace cif

View File

@@ -30,23 +30,31 @@
#include <atomic>
#include <cassert>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstring>
#include <cstddef>
#include <cstdlib>
#include <deque>
#include <exception>
#include <format>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>
#include <thread>
#include <tuple>
#include <utility>
#if __cpp_lib_jthread >= 201911L
#include <stop_token>
# include <stop_token>
#endif
namespace fs = std::filesystem;
@@ -83,9 +91,17 @@ namespace cif
uint32_t get_terminal_width()
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
return ::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
? csbi.srWindow.Right - csbi.srWindow.Left + 1
: 80;
if (::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
return 80;
}
std::tuple<uint32_t, uint32_t> get_terminal_width_and_height()
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
return { csbi.srWindow.Right - csbi.srWindow.Left + 1, csbi.srWindow.Bottom - csbi.srWindow.Top + 1 };
return { 80, 24 };
}
void write_to_console(const std::string &s)
@@ -112,21 +128,32 @@ void write_to_console(const std::string &s)
#else
# include <limits.h>
# include <climits>
# include <sys/ioctl.h>
# include <termios.h>
uint32_t get_terminal_width()
{
uint32_t result = 80;
uint32_t width = 80;
if (isatty(STDOUT_FILENO))
{
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
result = w.ws_col;
width = w.ws_col;
}
return result;
return width;
}
std::tuple<uint32_t, uint32_t> get_terminal_width_and_height()
{
if (isatty(STDOUT_FILENO))
{
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
return { w.ws_col, w.ws_row };
}
return { 80, 24 };
}
inline void write_to_console(const std::string &s)
@@ -148,7 +175,7 @@ struct progress_bar_impl
{
}
virtual ~progress_bar_impl() {}
virtual ~progress_bar_impl() = default;
virtual void consumed(uint64_t n);
virtual void progress(uint64_t p);
@@ -181,9 +208,9 @@ void progress_bar_impl::message(const std::string &msg)
void progress_bar_impl::print_done()
{
std::chrono::duration<double> elapsed = std::chrono::system_clock::now() - m_start;
std::string days, hours, minutes, seconds;
std::string days, hours, minutes;
uint64_t s = static_cast<uint64_t>(std::trunc(elapsed.count()));
auto s = static_cast<int>(std::rint(elapsed.count()));
if (s > 24 * 60 * 60)
{
days = std::format("{:d}d ", s / (24 * 60 * 60));
@@ -202,7 +229,7 @@ void progress_bar_impl::print_done()
s %= 60;
}
std::string msg = std::format("{} done in {}{}{}{:.1f}s", m_action, days, hours, minutes, s + 1e-6 * (elapsed.count() - s));
std::string msg = std::format("{} done in {}{}{}{:.1f}s", m_action, days, hours, minutes, s + 1e-6 * static_cast<double>(elapsed.count() - s));
uint32_t width = get_terminal_width();
@@ -221,10 +248,17 @@ struct simple_progress_bar_impl : public progress_bar_impl
{
}
~simple_progress_bar_impl()
~simple_progress_bar_impl() override
{
if (m_printed_any)
print_done();
try
{
if (m_printed_any)
print_done();
}
catch (const std::exception &ex)
{
std::cerr << "error finishing progress bar: " << ex.what() << '\n';
}
}
void consumed(uint64_t n) override
@@ -282,7 +316,7 @@ struct fancy_progress_bar_impl : public progress_bar_impl
{
}
~fancy_progress_bar_impl();
~fancy_progress_bar_impl() override;
#if __cpp_lib_jthread >= 201911L
void run(std::stop_token stoken);
@@ -295,12 +329,13 @@ struct fancy_progress_bar_impl : public progress_bar_impl
void message(const std::string &msg) override;
void print_progress();
void print_done() override;
std::mutex m_mutex;
std::condition_variable m_cv;
float m_progress;
uint32_t m_width, m_bar_width;
uint32_t m_width, m_bar_width, m_height;
uint32_t m_steps, m_last_steps = 0;
uint64_t m_last_consumed = 0;
#if __cpp_lib_jthread >= 201911L
@@ -372,7 +407,7 @@ void fancy_progress_bar_impl::run()
m_last_consumed = m_consumed;
// See if we need to do work
m_width = get_terminal_width();
std::tie(m_width, m_height) = get_terminal_width_and_height();
m_progress = static_cast<float>(m_consumed) / m_max_value;
m_bar_width = 7 * m_width / 10; // 70% of the width of the terminal
m_steps = static_cast<uint32_t>(std::ceil(m_progress * m_bar_width * kBlockCount));
@@ -382,23 +417,23 @@ void fancy_progress_bar_impl::run()
m_last_steps = m_steps;
// auto [w, h] = get_terminal_width_and_height();
if (not printedAny)
write_to_console("\x1b[?25l");
std::cout << std::format("\n\0337\033[{};{}r\0338\033[1A", 0, m_height - 1)
<< std::flush;
print_progress();
printedAny = true;
}
}
catch (...)
catch (const std::exception &ex)
{
std::cerr << "error finishing progress bar: " << ex.what() << '\n';
}
if (printedAny)
{
write_to_console("\r\x1b[?25h");
print_done();
}
}
void fancy_progress_bar_impl::consumed(uint64_t n)
@@ -434,7 +469,7 @@ void fancy_progress_bar_impl::print_progress()
}
std::string bar;
bar.reserve(m_bar_width * 4);
bar.reserve(m_bar_width * 4UL);
for (uint32_t i = 0; i < m_bar_width; ++i)
{
@@ -452,10 +487,10 @@ void fancy_progress_bar_impl::print_progress()
uint8_t r, g, b;
} fg{ 0, 3, 5 }, bg{ 0, 1, 2 };
auto esc_1 = std::format("\x1b[38;5;{}m\x1b[48;5;{}m",
auto esc_1 = std::format("\033[38;5;{}m\033[48;5;{}m",
16 + (fg.r * 36) + (fg.g * 6) + fg.b,
16 + (bg.r * 36) + (bg.g * 6) + bg.b);
std::string esc_2("\x1b[0m");
std::string esc_2("\033[0m");
bar = esc_1 + bar + esc_2;
@@ -463,14 +498,27 @@ void fancy_progress_bar_impl::print_progress()
? m_message
: m_message.substr(0, msg_width - 3) + "...";
write_to_console(std::format("{:{}} {} {:3d}%\r", msg, msg_width, bar,
static_cast<int>(std::ceil(m_progress * 100))));
std::cout << std::format("\0337\033[?25l\033[{};{}f{:{}} {} {:3d}%\033[?25h\0338", m_height, 1,
msg, msg_width,
bar,
static_cast<int>(std::ceil(m_progress * 100)))
<< std::flush;
}
void fancy_progress_bar_impl::print_done()
{
// wipe out progress bar first
std::tie(m_width, m_height) = get_terminal_width_and_height();
std::cout << std::format("\0337\033[{};{}H{}\033[{};{}r\0338", m_height, 0,
std::string(m_width, ' '), 0, m_height)
<< std::flush;
;
progress_bar_impl::print_done();
}
// --------------------------------------------------------------------
progress_bar::progress_bar(int64_t max_value, const std::string &message)
: m_impl(nullptr)
{
if (VERBOSE >= 0)
{
@@ -582,14 +630,14 @@ class rsrc_data
return s_instance;
}
const rsrc_imp *index() const { return m_index; }
[[nodiscard]] const rsrc_imp *index() const { return m_index; }
const char *data(unsigned int offset) const
[[nodiscard]] const char *data(unsigned int offset) const
{
return m_data + offset;
}
const char *name(unsigned int offset) const
[[nodiscard]] const char *name(unsigned int offset) const
{
return m_name + offset;
}
@@ -624,26 +672,18 @@ class rsrc
{
}
rsrc(const rsrc &other)
: m_impl(other.m_impl)
{
}
rsrc &operator=(const rsrc &other)
{
m_impl = other.m_impl;
return *this;
}
rsrc(const rsrc &other) = default;
rsrc &operator=(const rsrc &other) = default;
rsrc(std::filesystem::path path);
std::string name() const { return rsrc_data::instance().name(m_impl->m_name); }
[[nodiscard]] std::string name() const { return rsrc_data::instance().name(m_impl->m_name); }
const char *data() const { return rsrc_data::instance().data(m_impl->m_data); }
[[nodiscard]] const char *data() const { return rsrc_data::instance().data(m_impl->m_data); }
unsigned long size() const { return m_impl->m_size; }
[[nodiscard]] unsigned long size() const { return m_impl->m_size; }
explicit operator bool() const { return m_impl != NULL and m_impl->m_size > 0; }
explicit operator bool() const { return m_impl != nullptr and m_impl->m_size > 0; }
template <typename RSRC>
class iterator_t
@@ -660,16 +700,8 @@ class rsrc
{
}
iterator_t(const iterator_t &i)
: m_cur(i.m_cur)
{
}
iterator_t &operator=(const iterator_t &i)
{
m_cur = i.m_cur;
return *this;
}
iterator_t(const iterator_t &i) = default;
iterator_t &operator=(const iterator_t &i) = default;
reference operator*() { return m_cur; }
pointer operator->() { return &m_cur; }
@@ -699,17 +731,17 @@ class rsrc
using iterator = iterator_t<rsrc>;
iterator begin() const
[[nodiscard]] iterator begin() const
{
const rsrc_imp *impl = nullptr;
if (m_impl and m_impl->m_child)
impl = rsrc_data::instance().index() + m_impl->m_child;
return iterator(impl);
return { impl };
}
iterator end() const
[[nodiscard]] iterator end() const
{
return iterator(nullptr);
return { nullptr };
}
private:
@@ -757,11 +789,11 @@ template <typename CharT, typename Traits>
class basic_streambuf : public std::basic_streambuf<CharT, Traits>
{
public:
typedef CharT char_type;
typedef Traits traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
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;
/// \brief constructor taking a \a path to the resource in memory
basic_streambuf(const std::string &path)
@@ -779,20 +811,20 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
basic_streambuf(const basic_streambuf &) = delete;
basic_streambuf(basic_streambuf &&rhs)
basic_streambuf(basic_streambuf &&rhs) noexcept
: basic_streambuf(rhs.m_rsrc)
{
}
basic_streambuf &operator=(const basic_streambuf &) = delete;
basic_streambuf &operator=(basic_streambuf &&rhs)
basic_streambuf &operator=(basic_streambuf &&rhs) noexcept
{
swap(rhs);
return *this;
}
void swap(basic_streambuf &rhs)
void swap(basic_streambuf &rhs) noexcept
{
std::swap(m_begin, rhs.m_begin);
std::swap(m_end, rhs.m_end);
@@ -807,7 +839,7 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
m_current = m_begin;
}
int_type underflow()
int_type underflow() override
{
if (m_current == m_end)
return traits_type::eof();
@@ -815,7 +847,7 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
return traits_type::to_int_type(*m_current);
}
int_type uflow()
int_type uflow() override
{
if (m_current == m_end)
return traits_type::eof();
@@ -823,7 +855,7 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
return traits_type::to_int_type(*m_current++);
}
int_type pbackfail(int_type ch)
int_type pbackfail(int_type ch) override
{
if (m_current == m_begin or (ch != traits_type::eof() and ch != m_current[-1]))
return traits_type::eof();
@@ -831,13 +863,13 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
return traits_type::to_int_type(*--m_current);
}
std::streamsize showmanyc()
std::streamsize showmanyc() override
{
assert(std::less_equal<const char *>()(m_current, m_end));
return m_end - m_current;
}
pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which)
pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override
{
switch (dir)
{
@@ -866,7 +898,7 @@ class basic_streambuf : public std::basic_streambuf<CharT, Traits>
return m_current - m_begin;
}
pos_type seekpos(pos_type pos, std::ios_base::openmode which)
pos_type seekpos(pos_type pos, std::ios_base::openmode which) override
{
m_current = m_begin + pos;
@@ -895,28 +927,28 @@ template <typename CharT, typename Traits>
class basic_istream : public std::basic_istream<CharT, Traits>
{
public:
typedef CharT char_type;
typedef Traits traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
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;
private:
using __streambuf_type = basic_streambuf<CharT, Traits>;
using __istream_type = std::basic_istream<CharT, Traits>;
using _streambuf_type = basic_streambuf<CharT, Traits>;
using _istream_type = std::basic_istream<CharT, Traits>;
__streambuf_type m_buffer;
_streambuf_type m_buffer;
public:
basic_istream(const std::string &path)
: __istream_type(&m_buffer)
: _istream_type(&m_buffer)
, m_buffer(path)
{
this->init(&m_buffer);
}
basic_istream(rsrc &resource)
: __istream_type(&m_buffer)
: _istream_type(&m_buffer)
, m_buffer(resource)
{
this->init(&m_buffer);
@@ -925,30 +957,30 @@ class basic_istream : public std::basic_istream<CharT, Traits>
basic_istream(const basic_istream &) = delete;
basic_istream(basic_istream &&rhs)
: __istream_type(std::move(rhs))
: _istream_type(std::move(rhs))
, m_buffer(std::move(rhs.m_buffer))
{
__istream_type::set_rdbuf(&m_buffer);
_istream_type::set_rdbuf(&m_buffer);
}
basic_istream &operator=(const basic_istream &) = delete;
basic_istream &operator=(basic_istream &&rhs)
{
__istream_type::operator=(std::move(rhs));
_istream_type::operator=(std::move(rhs));
m_buffer = std::move(rhs.m_buffer);
return *this;
}
void swap(basic_istream &rhs)
{
__istream_type::swap(rhs);
_istream_type::swap(rhs);
m_buffer.swap(rhs.m_buffer);
}
__streambuf_type *rdbuf() const
_streambuf_type *rdbuf() const
{
return const_cast<__streambuf_type *>(&m_buffer);
return const_cast<_streambuf_type *>(&m_buffer);
}
};
@@ -996,8 +1028,8 @@ class resource_pool
std::unique_ptr<std::istream> load(fs::path name);
const auto data_directories() { return mDirs; }
const auto file_resources() { return mLocalResources; }
const auto &data_directories() { return mDirs; }
const auto &file_resources() { return mLocalResources; }
private:
resource_pool();
@@ -1006,18 +1038,15 @@ class resource_pool
{
std::unique_ptr<std::ifstream> result;
try
{
if (fs::exists(p))
{
std::unique_ptr<std::ifstream> file(new std::ifstream(p, std::ios::binary));
if (file->is_open())
result.reset(file.release());
}
}
catch (...)
std::error_code ec;
if (fs::exists(p, ec))
{
std::unique_ptr<std::ifstream> file = std::make_unique<std::ifstream>(p, std::ios::binary);
if (file->is_open())
result.reset(file.release());
}
if (ec != std::errc{} or result == nullptr)
std::cerr << "Error opening resource file " << std::quoted(p.string()) << '\n';
return result;
}
@@ -1083,7 +1112,7 @@ std::unique_ptr<std::istream> resource_pool::load(fs::path name)
{
mrsrc::rsrc rsrc(name);
if (rsrc)
result.reset(new mrsrc::istream(rsrc));
result = std::make_unique<mrsrc::istream>(rsrc);
}
return result;

View File

@@ -25,13 +25,29 @@
*/
#include "cif++/validate.hpp"
#include "cif++/category.hpp"
#include "cif++/dictionary_parser.hpp"
#include "cif++/utilities.hpp"
#include "cif++/cif++.hpp"
#include <cassert>
#include <charconv>
#include <cstddef>
#include <filesystem>
#include <format>
#include <iomanip>
#include <iostream>
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <ranges>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <utility>
#include <vector>
// The validator depends on regular expressions. Unfortunately,
// the implementation of std::regex in g++ is buggy and crashes
@@ -72,7 +88,7 @@ struct regex_impl
bool match(std::string_view v) const;
private:
pcre2_code *m_rx = nullptr;
pcre2_code_8 *m_rx = nullptr;
pcre2_match_data *m_data = nullptr;
mutable std::mutex m_mutex;
};
@@ -81,13 +97,13 @@ regex_impl::regex_impl(std::string_view rx)
{
int err_code;
size_t err_offset;
m_rx = pcre2_compile((PCRE2_SPTR)rx.data(), rx.length(), 0, &err_code, &err_offset, nullptr);
m_rx = pcre2_compile(reinterpret_cast<PCRE2_SPTR>(rx.data()), rx.length(), 0, &err_code, &err_offset, nullptr);
if (m_rx == nullptr)
{
PCRE2_UCHAR buffer[256];
int n = pcre2_get_error_message(err_code, buffer, sizeof(buffer));
char buffer[256];
int n = pcre2_get_error_message(err_code, reinterpret_cast<unsigned char *>(&buffer[0]), sizeof(buffer));
throw std::runtime_error(std::string("PCRE2 compilation failed: ") + std::string{ (char *)buffer, (char *)buffer + n });
throw std::runtime_error(std::string("PCRE2 compilation failed: ") + std::string{ buffer, buffer + n });
}
m_data = pcre2_match_data_create_from_pattern(m_rx, nullptr);
@@ -110,7 +126,7 @@ bool regex_impl::match(std::string_view v) const
bool result = false;
if (int rc = pcre2_match(m_rx, (PCRE2_SPTR)v.data(), v.length(), 0, 0, m_data, nullptr); rc >= 0)
if (int rc = pcre2_match(m_rx, reinterpret_cast<PCRE2_SPTR>(v.data()), v.length(), 0, 0, m_data, nullptr); rc >= 0)
result = true;
else if (rc != PCRE2_ERROR_NOMATCH)
std::cerr << "Error matching with pcre\n";
@@ -153,136 +169,90 @@ type_validator::type_validator(std::string_view name, DDL_PrimitiveType type, st
{
}
type_validator::type_validator(const type_validator &tv)
: m_name(tv.m_name)
, m_primitive_type(tv.m_primitive_type)
, m_rx(tv.m_rx)
int type_validator::compare(const item_value &a, const item_value &b) const
{
}
type_validator::~type_validator()
{
}
int type_validator::compare(std::string_view a, std::string_view b) const
{
int result = 0;
if (a.empty())
result = b.empty() ? 0 : -1;
else if (b.empty())
result = a.empty() ? 0 : +1;
else
switch (m_primitive_type)
{
switch (m_primitive_type)
using enum DDL_PrimitiveType;
case Numb:
{
case DDL_PrimitiveType::Numb:
{
double da, db;
if (a.is_number() and b.is_number())
return a.compare(b);
using namespace cif;
using namespace std;
auto da = a.get<double>();
auto db = b.get<double>();
std::from_chars_result ra, rb;
ra = from_chars(a.data(), a.data() + a.length(), da);
rb = from_chars(b.data(), b.data() + b.length(), db);
if (not(bool) ra.ec and not(bool) rb.ec)
{
auto d = da - db;
if (std::abs(d) > std::numeric_limits<double>::epsilon())
{
if (d > 0)
result = 1;
else if (d < 0)
result = -1;
}
}
else if ((bool)ra.ec)
result = 1;
else
result = -1;
break;
}
case DDL_PrimitiveType::UChar:
case DDL_PrimitiveType::Char:
{
// CIF is guaranteed to have ascii only, therefore this primitive code will do
// also, we're collapsing spaces
auto ai = a.begin(), bi = b.begin();
for (;;)
{
if (ai == a.end())
{
if (bi != b.end())
result = -1;
break;
}
else if (bi == b.end())
{
result = 1;
break;
}
char ca = *ai;
char cb = *bi;
if (m_primitive_type == DDL_PrimitiveType::UChar)
{
ca = tolower(ca);
cb = tolower(cb);
}
result = ca - cb;
if (result != 0)
break;
if (ca == ' ')
{
while (ai[1] == ' ')
++ai;
while (bi[1] == ' ')
++bi;
}
++ai;
++bi;
}
break;
}
return da < db
? -1
: da > db
? 1
: 0;
}
}
return result;
case UChar:
if (a.is_string() and b.is_string())
return a.compare(b, true);
return icompare(a.str(), b.str());
case Char:
if (a.is_string() and b.is_string())
return a.compare(b, false);
return a.str().compare(b.str());
default:
throw std::runtime_error("invalid primitive type");
}
}
// --------------------------------------------------------------------
void item_validator::operator()(const item_value &value) const
void item_validator::validate_value(const item_value &value) const
{
// std::error_code ec;
// if (not validate_value(value, ec))
// throw std::system_error(ec, std::string{ value } + " does not match rx for " + m_item_name);
std::error_code ec;
if (not validate_value(value, ec))
throw std::system_error(ec, std::format("'{}' is not a valid value for {}", value.str(), m_item_name));
}
bool item_validator::validate_value(const item_value &value, std::error_code &ec) const noexcept
{
ec.clear();
// if (not value.empty() and value != "?" and value != ".")
// {
// if (m_type != nullptr and not m_type->m_rx->match(value))
// ec = make_error_code(validation_error::value_does_not_match_rx);
// else if (not m_enums.empty() and m_enums.count(std::string{ value }) == 0)
// ec = make_error_code(validation_error::value_is_not_in_enumeration_list);
// }
if (not value.empty())
{
if (m_type != nullptr)
{
if (m_type->m_primitive_type == DDL_PrimitiveType::Numb)
{
if (not value.is_number())
ec = make_error_code(validation_error::value_is_not_a_number);
}
else
{
if (value.is_number())
ec = make_error_code(validation_error::value_is_not_a_char_string);
else
{
try
{
if (not m_type->m_rx->match(value.sv()))
ec = make_error_code(validation_error::value_does_not_match_rx);
}
catch (...)
{
ec = make_error_code(validation_error::value_does_not_match_rx);
}
return not(bool) ec;
if (ec == std::errc{} and not m_enums.empty() and m_enums.count(value.str()) == 0)
ec = make_error_code(validation_error::value_is_not_in_enumeration_list);
}
}
}
}
return ec == std::errc{};
}
// --------------------------------------------------------------------
@@ -294,15 +264,20 @@ void category_validator::add_item_validator(item_validator &&v)
v.m_category = m_name;
auto r = m_item_validators.insert(std::move(v));
if (not r.second and VERBOSE >= 4)
std::cout << "Could not add validator for item " << v.m_item_name << " to category " << m_name << '\n';
auto i = std::ranges::find(m_item_validators, v);
if (i != m_item_validators.end())
{
if (VERBOSE >= 4)
std::cout << "Could not add validator for item " << v.m_item_name << " to category " << m_name << '\n';
}
else
m_item_validators.emplace_back(std::move(v));
}
const item_validator *category_validator::get_validator_for_item(std::string_view item_name) const
{
const item_validator *result = nullptr;
auto i = m_item_validators.find(item_validator{ std::string(item_name) });
auto i = std::ranges::find(m_item_validators, item_validator{ std::string(item_name) });
if (i != m_item_validators.end())
result = &*i;
else if (VERBOSE > 4)
@@ -334,15 +309,6 @@ const item_validator *category_validator::get_validator_for_aliased_item(std::st
// --------------------------------------------------------------------
validator::validator(const validator &rhs)
: m_audit_conform(rhs.m_audit_conform)
, m_strict(rhs.m_strict)
, m_type_validators(rhs.m_type_validators)
, m_category_validators(rhs.m_category_validators)
, m_link_validators(rhs.m_link_validators)
{
}
void swap(validator &a, validator &b) noexcept
{
std::swap(a.m_audit_conform, b.m_audit_conform);
@@ -359,9 +325,7 @@ void validator::parse(std::istream &is)
void validator::add_type_validator(type_validator &&v)
{
auto r = m_type_validators.insert(std::move(v));
if (not r.second and VERBOSE > 4)
std::cout << "Could not add validator for type " << v.m_name << '\n';
m_type_validators.emplace(v);
}
const type_validator *validator::get_validator_for_type(std::string_view typeCode) const
@@ -371,16 +335,12 @@ const type_validator *validator::get_validator_for_type(std::string_view typeCod
auto i = m_type_validators.find(type_validator{ std::string(typeCode), DDL_PrimitiveType::Char, {} });
if (i != m_type_validators.end())
result = &*i;
else if (VERBOSE > 4)
std::cout << "No validator for type " << typeCode << '\n';
return result;
}
void validator::add_category_validator(category_validator &&v)
{
auto r = m_category_validators.insert(std::move(v));
if (not r.second and VERBOSE > 4)
std::cout << "Could not add validator for category " << v.m_name << '\n';
m_category_validators.emplace(v);
}
const category_validator *validator::get_validator_for_category(std::string_view category) const
@@ -389,8 +349,6 @@ const category_validator *validator::get_validator_for_category(std::string_view
auto i = m_category_validators.find(category_validator{ std::string(category) });
if (i != m_category_validators.end())
result = &*i;
else if (VERBOSE > 4)
std::cout << "No validator for category " << category << '\n';
return result;
}
@@ -481,12 +439,19 @@ void validator::report_error(std::error_code ec, bool fatal) const
void validator::report_error(std::error_code ec, std::string_view category,
std::string_view item, bool fatal) const
{
auto ex = item.empty() ? validation_exception(ec, category) : validation_exception(ec, category, item);
if (m_strict or fatal)
throw ex;
else if (VERBOSE > 0)
std::cerr << ex.what() << '\n';
{
if (item.empty())
throw validation_exception(ec, category);
else
throw validation_exception(ec, category, item);
}
if (VERBOSE > 0)
std::cerr << ec.message()
<< "; category: " << std::quoted(category)
<< " item: " << std::quoted(item)
<< '\n';
}
void validator::fill_audit_conform(category &audit_conform) const
@@ -539,62 +504,79 @@ validator_factory &validator_factory::instance()
return s_instance;
}
const validator &validator_factory::get(std::string_view dictionary_name)
const validator *validator_factory::get(std::string_view dictionary_name)
{
category audit_conform("audit_conform");
for (auto part : cif::split(dictionary_name, ";", true))
for (auto part : cif::split(dictionary_name, ";,", true))
audit_conform.emplace({ { "dict_name", part } });
return get(audit_conform);
}
const validator &validator_factory::get(const category &audit_conform)
const validator *validator_factory::get(const category &audit_conform)
{
if (audit_conform.empty())
throw std::runtime_error("Empty audit_conform category, cannot create a validator");
const validator *result = nullptr;
std::lock_guard lock(m_mutex);
std::scoped_lock lock(m_mutex);
// Check existing first
for (auto &v : m_validators)
{
if (v.matches_audit_conform(audit_conform))
return v;
result = &v;
}
// If the audit conform contains only one record, this is easy
if (audit_conform.size() == 1)
if (result == nullptr and audit_conform.size() == 1)
{
const auto &[name, version] =
audit_conform.front().get<std::string, std::optional<std::string>>("dict_name", "dict_version");
if (not name.empty())
return m_validators.emplace_back(construct_validator(name, version));
result = &m_validators.emplace_back(construct_validator(name, version));
}
// A new, merged dictionary
std::optional<validator> v;
for (const auto &[name, version] : audit_conform.rows<std::string, std::optional<std::string>>("dict_name", "dict_version"))
if (result == nullptr)
{
if (name.empty())
continue;
if (not v)
v = construct_validator(name, version);
else
// A new, merged dictionary
std::optional<validator> v;
for (const auto &[name, version] : audit_conform.rows<std::string, std::optional<std::string>>("dict_name", "dict_version"))
{
auto data = load_resource(name);
if (not data)
throw std::runtime_error("Could not load dictionary " + std::string{ name });
if (name.empty())
continue;
v->parse(*data);
if (not v) // first dict
v = construct_validator(name, version);
else // additional/extending dict
{
auto data = load_resource(name);
if (not data)
throw std::runtime_error("Could not load dictionary " + std::string{ name });
v->parse(*data);
}
}
if (v)
result = &m_validators.emplace_back(std::move(*v));
}
if (not v)
throw std::runtime_error("Missing dictionary information?");
return result;
}
return m_validators.emplace_back(std::move(*v));
const validator &validator_factory::operator[](const category &audit_conform)
{
auto v = get(audit_conform);
if (v == nullptr)
throw std::runtime_error("Could not load dictionary for audit_conform");
return *v;
}
const validator &validator_factory::operator[](std::string_view dictionary_name)
{
auto v = get(dictionary_name);
if (v == nullptr)
throw std::runtime_error("Could not load dictionary for " + std::string{ dictionary_name });
return *v;
}
validator validator_factory::construct_validator(std::string_view name, std::optional<std::string> version)

View File

@@ -41,15 +41,15 @@ list(
APPEND
CIFPP_tests
unit-v2
# unit-3d
# model
unit-3d
model
# query
# rename-compound
# sugar
# spinner
# reconstruction
# validate-pdbx
# matrix
rename-compound
sugar
spinner
reconstruction
validate-pdbx
cql
)
add_library(test-main OBJECT "${CMAKE_CURRENT_SOURCE_DIR}/test-main.cpp")
@@ -65,6 +65,7 @@ foreach(CIFPP_TEST IN LISTS CIFPP_tests)
target_link_libraries(${CIFPP_TEST} PRIVATE cifpp::cifpp Catch2::Catch2)
target_include_directories(${CIFPP_TEST} PRIVATE "${EIGEN_INCLUDE_DIR}")
target_compile_features(${CIFPP_TEST} PUBLIC cxx_std_23)
if(MSVC)
# Specify unwind semantics so that MSVC knowns how to handle exceptions

570
test/cql-test.cpp Normal file
View File

@@ -0,0 +1,570 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 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.
*/
#include "test-main.hpp"
#include <catch2/catch_test_macros.hpp>
#include <cif++/cif++.hpp>
#include <cif++/cql.hpp>
// --------------------------------------------------------------------
cif::file operator""_cf(const char *text, 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<char *>(text), length);
std::istream is(&buffer);
return cif::file(is);
}
// --------------------------------------------------------------------
const char *kAuthors[] = {
"Kleywegt, G.J.",
"Bergfors, T.",
"Senn, H.",
"Le Motte, P.",
"Gsell, B.",
"Shudo, K.",
"Jones, T.A.",
"Banaszak, L.",
"Winter, N.",
"Xu, Z.",
"Bernlohr, D.A.",
"Cowan, S.W.",
"Jones, T.A.",
"Bergfors, T.",
"Kleywegt, G.J.",
"Jones, T.A.",
"Cowan, S.W.",
"Newcomer, M.E.",
"Jones, T.A.",
"Jones, T.A.",
"Bergfors, T.",
"Sedzik, J.",
"Unge, T."
};
// Test simple SELECT
TEST_CASE("cql-1")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT name, ordinal FROM citation_author WHERE citation_id = 'primary';");
CHECK(r.size() == 7);
for (size_t ix = 0; auto row : r)
{
REQUIRE(ix < (sizeof(kAuthors) / sizeof(char *)));
CHECK(row[0].get<std::string>() == kAuthors[ix]);
CHECK(row[1].get<size_t>() == ix + 1);
CHECK(row["name"].get<std::string>() == kAuthors[ix]);
CHECK(row["ordinal"].get<size_t>() == ix + 1);
++ix;
}
r = tx.exec("SELECT ordinal, name FROM citation_author WHERE citation_id = 'primary';");
CHECK(r.size() == 7);
for (size_t ix = 0; auto row : r)
{
REQUIRE(ix < (sizeof(kAuthors) / sizeof(char *)));
CHECK(row[1].get<std::string>() == kAuthors[ix]);
CHECK(row[0].get<size_t>() == ix + 1);
CHECK(row["name"].get<std::string>() == kAuthors[ix]);
CHECK(row["ordinal"].get<size_t>() == ix + 1);
++ix;
}
r = tx.exec("SELECT * FROM citation_author WHERE citation_id = 'primary';");
CHECK(r.size() == 7);
for (int ix = 0; auto row : r)
{
REQUIRE(static_cast<size_t>(ix) < (sizeof(kAuthors) / sizeof(char *)));
for (auto fld : row)
{
switch (fld.num())
{
case 0:
CHECK(fld.name() == "citation_id");
CHECK(fld.get<std::string>() == "primary");
break;
case 1:
CHECK(fld.name() == "name");
CHECK(fld.get<std::string>() == kAuthors[ix]);
break;
case 2:
CHECK(fld.name() == "ordinal");
CHECK(fld.get<int>() == ix + 1);
break;
default:
CHECK(fld.name() == "identifier_ORCID");
CHECK(fld.is_null());
break;
}
}
CHECK(row["name"].get<std::string>() == kAuthors[ix]);
CHECK(row["ordinal"].get<int>() == ix + 1);
CHECK(row["citation_id"].get<std::string>() == "primary");
++ix;
}
}
// Test SELECT AS
TEST_CASE("cql-2")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT name AS v1, ordinal AS v2 FROM citation_author WHERE citation_id = 'primary';");
CHECK(r.size() == 7);
for (size_t ix = 0; auto row : r)
{
REQUIRE(ix < (sizeof(kAuthors) / sizeof(char *)));
CHECK(row[0].get<std::string>() == kAuthors[ix]);
CHECK(row[1].get<size_t>() == ix + 1);
CHECK(row["v1"].get<std::string>() == kAuthors[ix]);
CHECK(row["v2"].get<size_t>() == ix + 1);
++ix;
}
}
TEST_CASE("cql-3")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT name FROM citation_author WHERE ordinal = 10").one_field();
CHECK(r.get<std::string>() == kAuthors[9]);
}
TEST_CASE("cql-4")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT name FROM citation_author WHERE ordinal BETWEEN 10 AND 15");
REQUIRE(r.size() == 6);
}
TEST_CASE("cql-5")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT (SELECT year FROM citation WHERE id = citation_id) AS jaar FROM citation_author WHERE ordinal IS 23").one_field();
CHECK(r.name() == "jaar");
CHECK(r.get<int>() == 1988);
}
TEST_CASE("cql-6")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto r = tx.exec("SELECT COUNT(*) FROM citation WHERE page_last IS NULL").one_field();
CHECK(r.get<int>() == 4);
r = tx.exec("SELECT COUNT(*) FROM citation WHERE page_last IS NOT NULL").one_field();
CHECK(r.get<int>() == 1);
}
TEST_CASE("cql-stream-1")
{
cif::file f(gTestDir / ".." / "examples" / "1cbs.cif.gz");
auto &db = f.front();
db.load_dictionary("mmcif_pdbx.dic");
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
for (size_t ix = 0;
const auto &[name, ordinal] : tx.stream<std::string, size_t>(
"SELECT name, ordinal FROM citation_author WHERE citation_id = 'primary';"))
{
REQUIRE(ix < (sizeof(kAuthors) / sizeof(char *)));
CHECK(name == kAuthors[ix]);
CHECK(ordinal == (ix + 1));
++ix;
}
}
// --------------------------------------------------------------------
TEST_CASE("cql-insert-1")
{
auto f1 = R"(
data_T1
loop_
_table1.id
_table1.name
1 aap
2 noot)"_cf;
auto f0 = f1;
auto &db = f1.front();
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
auto count = tx.exec("SELECT COUNT(*) FROM table1;").one_field().get<int>();
CHECK(count == 2);
auto r = tx.exec("INSERT INTO table1 (id, name) VALUES (3, 'mies')");
count = tx.exec("SELECT COUNT(*) FROM table1").one_field().get<int>();
CHECK(count == 3);
(void)tx.exec("DELETE FROM table1 WHERE CAST(id AS INTEGER) = 1;");
count = tx.exec("SELECT COUNT(*) FROM table1;").one_field().get<int>();
CHECK(count == 2);
(void)tx.exec("UPDATE table1 SET name = 'amandel' WHERE CAST(id AS INTEGER) = 2");
auto f2 = R"(
data_T1
loop_
_table1.id
_table1.name
2 amandel
3 mies)"_cf;
CHECK(f1 == f2);
tx.rollback();
CHECK(f1 == f0);
}
// --------------------------------------------------------------------
TEST_CASE("cql-rename")
{
auto f1 = R"(
data_T1
loop_
_table1.id
_table1.name
1 aap
2 noot)"_cf;
auto &db = f1.front();
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
(void)tx.exec("ALTER TABLE table1 RENAME TO 'table2'");
auto f2 = R"(
data_T1
loop_
_table2.id
_table2.name
1 aap
2 noot)"_cf;
CHECK(f1 == f2);
}
// --------------------------------------------------------------------
TEST_CASE("cql-foreign-keys-1")
{
const char dict[] = R"(
data_test_dict.dic
_datablock.id test_dict.dic
_datablock.description
;
A test dictionary
;
_dictionary.title test_dict.dic
_dictionary.datablock_id test_dict.dic
_dictionary.version 1.0
loop_
_item_type_list.code
_item_type_list.primitive_code
_item_type_list.construct
_item_type_list.detail
code char
'[][_,.;:"&<>()/\{}'`~!@#$%A-Za-z0-9*|+-]*'
; code item types/single words ...
;
text char
'[][ \n\t()_,.;:"&<>/\{}'`~!@#$%?+=*A-Za-z0-9|^-]*'
; text item types / multi-line text ...
;
int numb
'[+-]?[0-9]+'
; int item types are the subset of numbers that are the negative
or positive integers.
;
save_cat_1
_category.description 'A simple test category'
_category.id cat_1
_category.mandatory_code no
_category_key.name '_cat_1.id'
save_
save__cat_1.id
_item.name '_cat_1.id'
_item.category_id cat_1
_item.mandatory_code yes
_item_aliases.dictionary cif_core.dic
_item_aliases.version 2.0.1
_item_linked.child_name '_cat_2.parent_id'
_item_linked.parent_name '_cat_1.id'
_item_type.code code
save_
save__cat_1.name
_item.name '_cat_1.name'
_item.category_id cat_1
_item.mandatory_code yes
_item_aliases.dictionary cif_core.dic
_item_aliases.version 2.0.1
_item_type.code text
save_
save_cat_2
_category.description 'A second simple test category'
_category.id cat_2
_category.mandatory_code no
_category_key.name '_cat_2.id'
save_
save__cat_2.id
_item.name '_cat_2.id'
_item.category_id cat_2
_item.mandatory_code yes
_item_aliases.dictionary cif_core.dic
_item_aliases.version 2.0.1
_item_type.code int
save_
save__cat_2.parent_id
_item.name '_cat_2.parent_id'
_item.category_id cat_2
_item.mandatory_code yes
_item_aliases.dictionary cif_core.dic
_item_aliases.version 2.0.1
_item_type.code code
save_
save__cat_2.desc
_item.name '_cat_2.desc'
_item.category_id cat_2
_item.mandatory_code yes
_item_aliases.dictionary cif_core.dic
_item_aliases.version 2.0.1
_item_type.code text
save_
)";
struct membuf : public std::streambuf
{
membuf(char *text, std::size_t length)
{
this->setg(text, text, text + length);
}
} buffer(const_cast<char *>(dict), sizeof(dict) - 1);
std::istream is_dict(&buffer);
cif::validator validator(is_dict);
cif::file f;
// --------------------------------------------------------------------
const char data[] = R"(
data_test
loop_
_cat_1.id
_cat_1.name
1 Aap
2 Noot
3 Mies
loop_
_cat_2.id
_cat_2.parent_id
_cat_2.desc
1 1 'Een dier'
2 1 'Een andere aap'
3 2 'walnoot bijvoorbeeld'
)";
struct data_membuf : public std::streambuf
{
data_membuf(char *text, std::size_t length)
{
this->setg(text, text, text + length);
}
} data_buffer(const_cast<char *>(data), sizeof(data) - 1);
std::istream is_data(&data_buffer);
f.load(is_data);
f.front().set_validator(&validator);
auto &db = f.front();
SECTION("stream")
{
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
for (const auto &desc : tx.stream<std::string>(R"(SELECT b.desc FROM cat_1 a, cat_2 b WHERE a.id = b.parent_id AND a.name = 'Noot')"))
{
CHECK(desc == "walnoot bijvoorbeeld");
}
}
// Check cascading delete
SECTION("delete")
{
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
tx.exec("DELETE FROM cat_1 WHERE id = 1");
CHECK(db["cat_1"].size() == 2);
CHECK(db["cat_2"].size() == 1);
tx.rollback();
CHECK(db["cat_1"].size() == 3);
CHECK(db["cat_2"].size() == 3);
}
// Check cascading update
SECTION("update")
{
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
tx.exec("UPDATE cat_1 SET id = '4' WHERE id = '1'");
CHECK(db["cat_1"].size() == 3);
CHECK(db["cat_2"].size() == 3);
CHECK(db["cat_1"].count(cif::key("id") == 4) == 1);
CHECK(db["cat_2"].count(cif::key("parent_id") == 4) == 2);
std::cout << db;
tx.rollback();
CHECK(db["cat_1"].size() == 3);
CHECK(db["cat_2"].size() == 3);
CHECK_FALSE(db["cat_1"].contains(cif::key("id") == 4));
CHECK_FALSE(db["cat_2"].contains(cif::key("parent_id") == 4));
std::cout << db;
}
}
// --------------------------------------------------------------------
TEST_CASE("drop-table")
{
auto f1 = R"(
data_T1
loop_
_table1.id
_table1.name
1 aap
2 noot)"_cf;
auto &db = f1.front();
cif::cql::connection connection(db);
cif::cql::transaction tx(connection);
SECTION("commit")
{
(void)tx.exec("DROP TABLE table1;");
tx.commit();
CHECK(db.empty());
}
// Ah, too bad: this doesn't work
// SECTION("rollback")
// {
// (void)tx.exec("DROP TABLE table1;");
// tx.rollback();
// CHECK(not db.empty());
// CHECK(db["table1"].size() == 2);
// }
}

View File

@@ -1,4 +1,30 @@
#include <cif++.hpp>
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 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.
*/
#include <cif++/cif++.hpp>
class dummy_parser : public cif::sac_parser
{

View File

@@ -1,103 +0,0 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 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.
*/
#include "cif++/matrix.hpp"
#include "test-main.hpp"
#include <catch2/catch_test_macros.hpp>
#include <cif++.hpp>
TEST_CASE("m1")
{
cif::matrix3x3<int> m = cif::identity_matrix<int>(3);
CHECK(cif::determinant(m) == 1);
}
TEST_CASE("m2")
{
cif::matrix4x4<int> m = cif::identity_matrix<int>(4);
cif::sub_matrix<cif::matrix4x4<int>> ms(m, 1, 1);
CHECK(ms == cif::identity_matrix<int>(3));
}
TEST_CASE("m3")
{
cif::matrix4x4<int> m{
{ 1, 2, 3, 4, //
5, 6, 7, 8, //
9, 10, 11, 12, //
13, 14, 15, 16 }
};
cif::sub_matrix<cif::matrix4x4<int>> ms(m, 1, 1);
cif::matrix3x3<int> t{
{ 1, 3, 4, 9, 11, 12, 13, 15, 16 }
};
CHECK(ms == t);
}
TEST_CASE("m4")
{
cif::matrix4x4<int> m{
{
-2,
3,
1,
0,
4,
1,
-3,
2,
0,
-1,
2,
5,
3,
2,
0,
-4,
}
};
// std::cout << m << "\n\n";
// std::cout << cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 0)) << "\n\n";
// std::cout << cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 1)) << "\n\n";
// std::cout << cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 2)) << "\n\n";
// std::cout << cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 3)) << "\n\n";
// std::cout << cif::determinant(cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 0))) << "\n\n";
// std::cout << cif::determinant(cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 1))) << "\n\n";
// std::cout << cif::determinant(cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 2))) << "\n\n";
// std::cout << cif::determinant(cif::matrix3x3<int>(cif::sub_matrix<decltype(m)>(m, 0, 3))) << "\n\n";
CHECK(cif::determinant(m) == 332);
}

View File

@@ -26,9 +26,8 @@
#include "test-main.hpp"
#include <stdexcept>
#include <cif++.hpp>
#include <catch2/catch_test_macros.hpp>
#include <cif++/cif++.hpp>
// --------------------------------------------------------------------
@@ -54,7 +53,7 @@ TEST_CASE("create_nonpoly_1")
cif::file file;
auto &&[dbi, ignore] = file.emplace("TEST"); // create a datablock
dbi->set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
dbi->load_dictionary("mmcif_pdbx.dic");
cif::mm::structure structure(file);
@@ -82,7 +81,7 @@ _atom_site.pdbx_formal_charge
# that's enough to test with
)"_cf;
atoms.front().set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
atoms.front().load_dictionary("mmcif_pdbx.dic");
auto &hem_data = atoms["HEM"];
auto &atom_site = hem_data["atom_site"];
@@ -94,8 +93,7 @@ _atom_site.pdbx_formal_charge
structure.create_non_poly(entity_id, atom_data);
auto expected = R"(
data_TEST
auto expected = R"(data_TEST
#
_pdbx_nonpoly_scheme.asym_id A
_pdbx_nonpoly_scheme.ndb_seq_num 1
@@ -139,7 +137,7 @@ _chem_comp.id HEM
_chem_comp.type NON-POLYMER
_chem_comp.name 'PROTOPORPHYRIN IX CONTAINING FE'
_chem_comp.formula 'C34 H32 Fe N4 O4'
_chem_comp.formula_weight 616.487000
_chem_comp.formula_weight 616.487
#
_pdbx_entity_nonpoly.entity_id 1
_pdbx_entity_nonpoly.name 'PROTOPORPHYRIN IX CONTAINING FE'
@@ -148,7 +146,7 @@ _pdbx_entity_nonpoly.comp_id HEM
_entity.id 1
_entity.type non-polymer
_entity.pdbx_description 'PROTOPORPHYRIN IX CONTAINING FE'
_entity.formula_weight 616.487000
_entity.formula_weight 616.487
#
_struct_asym.id A
_struct_asym.entity_id 1
@@ -159,14 +157,20 @@ _struct_asym.details ?
_atom_type.symbol C
)"_cf;
expected.front().set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
expected.front().load_dictionary("mmcif_pdbx.dic");
if (not(expected.front() == structure.get_datablock()))
{
std::cerr << expected.front() << '\n'
CHECK(false);
std::cout << expected << '\n'
<< '\n'
<< structure.get_datablock() << '\n';
REQUIRE(false);
std::ofstream of("/tmp/a");
of << expected;
file.save("/tmp/b");
}
}
@@ -178,7 +182,7 @@ TEST_CASE("create_nonpoly_2")
cif::file file;
auto &&[dbi, ignore] = file.emplace("TEST"); // create a datablock
dbi->set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
dbi->load_dictionary("mmcif_pdbx.dic");
cif::mm::structure structure(file);
@@ -195,9 +199,9 @@ TEST_CASE("create_nonpoly_2")
{ "type_symbol", type_symbol },
{ "label_atom_id", label_atom_id },
{ "auth_atom_id", label_atom_id },
{ "Cartn_x", Cartn_x },
{ "Cartn_y", Cartn_y },
{ "Cartn_z", Cartn_z } });
{ "Cartn_x", { Cartn_x, 3 } },
{ "Cartn_y", { Cartn_y, 3 } },
{ "Cartn_z", { Cartn_z, 3 } } });
if (atoms.size() == 4)
break;
@@ -243,14 +247,14 @@ _atom_site.auth_atom_id
_atom_site.pdbx_PDB_model_num
1 A ? A CHA HEM 1 . C HETATM ? 2.748 -19.531 39.896 1.00 ? 1 HEM CHA 1
2 A ? A CHB HEM 1 . C HETATM ? 3.258 -17.744 35.477 1.00 ? 1 HEM CHB 1
3 A ? A CHC HEM 1 . C HETATM ? 1.703 -21.9 33.637 1.00 ? 1 HEM CHC 1
3 A ? A CHC HEM 1 . C HETATM ? 1.703 -21.900 33.637 1.00 ? 1 HEM CHC 1
4 A ? A CHD HEM 1 . C HETATM ? 1.149 -23.677 38.059 1.00 ? 1 HEM CHD 1
#
_chem_comp.id HEM
_chem_comp.type NON-POLYMER
_chem_comp.name 'PROTOPORPHYRIN IX CONTAINING FE'
_chem_comp.formula 'C34 H32 Fe N4 O4'
_chem_comp.formula_weight 616.487000
_chem_comp.formula_weight 616.487
#
_pdbx_entity_nonpoly.entity_id 1
_pdbx_entity_nonpoly.name 'PROTOPORPHYRIN IX CONTAINING FE'
@@ -259,7 +263,7 @@ _pdbx_entity_nonpoly.comp_id HEM
_entity.id 1
_entity.type non-polymer
_entity.pdbx_description 'PROTOPORPHYRIN IX CONTAINING FE'
_entity.formula_weight 616.487000
_entity.formula_weight 616.487
#
_struct_asym.id A
_struct_asym.entity_id 1
@@ -270,13 +274,13 @@ _struct_asym.details ?
_atom_type.symbol C
)"_cf;
expected.front().set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
expected.front().load_dictionary("mmcif_pdbx.dic");
REQUIRE(expected.front() == structure.get_datablock());
if (not(expected.front() == structure.get_datablock()))
{
// REQUIRE(false);
CHECK(false);
std::cout << expected.front() << '\n'
<< '\n'
<< structure.get_datablock() << '\n';
@@ -354,14 +358,14 @@ _struct_asym.details ?
#
)"_cf;
data.front().set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
data.front().load_dictionary("mmcif_pdbx.dic");
cif::mm::structure s(data);
REQUIRE(s.get_atom_by_id("1").get_label_atom_id() == "CHA");
REQUIRE(s.get_atom_by_id("2").get_label_atom_id() == "CHC");
REQUIRE(s.get_atom_by_id("3").get_label_atom_id() == "CHB");
REQUIRE(s.get_atom_by_id("4").get_label_atom_id() == "CHD");
CHECK(s.get_atom_by_id("1").get_label_atom_id() == "CHA");
CHECK(s.get_atom_by_id("2").get_label_atom_id() == "CHC");
CHECK(s.get_atom_by_id("3").get_label_atom_id() == "CHB");
CHECK(s.get_atom_by_id("4").get_label_atom_id() == "CHD");
}
// --------------------------------------------------------------------
@@ -382,19 +386,19 @@ TEST_CASE("atom_numbers_1")
{
auto atom = structure.get_atom_by_id(id);
REQUIRE(atom.get_label_asym_id() == label_asym_id);
REQUIRE(atom.get_label_seq_id() == label_seq_id);
REQUIRE(atom.get_label_atom_id() == label_atom_id);
REQUIRE(atom.get_auth_seq_id() == auth_seq_id);
REQUIRE(atom.get_label_comp_id() == label_comp_id);
CHECK(atom.get_label_asym_id() == label_asym_id);
CHECK(atom.get_label_seq_id() == label_seq_id);
CHECK(atom.get_label_atom_id() == label_atom_id);
CHECK(atom.get_auth_seq_id() == auth_seq_id);
CHECK(atom.get_label_comp_id() == label_comp_id);
REQUIRE(ai != atoms.end());
CHECK(ai != atoms.end());
REQUIRE(ai->id() == id);
CHECK(ai->id() == id);
++ai;
}
REQUIRE(ai == atoms.end());
CHECK(ai == atoms.end());
}
// --------------------------------------------------------------------
@@ -414,9 +418,7 @@ TEST_CASE("test_load_2")
auto &pdbx_poly_seq_scheme = db["pdbx_poly_seq_scheme"];
for (auto &poly : s.polymers())
{
REQUIRE(poly.size() == pdbx_poly_seq_scheme.find("asym_id"_key == poly.get_asym_id()).size());
}
CHECK(poly.size() == pdbx_poly_seq_scheme.find("asym_id"_key == poly.get_asym_id()).size());
}
TEST_CASE("remove_residue_1")
@@ -429,7 +431,24 @@ TEST_CASE("remove_residue_1")
cif::mm::structure s(file);
s.remove_residue(s.get_residue("B"));
REQUIRE_NOTHROW(s.validate_atoms());
CHECK_NOTHROW(s.validate_atoms());
}
// --------------------------------------------------------------------
TEST_CASE("test_alternates_1")
{
using namespace cif::literals;
const std::filesystem::path example(gTestDir / ".." / "examples" / "1cbs.cif.gz");
cif::file file(example.string());
// auto &db = file.front();
cif::mm::structure s(file);
for (auto atom : s.atoms())
CHECK_FALSE(atom.is_alternate());
}
// --------------------------------------------------------------------
@@ -566,7 +585,7 @@ _struct_asym.details ?
#
)"_cf;
data.front().set_validator(&cif::validator_factory::instance().get("mmcif_pdbx.dic"));
data.front().load_dictionary("mmcif_pdbx.dic");
SECTION("max")
{

View File

@@ -26,7 +26,7 @@
#include "test-main.hpp"
#include <cif++.hpp>
#include <cif++/cif++.hpp>
#include <iostream>
#include <fstream>

File diff suppressed because it is too large Load Diff

1573
test/reconstruct/1cbs.pdb Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -24,9 +24,10 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/utilities.hpp"
#include "test-main.hpp"
#include <cif++.hpp>
#include <cif++/cif++.hpp>
#include <filesystem>
#include <iostream>
@@ -34,6 +35,8 @@
TEST_CASE("reconstruct")
{
cif::VERBOSE = 1;
cif::compound_factory::instance().push_dictionary(gTestDir / "REA.cif");
for (std::filesystem::directory_iterator i(gTestDir / "reconstruct"); i != std::filesystem::directory_iterator{}; ++i)
@@ -55,9 +58,18 @@ TEST_CASE("reconstruct")
std::error_code ec;
CHECK_FALSE(cif::pdb::is_valid_pdbx_file(f, ec));
CHECK((bool)ec);
CHECK(ec != std::errc{});
CHECK(cif::pdb::reconstruct_pdbx(f));
auto valid = cif::pdb::reconstruct_pdbx(f);
CHECK(valid);
if (not valid)
{
std::ofstream of(std::filesystem::temp_directory_path() / i->path().filename());
of << f;
of.close();
}
}
}
}

View File

@@ -26,7 +26,7 @@
#include "test-main.hpp"
#include <cif++.hpp>
#include <cif++/cif++.hpp>
#include <iostream>
#include <fstream>

View File

@@ -28,7 +28,7 @@
#include <stdexcept>
#include <cif++.hpp>
#include <cif++/cif++.hpp>
// --------------------------------------------------------------------
@@ -183,4 +183,6 @@ TEST_CASE("delete_sugar_1")
// file.save(gTestDir / "test-create_sugar_3.cif");
cif::mm::structure s2(file);
file.save("/tmp/min-s.cif");
}

View File

@@ -1,13 +1,42 @@
#define CATCH_CONFIG_RUNNER 1
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 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.
*/
#define CATCH_CONFIG_RUNNER 1 // NOLINT
#include "test-main.hpp"
#include <cif++/utilities.hpp>
#include <cif++/compound.hpp>
std::filesystem::path gTestDir = std::filesystem::current_path();
std::filesystem::path gTestDir;
int main(int argc, char *argv[])
{
gTestDir = std::filesystem::current_path();
Catch::Session session; // There must be exactly one instance
// Build a new parser on top of Catch2's
@@ -33,7 +62,7 @@ int main(int argc, char *argv[])
// initialize CCD location
cif::add_file_resource("components.cif", gTestDir / ".." / "rsrc" / "ccd-subset.cif");
// cif::compound_factory::instance().push_dictionary(gTestDir / "HEM.cif");
cif::compound_factory::instance().push_dictionary(gTestDir / "HEM.cif");
return session.run();
}

View File

@@ -24,20 +24,18 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cif++/point.hpp"
#include "cif++/cif++.hpp"
#include "test-main.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cif++.hpp>
#include <stdexcept>
#if defined(_MSC_VER)
# pragma warning(disable : 5054) // warning C5054: operator '&': deprecated between enumerations of different types
# pragma warning(disable : 4127) // conditional expression is constant
#endif
#include <Eigen/Eigenvalues>
// #include <Eigen/Eigenvalues>
// --------------------------------------------------------------------
@@ -89,7 +87,7 @@ TEST_CASE("t1")
cif::center_points(p1);
for (auto &p : p2)
p.rotate(q);
p = cif::rotate(p, q);
cif::center_points(p2);
@@ -100,7 +98,7 @@ TEST_CASE("t1")
CHECK_THAT(std::fmod(360 + angle, 360), Catch::Matchers::WithinRel(std::fmod(360 - angle0, 360), 0.01));
for (auto &p : p1)
p.rotate(q2);
p = q2 * p;
auto rmsd = cif::RMSd(p1, p2);
@@ -117,9 +115,9 @@ TEST_CASE("t2")
{ 1, 2, 0 }
};
cif::point xp = cif::cross_product(p[1] - p[0], p[2] - p[0]);
auto xp = glm::cross(p[1] - p[0], p[2] - p[0]);
auto q = cif::construct_from_angle_axis(45, xp); // mmcif::Normalize(Quaternion{45 * mmcif::kPI / 180, xp.mX, xp.mY, xp.mZ});
auto q = cif::construct_from_angle_axis(45, xp);
auto &&[angle, axis] = cif::quaternion_to_angle_axis(q);
@@ -134,16 +132,16 @@ TEST_CASE("t3")
{ 1, 2, 0 }
};
cif::point xp = cif::cross_product(p[1] - p[0], p[2] - p[0]);
cif::point xp = glm::cross(p[1] - p[0], p[2] - p[0]);
auto q = cif::construct_from_angle_axis(45, xp); // mmcif::Normalize(Quaternion{45 * mmcif::kPI / 180, xp.mX, xp.mY, xp.mZ});
auto q = cif::construct_from_angle_axis(45, xp);
auto v = p[1];
v -= p[0];
v.rotate(q);
v = q * v;
v += p[0];
std::cout << v << '\n';
std::println(std::cout, "{}", v);
double a = cif::angle(v, p[0], p[1]);
@@ -167,22 +165,22 @@ TEST_CASE("dh_q_0")
auto q = cif::construct_from_angle_axis(90, axis);
p.rotate(q);
p = q * p;
REQUIRE(std::abs(p.m_x - 1.f) < 0.01f);
REQUIRE(std::abs(p.m_y - 0.f) < 0.01f);
REQUIRE(std::abs(p.m_z - 1.f) < 0.01f);
REQUIRE(std::abs(p.x - 1.f) < 0.01f);
REQUIRE(std::abs(p.y - 0.f) < 0.01f);
REQUIRE(std::abs(p.z - 1.f) < 0.01f);
a = cif::dihedral_angle(t[0], t[1], t[2], p);
REQUIRE(std::abs(a - 90.f) < 0.01f);
q = cif::construct_from_angle_axis(-90, axis);
p.rotate(q);
p = q * p;
REQUIRE(std::abs(p.m_x - 1.f) < 0.01f);
REQUIRE(std::abs(p.m_y - 1.f) < 0.01f);
REQUIRE(std::abs(p.m_z - 0.f) < 0.01f);
REQUIRE(std::abs(p.x - 1.f) < 0.01f);
REQUIRE(std::abs(p.y - 1.f) < 0.01f);
REQUIRE(std::abs(p.z - 0.f) < 0.01f);
a = cif::dihedral_angle(t[0], t[1], t[2], p);
REQUIRE(std::abs(a - 0.f) < 0.01f);
@@ -224,116 +222,52 @@ TEST_CASE("dh_q_1")
{
auto q = cif::construct_for_dihedral_angle(pts[0], pts[1], pts[2], pts[3], angle, 1);
pts[3].rotate(q, pts[2]);
pts[3] = cif::rotate(pts[3], q, pts[2]);
auto dh = cif::dihedral_angle(pts[0], pts[1], pts[2], pts[3]);
CHECK_THAT(dh, Catch::Matchers::WithinRel(angle, 0.1f));
}
}
// --------------------------------------------------------------------
// TEST_CASE("m2q_0")
// {
// for (std::size_t i = 0; i < cif::kSymopNrTableSize; ++i)
// {
// auto d = cif::kSymopNrTable[i].symop().data();
// cif::matrix3x3<float> rot;
// float Qxx = rot(0, 0) = d[0];
// float Qxy = rot(0, 1) = d[1];
// float Qxz = rot(0, 2) = d[2];
// float Qyx = rot(1, 0) = d[3];
// float Qyy = rot(1, 1) = d[4];
// float Qyz = rot(1, 2) = d[5];
// float Qzx = rot(2, 0) = d[6];
// float Qzy = rot(2, 1) = d[7];
// float Qzz = rot(2, 2) = d[8];
// Eigen::Matrix4f em;
// em << Qxx - Qyy - Qzz, Qyx + Qxy, Qzx + Qxz, Qzy - Qyz,
// Qyx + Qxy, Qyy - Qxx - Qzz, Qzy + Qyz, Qxz - Qzx,
// Qzx + Qxz, Qzy + Qyz, Qzz - Qxx - Qyy, Qyx - Qxy,
// Qzy - Qyz, Qxz - Qzx, Qyx - Qxy, Qxx + Qyy + Qzz;
// Eigen::EigenSolver<Eigen::Matrix4f> es(em / 3);
// auto ev = es.eigenvalues();
// std::size_t bestJ = 0;
// float bestEV = -1;
// for (std::size_t j = 0; j < 4; ++j)
// {
// if (bestEV < ev[j].real())
// {
// bestEV = ev[j].real();
// bestJ = j;
// }
// }
// if (std::abs(bestEV - 1) > 0.01)
// continue; // not a rotation matrix
// auto col = es.eigenvectors().col(bestJ);
// auto q = normalize(cif::quaternion{
// static_cast<float>(col(3).real()),
// static_cast<float>(col(0).real()),
// static_cast<float>(col(1).real()),
// static_cast<float>(col(2).real()) });
// cif::point p1{ 1, 1, 1 };
// cif::point p2 = p1;
// p2.rotate(q);
// cif::point p3 = rot * p1;
// CHECK_THAT(p2.m_x, Catch::Matchers::WithinRel(p3.m_x, 0.01f));
// CHECK_THAT(p2.m_y, Catch::Matchers::WithinRel(p3.m_y, 0.01f));
// CHECK_THAT(p2.m_z, Catch::Matchers::WithinRel(p3.m_z, 0.01f));
// }
// }
TEST_CASE("m2q_0a")
/* TEST_CASE("m2q_0a")
{
for (std::size_t i = 0; i < cif::kSymopNrTableSize; ++i)
{
auto d = cif::kSymopNrTable[i].symop().data();
for (std::size_t i = 0; i < cif::kSymopNrTableSize; ++i)
{
auto d = cif::kSymopNrTable[i].symop().data();
Eigen::Matrix3f rot;
rot << static_cast<float>(d[0]), static_cast<float>(d[1]), static_cast<float>(d[2]), static_cast<float>(d[3]), static_cast<float>(d[4]), static_cast<float>(d[5]), static_cast<float>(d[6]), static_cast<float>(d[7]), static_cast<float>(d[8]);
Eigen::Matrix3f rot;
rot << static_cast<float>(d[0]), static_cast<float>(d[1]), static_cast<float>(d[2]), static_cast<float>(d[3]), static_cast<float>(d[4]), static_cast<float>(d[5]), static_cast<float>(d[6]), static_cast<float>(d[7]), static_cast<float>(d[8]);
// check to see if this matrix contains a true rotation
if (rot * rot.transpose() != Eigen::Matrix3f::Identity() or rot.determinant() != 1)
continue;
// check to see if this matrix contains a true rotation
if (rot * rot.transpose() != Eigen::Matrix3f::Identity() or rot.determinant() != 1)
continue;
Eigen::Quaternionf qe(rot);
Eigen::Quaternionf qe(rot);
auto q = normalize(cif::quaternion{ qe.w(), qe.x(), qe.y(), qe.z() });
auto q = normalize(cif::quaternion{ qe.w(), qe.x(), qe.y(), qe.z() });
cif::point p1{ 1, 1, 1 };
cif::point p2 = p1;
p2.rotate(q);
cif::point p1{ 1, 1, 1 };
cif::point p2 = p1;
p2 = q * p2;
cif::matrix3x3<float> rot_c({ static_cast<float>(d[0]),
static_cast<float>(d[1]),
static_cast<float>(d[2]),
static_cast<float>(d[3]),
static_cast<float>(d[4]),
static_cast<float>(d[5]),
static_cast<float>(d[6]),
static_cast<float>(d[7]),
static_cast<float>(d[8]) });
cif::glm::mat3 rot_c({ static_cast<float>(d[0]),
static_cast<float>(d[1]),
static_cast<float>(d[2]),
static_cast<float>(d[3]),
static_cast<float>(d[4]),
static_cast<float>(d[5]),
static_cast<float>(d[6]),
static_cast<float>(d[7]),
static_cast<float>(d[8]) });
cif::point p3 = rot_c * p1;
cif::point p3 = rot_c * p1;
CHECK_THAT(p2.m_x, Catch::Matchers::WithinRel(p3.m_x, 0.01f));
CHECK_THAT(p2.m_y, Catch::Matchers::WithinRel(p3.m_y, 0.01f));
CHECK_THAT(p2.m_z, Catch::Matchers::WithinRel(p3.m_z, 0.01f));
}
CHECK_THAT(p2.x, Catch::Matchers::WithinRel(p3.x, 0.01f));
CHECK_THAT(p2.y, Catch::Matchers::WithinRel(p3.y, 0.01f));
CHECK_THAT(p2.z, Catch::Matchers::WithinRel(p3.z, 0.01f));
}
}
*/
// "TEST_CASE(m2q_1")
// {
@@ -341,7 +275,7 @@ TEST_CASE("m2q_0a")
// {
// auto d = cif::kSymopNrTable[i].symop().data();
// cif::matrix3x3<float> rot;
// cif::glm::mat3 rot;
// float Qxx = rot(0, 0) = d[0];
// float Qxy = rot(0, 1) = d[1];
// float Qxz = rot(0, 2) = d[2];
@@ -384,13 +318,13 @@ TEST_CASE("m2q_0a")
// cif::point p1{ 1, 1, 1 };
// cif::point p2 = p1;
// p2.rotate(q);
// p2 = q * p2;
// cif::point p3 = rot * p1;
// REQUIRE(p2.m_x == p3.m_x);
// REQUIRE(p2.m_y == p3.m_y);
// REQUIRE(p2.m_z == p3.m_z);
// REQUIRE(p2.x == p3.x);
// REQUIRE(p2.y == p3.y);
// REQUIRE(p2.z == p3.z);
// }
// }
@@ -404,15 +338,15 @@ TEST_CASE("symm_1")
cif::point f = fractional(p, c);
CHECK_THAT(f.m_x, Catch::Matchers::WithinRel(0.1f, 0.01f));
CHECK_THAT(f.m_y, Catch::Matchers::WithinRel(0.1f, 0.01f));
CHECK_THAT(f.m_z, Catch::Matchers::WithinRel(0.1f, 0.01f));
CHECK_THAT(f.x, Catch::Matchers::WithinRel(0.1f, 0.01f));
CHECK_THAT(f.y, Catch::Matchers::WithinRel(0.1f, 0.01f));
CHECK_THAT(f.z, Catch::Matchers::WithinRel(0.1f, 0.01f));
cif::point o = orthogonal(f, c);
CHECK_THAT(o.m_x, Catch::Matchers::WithinRel(1.f, 0.01f));
CHECK_THAT(o.m_y, Catch::Matchers::WithinRel(1.f, 0.01f));
CHECK_THAT(o.m_z, Catch::Matchers::WithinRel(1.f, 0.01f));
CHECK_THAT(o.x, Catch::Matchers::WithinRel(1.f, 0.01f));
CHECK_THAT(o.y, Catch::Matchers::WithinRel(1.f, 0.01f));
CHECK_THAT(o.z, Catch::Matchers::WithinRel(1.f, 0.01f));
}
TEST_CASE("symm_2")
@@ -451,46 +385,19 @@ TEST_CASE("symm_4")
CHECK_THAT(distance(a, sg(a, c, "1_554"_symop)), Catch::Matchers::WithinRel(static_cast<float>(c.get_c()), 0.01f));
auto sb2 = sg(b, c, "4_565"_symop);
CHECK_THAT(sb.m_x, Catch::Matchers::WithinRel(sb2.m_x, 0.01f));
CHECK_THAT(sb.m_y, Catch::Matchers::WithinRel(sb2.m_y, 0.01f));
CHECK_THAT(sb.m_z, Catch::Matchers::WithinRel(sb2.m_z, 0.01f));
CHECK_THAT(sb.x, Catch::Matchers::WithinRel(sb2.x, 0.01f));
CHECK_THAT(sb.y, Catch::Matchers::WithinRel(sb2.y, 0.01f));
CHECK_THAT(sb.z, Catch::Matchers::WithinRel(sb2.z, 0.01f));
CHECK_THAT(distance(a, sb2), Catch::Matchers::WithinRel(7.42f, 0.01f));
}
// --------------------------------------------------------------------
TEST_CASE("symm_4wvp_1")
{
using namespace cif::literals;
cif::file f(gTestDir / "4wvp.cif.gz");
auto &db = f.front();
cif::mm::structure s(db);
cif::crystal c(db);
cif::point p{ -78.722f, 98.528f, 11.994f };
auto a = s.get_residue("A", 10, "").get_atom_by_atom_id("O");
auto sp1 = c.symmetry_copy(a.get_location(), "2_565"_symop);
CHECK_THAT(sp1.m_x, Catch::Matchers::WithinAbs(p.m_x, 0.5f));
CHECK_THAT(sp1.m_y, Catch::Matchers::WithinAbs(p.m_y, 0.5f));
CHECK_THAT(sp1.m_z, Catch::Matchers::WithinAbs(p.m_z, 0.5f));
const auto &[d, sp2, so] = c.closest_symmetry_copy(p, a.get_location());
REQUIRE(d < 1);
CHECK_THAT(sp2.m_x, Catch::Matchers::WithinAbs(p.m_x, 0.5f));
CHECK_THAT(sp2.m_y, Catch::Matchers::WithinAbs(p.m_y, 0.5f));
CHECK_THAT(sp2.m_z, Catch::Matchers::WithinAbs(p.m_z, 0.5f));
}
TEST_CASE("symm_2bi3_1")
{
cif::file f(gTestDir / "2bi3.cif.gz");
f.front().set_validator(cif::validator_factory::instance().get("mmcif_pdbx.dic"));
auto &db = f.front();
cif::mm::structure s(db);
@@ -517,17 +424,17 @@ TEST_CASE("symm_2bi3_1")
auto sa1 = c.symmetry_copy(a1.get_location(), cif::sym_op(symm1));
auto sa2 = c.symmetry_copy(a2.get_location(), cif::sym_op(symm2));
CHECK_THAT(cif::distance(sa1, sa2), Catch::Matchers::WithinAbs(dist, 0.5f));
CHECK_THAT(glm::distance(sa1, sa2), Catch::Matchers::WithinAbs(dist, 0.01f));
auto pa1 = a1.get_location();
const auto &[d, p, so] = c.closest_symmetry_copy(pa1, a2.get_location());
CHECK_THAT(p.m_x, Catch::Matchers::WithinAbs(sa2.m_x, 0.5f));
CHECK_THAT(p.m_y, Catch::Matchers::WithinAbs(sa2.m_y, 0.5f));
CHECK_THAT(p.m_z, Catch::Matchers::WithinAbs(sa2.m_z, 0.5f));
CHECK_THAT(p.x, Catch::Matchers::WithinAbs(sa2.x, 0.01f));
CHECK_THAT(p.y, Catch::Matchers::WithinAbs(sa2.y, 0.01f));
CHECK_THAT(p.z, Catch::Matchers::WithinAbs(sa2.z, 0.01f));
CHECK_THAT(d, Catch::Matchers::WithinAbs(dist, 0.5f));
CHECK_THAT(d, Catch::Matchers::WithinAbs(dist, 0.01f));
REQUIRE(so.string() == symm2);
}
}
@@ -537,6 +444,7 @@ TEST_CASE("symm_2bi3_1a")
using namespace cif::literals;
cif::file f(gTestDir / "2bi3.cif.gz");
f.front().set_validator(cif::validator_factory::instance().get("mmcif_pdbx.dic"));
auto &db = f.front();
@@ -554,25 +462,28 @@ TEST_CASE("symm_2bi3_1a")
"ptnr2_label_asym_id", "ptnr2_label_seq_id", "ptnr2_auth_seq_id", "ptnr2_label_atom_id", "ptnr2_symmetry",
"pdbx_dist_value"))
{
cif::point p1 = atom_site.find1<float, float, float>(
auto t1 = atom_site.find1<float, float, float>(
"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");
cif::point p2 = atom_site.find1<float, float, float>(
auto t2 = atom_site.find1<float, float, float>(
"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");
cif::point p1{ std::get<0>(t1), std::get<1>(t1), std::get<2>(t1) };
cif::point p2{ std::get<0>(t2), std::get<1>(t2), std::get<2>(t2) };
auto sa1 = c.symmetry_copy(p1, cif::sym_op(symm1));
auto sa2 = c.symmetry_copy(p2, cif::sym_op(symm2));
CHECK_THAT(cif::distance(sa1, sa2), Catch::Matchers::WithinAbs(dist, 0.5f));
CHECK_THAT(glm::distance(sa1, sa2), Catch::Matchers::WithinAbs(dist, 0.01f));
const auto &[d, p, so] = c.closest_symmetry_copy(p1, p2);
CHECK_THAT(p.m_x, Catch::Matchers::WithinAbs(sa2.m_x, 0.5f));
CHECK_THAT(p.m_y, Catch::Matchers::WithinAbs(sa2.m_y, 0.5f));
CHECK_THAT(p.m_z, Catch::Matchers::WithinAbs(sa2.m_z, 0.5f));
CHECK_THAT(p.x, Catch::Matchers::WithinAbs(sa2.x, 0.01f));
CHECK_THAT(p.y, Catch::Matchers::WithinAbs(sa2.y, 0.01f));
CHECK_THAT(p.z, Catch::Matchers::WithinAbs(sa2.z, 0.01f));
CHECK_THAT(d, Catch::Matchers::WithinAbs(dist, 0.5f));
CHECK_THAT(d, Catch::Matchers::WithinAbs(dist, 0.01f));
REQUIRE(so.string() == symm2);
}
}
@@ -580,6 +491,7 @@ TEST_CASE("symm_2bi3_1a")
TEST_CASE("symm_3bwh_1")
{
cif::file f(gTestDir / "3bwh.cif.gz");
f.front().set_validator(cif::validator_factory::instance().get("mmcif_pdbx.dic"));
auto &db = f.front();
@@ -595,7 +507,7 @@ TEST_CASE("symm_3bwh_1")
const auto &[d, p, so] = c.closest_symmetry_copy(a1.get_location(), a2.get_location());
CHECK_THAT(d, Catch::Matchers::WithinAbs(distance(a1.get_location(), p), 0.5f));
CHECK_THAT(d, Catch::Matchers::WithinAbs(distance(a1.get_location(), p), 0.01f));
}
}
}
@@ -603,6 +515,7 @@ TEST_CASE("symm_3bwh_1")
TEST_CASE("volume_3bwh_1")
{
cif::file f(gTestDir / "1juh.cif.gz");
f.front().set_validator(cif::validator_factory::instance().get("mmcif_pdbx.dic"));
auto &db = f.front();
@@ -643,7 +556,7 @@ TEST_CASE("smallest_sphere-1")
for (int i = 0; i < 1000; ++i)
{
auto [c, r] = cif::smallest_sphere_around_points(pts);
CHECK_THAT(cif::distance(c, cif::point{ 0, 0.743099928, 51.1741028 }), Catch::Matchers::WithinAbs(0.f, 0.01f));
CHECK_THAT(glm::distance(c, cif::point{ 0, 0.743099928, 51.1741028 }), Catch::Matchers::WithinAbs(0.f, 0.01f));
CHECK_THAT(r, Catch::Matchers::WithinAbs(7.31248331f, 0.01f));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@
#include "test-main.hpp"
#include <cif++.hpp>
#include <cif++/cif++.hpp>
#include <stdexcept>
@@ -217,7 +217,7 @@ A 1 5 GLY 5 5 5 GLY GLY A . n
{ "entity_id", "1" },
{ "seq_id", "1" },
{ "mon_id", "ALA" },
{ "ndb_seq_num", "1" },
{ "ndb_seq_num", 1 },
{ "pdb_seq_num", "1" },
{ "auth_seq_num", "1" },
{ "pdb_mon_id", "ALA" },